Я пишу свою собственную реализацию нейронной сети на C++ (я приложил то, что, по моему мнению, может вызывать ошибки), чтобы лучше понимать нейронные сети и градиентный спуск.
Я тестирую свою реализацию на pytorch ( см. ниже) и посмотреть, насколько хорошо они справляются друг с другом в прогнозировании цен на жилье с помощью набора данных о жилье в Калифорнии.
Я столкнулся с проблемой, над которой застрял уже неделю. Моя реализация хорошо работает со стохастическим градиентным спуском, но при использовании пакетного градиентного спуска градиенты взрываются уже через 1 эпоху. Я пробовал использовать дырявый Relu на случай, если причина в этом, но безуспешно. При использовании pytorch и пакетного градиентного спуска эта проблема не возникает. Мне интересно, чем отличается моя реализация градиентного спуска/потери mse от реализации pytorch, и что я могу изменить в своей реализации, чтобы предотвратить взрывные градиенты.
Другие примечания о моей реализации:
Я использую инициализацию He для своих весов.
Функции, связанные с градиентом спуск и функция потерь:
#include "optimiser.h"
#include
pair mse_loss(matrix &estimated, matrix &actual){
//defined for a 1d horizontal vectors
// defined as 1/(2N) * sum((pred - actual)^2). 1/N is calculated in forwards function defined below;
// Note: replacing 1/M with 1/2 for ease w/ backpropogation algorithm
if(estimated.rows != 1 || estimated.columns != actual.columns|| actual.rows != 1){
throw std::invalid_argument( "y_estimated and y_actual are not of same dimensions!" );
}
matrix error = estimated-actual;
// want to return gradient: only defined for 1 output -> can probably change by returning matrix and having error as a matrix
return make_pair(error, (error*error.transpose())[0][0]); // returns (pred-y), error
}
void backpropogate(model &myModel, double learningRate){
for(int i = 0; i < myModel.structure.size(); i++){
reluLayer &currLayer = myModel.structure;
currLayer.updateWeights(learningRate);
}
}
double forward(model &myModel, matrix &x, matrix &y, double batchNum){
matrix prediction = myModel.forwardPass(x);
matrix errorGrad; //will maintain invariant that errorGrad is always of shape 1x(num_inputs_for_layer);
double error;
tie(errorGrad, error) = mse_loss(prediction, y);
//backpropagation mathematics
error = error / batchNum; // 1/N for actual error
errorGrad = errorGrad/batchNum; // d(estimate-output)^2 / d(estimate-output)
for(int i = myModel.structure.size()-1; i >= 0; i--){
reluLayer& currLayer = myModel.structure;
if(errorGrad.columns != currLayer.lastOutput.columns){
throw std::invalid_argument( "there is an error in my backpropogation code" );
}
if(myModel.structure.isRelu){
for(int j = 0; j < errorGrad.columns; j++){
errorGrad[0][j] = (currLayer.lastOutput[0][j] > 0) ? errorGrad[0][j] : 0.01*errorGrad[0][j];//// d relu(wx+b) / d(wx+b);
}
}
Я понимаю, что существуют методы, позволяющие избежать или обрабатывать взрывные градиенты, такие как пакетная нормализация или отсечение градиента. Но я считаю, что pytorch не использует эти методы по умолчанию. Я повторил то, что пытаюсь сделать, с помощью pytorch, и не вижу этих взрывающихся градиентов. См. ниже:
import os
import torch
from torch import nn
from torch.utils.data import DataLoader, Dataset
from torchvision import datasets, transforms
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.datasets import fetch_california_housing
import matplotlib.pyplot as plt
data = fetch_california_housing()
print(data.feature_names)
X, y = data.data, data.target
#train-test split of the dataset
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.7, shuffle=True)
def train_model(model, criterion, optimizer, num_epochs=25):
# Create a temporary directory to save training checkpoints
hist = []
for epoch in range(num_epochs):
print(f'Epoch {epoch}/{num_epochs - 1}')
print('-' * 10)
# Each epoch has a training and validation phase
for phase in ['train', 'test']:
if phase == 'train':
model.train() # Set model to training mode
else:
model.eval() # Set model to evaluate mode
running_loss = 0.0
running_corrects = 0
# Iterate over data.
for inputs, labels in myLoader[phase]:
inputs = inputs.to(device)
labels = labels.to(device)
# zero the parameter gradients
optimizer.zero_grad()
# forward
# track history if only in train
with torch.set_grad_enabled(phase == 'train'):
outputs = model(inputs)
_, preds = torch.max(outputs, 1)
loss = criterion(outputs, labels)
# backward + optimize only if in training phase
if phase == 'train':
loss.backward()
# statistics
running_loss += loss.item() * inputs.size(0)
if phase == 'train':
optimizer.step() # batch grad descent (after going through all data)
epoch_loss = running_loss / 50
if phase == 'test':
hist.append(epoch_loss)
Я пишу свою собственную реализацию нейронной сети на C++ (я приложил то, что, по моему мнению, может вызывать ошибки), чтобы лучше понимать нейронные сети и градиентный спуск. Я тестирую свою реализацию на pytorch ( см. ниже) и посмотреть, насколько хорошо они справляются друг с другом в прогнозировании цен на жилье с помощью набора данных о жилье в Калифорнии. Я столкнулся с проблемой, над которой застрял уже неделю. Моя реализация хорошо работает со стохастическим градиентным спуском, но при использовании пакетного градиентного спуска градиенты взрываются уже через 1 эпоху. Я пробовал использовать дырявый Relu на случай, если причина в этом, но безуспешно. При использовании pytorch и пакетного градиентного спуска эта проблема не возникает. Мне интересно, чем отличается моя реализация градиентного спуска/потери mse от реализации pytorch, и что я могу изменить в своей реализации, чтобы предотвратить взрывные градиенты. Другие примечания о моей реализации: [list] [*]Я использую инициализацию He для своих весов. [/list] Функции, связанные с градиентом спуск и функция потерь: #include "optimiser.h" #include
pair mse_loss(matrix &estimated, matrix &actual){ //defined for a 1d horizontal vectors // defined as 1/(2N) * sum((pred - actual)^2). 1/N is calculated in forwards function defined below; // Note: replacing 1/M with 1/2 for ease w/ backpropogation algorithm if(estimated.rows != 1 || estimated.columns != actual.columns|| actual.rows != 1){ throw std::invalid_argument( "y_estimated and y_actual are not of same dimensions!" ); }
matrix error = estimated-actual;
// want to return gradient: only defined for 1 output -> can probably change by returning matrix and having error as a matrix return make_pair(error, (error*error.transpose())[0][0]); // returns (pred-y), error
}
void backpropogate(model &myModel, double learningRate){ for(int i = 0; i < myModel.structure.size(); i++){ reluLayer &currLayer = myModel.structure[i]; currLayer.updateWeights(learningRate); } }
double forward(model &myModel, matrix &x, matrix &y, double batchNum){ matrix prediction = myModel.forwardPass(x); matrix errorGrad; //will maintain invariant that errorGrad is always of shape 1x(num_inputs_for_layer); double error;
tie(errorGrad, error) = mse_loss(prediction, y);
//backpropagation mathematics error = error / batchNum; // 1/N for actual error errorGrad = errorGrad/batchNum; // d(estimate-output)^2 / d(estimate-output)
for(int i = myModel.structure.size()-1; i >= 0; i--){ reluLayer& currLayer = myModel.structure[i];
if(errorGrad.columns != currLayer.lastOutput.columns){ throw std::invalid_argument( "there is an error in my backpropogation code" ); } if(myModel.structure[i].isRelu){ for(int j = 0; j < errorGrad.columns; j++){ errorGrad[0][j] = (currLayer.lastOutput[0][j] > 0) ? errorGrad[0][j] : 0.01*errorGrad[0][j];//// d relu(wx+b) / d(wx+b); } }
Я понимаю, что существуют методы, позволяющие избежать или обрабатывать взрывные градиенты, такие как пакетная нормализация или отсечение градиента. Но я считаю, что pytorch не использует эти методы по умолчанию. Я повторил то, что пытаюсь сделать, с помощью pytorch, и не вижу этих взрывающихся градиентов. См. ниже: import os import torch from torch import nn from torch.utils.data import DataLoader, Dataset from torchvision import datasets, transforms import numpy as np from sklearn.model_selection import train_test_split from sklearn.datasets import fetch_california_housing import matplotlib.pyplot as plt
data = fetch_california_housing() print(data.feature_names)
X, y = data.data, data.target
#train-test split of the dataset X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.7, shuffle=True)
def train_model(model, criterion, optimizer, num_epochs=25): # Create a temporary directory to save training checkpoints hist = []
for epoch in range(num_epochs): print(f'Epoch {epoch}/{num_epochs - 1}') print('-' * 10)
# Each epoch has a training and validation phase for phase in ['train', 'test']: if phase == 'train': model.train() # Set model to training mode else: model.eval() # Set model to evaluate mode
running_loss = 0.0 running_corrects = 0
# Iterate over data. for inputs, labels in myLoader[phase]: inputs = inputs.to(device) labels = labels.to(device)
# zero the parameter gradients optimizer.zero_grad()
# forward # track history if only in train with torch.set_grad_enabled(phase == 'train'): outputs = model(inputs) _, preds = torch.max(outputs, 1) loss = criterion(outputs, labels)
# backward + optimize only if in training phase if phase == 'train': loss.backward()
# statistics running_loss += loss.item() * inputs.size(0) if phase == 'train': optimizer.step() # batch grad descent (after going through all data)
epoch_loss = running_loss / 50 if phase == 'test': hist.append(epoch_loss)