查看原文
其他

数学系列·E03S01 | 神经网络背后的数学:深入研究当前AI的支柱--神经网络,了解其数学原理,从头实现并探索其应用

ai汤源 AI2Paradigm
2024-08-03


点击

上方蓝字关注我们

图:正是看到Anil的这本书,使我重新整理这季“AI背后的数学系列”

𝕀²·ℙarad𝕚g𝕞智能平方范式之AI背后的数学。

本文是数学系列第三篇。数学math, 可能是现实宇宙与我们所感知宇宙之间唯一的真。本文转译自Cristian Leo,原文载自Towards Data Science。腐前两篇文章链接:数学系列·E01S01|xAI团队Greg Yang的恐怖数学长书单,Elon点赞!数学系列·E02S01|真正搞懂AI可能还需要懂点Math:这是可以自学的数学基础书单及自学方法





题记

神经网络是人工智能(AI)的核心,为从发现照片中的物体到翻译语言等各种应用提供了动力。在本文中,我们将深入探讨什么是神经网络、神经网络是如何工作的,以及为什么神经网络在当今技术驱动的世界中举足轻重。
  • 索引
    - 1: 了解基础知识
    ∘ 1.1:什么是神经网络?
    ∘ 1.2: 神经网络的类型
    - 2: 神经网络的架构
    ∘ 2.1:神经元的结构
    ∘ 2.2:层
    ∘ 2.3:层在学习中的作用
    - 3: 神经网络数学
    ∘ 3.1:加权和
    ∘ 3.2: 激活函数
    ∘ 3.3: 反向传播:神经学习的核心
    ∘ 3.4:逐步示例
    ∘ 3.5: 改进
    - 4: 实现神经网络
    ∘ 4.1: 用 Python 构建简单的神经网络
    ∘ 4.2:利用库实现神经网络(TensorFlow)
    - 5: 挑战
    ∘ 5.1:克服过度拟合
    - 6: 结论
  • 正文

1: 了解基本知识
1.1:什么是神经网络?
神经网络是生物学和计算机科学的完美结合,其灵感来源于我们大脑处理复杂计算任务的设置。从本质上讲,神经网络是一种算法,旨在发现模式并理解感官数据,从而完成很多工作,如识别人脸、理解口语、进行预测和理解自然语言等。

来自生物神经网络的启发

-来自DALL-E

我们的大脑约有 860 亿个神经元,它们连接成一个复杂的网络。这些神经元通过被称为 "突触 "的连接进行交流,突触上的信号可强可弱,从而影响信息的传递。这是我们学习和记忆的基础。

人工神经网络借鉴了生物神经网络这本书的一页内容,使用层层连接的数字神经元或节点。输入层接收数据,隐藏层咀嚼数据,输出层输出结果。当网络获得更多数据时,它会调整连接强度(或 "权重")来进行学习,这有点像我们大脑的突触是如何加强或减弱的。

从感知器到深度学习
神经网络始于 1958 年的感知器(perceptron),这要归功于弗兰克-罗森布拉特(Frank Rosenblatt)。这是一种基本的神经网络,用于完成简单的 "是 "或 "否 "型任务。从那时起,我们建立了更复杂的网络,如多层感知器(MLP),由于拥有一个或多个隐藏层,它可以理解更复杂的数据关系。

随后,深度学习应运而生,它是一种拥有大量层级的神经网络。这些深度神经网络能够从海量数据中学习,从击败人类围棋选手到使得自动驾驶汽车成为可能,它们是我们听到的许多人工智能突破的幕后推手。

通过模式进行理解
神经网络最大的优势之一,就是能够学习数据中的模式,而无需直接为特定任务编程。这一过程被称为 "训练",它能让神经网络捕捉总体趋势,并根据所学做出预测或决策。

得益于这种能力,神经网络具有超强的通用性,可用于从图像识别到语言翻译,再到预测股市趋势等广泛的应用领域。它们证明,曾经被认为需要人类智能的任务,现在也可以由人工智能来完成。
1.2: 神经网络的类型

在深入了解神经网络的结构和数学知识之前,让我们先来看看目前最常见的神经网络类型。这将让我们更好地了解它们的潜力和能力。

前馈神经网络(FNN)
从基础开始,前馈神经网络是最简单的类型。它就像数据的单行道--信息从输入端直接传输,经过任何隐藏层,然后从另一端输出。这些网络是进行简单预测和分类的首选。

卷积神经网络(CNN)
卷积神经网络(CNN)是计算机视觉领域的 "大炮"。由于具有专门的层,它们能够捕捉图像中的空间模式。这种能力使它们成为识别图像、发现图像中的物体并对所见的图像进行分类的明星。正是因为它们的存在,你的手机才能在照片中分辨出狗和猫。
递归神经网络(RNN)
递归神经网络(RNN)具有某种记忆功能,因此非常适合处理任何涉及数据序列的问题,如句子、DNA 序列、笔迹或股市趋势。它们将信息循环往复,从而记住序列中之前的输入。这使它们成为预测句子中下一个单词或理解口语等任务的王牌。
长短期记忆网络(LSTM)
LSTM 是一种特殊的 RNN,用于长时间记忆事物。它们旨在解决 RNN 因长时间连续记忆而遗忘的问题。如果您要处理需要长时间保持信息的复杂任务,如翻译段落或预测电视剧的下一步剧情,那么 LSTM 就是您的最佳选择。
生成对抗网络(GAN)
想象一下两个人工智能在玩猫捉老鼠的游戏:一个生成虚假数据(如图像),另一个试图找出真假。这就是 GAN。通过这种设置,GAN 可以创造出无比逼真的图像、音乐、文字等。它们是神经网络世界的艺术家,从零开始生成新的、逼真的数据。
2: 神经网络的架构
神经网络的核心是我们所说的神经元或节点,其灵感来源于我们大脑中的神经细胞。这些人工神经元是处理接收、压缩和传递信息等繁重工作的工作母机。让我们深入了解这些神经元是如何构建的。

2.1:神经元的结构
神经元的输入要么直接来自我们感兴趣的数据,要么来自其他神经元的输出。这些输入就像一个列表,列表中的每一项都代表了数据的不同特征。
对于每一个输入,神经元都会做一些运算:将输入乘以一个 "权重",然后加上一个 "偏置"。权重是神经元决定输入重要性的一种方式,而偏置则是确保神经元输出恰到好处的一种调整。在网络的训练过程中,它会调整这些权重和偏置,以便更好地完成工作。
接下来,神经元会将所有这些加权输入(weights)和偏置(biases)相加,并通过一个名为激活函数的特殊函数来运行总和。这一步就是神奇的地方,神经元可以通过非线性方式弯曲和拉伸数据,从而处理复杂的模式。该函数的常用选择有 ReLU、Sigmoid 和 Tanh,每种函数都有其调整数据的方式。
2.2:层(layers)


-三层 FNN 架构


神经网络是分层结构的,有点像分层蛋糕,每层由多个神经元组成。这些层的堆叠方式构成了网络的架构:

输入层
数据从这里进入网络。这里的每个神经元对应数据的一个特征。在上图中,输入层是左侧第一层,包含两个节点。

隐藏层
如上图所示,这些层夹在输入层和输出层之间。你可能只有一个或一堆这样的隐藏层,做着繁重的计算和转换工作。层数越多(每层中的神经元越多),网络就能学习到越复杂的模式。但是,这也意味着需要更多的计算能力,网络也更有可能过于依赖训练数据,这就是所谓的过拟合问题。

输出层
这是网络的最后一站,它在这里输出结果。根据任务的不同,例如如果是对数据进行分类,这一层可能会为每个类别设置一个神经元,使用类似 softmax 函数的方法来给出每个类别的概率。在上图中,最后一层只有一个节点,表明该层用于回归任务。

2.3:层在学习中的作用

隐藏层是网络的特征探测器。随着数据在这些层中的移动,网络能更好地发现和组合输入特征,并将它们分层,形成对数据更复杂的理解。
数据每通过一层,网络就能捕捉到更复杂的模式。早期的网络层可能会学习形状或纹理等基本知识,而较深的网络层则会掌握更复杂的概念,如识别图片中的物体或人脸。
3: 神经网络实现中的数学
3.1:加权和
神经计算过程的第一步是汇总神经元的输入,每个输入乘以各自的权重,然后加上一个偏置项。这一操作称为加权和或线性组合。其数学表达式为

-NN 加权和公式
其中:
  • z 是加权和、

  • wi 代表与第 i 个输入相关的权重、

  • xi 是神经元的第 i 个输入、

  • b 是偏置项,这是一个独特的参数,可以在调整加权和的同时调整输出。
加权和至关重要,因为它构成了神经元在进行任何非线性变换之前的原始输入信号。它允许网络对输入进行线性变换,调整神经元输出中每个输入的重要性(权重)。
3.2:激活函数
正如我们之前所说,激活函数在决定神经网络的输出方面起着举足轻重的作用。它们是决定神经元是否应被激活的数学等式。激活函数为网络引入了非线性特性,使其能够学习复杂的数据模式,并执行超越单纯线性分类的任务,这对深度学习模型至关重要。在此,我们将深入探讨几种关键类型的激活函数及其意义:

Sigmoid 激活函数

-Sigmoid曲线图
这个函数将输入值压缩到 0 和 1 之间的一个很小范围内。它就像是将任何数值,无论大小,转换成一个概率。

-Sigmoid函数

你会在二元分类网络的最后一层看到 sigmoid 函数,在这里你需要在两个选项(是或否、真或假、1 或 0)之间做出决定。
双曲正切函数 (tanh)


-tanh曲线图

tanh 会将输出范围拉伸到-1 和 1 之间,从而将数据集中在 0 附近,使下层更容易从中学习。

-tanh函数

它通常出现在隐藏层中,通过平衡输入信号来帮助模拟更复杂的数据关系。

整流线性单元 (ReLU)


-ReLU曲线图

ReLU 就像一个看门人,它可以原封不动地传递正值,但会阻止负值,将其变为零。这种简单性使其非常高效,有助于克服深度神经网络训练中的一些棘手问题。

-ReLU函数

ReLU 的简单性和高效性使其大受欢迎,尤其是在卷积神经网络(CNN)和深度学习模型中。
泄漏整流线性单元(Leaky ReLU)

-Leaky ReLU曲线图
Leaky ReLU 允许在输入小于零时出现微小的非零梯度,这使得神经元即使在不主动发射时也能保持活力。

-Leaky ReLU

这是对 ReLU 的一种调整,用于网络可能出现 "死亡神经元 "的情况,确保网络的所有部分都能长期保持活跃。
指数线性单元 (ELU)

-ELU曲线图

ELU 可以平滑负输入函数(使用参数 α 进行缩放),允许负输出,但曲线较为平缓。这有助于网络保持更接近零的平均激活度,从而改善学习动态。

-ELU函数
适用于 ReLU 的尖锐阈值可能会减慢学习速度的深度网络。
Softmax函数

-Softmax曲线图
softmax 函数通过指数化和归一化将神经元的原始输出分数 logits 转化为概率。它确保输出值的总和为 1,从而使它们可以直接解释为概率。

-Softmax函数

它是多类分类问题中输出层的常用函数,在这种情况下,每个神经元都对应一个不同的类,而你想要选出最有可能的一个。
3.3:反向传播:神经网络学习的核心

反向传播(Backpropagation)是 "误差反向传播 "的简称,是一种有效计算网络中所有权重的损失函数梯度的方法。它包括两个主要阶段:前向传递,即输入数据通过网络产生输出;后向传递,即输出与目标值进行比较,并将误差反向传播到网络以更新权重。

反向传播的精髓在于微积分的链式法则,即通过乘以后面各层的梯度来计算每个权重的损失函数梯度。这一过程揭示了每个权重对误差的影响程度,为权重的调整提供了清晰的路径。

反向传播的链式规则可表示如下:

-反向传播中的规则链

其中:

∂a/∂L 是损失函数对激活的梯度、

∂z/∂a 是加权输入 z 的激活函数梯度、

∂w/∂z 是加权输入到权重 w 的梯度、

z 代表加权输入总和,a 代表激活。

梯度下降优化权重

梯度下降法是一种优化算法,用于最小化神经网络中的损失函数。它的工作原理是沿着损失最小的方向迭代移动权重。每次迭代中权重的调整量由学习率决定,学习率是一个控制步长的超参数。

在数学上,梯度下降法的权重更新规则可以表示为


-梯度下降公式

其中:

w-new 和 w-old 分别代表权重的更新(新)值和当前(旧)值、
η 是学习率,是一个超参数,用于控制负梯度方向上的步长、

∂w/∂L 是权重损失函数的梯度。

实际上,反向传播和梯度下降是同时进行的。反向传播计算网络中每个权重的梯度(误差的方向和大小),而梯度下降则利用这一信息更新权重,使损失最小化。这个迭代过程一直持续到模型收敛到损失最小或达到标准为止。

3.4:分步实例

让我们来探讨一个简单神经网络中涉及反向传播和梯度下降的例子。这个神经网络只有一个隐藏层。我们将使用一个数据点进行一次迭代训练,以了解这些过程是如何更新网络权重的。

网络结构:

  • 输入:x1、x2(2 维输入向量)
  • 隐藏层:2 个神经元,激活函数 f(z)=ReLU(z)=max(0,z)
  • 输出层:1 个神经元,具有激活函数 g(z)=σ(z)=1+e-z1(用于二元分类的 Sigmoid 函数)
  • 损失函数:二元交叉熵损失。

前向传递
给定输入 x1、x2、权重 w 和偏置 b,前向传递计算网络的输出。单隐层网络的流程如下:隐层采用 ReLU 激活,输出层采用 sigmoid 激活:

1.1: 输入到隐藏层
假设从输入到隐藏层的初始权重分别为 w11、w12、w21、w22,两个隐藏神经元的偏置分别为 b1、b2。

给定输入向量 [x1,x2],隐藏层中每个神经元的加权和为:

-隐藏层加权和

应用 ReLU 激活函数:


-隐藏层ReLU激活

1.2: 从隐藏层到输出层

假设从隐藏层到输出神经元的权重为 w31、w32,偏置为 b3。

输出神经元的加权和为:

-输出层加权和

应用 Sigmoid 激活函数后的输出为:

-输出层Sigmoid激活


损失计算(二元交叉熵):

-交叉熵公式
后向传递(反向传播):

现在情况变得有点复杂,因为我们需要根据前向传递中应用的公式计算梯度。

输出层梯度
让我们从输出层开始。z3 的损失函数导数为:


-输出层激活梯度

输出层权重和偏置的损失梯度:


-输出层梯度


隐藏层梯度

隐藏层激活的损失梯度(应用链式规则):

-隐藏层激活梯度

有关隐层权重和偏置的损失梯度:


-隐藏层梯度

然后重复这些步骤,直到达到某个标准(如最大epochs)为止。

3.5:改进

梯度下降法的基本思想很简单--朝着误差最小的方向小步前进--但为了提高效率和效果,我们对这种方法进行了一些调整和改进。

随机梯度下降法(SGD)
随机梯度下降法(SGD)采用了梯度下降法的核心思想,但改变了方法,每次只使用一个训练实例来计算梯度和更新权重。这种方法类似于根据个人的快速观察做出决策,而不是等待收集所有人的意见。由于模型更新更频繁,计算负担更小,因此学习过程会更快。

要了解有关 SGD 的更多信息,请参阅作者相关文章。

ADAM(自适应时刻估计)
ADAM 是 Adaptive Moment Estimation(自适应矩估计)的简称,它就像是 SGD 青春活力的睿智顾问。它采用了根据数据梯度调整权重的概念,但对模型中的每个参数都采用了更复杂、更个性化的方法。ADAM 结合了其他两种梯度下降改进方法 AdaGrad 和 RMSProp 的理念,根据梯度的第一矩(均值)和第二矩(非中心方差)调整网络中每个权重的学习率。

要了解有关 ADAM的更多信息,请参阅作者相关文章。

4: 实现一个神经网络
4.1:用 Python 构建一个简单的神经网络

最后,让我们从头开始创建一个神经网络。为了提高可读性,我将把代码分为 4 部分:NeuralNetwork 类、训练器类和实现。

你可以在这本 Jupyter 笔记本上找到全部代码。该笔记本包含一个微调奖励,可能会提高神经网络的性能:github.com/cristianleoo/models-from-scratch-python/blob/main/Neural%20Network/demo.ipynb

神经网络类

让我们从 NN 类开始,它定义了神经网络的架构:

import numpy as np
class NeuralNetwork: """ A simple neural network with one hidden layer.
Parameters: ----------- input_size: int The number of input features hidden_size: int The number of neurons in the hidden layer output_size: int The number of neurons in the output layer loss_func: str The loss function to use. Options are 'mse' for mean squared error, 'log_loss' for logistic loss, and 'categorical_crossentropy' for categorical crossentropy. """ def __init__(self, input_size, hidden_size, output_size, loss_func='mse'): self.input_size = input_size self.hidden_size = hidden_size self.output_size = output_size self.loss_func = loss_func # Initialize weights and biases self.weights1 = np.random.randn(self.input_size, self.hidden_size) self.bias1 = np.zeros((1, self.hidden_size)) self.weights2 = np.random.randn(self.hidden_size, self.output_size) self.bias2 = np.zeros((1, self.output_size))
# track loss self.train_loss = [] self.test_loss = [] def forward(self, X): """ Perform forward propagation. Parameters: ----------- X: numpy array The input data Returns: -------- numpy array The predicted output """ # Perform forward propagation self.z1 = np.dot(X, self.weights1) + self.bias1 self.a1 = self.sigmoid(self.z1) self.z2 = np.dot(self.a1, self.weights2) + self.bias2 if self.loss_func == 'categorical_crossentropy': self.a2 = self.softmax(self.z2) else: self.a2 = self.sigmoid(self.z2) return self.a2 def backward(self, X, y, learning_rate): """ Perform backpropagation.
Parameters: ----------- X: numpy array The input data y: numpy array The target output learning_rate: float The learning rate """ # Perform backpropagation m = X.shape[0] # Calculate gradients if self.loss_func == 'mse': self.dz2 = self.a2 - y elif self.loss_func == 'log_loss': self.dz2 = -(y/self.a2 - (1-y)/(1-self.a2)) elif self.loss_func == 'categorical_crossentropy': self.dz2 = self.a2 - y else: raise ValueError('Invalid loss function') self.dw2 = (1 / m) * np.dot(self.a1.T, self.dz2) self.db2 = (1 / m) * np.sum(self.dz2, axis=0, keepdims=True) self.dz1 = np.dot(self.dz2, self.weights2.T) * self.sigmoid_derivative(self.a1) self.dw1 = (1 / m) * np.dot(X.T, self.dz1) self.db1 = (1 / m) * np.sum(self.dz1, axis=0, keepdims=True) # Update weights and biases self.weights2 -= learning_rate * self.dw2 self.bias2 -= learning_rate * self.db2 self.weights1 -= learning_rate * self.dw1 self.bias1 -= learning_rate * self.db1 def sigmoid(self, x): """ Sigmoid activation function. Parameters: ----------- x: numpy array The input data Returns: -------- numpy array The output of the sigmoid function """ return 1 / (1 + np.exp(-x)) def sigmoid_derivative(self, x): """ Derivative of the sigmoid activation function.
Parameters: ----------- x: numpy array The input data Returns: -------- numpy array The output of the derivative of the sigmoid function """ return x * (1 - x) def softmax(self, x): """ Softmax activation function.
Parameters: ----------- x: numpy array The input data Returns: -------- numpy array The output of the softmax function """ exps = np.exp(x - np.max(x, axis=1, keepdims=True)) return exps/np.sum(exps, axis=1, keepdims=True)

初始化

def __init__(self, input_size, hidden_size, output_size, loss_func='mse'): self.input_size = input_size self.hidden_size = hidden_size self.output_size = output_size self.loss_func = loss_func # Initialize weights and biases self.weights1 = np.random.randn(self.input_size, self.hidden_size) self.bias1 = np.zeros((1, self.hidden_size)) self.weights2 = np.random.randn(self.hidden_size, self.output_size) self.bias2 = np.zeros((1, self.output_size))
# track loss self.train_loss = [] self.test_loss = []
__init__ 方法初始化 NeuralNetwork 类的新实例。它的参数包括输入层(input_size)、隐藏层(hidden_size)和输出层(output_size)的大小,以及要使用的损失函数类型(loss_func),默认为均方误差('mse')。
weights1 连接输入层和隐藏层,weights2 连接隐藏层和输出层。偏置(bias1bias2)初始化为零数组。权重的初始化使用随机数来打破对称性,而偏置的初始化则使用零作为起点。
它还初始化了两个列表 train_loss test_loss,分别用于跟踪训练和测试阶段的损失。

前向传播(forward方法


def forward(self, X): # Perform forward propagation self.z1 = np.dot(X, self.weights1) + self.bias1 self.a1 = self.sigmoid(self.z1) self.z2 = np.dot(self.a1, self.weights2) + self.bias2 if self.loss_func == 'categorical_crossentropy': self.a2 = self.softmax(self.z2) else: self.a2 = self.sigmoid(self.z2) return self.a2

forward方法获取输入数据 X 并将其传递给网络。它计算加权和(z1、z2),并对这些和应用激活函数(sigmoid 或 softmax,取决于损失函数),从而得到激活值(a1、a2)。

对于隐层,它始终使用 sigmoid 激活函数。对于输出层,如果损失函数是 "categorical_crossentropy",则使用 softmax,否则使用 sigmoid。选择 sigmoid 还是 softmax 取决于任务的性质(二元/多类分类)。

此方法会返回网络的最终输出 (a2),可用于进行预测。

反向传播(backward 方法)

ef backward(self, X, y, learning_rate): # Perform backpropagation m = X.shape[0] # Calculate gradients if self.loss_func == 'mse': self.dz2 = self.a2 - y elif self.loss_func == 'log_loss': self.dz2 = -(y/self.a2 - (1-y)/(1-self.a2)) elif self.loss_func == 'categorical_crossentropy': self.dz2 = self.a2 - y else: raise ValueError('Invalid loss function') self.dw2 = (1 / m) * np.dot(self.a1.T, self.dz2) self.db2 = (1 / m) * np.sum(self.dz2, axis=0, keepdims=True) self.dz1 = np.dot(self.dz2, self.weights2.T) * self.sigmoid_derivative(self.a1) self.dw1 = (1 / m) * np.dot(X.T, self.dz1) self.db1 = (1 / m) * np.sum(self.dz1, axis=0, keepdims=True) # Update weights and biases self.weights2 -= learning_rate * self.dw2 self.bias2 -= learning_rate * self.db2 self.weights1 -= learning_rate * self.dw1 self.bias1 -= learning_rate * self.db1

backward方法采用反向传播算法,根据预测输出与实际输出(y)之间的误差更新网络中的权重和偏置。

它利用链式法则计算权重和偏置的损失函数梯度(dw2、db2、dw1、db1)。梯度表示需要调整多少权重和偏置才能将误差最小化。

学习率(learning_rate)控制更新时的步长。然后,该方法通过减去学习率与各自梯度的乘积来更新权重和偏置。

根据所选的损失函数会进行不同的梯度计算,这说明了网络适应各种任务的灵活性。

激活函数(sigmoid, sigmoid_derivative, softmax 方法)

def sigmoid(self, x): return 1 / (1 + np.exp(-x))
def sigmoid_derivative(self, x): return x * (1 - x)
def softmax(self, x): exps = np.exp(x - np.max(x, axis=1, keepdims=True)) return exps/np.sum(exps, axis=1, keepdims=True)

sigmoid:这种方法实现了 sigmoid 激活函数,将输入值压缩到 0 和 1 之间。

sigmoid_derivative:该方法计算 sigmoid 函数的导数,在反向传播过程中用于计算梯度。

softmax:软最大值:softmax 函数用于多类分类问题。它通过提取每个输出的指数,然后对这些值进行归一化处理,使其总和为 1,从而将网络得分转换为概率。

训练器Trainer

下面的代码介绍了一个用于训练神经网络模型的训练器类Trainer。它封装了进行训练所需的一切,包括执行训练周期(epochs)、计算损失以及根据损失通过反向传播调整模型参数。

class Trainer: """ A class to train a neural network.
Parameters: ----------- model: NeuralNetwork The neural network model to train loss_func: str The loss function to use. Options are 'mse' for mean squared error, 'log_loss' for logistic loss, and 'categorical_crossentropy' for categorical crossentropy. """ def __init__(self, model, loss_func='mse'): self.model = model self.loss_func = loss_func self.train_loss = [] self.test_loss = []
def calculate_loss(self, y_true, y_pred): """ Calculate the loss.
Parameters: ----------- y_true: numpy array The true output y_pred: numpy array The predicted output Returns: -------- float The loss """ if self.loss_func == 'mse': return np.mean((y_pred - y_true)**2) elif self.loss_func == 'log_loss': return -np.mean(y_true*np.log(y_pred) + (1-y_true)*np.log(1-y_pred)) elif self.loss_func == 'categorical_crossentropy': return -np.mean(y_true*np.log(y_pred)) else: raise ValueError('Invalid loss function')
def train(self, X_train, y_train, X_test, y_test, epochs, learning_rate): """ Train the neural network.
Parameters: ----------- X_train: numpy array The training input data y_train: numpy array The training target output X_test: numpy array The test input data y_test: numpy array The test target output epochs: int The number of epochs to train the model learning_rate: float The learning rate """ for _ in range(epochs): self.model.forward(X_train) self.model.backward(X_train, y_train, learning_rate) train_loss = self.calculate_loss(y_train, self.model.a2) self.train_loss.append(train_loss) self.model.forward(X_test) test_loss = self.calculate_loss(y_test, self.model.a2) self.test_loss.append(test_loss)

下面是该类及其方法的详细介绍:

类初始化__init__ 方法)

def __init__(self, model, loss_func='mse'): self.model = model self.loss_func = loss_func self.train_loss = [] self.test_loss = []

构造函数的输入是一个神经网络模型(model)和一个损失函数(loss_func)。如果没有指定,损失函数默认为均方误差('mse')。

它初始化了 train_losstest_loss 列表,以跟踪训练和测试阶段的损失值,从而可以随时间推移监控模型的性能。

计算损失calcul_loss 方法)

def calculate_loss(self, y_true, y_pred): if self.loss_func == 'mse': return np.mean((y_pred - y_true)**2) elif self.loss_func == 'log_loss': return -np.mean(y_true*np.log(y_pred) + (1-y_true)*np.log(1-y_pred)) elif self.loss_func == 'categorical_crossentropy': return -np.mean(y_true*np.log(y_pred)) else: raise ValueError('Invalid loss function')

该方法使用指定的损失函数计算预测输出(y_pred)和真实输出(y_true)之间的损失。这对评估模型的性能和执行反向传播至关重要。

该方法支持三种类型的损失函数:

  • 平均平方误差('mse'):用于回归任务,计算预测值与真实值之差的平方平均数。
  • 逻辑损失('log_loss'):适用于二元分类问题,使用对数似然法计算损失。
  • 分类交叉熵('categorical_crossentropy'):适用于多类分类任务,测量真实标签与预测值之间的差异。

如果提供的损失函数无效,则会引发 ValueError

训练模型(train方法)

def train(self, X_train, y_train, X_test, y_test, epochs, learning_rate): for _ in range(epochs): self.model.forward(X_train) self.model.backward(X_train, y_train, learning_rate) train_loss = self.calculate_loss(y_train, self.model.a2) self.train_loss.append(train_loss) self.model.forward(X_test) test_loss = self.calculate_loss(y_test, self.model.a2) self.test_loss.append(test_loss)

train 方法使用训练数据集(X_train, y_train)和测试数据集(X_test, y_test)管理指定次数的训练过程。该方法还需要一个 learning_rate 参数,用于影响反向传播过程中参数更新的步长。

对于每个 epoch(训练周期),该方法会执行以下步骤:

  1. 训练数据的前向传递:使用模型的前向方法计算训练数据的预测输出。
  2. 后向传递(参数更新):它使用模型的后向方法,使用训练数据和标签(y_train)以及学习率,根据损失计算出的梯度更新模型的权重和偏置。
  3. 计算训练损失:使用 calculate_loss 方法利用训练标签和预测结果计算训练损失。然后将该损失附加到 train_loss 列表中,以进行监控。
  4. 前向传递测试数据:同样,该方法会计算测试数据的预测结果,以评估模型在未见数据上的性能。
  5. 计算测试损失:利用测试标签和预测结果计算测试损失,并将此损失附加到 test_loss 列表中。

实施

在本节中,我将概述加载数据集、准备训练数据集以及使用数据集训练神经网络完成分类任务的完整流程。这一过程包括数据预处理、模型创建、训练和评估。

在本任务中,我们将使用开源(BSD-3 许可证)sci-kit learn 库中的 digits 数据集。

# Load the digits datasetdigits = load_digits()
# Preprocess the datasetscaler = MinMaxScaler()X = scaler.fit_transform(digits.data)y = digits.target
# One-hot encode the target outputencoder = OneHotEncoder(sparse=False)y_onehot = encoder.fit_transform(y.reshape(-1, 1))
# Split the dataset into training and testing setsX_train, X_test, y_train, y_test = train_test_split(X, y_onehot, test_size=0.2, random_state=42)
# Create an instance of the NeuralNetwork classinput_size = X.shape[1]hidden_size = 64output_size = len(np.unique(y))loss_func = 'categorical_crossentropy'epochs = 1000learning_rate = 0.1
nn = NeuralNetwork(input_size, hidden_size, output_size, loss_func)
trainer = Trainer(nn, loss_func)trainer.train(X_train, y_train, X_test, y_test, epochs, learning_rate)
# Convert y_test from one-hot encoding to labelsy_test_labels = np.argmax(y_test, axis=1)
# Evaluate the performance of the neural networkpredictions = np.argmax(nn.forward(X_test), axis=1)accuracy = np.mean(predictions == y_test_labels)print(f"Accuracy: {accuracy:.2%}")

让我们过一下每一个步骤:

加载数据集

# Load the digits datasetdigits = load_digits()


-数字数据集前 10 张图像

这里使用的数据集是数字数据集,通常用于识别手写数字的分类任务。

预处理数据集

# Preprocess the datasetscaler = MinMaxScaler()X = scaler.fit_transform(digits.data)y = digits.target

使用 MinMaxScaler 将数据集的特征缩放到 0 和 1 之间。这是一个常见的预处理步骤,可确保所有输入特征具有相同的比例,从而帮助神经网络更有效地学习。

缩放后的特征存储在 X 中,目标标签(每个图像代表的数字)存储在 y 中。

对目标输出进行one-hot编码

# One-hot encode the target outputencoder = OneHotEncoder(sparse=False)y_onehot = encoder.fit_transform(y.reshape(-1, 1))

由于这是一项包含多个类别的分类任务,因此使用 OneHotEncoder 对目标标签进行一次性编码。One-hot 编码将分类目标数据转换成神经网络更容易理解和处理的格式,尤其是在分类任务中。

分割数据集

# Split the dataset into training and testing setsX_train, X_test, y_train, y_test = train_test_split(X, y_onehot, test_size=0.2, random_state=42)

使用 train_test_split 将数据集分成训练集和测试集,其中 80% 的数据用于训练,20% 用于测试。通过这种拆分,可以在一部分数据上训练模型,然后在另一部分未见数据上评估其性能,以检查模型的泛化程度。

创建 NeuralNetwork 类的实例

# Create an instance of the NeuralNetwork classinput_size = X.shape[1]hidden_size = 64output_size = len(np.unique(y))loss_func = 'categorical_crossentropy'epochs = 1000learning_rate = 0.1
nn = NeuralNetwork(input_size, hidden_size, output_size, loss_func)

创建的神经网络实例具有指定的输入大小(特征数量)、隐藏大小(隐藏层中神经元的数量)、输出大小(唯一标签的数量)和要使用的损失函数。输入大小与特征个数相匹配,输出大小与唯一目标类别个数相匹配,并选择隐藏层大小。

训练神经网络

trainer = Trainer(nn, loss_func)trainer.train(X_train, y_train, X_test, y_test, epochs, learning_rate)

使用神经网络和损失函数创建训练类实例。然后调用 train 方法,调用训练数据集和测试数据集以及指定的历元数和学习率。这个过程会反复调整神经网络的权重和偏置,使用训练数据进行学习,使用测试数据进行验证,从而使损失函数最小化。

评估性能

# Convert y_test from one-hot encoding to labelsy_test_labels = np.argmax(y_test, axis=1)
# Evaluate the performance of the neural networkpredictions = np.argmax(nn.forward(X_test), axis=1)accuracy = np.mean(predictions == y_test_labels)print(f"Accuracy: {accuracy:.2%}")

训练结束后,在测试集上对模型的性能进行评估。由于目标是单次编码的,因此使用 np.argmax 将单次编码的预测结果转换回标签形式。通过比较这些预测标签和实际标签(y_test_labels),计算出模型的准确性,然后打印出来。

现在,这段代码还缺少我们提到的一些激活函数、SGD 或 ADAM 优化器等改进功能。我把这个机会留给你,让你用自己的代码填补空白,把这段代码变成你自己的代码。这样,你才能真正掌握神经网络。

4.2:利用库实现神经网络(TensorFlow)

嗯,这可真不少!幸运的是,我们不需要每次都写这么长的代码。我们可以利用 Tensorflow 和 PyTorch 等库,它们可以用最少的代码为我们创建深度学习模型。在本示例中,我们将创建并解释在数字数据集上训练神经网络的 TensorFlow 版本,与之前描述的过程类似。

和之前一样,我们首先导入所需的库和数据集,然后按照之前的方式对其进行预处理。

import tensorflow as tffrom sklearn.datasets import load_digitsfrom sklearn.model_selection import train_test_splitfrom sklearn.preprocessing import MinMaxScaler, OneHotEncoder
# Load the digits datasetdigits = load_digits()
# Scale the features to a range between 0 and 1scaler = MinMaxScaler()X_scaled = scaler.fit_transform(digits.data)
# One-hot encode the target labelsencoder = OneHotEncoder(sparse=False)y_onehot = encoder.fit_transform(digits.target.reshape(-1, 1))
# Split the dataset into training and testing setsX_train, X_test, y_train, y_test = train_test_split(X_scaled, y_onehot, test_size=0.2, random_state=42)

其次,让我们建立 NN:

# Define the model architecturemodel = tf.keras.models.Sequential([ tf.keras.layers.Dense(64, activation='relu', input_shape=(X_train.shape[1],)), tf.keras.layers.Dense(len(np.unique(digits.target)), activation='softmax')])

这里创建的是一个顺序模型,表示层的线性堆叠。

第一层是一个具有 64 个单元(神经元)和 ReLU 激活的密集连接层。它的输入来自形状 (X_train.shape[1],),与数据集中的特征数量相匹配。

输出层的单元数等于唯一目标类别的数量,并使用 softmax 激活函数输出每个类别的概率。

# Compile the modelmodel.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])

该模型采用 Adam 优化器和分类交叉熵作为损失函数,适用于多类分类任务。精确度被指定为评估指标。

最后,让我们来训练和评估一下我们的 NN 的性能:

# Train the modelhistory = model.fit(X_train, y_train, epochs=1000, validation_data=(X_test, y_test), verbose=2)
# Evaluate the model on the test settest_loss, test_accuracy = model.evaluate(X_test, y_test, verbose=2)print(f"Test accuracy: {test_accuracy:.2%}")

使用拟合方法对模型进行 1000 次历时训练,并将测试集作为验证数据。verbose=2 表示每个历元打印一行日志。

最后,使用 evaluate 方法在测试集上评估模型的性能,并打印测试准确率。

5: 挑战
5.1:克服过度拟合
过度拟合是指神经网络对训练数据过于痴迷,对所有微小的细节和噪音都了如指掌,以至于在处理新的、未见过的数据时举步维艰。这就好比你在考试前拼命学习,一字不差地背诵教科书,但却无法将所学应用到任何措辞不同的问题上。这个问题会影响模型在现实世界中的良好表现,而在现实世界中,将所学知识概括或应用到新场景中是关键所在。幸运的是,有几种巧妙的技术可以帮助防止或减少过拟合,使我们的模型更加灵活,为真实世界做好准备。让我们来看看其中的几种,不过不用担心现在就要掌握所有这些技术,因为我将在另一篇文章中介绍反过拟合技术。

Dropout:这就好比在训练过程中随机关闭网络中的一些神经元。它可以阻止神经元之间产生过多依赖,迫使网络学习更强大的功能,而不是仅仅依赖一组特定的神经元来进行预测。

早期停止
这包括在训练过程中观察模型在验证集(单独的数据块)上的表现。如果模型在验证集上的表现开始变差,这就表明模型开始过拟合,是时候停止训练了。

使用验证集
将数据分为三个集--训练集、验证集和测试集--有助于密切关注过拟合情况。验证集用于调整模型和挑选最佳版本,而测试集则可以让你对模型的表现进行公平的评估。

简化模型
有时,少即是多。如果模型过于复杂,可能会从训练数据中获取噪声。通过选择更简单的模型或减少层数,我们可以降低过度拟合的风险。

在你尝试使用 NN 时,你会发现微调和解决过拟合问题对 NN 的性能起着至关重要的作用。确保掌握反过拟合技术是成功数据科学家的必修课。鉴于其重要性,我将用一整篇文章来介绍这些技术,以确保您可以微调最佳的 NN,并保证您的项目获得最佳性能。

6: 结论

深入了解神经网络的世界,让我们看到了这些模型在人工智能领域蕴藏的巨大潜力。从神经网络如何使用加权和及激活函数来处理信息等基础知识开始,我们看到了反向传播和梯度下降等技术如何让神经网络从数据中学习。特别是在图像识别等领域,我们亲眼目睹了神经网络如何解决复杂的挑战并推动技术进步。

展望未来,很显然,我们才刚刚开始 "深度学习 "这一漫长的旅程。在接下来的文章中,我们将讨论更先进的深度学习架构、微调方法等!

Bibliograph参考书目

  1. Goodfellow, Ian, Yoshua Bengio, and Aaron Courville. “Deep Learning.” MIT Press, 2016. This comprehensive textbook provides an extensive overview of deep learning, covering the mathematical underpinnings and practical aspects of neural networks.

  2. LeCun, Yann, Yoshua Bengio, and Geoffrey Hinton. “Deep learning.” Nature 521, no. 7553 (2015): 436–444. A landmark paper by pioneers in the field, summarizing the key concepts and achievements in deep learning and neural networks.


原文链接

-Cristian Leo:https://medium.com/towards-data-science/the-math-behind-neural-networks-a34a51b93873


附录:𝕀²·ℙarad𝕚g𝕞智能平方范式研究

H𝕀:Humanity Intelligence [Sys1&2@BNN] 

A𝕀:Artifical Intelligence [LLM@ANN] 

𝕀²:H𝕀 𝕩 A𝕀 [bio- | silico-] 

ℙarad𝕚g𝕞:认知范式或BNN认知大模型 

A𝕀与H𝕀当前在玩一个语言游戏。A𝕀最大的问题是已知一点白外的未知的黑;H𝕀最大的问题是不断演进的sys2理性白中的sys1的黑


往期推荐





互为Prompt | 大型语言模型的预训练·GPT智能到底意味着什么?

AI平方范式智库·访谈系列E01S01 | AI大神Ilya访谈揭秘GPT-4成功背后的关键

AI平方范式智库·访谈系列E02S01 | Ilya的AGI信念-Feel the AGI

AI平方范式智库·访谈系列E03S01 | 从预训练模型到可靠可用AGI



扫码加群,

链接智库!


AI平方范式智库




那些prompt了我的,

是否也prompt了你...



继续滑动看下一个
AI2Paradigm
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存