从零开始训练神经网络

发布时间:2024年01月16日

训练(随机梯度下降)

我已经定义了向前和向后传递,但如何开始使用它们?我必须创建一个训练循环,并使用随机梯度下降 (SGD) 作为优化器来更新神经网络的参数。训练函数中有两个主要循环。一个循环表示 epoch 数,即我遍历整个数据集的次数,第二个循环用于逐个遍历每个观察值。

对于每个观测值,我都会使用 进行前向传递,这是数组中长度为 784 的一张图像,如前所述。前向传递的 与 一起使用,后者是后向传递中的独热编码标签(真实值)。这给了我一本关于神经网络中权重更新的字典。x``output``y

def train(self, x_train, y_train, x_val, y_val):
 ?  start_time = time.time()
 ?  for iteration in range(self.epochs):
 ? ? ?  for x,y in zip(x_train, y_train):
 ? ? ? ? ?  output = self.forward_pass(x)
 ? ? ? ? ?  changes_to_w = self.backward_pass(y, output)
 ? ? ? ? ?  self.update_network_parameters(changes_to_w)
?
 ? ? ?  accuracy = self.compute_accuracy(x_val, y_val)
 ? ? ?  print('Epoch: {0}, Time Spent: {1:.2f}s, Accuracy: {2}'.format(
 ? ? ? ? ?  iteration+1, time.time() - start_time, accuracy
 ? ? ?  ))

显示更多

该函数具有 SGD 更新规则的代码,该规则只需要权重的梯度作为输入。需要明确的是,SGD 涉及使用来自后向传递的反向传播来计算梯度,而不仅仅是更新参数。它们似乎是分开的,应该分开考虑,因为这两种算法是不同的。update_network_parameters()

def update_network_parameters(self, changes_to_w):
 ?  '''
 ? ? ?  Update network parameters according to update rule from
 ? ? ?  Stochastic Gradient Descent.
?
 ? ? ?  θ = θ - η * ?J(x, y),
 ? ? ? ? ?  theta θ: ? ? ? ? ?  a network parameter (e.g. a weight w)
 ? ? ? ? ?  eta η: ? ? ? ? ? ?  the learning rate
 ? ? ? ? ?  gradient ?J(x, y):  the gradient of the objective function,
 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?  i.e. the change for a specific theta θ
 ?  '''
?
 ?  for key, value in changes_to_w.items():
 ? ? ?  for w_arr in self.params[key]:
 ? ? ? ? ?  w_arr -= self.l_rate * value

显示更多

在更新了神经网络的参数后,我可以测量我之前准备的验证集的准确性,以验证网络在整个数据集上的每次迭代后的性能。

以下代码使用一些与训练函数相同的部分。首先,它进行前向传递,然后找到网络的预测,并检查与标签是否相等。之后,我将预测结果相加并除以 100 以找到准确性。接下来,我平均每个类的准确性。

def compute_accuracy(self, x_val, y_val):
 ?  '''
 ? ? ?  This function does a forward pass of x, then checks if the indices
 ? ? ?  of the maximum value in the output equals the indices in the label
 ? ? ?  y. Then it sums over each prediction and calculates the accuracy.
 ?  '''
 ?  predictions = []
?
 ?  for x, y in zip(x_val, y_val):
 ? ? ?  output = self.forward_pass(x)
 ? ? ?  pred = np.argmax(output)
 ? ? ?  predictions.append(pred == y)
?
 ?  summed = sum(pred for pred in predictions) / 100.0
 ?  return np.average(summed)

显示更多

最后,在知道会发生什么之后,我可以调用训练函数。我使用训练和验证数据作为训练函数的输入,然后等待。

dnn.train(x_train, y_train, x_val, y_val)

显示更多

请注意,结果可能会有很大差异,具体取决于权重的初始化方式。我的结果准确率为0%-95%。

以下是概述所发生情况的完整代码。

from sklearn.datasets import fetch_openml
from keras.utils.np_utils import to_categorical
import numpy as np
from sklearn.model_selection import train_test_split
import time
?
x, y = fetch_openml('mnist_784', version=1, return_X_y=True)
x = (x/255).astype('float32')
y = to_categorical(y)
?
x_train, x_val, y_train, y_val = train_test_split(x, y, test_size=0.15, random_state=42)
?
class DeepNeuralNetwork():
 ?  def __init__(self, sizes, epochs=10, l_rate=0.001):
 ? ? ?  self.sizes = sizes
 ? ? ?  self.epochs = epochs
 ? ? ?  self.l_rate = l_rate
?
 ? ? ?  # we save all parameters in the neural network in this dictionary
 ? ? ?  self.params = self.initialization()
?
 ?  def sigmoid(self, x, derivative=False):
 ? ? ?  if derivative:
 ? ? ? ? ?  return (np.exp(-x))/((np.exp(-x)+1)**2)
 ? ? ?  return 1/(1 + np.exp(-x))
?
 ?  def softmax(self, x):
 ? ? ?  # Numerically stable with large exponentials
 ? ? ?  exps = np.exp(x - x.max())
 ? ? ?  return exps / np.sum(exps, axis=0)
?
 ?  def initialization(self):
 ? ? ?  # number of nodes in each layer
 ? ? ?  input_layer=self.sizes[0]
 ? ? ?  hidden_1=self.sizes[1]
 ? ? ?  hidden_2=self.sizes[2]
 ? ? ?  output_layer=self.sizes[3]
?
 ? ? ?  params = {
 ? ? ? ? ?  'W1':np.random.randn(hidden_1, input_layer) * np.sqrt(1. / hidden_1),
 ? ? ? ? ?  'W2':np.random.randn(hidden_2, hidden_1) * np.sqrt(1. / hidden_2),
 ? ? ? ? ?  'W3':np.random.randn(output_layer, hidden_2) * np.sqrt(1. / output_layer)
 ? ? ?  }
?
 ? ? ?  return params
?
 ?  def forward_pass(self, x_train):
 ? ? ?  params = self.params
?
 ? ? ?  # input layer activations becomes sample
 ? ? ?  params['A0'] = x_train
?
 ? ? ?  # input layer to hidden layer 1
 ? ? ?  params['Z1'] = np.dot(params["W1"], params['A0'])
 ? ? ?  params['A1'] = self.sigmoid(params['Z1'])
?
 ? ? ?  # hidden layer 1 to hidden layer 2
 ? ? ?  params['Z2'] = np.dot(params["W2"], params['A1'])
 ? ? ?  params['A2'] = self.sigmoid(params['Z2'])
?
 ? ? ?  # hidden layer 2 to output layer
 ? ? ?  params['Z3'] = np.dot(params["W3"], params['A2'])
 ? ? ?  params['A3'] = self.softmax(params['Z3'])
?
 ? ? ?  return params['A3']
?
 ?  def backward_pass(self, y_train, output):
 ? ? ?  '''
 ? ? ? ? ?  This is the backpropagation algorithm, for calculating the updates
 ? ? ? ? ?  of the neural network's parameters.
?
 ? ? ? ? ?  Note: There is a stability issue that causes warnings. This is
 ? ? ? ? ? ? ? ?  caused  by the dot and multiply operations on the huge arrays.
?
 ? ? ? ? ? ? ? ?  RuntimeWarning: invalid value encountered in true_divide
 ? ? ? ? ? ? ? ?  RuntimeWarning: overflow encountered in exp
 ? ? ? ? ? ? ? ?  RuntimeWarning: overflow encountered in square
 ? ? ?  '''
 ? ? ?  params = self.params
 ? ? ?  change_w = {}
?
 ? ? ?  # Calculate W3 update
 ? ? ?  error = output - y_train
 ? ? ?  change_w['W3'] = np.dot(error, params['A3'])
?
 ? ? ?  # Calculate W2 update
 ? ? ?  error = np.multiply( np.dot(params['W3'].T, error), self.sigmoid(params['Z2'], derivative=True) )
 ? ? ?  change_w['W2'] = np.dot(error, params['A2'])
?
 ? ? ?  # Calculate W1 update
 ? ? ?  error = np.multiply( np.dot(params['W2'].T, error), self.sigmoid(params['Z1'], derivative=True) )
 ? ? ?  change_w['W1'] = np.dot(error, params['A1'])
?
 ? ? ?  return change_w
?
 ?  def update_network_parameters(self, changes_to_w):
 ? ? ?  '''
 ? ? ? ? ?  Update network parameters according to update rule from
 ? ? ? ? ?  Stochastic Gradient Descent.
?
 ? ? ? ? ?  θ = θ - η * ?J(x, y),
 ? ? ? ? ? ? ?  theta θ: ? ? ? ? ?  a network parameter (e.g. a weight w)
 ? ? ? ? ? ? ?  eta η: ? ? ? ? ? ?  the learning rate
 ? ? ? ? ? ? ?  gradient ?J(x, y):  the gradient of the objective function,
 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?  i.e. the change for a specific theta θ
 ? ? ?  '''
?
 ? ? ?  for key, value in changes_to_w.items():
 ? ? ? ? ?  for w_arr in self.params[key]:
 ? ? ? ? ? ? ?  w_arr -= self.l_rate * value
?
 ?  def compute_accuracy(self, x_val, y_val):
 ? ? ?  '''
 ? ? ? ? ?  This function does a forward pass of x, then checks if the indices
 ? ? ? ? ?  of the maximum value in the output equals the indices in the label
 ? ? ? ? ?  y. Then it sums over each prediction and calculates the accuracy.
 ? ? ?  '''
 ? ? ?  predictions = []
?
 ? ? ?  for x, y in zip(x_val, y_val):
 ? ? ? ? ?  output = self.forward_pass(x)
 ? ? ? ? ?  pred = np.argmax(output)
 ? ? ? ? ?  predictions.append(pred == y)
?
 ? ? ?  summed = sum(pred for pred in predictions) / 100.0
 ? ? ?  return np.average(summed)
?
 ?  def train(self, x_train, y_train, x_val, y_val):
 ? ? ?  start_time = time.time()
 ? ? ?  for iteration in range(self.epochs):
 ? ? ? ? ?  for x,y in zip(x_train, y_train):
 ? ? ? ? ? ? ?  output = self.forward_pass(x)
 ? ? ? ? ? ? ?  changes_to_w = self.backward_pass(y, output)
 ? ? ? ? ? ? ?  self.update_network_parameters(changes_to_w)
?
 ? ? ? ? ?  accuracy = self.compute_accuracy(x_val, y_val)
 ? ? ? ? ?  print('Epoch: {0}, Time Spent: {1:.2f}s, Accuracy: {2}'.format(
 ? ? ? ? ? ? ?  iteration+1, time.time() - start_time, accuracy
 ? ? ? ? ?  ))
?
dnn = DeepNeuralNetwork(sizes=[784, 128, 64, 10])
dnn.train(x_train, y_train, x_val, y_val)

显示更多

NumPy 中的良好练习

您可能已经注意到,该代码的可读性很强,但它占用了大量空间,可以优化为循环运行。这是一个优化和改进它的机会。如果你是这个主题的新手,以下练习的难度很容易很难,最后一个练习是最难的。

  1. 简单:实现 ReLU 激活函数或任何其他激活函数。检查 sigmoid 函数的实现方式以供参考,并记住实现导数。使用 ReLU 激活函数代替 sigmoid 函数。

  2. 简单:初始化偏差并将它们添加到前向传递中的激活函数之前的 Z,并在后向传递中更新它们。当您尝试添加偏差时,请注意数组的维度。

  3. 中:优化前向和后向传递,使它们在每个函数中循环运行。这使得代码更易于修改,并且可能更易于维护。for

    • 优化为神经网络进行权重的初始化函数,以便您可以在不使神经网络失败的情况下修改参数。sizes=[]

  4. 中:实现小批量梯度下降,取代随机梯度下降。不要更新每个样品的参数,而是根据小批量中每个样品累积的梯度总和的平均值进行更新。小批量的大小通常在 64 以下。

  5. 困难:实现 Adam 优化器。这应该在训练功能中实现。

    1. 通过添加额外的术语来实现 Momentum

    2. 基于 AdaGrad 优化器实现自适应学习率

    3. 结合步骤 1 和 2 来实现 Adam

我的信念是,如果你完成这些练习,你就会有一个良好的基础。下一步是实现卷积、滤波器等,但这留待以后的文章使用。

作为免责声明,这些练习没有解决方案。

PyTorch

现在,我已经展示了如何通过反向传播为前馈神经网络实现这些计算,让我们看看与 NumPy 相比,PyTorch 为我们节省了多少时间。

加载 MNIST 数据集

其中一件看起来比它应该更复杂或更难理解的事情是使用 PyTorch 加载数据集。

首先定义数据的转换,指定它应该是一个张量,并且应该对其进行规范化。然后,将 in 与数据集结合使用来加载数据集。这就是您所需要的。稍后将了解如何从这些加载程序中解压缩值。DataLoader``import

import torch
from torchvision import datasets, transforms
?
transform = transforms.Compose([
 ? ? ? ? ? ? ?  transforms.ToTensor(),
 ? ? ? ? ? ? ?  transforms.Normalize((0.1307,), (0.3081,))
 ? ? ? ? ?  ])
?
train_loader = torch.utils.data.DataLoader(
 ?  datasets.MNIST('data', train=True, download=True, transform=transform))
?
test_loader = torch.utils.data.DataLoader(
 ?  datasets.MNIST('data', train=False, transform=transform))

显示更多

训练

我定义了一个名为类似于之前用 NumPy 编写的类的类。这个类有一些相同的方法,但你可以清楚地看到,我不需要考虑初始化网络参数,也不需要考虑PyTorch中的向后传递,因为这些函数和计算精度的函数都消失了。Net``DeepNeuralNetwork

import time
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
?
class Net(nn.Module):
 ?  def __init__(self, epochs=10):
 ? ? ?  super(Net, self).__init__()
 ? ? ?  self.linear1 = nn.Linear(784, 128)
 ? ? ?  self.linear2 = nn.Linear(128, 64)
 ? ? ?  self.linear3 = nn.Linear(64, 10)
?
 ? ? ?  self.epochs = epochs
?
 ?  def forward_pass(self, x):
 ? ? ?  x = self.linear1(x)
 ? ? ?  x = torch.sigmoid(x)
 ? ? ?  x = self.linear2(x)
 ? ? ?  x = torch.sigmoid(x)
 ? ? ?  x = self.linear3(x)
 ? ? ?  x = torch.softmax(x, dim=0)
 ? ? ?  return x
?
 ?  def one_hot_encode(self, y):
 ? ? ?  encoded = torch.zeros([10], dtype=torch.float64)
 ? ? ?  encoded[y[0]] = 1.
 ? ? ?  return encoded
?
 ?  def train(self, train_loader, optimizer, criterion):
 ? ? ?  start_time = time.time()
 ? ? ?  loss = None
?
 ? ? ?  for iteration in range(self.epochs):
 ? ? ? ? ?  for x,y in train_loader:
 ? ? ? ? ? ? ?  y = self.one_hot_encode(y)
 ? ? ? ? ? ? ?  optimizer.zero_grad()
 ? ? ? ? ? ? ?  output = self.forward_pass(torch.flatten(x))
 ? ? ? ? ? ? ?  loss = criterion(output, y)
 ? ? ? ? ? ? ?  loss.backward()
 ? ? ? ? ? ? ?  optimizer.step()
?
 ? ? ? ? ?  print('Epoch: {0}, Time Spent: {1:.2f}s, Loss: {2}'.format(
 ? ? ? ? ? ? ?  iteration+1, time.time() - start_time, loss
 ? ? ? ? ?  ))

显示更多

在阅读本类时,请注意 PyTorch 已经为我们实现了所有相关的激活函数,以及不同类型的层。你甚至不必考虑它。您可以只定义一些图层,例如全连接图层。nn.Linear()

我之前已经导入了优化器,现在我指定了要使用的优化器,以及损失的标准。我将优化器和标准都传递到训练函数中,PyTorch 开始运行示例,就像在 NumPy 中一样。我甚至可以包括一个衡量准确性的指标,但为了衡量损失而忽略了这一点。

model = Net()
?
optimizer = optim.SGD(model.parameters(), lr=0.001)
criterion = nn.BCEWithLogitsLoss()
?
model.train(train_loader, optimizer, criterion)

显示更多

使用 Keras 的 TensorFlow 2.0

对于神经网络的 TensorFlow/Keras 版本,我选择使用一种简单的方法,最大限度地减少代码行数。这意味着我没有定义任何类,而是使用 Keras 的高级 API 来制作一个仅用几行代码的神经网络。如果你刚刚开始学习神经网络,你会发现使用 Keras 时进入门槛最低。因此,我推荐它。

我首先导入以后需要的所有函数。

import tensorflow as tf
from tensorflow.keras.datasets import mnist
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.layers import Flatten, Dense
from tensorflow.keras.losses import BinaryCrossentropy

显示更多

我只需这几行代码即可加载数据集并对其进行预处理。请注意,我只对训练数据进行预处理,因为我不打算将验证数据用于此方法。稍后,我将解释如何使用验证数据。

(x_train, y_train), (x_val, y_val) = mnist.load_data()
?
x_train = x_train.astype('float32') / 255
y_train = to_categorical(y_train)

显示更多

下一步是定义模型。在 Keras 中,在知道要将哪些图层应用于数据后,这非常简单。在本例中,我将使用完全连接的层,如 NumPy 示例所示。在 Keras 中,这是由函数完成的。Dense()

定义模型的层后,我将编译模型并定义优化器、损失函数和度量。最后,我可以告诉 Keras 拟合 10 个 epoch 的训练数据,就像在其他示例中一样。

model = tf.keras.Sequential([
 ?  Flatten(input_shape=(28, 28)),
 ?  Dense(128, activation='sigmoid'),
 ?  Dense(64, activation='sigmoid'),
 ?  Dense(10)
])
?
model.compile(optimizer='SGD',
 ? ? ? ? ? ?  loss=BinaryCrossentropy(),
 ? ? ? ? ? ?  metrics=['accuracy'])
?
model.fit(x_train, y_train, epochs=10)

显示更多

如果要使用验证数据,可以使用 fit 函数的参数传入:validation_data

model.fit(x_train, y_train, epochs=10, validation_data=(x_val, y_val))

显示更多

结论

本文介绍了如何在没有框架帮助的情况下构建神经网络的基础知识,这些框架可能使其更易于使用。我构建了一个具有 4 层的基本深度神经网络,并解释了如何通过实现前向和后向传递(反向传播)来构建基本的深度神经网络。

文章来源:https://blog.csdn.net/2401_82469710/article/details/135593240
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。