微信营销手机网站模板,长春seo顾问,wordpress主题 yusi,抖音代运营交1600押金本节将从梯度下降法向外拓展#xff0c;介绍更常用的优化算法#xff0c;实现神经网络的学习和迭代。在本节课结束将完整实现一个神经网络训练的全流程。
对于像神经网络这样的复杂模型#xff0c;可能会有数百个 w w w的存在#xff0c;同时如果我们使用的是像交叉熵这样…本节将从梯度下降法向外拓展介绍更常用的优化算法实现神经网络的学习和迭代。在本节课结束将完整实现一个神经网络训练的全流程。
对于像神经网络这样的复杂模型可能会有数百个 w w w的存在同时如果我们使用的是像交叉熵这样复杂的损失函数机器学习中还有更多更加复杂的函数令所有权重的导数为0并一个个求解方程的难度很大、工作量也很大。因此我们转换思路不追求一步到位而使用 迭代 的方式逐渐接近损失函数的最小值。这就是优化算法的具体工作优化算法的相关知识也都是关于“逐步迭代到损失函数最小值”的具体操作。 41 梯度下降中的两个关键问题
简单复习一下梯度下降的核心流程。观察SSE和交叉熵损失 C E L o s s − ∑ i 1 m y i ( k j ) ln σ i 其中 σ s o f t m a x ( z ) , z W X S S E 1 m ∑ i 1 m ( z i − z ^ i ) 2 其中 z X w CELoss -\sum_{i1}^{m} y_{i(kj)} \ln \sigma_i \quad \text{其中} \quad \sigma softmax(z), \quad z WX \\ SSE \frac{1}{m} \sum_{i1}^{m} (z_i - \hat{z}_i)^2 \quad \text{其中} \quad z Xw CELoss−i1∑myi(kj)lnσi其中σsoftmax(z),zWXSSEm1i1∑m(zi−z^i)2其中zXw 不难发现任意损失函数中总是包含特征张量 X X X真实标签 y y y或 z z z权重矩阵或权重向量 w w w三个元素其中特征张量 X X X和真实标签来自我们的数据所以只要给定一组权重 w w w就一定能够得出损失函数的具体数值。
假设数据只有一个标签因此只有一个权重 w w w我们常常会绘制以 w w w为横坐标 L ( w ) L(w) L(w)为纵坐标的图像 在梯度下降最开始时我们会随机设定初始权重 w ( 0 ) w_{(0)} w(0)对应的纵坐标 L ( w 0 ) L(w_{0}) L(w0)就是初始的损失函数值坐标点 w ( 0 ) , L ( w ( 0 ) ) w_{(0)},L(w_{(0)}) w(0),L(w(0))就是梯度下降的起始点。
接下来我们从起始点开始让自变量 w w w向损失函数 L ( w ) L(w) L(w)减小最快的方向移动 。以上方的图像为例我们一眼就能看出减小损失函数最快的方向是横坐标的右侧也就是 w w w逐渐变大的方向但起始点是一个“盲人”它看不到也听不到全局每次走之前得用“拐杖”探探路确定下方向。
并且对于复杂的图像而言比如说下面这张图最开始时 L ( w ) L(w) L(w)减小最快的方向红色箭头指示的方向不一定指向函数真正的最小值。如果一条路走到黑最后反而可能找不到最小值点所以”盲人“起始点每次只敢走一小段距离。每走一段距离就要重新确认一下方向对不对走很多步之后才能够到达或接近损失函数的最小值。 在这个过程中每步的方向就是当前坐标点对应的梯度向量的反方向 每步的举例就是步长×当前坐标点所对应的梯度向量的大小也就是梯度向量的模长梯度向量的性质保证了沿着其反方向、按照其大小进行移动就能够接近损失函数的最小值。在这个过程中存在两个关键问题
怎么找出梯度向量的方向和大小怎么让坐标点按照梯度向量的反方向移动与梯度向量大小相等的距离 41.1 找出梯度向量的方向和大小 梯度向量是多元函数上各个自变量的偏导数组成的向量比如损失函数是 L ( w 1 , w 2 , b ) L(w_1,w_2,b) L(w1,w2,b)在损失函数上对 w 1 , w 2 , b w_1,w_2,b w1,w2,b这三个自变量求偏导数求得的梯度向量的表达式就是 [ ∂ L ∂ w 1 , ∂ L ∂ w 2 , ∂ L ∂ b ] T \left[ \frac{\partial L}{\partial w_1}, \frac{\partial L}{\partial w_2}, \frac{\partial L}{\partial b} \right]^T [∂w1∂L,∂w2∂L,∂b∂L]T简写为 g r a d L ( w 1 , w 2 ) grad L(w_1,w_2) gradL(w1,w2)或者 ∇ L ( w 1 , w 2 ) \nabla L(w_1,w_2) ∇L(w1,w2)。 对于任意向量来说其大小和方向都是依赖于向量元素具体的值而确定。比如向量12的方向就是从原点00指向点12的方向大小就是 1 2 2 2 \sqrt{1^22^2} 1222 向量的大小也叫模长梯度向量的大小和方向也是如此计算。梯度向量中的具体元素就是各个自变量的偏导数这些偏导数的具体值必须依赖于当前所在坐标点的值进行计算。 所以
梯度的大小和方向对每个坐标点而言是独一无二的。坐标点一旦变化梯度向量的方向和大小也会变化。每走到一个新的点读取该点的坐标并带入梯度向量的表达式进行计算就可以得到当前点对应的梯度向量的方向和大小。
而我们现在的坐标空间是由损失函数 L ( w ) L(w) L(w)和权重 w w w构成只要得到一组坐标值就可以求解当前的梯度向量。这一步骤中最大的难点在于如何获得梯度向量的表达式——也就是损失函数对各个自变量求偏导后的表达式。 41.2 让坐标点移动起来进行一次迭代
有了大小和方向来看第二个问题怎么让坐标点按照梯度向量的反方向移动与梯度向量大小相等的距离
假设现在我们有坐标点A(10, 7.5)向量 g ⃗ \vec{g} g 为(5, 5)大小为 5 2 5\sqrt{2} 52 。现在我们让点A向 g ⃗ \vec{g} g 的反方向移动 5 2 5\sqrt{2} 52 的距离如图所示 首先将向量和点都展示在同一坐标系中然后找出向量反方向、同等长度的向量红色箭头。然后将反方向的向量平移让其起点落在A点上。平移过后反方向的向量所指向的新的点被命名为 从A点到A‘点的过程就是让A向 g ⃗ \vec{g} g 的反方向移动 5 2 5\sqrt{2} 52 的距离的结果。不难发现其实从A点移动到A’的过程改变的是A的两个坐标值——两个坐标值分别被减去了5就得到了A‘点。
现在假设我们的初始点是 w 1 ( 0 ) w 2 ( 0 ) w_{1(0)}w_{2(0)} w1(0)w2(0)梯度向量是 ∂ L ∂ w 1 , ∂ L ∂ w 2 \frac{\partial L}{\partial w_1}, \frac{\partial L}{\partial w_2} ∂w1∂L,∂w2∂L那让坐标点按照梯度向量的反方向移动的方法如下 w 1 ( 1 ) w 1 ( 0 ) − ∂ L ∂ w 1 w 2 ( 1 ) w 2 ( 0 ) − ∂ L ∂ w 2 w_{1(1)} w_{1(0)} - \frac{\partial L}{\partial w_1} \\ w_{2(1)} w_{2(0)} - \frac{\partial L}{\partial w_2} w1(1)w1(0)−∂w1∂Lw2(1)w2(0)−∂w2∂L 将两个 w w w写在同一个权重向量里用 t t t代表走到了第 t t t步即进行第 t t t次迭代则有 w ( t 1 ) w ( t ) − ∂ L ∂ w w_{(t1)} w_{(t)} - \frac{\partial L}{\partial w} w(t1)w(t)−∂w∂L 为了控制每次走的距离的大小我们将步长 η \eta η又叫做学习率加入公式就可以将上面的式子改写为 w ( t 1 ) w ( t ) − η ∂ L ∂ w w_{(t1)} w_{(t)} - \eta \frac{\partial L}{\partial w} w(t1)w(t)−η∂w∂L 这就是我们迭代权重的迭代公式其中偏导数的大小影响整体梯度向量的大小偏导数前的减号影响整体梯度向量的方向。 当我们将 设置得很大梯度下降的每一个步子就迈得很大走得很快当我们将步长设置较小梯度下降就走得很慢。我们使用一个式子就同时控制了大小和方向。
当然了一旦迭代 我们的损失函数也会发生变化。在整个损失函数的图像上红色箭头就是梯度向量的反方向灰色箭头就是每个权重对应的迭代。 42 找出距离和方向反向传播
42.1 反向传播的定义与价值
在梯度下降的最初我们需要先找出坐标点对应的梯度向量。梯度向量是各个自变量求偏导后的表达式再带入坐标点计算出来的在这一步骤中最大的难点在于如何获得梯度向量的表达式——也就是损失函数对各个自变量求偏导后的表达式。在单层神经网络例如逻辑回归二分类单层神经网络中我们有如下计算 其中BCEloss是二分类交叉熵损失函数。在这个计算图中从左向右计算以此的过程就是正向传播因此进行以此计算后我们会获得所有节点上的张量的值z、sigma以及loss 。根据梯度向量的定义在这个计算过程中我们要求的是损失函数对 w w w的导数所以求导过程需要涉及到的链路如下 用公式来表示则为在以下式子上求解对 w w w的导数 ∂ L o s s ∂ w , 其中 L o s s − ∑ i 1 m ( y i ⋅ ln ( σ i ) ( 1 − y i ) ⋅ ln ( 1 − σ i ) ) − ∑ i 1 m ( y i ⋅ ln ( 1 1 e − X i w ) ( 1 − y i ) ⋅ ln ( 1 − 1 1 e − X i w ) ) \begin{align*} \frac{\partial Loss}{\partial w}, \text{其中} \\ Loss -\sum_{i1}^{m} (y_i \cdot \ln(\sigma_i) (1 - y_i) \cdot \ln(1 - \sigma_i)) \\ -\sum_{i1}^{m} \left( y_i \cdot \ln\left(\frac{1}{1 e^{-X_i w}}\right) (1 - y_i) \cdot \ln\left(1 - \frac{1}{1 e^{-X_i w}}\right) \right) \end{align*} ∂w∂Loss,其中Loss−i1∑m(yi⋅ln(σi)(1−yi)⋅ln(1−σi))−i1∑m(yi⋅ln(1e−Xiw1)(1−yi)⋅ln(1−1e−Xiw1)) 可以看出已经很复杂了。
更夸张的是在双层的、各层激活函数都是sigmoid的二分类神经网络上我们有如下计算流程 同样的进行从左到右的正向传播之后我们会获得所有节点上的张量。其中涉及到的求导链路如下 用公式来表示对 w ( 1 → 2 ) w^{(1\rightarrow2)} w(1→2)我们有 ∂ L o s s ∂ w ( 1 → 2 ) , 其中 L o s s − ∑ i 1 m ( y i ⋅ ln ( σ i ( 2 ) ) ( 1 − y i ) ⋅ ln ( 1 − σ i ( 2 ) ) ) − ∑ i 1 m ( y i ⋅ ln ( 1 1 e − σ i ( 1 ) w ( 1 → 2 ) ) ( 1 − y i ) ⋅ ln ( 1 − 1 1 e − σ i ( 1 ) w ( 1 → 2 ) ) ) \frac{\partial Loss}{\partial w^{(1\rightarrow 2)}}, \text{其中} \\ Loss -\sum_{i1}^{m} \left( y_i \cdot \ln(\sigma_i^{(2)}) (1 - y_i) \cdot \ln(1 - \sigma_i^{(2)}) \right) \\ -\sum_{i1}^{m} \left( y_i \cdot \ln\left(\frac{1}{1 e^{-\sigma_i^{(1)} w^{(1\rightarrow 2)}}}\right) (1 - y_i) \cdot \ln\left(1 - \frac{1}{1 e^{-\sigma_i^{(1)} w^{(1\rightarrow 2)}}}\right) \right) ∂w(1→2)∂Loss,其中Loss−i1∑m(yi⋅ln(σi(2))(1−yi)⋅ln(1−σi(2)))−i1∑m(yi⋅ln(1e−σi(1)w(1→2)1)(1−yi)⋅ln(1−1e−σi(1)w(1→2)1)) 对 w ( 0 → 1 ) w^{(0\rightarrow1)} w(0→1)我们有 ∂ L o s s ∂ w ( 0 → 1 ) 其中 L o s s − ∑ i 1 m ( y i ⋅ ln ( σ i ( 2 ) ) ( 1 − y i ) ⋅ ln ( 1 − σ i ( 2 ) ) ) − ∑ i 1 m ( y i ⋅ ln ( 1 1 e − 1 1 e − X i w ( 0 → 1 ) w ( 1 → 2 ) ) ( 1 − y i ) ⋅ ln ( 1 − 1 1 e − 1 1 e − X i w ( 0 → 1 ) w ( 1 → 2 ) ) ) \frac{\partial Loss}{\partial w^{(0 \rightarrow 1)}} \\ 其中 Loss -\sum_{i1}^{m}(y_i \cdot \ln(\sigma_i^{(2)}) (1 - y_i) \cdot \ln(1 - \sigma_i^{(2)})) \\ -\sum_{i1}^{m}(y_i \cdot \ln(\frac{1}{1 e^{-\frac{1}{1 e^{-X_i w^{(0 \rightarrow 1)}} w^{(1 \rightarrow 2)}}}}) (1 - y_i) \cdot \ln(1 - \frac{1}{1 e^{-\frac{1}{1 e^{-X_i w^{(0 \rightarrow 1)}} w^{(1 \rightarrow 2)}}}})) ∂w(0→1)∂Loss其中Loss−i1∑m(yi⋅ln(σi(2))(1−yi)⋅ln(1−σi(2)))−i1∑m(yi⋅ln(1e−1e−Xiw(0→1)w(1→2)11)(1−yi)⋅ln(1−1e−1e−Xiw(0→1)w(1→2)11)) 对于需要对这个式子求导大家感受如何而这只是一个两层的二分类神经网络对于复杂神经网络来说所需要做得求导工作是无法想象的。 求导过程的复杂是神经网络历史上的一大难题这个难题直到1986年才真正被解决。1986年Rumelhart、Hinton和Williams提出了反向传播算法Backpropagation algorithm又叫做Delta法则利用链式法则成功实现了复杂网络求导过程的简单化。 接下来我们就来看看反向传播是怎么解决复杂求导问题的。
在高等数学中存在着如下规则 假设有函数 u h ( z ) , z f ( w ) , 且两个函数在各自自变量的定义域上都可导则有 ∂ u ∂ w ∂ u ∂ z ⋅ ∂ z ∂ w 假设有函数u h(z),z f(w),且两个函数在各自自变量的定义域上都可导则有 \\ \frac{\partial u}{\partial w} \frac{\partial u}{\partial z} \cdot \frac{\partial z}{\partial w} 假设有函数uh(z),zf(w),且两个函数在各自自变量的定义域上都可导则有∂w∂u∂z∂u⋅∂w∂z 感性但不严谨地来说当一个函数是由多个函数嵌套而成最外层函数向最内层自变量求导的值等于外层函数对外层自变量求导的值 ***** 内层函数对内层自变量求导的值。 这就是链式法则。当函数之间存在复杂的嵌套关系并且我们需要从最外层的函数向最内层的自变量求导时链式法则可以让求导过程变得异常简单。 我们可以用链式法则将 ∂ L o s s ∂ w ( 1 → 2 ) \frac{\partial Loss}{\partial w^{(1\rightarrow 2)}} ∂w(1→2)∂Loss拆解为如下结构 ∂ L o s s ∂ w ( 1 → 2 ) ∂ L ( σ ) ∂ σ ∗ ∂ σ ( z ) ∂ z ∗ ∂ z ( w ) ∂ w 其中 ∂ L ( σ ) ∂ σ ∂ ( − ∑ i 1 m ( y i ∗ ln ( σ i ) ( 1 − y i ) ∗ ln ( 1 − σ i ) ) ) ∂ σ ∑ i 1 m ∂ ( − ( y i ∗ ln ( σ i ) ( 1 − y i ) ∗ ln ( 1 − σ i ) ) ) ∂ σ 求导不影响加和符号因此暂时不看加和符号 − ( y ∗ 1 σ ( 1 − y ) ∗ 1 1 − σ ∗ ( − 1 ) ) − ( y σ y − 1 1 − σ ) − ( y ( 1 − σ ) ( y − 1 ) σ σ ( 1 − σ ) ) − ( y − y σ y σ − σ σ ( 1 − σ ) ) σ − y σ ( 1 − σ ) \frac{\partial Loss}{\partial w^{(1 \rightarrow 2)}} \frac{\partial L(\sigma)}{\partial \sigma} * \frac{\partial \sigma(z)}{\partial z} * \frac{\partial z(w)}{\partial w} \\ 其中 \\ \frac{\partial L(\sigma)}{\partial \sigma} \frac{\partial \left(-\sum_{i1}^{m}(y_i * \ln(\sigma_i) (1 - y_i) * \ln(1 - \sigma_i))\right)}{\partial \sigma} \\ \sum_{i1}^{m} \frac{\partial \left(-(y_i * \ln(\sigma_i) (1 - y_i) * \ln(1 - \sigma_i))\right)}{\partial \sigma} \\ 求导不影响加和符号因此暂时不看加和符号 \\ -(y * \frac{1}{\sigma} (1 - y) * \frac{1}{1 - \sigma} * (-1)) \\ -\left(\frac{y}{\sigma} \frac{y - 1}{1 - \sigma}\right) \\ -\left(\frac{y(1 - \sigma) (y - 1)\sigma}{\sigma(1 - \sigma)}\right) \\ -\left(\frac{y - y\sigma y\sigma - \sigma}{\sigma(1 - \sigma)}\right) \\ \frac{\sigma - y}{\sigma(1 - \sigma)} ∂w(1→2)∂Loss∂σ∂L(σ)∗∂z∂σ(z)∗∂w∂z(w)其中∂σ∂L(σ)∂σ∂(−∑i1m(yi∗ln(σi)(1−yi)∗ln(1−σi)))i1∑m∂σ∂(−(yi∗ln(σi)(1−yi)∗ln(1−σi)))求导不影响加和符号因此暂时不看加和符号−(y∗σ1(1−y)∗1−σ1∗(−1))−(σy1−σy−1)−(σ(1−σ)y(1−σ)(y−1)σ)−(σ(1−σ)y−yσyσ−σ)σ(1−σ)σ−y 假设我们已经进行过以此正向传播那此时的 σ \sigma σ就是 σ ( 2 ) \sigma^{(2)} σ(2) y y y就是真实标签我们可以很容易计算出 σ − y σ ( 1 − σ ) \frac{\sigma - y}{\sigma(1 - \sigma)} σ(1−σ)σ−y的数值。
再来看剩下的两部分 ∂ σ ( z ) ∂ z ∂ 1 1 e − z ∂ z ∂ ( 1 e − z ) − 1 ∂ z − 1 ⋅ ( 1 e − z ) − 2 ⋅ e − z ⋅ ( − 1 ) e − z ( 1 e − z ) 2 1 e − z − 1 ( 1 e − z ) 2 1 e − z ( 1 e − z ) 2 − 1 ( 1 e − z ) 2 1 1 e − z − 1 ( 1 e − z ) 2 1 1 e − z ( 1 − 1 1 e − z ) σ ( 1 − σ ) \frac{\partial \sigma(z)}{\partial z} \frac{\partial \frac{1}{1 e^{-z}}}{\partial z} \\ \frac{\partial (1 e^{-z})^{-1}}{\partial z} \\ -1 \cdot (1 e^{-z})^{-2} \cdot e^{-z} \cdot (-1) \\ \frac{e^{-z}}{(1 e^{-z})^2} \\ \frac{1 e^{-z} - 1}{(1 e^{-z})^2} \\ \frac{1 e^{-z}}{(1 e^{-z})^2} - \frac{1}{(1 e^{-z})^2} \\ \frac{1}{1 e^{-z}} - \frac{1}{(1 e^{-z})^2} \\ \frac{1}{1 e^{-z}} \left(1 - \frac{1}{1 e^{-z}}\right) \\ \sigma(1 - \sigma) ∂z∂σ(z)∂z∂1e−z1∂z∂(1e−z)−1−1⋅(1e−z)−2⋅e−z⋅(−1)(1e−z)2e−z(1e−z)21e−z−1(1e−z)21e−z−(1e−z)211e−z1−(1e−z)211e−z1(1−1e−z1)σ(1−σ) 此时的 σ \sigma σ还是 σ ( 2 ) \sigma^{(2)} σ(2)。接着 ∂ z ( w ) ∂ w ∂ σ ( 1 ) w ∂ w σ ( 1 ) \frac{\partial z(w)}{\partial w} \frac{\partial \sigma^{(1)} w}{\partial w} \\ \sigma^{(1)} ∂w∂z(w)∂w∂σ(1)wσ(1) 对任意一个特征权重 w w w而言 ∂ z ( w ) ∂ w \frac{\partial z(w)}{\partial w} ∂w∂z(w)的值就等于其对应的输入值所以如果是对于单层逻辑回归而言这里的求导结果应该是 x x x。不过现在我们是对于双层神经网络的输出层而言所以这个输入就是从中间层传过来的 σ 1 \sigma^1 σ1。现在将三个导数公式整合 ∂ L o s s ∂ w ( 1 → 2 ) ∂ L ( σ ) ∂ σ ∗ ∂ σ ( z ) ∂ z ∗ ∂ z ( w ) ∂ w σ ( 2 ) − y σ 2 ( 1 − σ ( 2 ) ) ∗ σ ( 2 ) ( 1 − σ ( 2 ) ) ∗ σ ( 1 ) σ ( 1 ) ( σ ( 2 ) − y ) \frac{\partial Loss}{\partial w^{(1 \rightarrow 2)}} \frac{\partial L(\sigma)}{\partial \sigma} * \frac{\partial \sigma(z)}{\partial z} * \frac{\partial z(w)}{\partial w} \\ \frac{\sigma^{(2)} - y}{\sigma^2 (1 - \sigma^{(2)})} * \sigma^{(2)} (1 - \sigma^{(2)}) * \sigma^{(1)} \\ \sigma^{(1)} (\sigma^{(2)} - y) ∂w(1→2)∂Loss∂σ∂L(σ)∗∂z∂σ(z)∗∂w∂z(w)σ2(1−σ(2))σ(2)−y∗σ(2)(1−σ(2))∗σ(1)σ(1)(σ(2)−y) 可以发现将三个偏导数相乘之后得到的最终的表达式其实非常简单。并且其中所需要的数据都是我们在正向传播过程中已经计算出来的节点上的张量。同理我们也可以得到对 w ( 0 → 1 ) w^{(0\rightarrow1)} w(0→1)的导数。
根据链式法则就有 ∂ L o s s ∂ w ( 0 → 1 ) ∂ L ( σ ) ∂ σ ( 2 ) ∗ ∂ σ ( z ) ∂ z ( 2 ) ∗ ∂ z ( σ ) ∂ σ ( 1 ) ∗ ∂ σ ( z ) ∂ z ( 1 ) ∗ ∂ z ( w ) ∂ w ( 0 → 1 ) \frac{\partial Loss}{\partial w^{(0 \rightarrow 1)}} \frac{\partial L(\sigma)}{\partial \sigma^{(2)}} * \frac{\partial \sigma(z)}{\partial z^{(2)}} * \frac{\partial z(\sigma)}{\partial \sigma^{(1)}} * \frac{\partial \sigma(z)}{\partial z^{(1)}} * \frac{\partial z(w)}{\partial w^{(0 \rightarrow 1)}} ∂w(0→1)∂Loss∂σ(2)∂L(σ)∗∂z(2)∂σ(z)∗∂σ(1)∂z(σ)∗∂z(1)∂σ(z)∗∂w(0→1)∂z(w)
其中前两项是在求解 w ( 1 → 2 ) w^{(1 \rightarrow 2)} w(1→2)时求解过的而后三项的求解结果都显而易见 ( σ ( 2 ) − y ) ∗ ∂ z ( σ ) ∂ σ ( 1 ) ∗ ∂ σ ( z ) ∂ z ( 1 ) ∗ ∂ z ( w ) ∂ w ( 0 → 1 ) ( σ ( 2 ) − y ) ∗ w 1 → 2 ∗ ( σ ( 1 ) ( 1 − σ ( 1 ) ) ) ∗ X (\sigma^{(2)} - y) * \frac{\partial z(\sigma)}{\partial \sigma^{(1)}} * \frac{\partial \sigma(z)}{\partial z^{(1)}} * \frac{\partial z(w)}{\partial w^{(0 \rightarrow 1)}} \\ (\sigma^{(2)} - y) * w^{1 \rightarrow 2} * (\sigma^{(1)}(1 - \sigma^{(1)})) * X (σ(2)−y)∗∂σ(1)∂z(σ)∗∂z(1)∂σ(z)∗∂w(0→1)∂z(w)(σ(2)−y)∗w1→2∗(σ(1)(1−σ(1)))∗X 同样这个表达式现在变得非常简单并且这个表达式中所需要的全部张量都是我们在正向传播中已经计算出来储存好的或者再模型建立之初就设置好的因此在计算 w ( 0 → 1 ) w^{(0\rightarrow1)} w(0→1)的导数时无需再重新计算如 σ ( 2 ) \sigma^{(2)} σ(2)这样的张量这就为神经网络计算导数节省了时间。你是否注意到我们是从左向右从输出向输入逐渐往前求解导数的表达式并且我们所使用的节点上的张量也是从后向前逐渐用到这和我们正向传播的过程完全相反。
这种从左到右不断使用正向传播中的元素对梯度向量进行计算的方式就是反向传播。 42.2 Pytorch实现反向传播
在梯度下降中每走一步都需要更新梯度所以计算量是巨大的。幸运的是PyTorch可以帮助我们自动计算梯度我们只需要提取梯度向量的值来进行迭代就可以了。在PyTorch中我们有两种方式实现梯度计算。一种是使用我们之前已经学过的AutoGrad。在使用AutoGrad时我们可以使用torch.autograd.grad()函数计算出损失函数上具体某个点/某个变量的导数当我们需要了解具体某个点的导数值时autograd会非常关键比如
import torch
# requires_grad表示允许对X进行梯度计算
x torch.tensor(1.,requires_grad True)
y x ** 2#这里返回的是在函数yx**2上x1时的导数值。
torch.autograd.grad(y, x) 对于单层神经网络autograd.grad会非常有效。但深层神经网络就不太适合使用grad函数了。对于深层神经网络我们需要一次性计算大量权重对应的导数值并且这些权重是以层为单位组织成一个个权重的矩阵要一个个放入autograd来进行计算有点麻烦。所以我们会直接使PyTorch提供的基于autograd的反向传播功能lossfunction.backward()来进行计算。
注意在实现反向传播之前首先要完成模型的正向传播并且要定义损失函数因此我们会借助之前的课程中我们完成的三层神经网络的类和数据500行20个特征的随机数据来进行正向传播。
我们来看具体的代码
# 导入库、数据、定义神经网络类完成正向传播
import torch
import torch.nn as nn
from torch.nn import functional as F# 确定数据
torch.manual_seed(420)
X torch.rand((500, 20), dtype torch.float32)*100
y torch.randint(low 0, high 3, size (500, 1), dtype torch.float32)# 定义神经网络的架构
class Model(nn.Module):def __init__(self, in_features, out_features):super(Model,self)__init__()self.linear1 nn.Linear(in_features, 13, bias True)self.linear2 nn.Linear(13, 8, bias True)self.output nn.Linear(8, out_features, bias True)def forward(self, x):z1 self.linear(x)sigma1 torch.relu(z1)z2 self.linear2(sigma1)sigma2 torch.sigmoid(z2)zhat self.output(sigma2)这是一个三分类的神经网络因此需要调用的损失函数为多分类交叉熵CELCEL类已经内置了sigmoid功能因此删除输出层上的sigmoid函数并将最终输出改为zhat# sigma3 F.softmax(z3, dim 1)return zhatinput_ X.shape[1] # 特征数目
output_ len(y.unique()) # 分类数目# 实例化神经网络
torch.manual_seed(420)
net Model(in_features input_, out_features output_)# 前向传播
zhat net.forward(X)# 定义损失函数
criterion nn.CrossEntropyLoss()
loss criterion(zhat, y.shape(500).long())# 不会返回任何值
net.linear1.weight.grad# 反向传播backward是任意损失函数类都可以调用的方法对任意损失函数backward都会求解其中全部w的梯度
loss.backward()# 返回相应的梯度
net.linear1.weight.grad# 与可以重复进行的正向传播不同一次正向传播后反向传播只能进行一次
# 如果希望能够重复进行反向传播可以在进行第一次反向传播的时候加上参数retain_graph
loss.backward(retain_graphTrue)backward求解出的结果的结构与对应的权重矩阵的结构一模一样因为一个权重就对应了一个偏导数。
唯一需要说明的点是在使用autograd的时候我们强调了requires_grad的用法但在定义打包好的类以及使用loss.backward()的时候我们却没有给任何数据定义requires_gradTrue。这是因为 当使用nn.Module继承后的类进行正向传播时我们的权重 w w w是自动生成的在生成时就被自动设置为允许计算梯度requires_gradTrue所以不需要我们自己去设置 同时观察我们的反向传播过程 不难发现我们的特征张量 X X X与真实标签 y y y都不在反向传播的过程当中但是 X X X与 y y y其实都是损失函数计算需要用的值在计算图上这些值都位于叶子节点上我们在定义损失函数时并没有告诉损失函数哪些值是自变量哪些是常数那backward函数是怎么判断具体求解哪个对象的梯度的呢? 其实就是靠requires_grad。首先backward值会识别叶子节点不在叶子上的变量是不会被backward考虑的。对于全部叶子节点来说只有属性requires_gradTrue的节点才会被计算。在设置 X X X与 y y y时我们都没有写requires_grad参数也就是默认让“允许求解梯度”这个选项为False所以backward在计算的时候就只会计算关于 w w w的部分。 当然我们也可以将 X X X和 y y y或者任何除了权重以及截距的量的requires_grad打开一旦我们设置为Truebackward就会在帮助我们计算 的导数的同时也帮我们计算以 X X X或 y y y为自变量的导数。在正常的梯度下降和反向传播过程中我们是不需要这些导数的因此我们一律不去管requires_grad的设置就让它默认为False以节约计算资源。当然如果你的 w w w是自己设置的千万记得一定要设置requires_gradTrue。 43 移动坐标点
43.1 走出第一步
有了大小和方向接下来就可以开始走出我们的第一步了。来看权重的迭代公式 w ( t 1 ) w ( t ) − η ∂ L ( w ) ∂ w \mathbf{w}_{(t1)} \mathbf{w}_{(t)} - \eta \frac{\partial L(\mathbf{w})}{\partial \mathbf{w}} w(t1)w(t)−η∂w∂L(w) 现在我们的偏导数部分已经计算出来了就是我们使用backward求解出的结果。而 η \eta η学习率或者步长是我们在迭代开始之前就人为设置好的一般是0.01~0.5之间的某个小数。因此现在我们已经可以无障碍地使用代码实现权重的迭代了
# 在这里数据是生成的随机数为了显示效果设置了步长为10正常不会使用这么大的步长
# 步长、学习率的英文是learning rate所以常常简写为lr
lr 10dw net.linear1.weight.grad
w net.linear1.weight.data# 对任意w可以有
w - lr * dw普通梯度下降就是在重复正向传播、计算梯度、更新权重的过程但这个过程往往非常漫长。如大家所见步长设置为0.001时我们看不到 w w w任何的变化只有当步长设置得非常巨大我们才能够看到一些改变但是巨大的步长可能会让我们跳过真正的最小值所以我们无法将步长设置得很大无论如何梯度下降都是一个缓慢的过程。
在这个基础上我们提出了加速迭代的数个方法其中一个很关键的方法就是使用动量Momentum。 43.2 从第一步到第二步动量法Momentum
之前我们说过在梯度下降过程中起始点是一个“盲人”它看不到也听不到全局所以我们每移动一次都要重新计算方向与距离并且每次只能走一小步。但不只限于此起始点不仅看不到前面的路也无法从过去走的路中学习。 想象一下我们被蒙上眼睛由另一个人喊口号来给与我们方向让我们移动假设喊口号的人一直喊”向前向前向前。“因为我们看不见在最初的三四次我们可能都只会向前走一小步但如果他一直让我们向前我们就会失去耐心转而向前走一大步因为我们可以预测前面很长一段路大概都是需要向前的。对梯度下降来说也是同样的道理——如果在很长一段时间内起始点一直向相似的方向移动那按照步长一小步一小步地磨着向前是没有意义的既浪费计算资源又浪费时间此时就应该大胆地照着这个方向走一大步。相对的如果我们很长时间都走向同一个方向突然让我们转向那我们转向的第一步就应该非常谨慎走一小步。 不难发现真正高效的方法是在历史方向与现有方向相同的情况下迈出大步子在历史方向与现有方向相反的情况下迈出小步子。那要怎么才能让起始点了解过去的方向呢我们让上一步的梯度向量与现在这一点的梯度向量以加权的方式求和求解出受到上一步大小和方向影响的真实下降方向再让坐标点向真实下降方向移动。 在坐标轴上可以表示为 其中对上一步的梯度向量加上的权重被称为动量参数也叫做衰减力度通常使用 γ \gamma γ进行表示对这一点的梯度向量加上的权重就是步长依然表示为 η \eta η真实移动的向量为 v v v被称为”动量“Momentum。将上述过程使用公式表示则有 v ( t ) γ v ( t − 1 ) − η ∂ L ∂ w w ( t 1 ) w ( t ) v ( t ) v_{(t)} \gamma v_{(t-1)} - \eta \frac{\partial L}{\partial \mathbf{w}} \\ \mathbf{w}_{(t1)} \mathbf{w}_{(t)} v_{(t)} v(t)γv(t−1)−η∂w∂Lw(t1)w(t)v(t) 在第一步中没有历史梯度方向因此第一步的真实方向就是起始点梯度的反方向 v 0 0 v_00 v00。其中 v ( t − 1 ) v_{(t-1)} v(t−1)代表了之前所有步骤所累积的动量和。在这种情况下梯度下降的方向有了“惯性”受到历史累计动量的影响当新坐标点的梯度反方向与历史累计动量的方向一致时历史累计动量会加大实际方向的步子相反当新坐标点的梯度反方向与历史累计动量的方向不一致时历史累计动量会减小实际方向的步子。
我们可以很容易地在PyTorch中实现动量法
lr 0.1 # 学习率
gamma 0.9 # 衰减力度dw net.linear1.weight.grad
w net.linear1.weight.data
# v要能够跟dw相减因此必须和dw保持相同的结构初始v为0但后续v会越来越大
v torch.zeros(dw.shape[0],dw.shape[1])# 对任意w可以有
v gamma * v - lr * dw
w-v当加入gamma之后即便是较小的步长也可以让 w w w发生变化 43.3 torch.optim实现带动量的梯度下降
在PyTorch库的架构中拥有专门实现优化算法的模块torch.optim。之前所说的迭代流程都可以通过torch.optim模块来简单地实现。 接下来我们就基于之前定义的类Model来实现梯度下降的一轮迭代
# 导入库
import torch
import torch.nn.as nn
import torch.optim as optim
from torch.nn import functional as F# 确定数据、确定优先需要设置的值
lr 0.1
gamma 0.9torch.manual_seed(420)
X torch.rand((500, 20), dtype float32) * 100
y torch.randint(low 0, high 3, size (500, 1), dtype torch.float32)input_ X.shape[1] # 特征的数目
output_ len(y.unique()) # 分类的数目# 定义神经网络的架构
class Model(nn.Module):def __init__(self, in_features, out_features):super(Model, self).__init__()self.linear1 nn.Linear(in_features, 13, bias True)self.linear2 nn.Linear(13, 8, bias True)self.output nn.Linear(8, out_features, bias True)def forward(self, x):z1 self.linear1(x)sigma1 torch.relu(z1)z2 self.linear2(sigma1)sigma2 torch.sigmoid(z2)z3 self.output(sigma2)# sigma3 F.softmax(z3, dim 1)return z3# 实例化神经网络调用优化算法需要的参数
torch.manual_seed(420)
net Model(in_features input_, out_features output_)# 定义损失函数
criterion nn.CrossEntropyLoss()# 定义优化算法
opt optim.SGD(net.parameters(), lr lr, momentum gamma)
# net.parameters():一次性导出所有神经网络架构下全部的权重和截距接下来开始进行一轮梯度下降
# 向前传播
zhat net.forward(X)
# 损失函数值
loss criterion(zhat, y.reshape(500).long())
# 反向传播
loss.backward()
# 更新权重w从这一瞬间开始坐标点就发生了变化所有的梯度必须重新计算
opt.step() #更新w和v
# 清除原来储存好的基于上一个坐标点计算的梯度为下一次计算梯度腾出空间
opt.zero_grad()44 开始迭代batch_size与epoches
44.1 为什么要有小批量
在实现一轮梯度下降之后只要在梯度下降的代码外面加上一层循环就可以顺利实现迭代多次的梯度下降了。但在那之前还有另外一个问题。为了提升梯度下降的速度我们在使用了动量法同时我们也要在使用的数据上下功夫。
在深度学习的世界中神经网络的训练对象往往是图像、文字、语音、视频等非结构化数据这些数据的特点之一就是特征张量一般都是大量高维的数据。比如在深度学习教材中总是被用来做例子的入门级数据MNIST其训练集的结构为(60000,784)。对比机器学习中的入门级数据鸢尾花结构为(150,4),两者完全不在一个量级上。在深度学习中如果梯度下降的每次迭代都使用全部数据将会非常耗费计算资源且样本量越大计算开销越高。虽然PyTorch被设计成天生能够处理巨量数据但我们还是需要在数据量这一点上下功夫。
这一节我们开始介绍小批量随机梯度下降mini-batch stochastic gradient descent简写为mini-batch SGD。
小批量随机梯度下降是深度学习入门级的优化算法梯度下降是入门级之下的其求解与迭代流程与传统梯度下降GD基本一致不过二者在迭代权重时使用的数据 这一点上存在巨大的不同。
传统梯度下降在每次进行权重迭代即循环时都使用全部数据 每次迭代所使用的数据也都一致。而mini-batch SGD是每次迭代前都会从整体采样一批固定数目的样本组成批次batch B B B并用 B B B中的样本进行梯度计算以减少样本量。 为什么会选择mini-batch SGD作为神经网络的入门级优化算法呢 有两个比较主流的原因。第一个是比起传统梯度下降mini-batch SGD更可能找到全局最小值 。
梯度下降是通过最小化损失函数来找对应参数向量的优化算法。对于任意损失函数 L ( w ) L(w) L(w)而言如果 L ( w ) L(w) L(w)在其他点上的值比在 w ∗ w^* w∗上的值更小那么 L ( w ∗ ) L(w^*) L(w∗)很可能就是一个局部最小值local minimum。
如果 L ( w ) L(w) L(w)在 w ∗ w^* w∗上的值是目标函数在整个定义域上的最小值那么 L ( w ∗ ) L(w^*) L(w∗)就是全局最小值global minimum。 尽可能找到全局最优一直都是优化算法的目标。为什么说mini-batch SGD更容易找到全局最优呢 传统梯度下降是每次迭代时都使用全部数据的梯度下降所以每次使用的数据是一致的因此梯度向量的方向和大小都只受到权重 w w w的影响所以梯度方向的变化相对较小很多时候看起来梯度甚至是指向一个方向如上图所示。这样带来的优势是可以使用较大的步长快速迭代直到找到最小值。但是缺点也很明显由于梯度方向不容易发生巨大变化所以一旦在迭代过程中落入局部最优的范围传统梯度下降就很难跳出局部最优再去寻找全局最优解了。
而mini-batch SGD在每次迭代前都会随机抽取一批数据所以每次迭代时带入梯度向量表达式的数据是不同的梯度的方向同时受到系数 w , b w,b w,b和带入的训练数据的影响因此每次迭代时梯度向量的方向都会发生较大变化。并且当抽样的数据量越小本次迭代中使用的样本数据与上一次迭代中使用的样本数据之间的差异就可能越大这也导致本次迭代中梯度的方向与上一次迭代中梯度的方向有巨大差异。所以对于mini-batch SGD而言它的梯度下降路线看起来往往是曲折的折线如上图所示。
极端情况下当我们每次随机选取的批量中只有一个样本时梯度下降的迭代轨迹就会变得异常不稳定如上图所示。我们称这样的梯度下降为随机梯度下降stochastic gradient descentSGD。
mini-batch SGD的优势是算法不会轻易陷入局部最优由于每次梯度向量的方向都会发生巨大变化因此一旦有机会算法就能够跳出局部最优走向全局最优当然也有可能是跳出一个局部最优走向另一个局部最优。不过缺点是需要的迭代次数变得不明。如果最开始就在全局最优的范围内那可能只需要非常少的迭代次数就收敛但是如果最开始落入了局部最优的范围或全局最优与局部最优的差异很小那可能需要花很长的时间、经过很多次迭代才能够收敛毕竟不断改变的方向会让迭代的路线变得曲折。
从整体来看为了mini-batch SGD这“不会轻易被局部最优困住”的优点我们在神经网络中使用它作为优化算法或优化算法的基础。当然还有另一个流传更广、更为认知的理由支持我们使用mini-batch SGDmini-batch SGD可以提升神经网络的计算效率让神经网络计算更快。
为了解决计算开销大的问题我们要使用mini-batch SGD。考虑到可以从全部数据中选出一部分作为全部数据的“近似估计然后用选出的这一部分数据来进行迭代每次迭代需要计算的数据量就会更少计算消耗也会更少因此神经网络的速度会提升。当然了这并不是说使用1001个样本进行迭代一定比使用1000个样本进行迭代速度要慢而是指每次迭代中计算上十万级别的数据会比迭代中只计算一千个数据慢得多。 44.2 batch_size与epoches
在mini-batch SGD中我们选择的批量batch含有的样本数被称为batch_size批量尺寸这个尺寸一定是小于数据量的某个正整数值。每次迭代之前我们需要从数据集中抽取batch_size个数据用于训练。
在普通梯度下降中因为没有抽样所以每次迭代就会将所有数据都使用一次迭代了t次时算法就将数据学习了t次。可以想象对同样的数据算法学习得越多也有应当对数据的状况理解得越深也就学得越好。然而并不是对一个数据学习越多越好毕竟学习得越多训练时间就越长同时我们能够收集到的数据只是“样本”并不能够代表真实世界的客观情况。 例如我们从几万张猫与狗的照片中学到的内容并不一定就能适用于全世界所有的猫和狗。如果我们的照片中猫咪都是有毛的那神经网络对数据学习的程度越深它就越有可能认不出无毛猫。 因此虽然我们希望算法对数据了解很深但我们也希望算法不要变成”书呆子“要保留一些灵活性保留一些泛化能力。关于这一点我们之后会详细展开来说明但大家现在需要知道的是算法对同样的数据进行学习的次数并不是越多越好。
在mini-batch SGD中因为每次迭代时都只使用了一小部分数据所以它迭代的次数并不能代表全体数据一共被学习了多少次。所以我们需要另一个重要概念epoch读音/ˈepək/来定义全体数据一共被学习了多少次。
epoch 是衡量训练数据被使用次数的单位一个epoch表示优化算法将全部训练数据都使用了一次。 它与梯度下降中的迭代次数有非常深的关系我们常使用“完成1个epoch需要n次迭代“这样的语言。
假设一个数据集总共有m个样本我们选择的batch_size是 N B N_B NB即每次迭代时都使用 N B N_B NB个样本则一个epoch所需的迭代次数的计算公式如下 完成一个 e p o c h 所需要的迭代次数 n m N B 完成一个epoch所需要的迭代次数n \frac{m}{N_B} 完成一个epoch所需要的迭代次数nNBm 在深度学习中我们常常定义num_epoches作为梯度下降的最外层循环batch_size作为内层循环。有时候我们希望数据被多学习几次来增加模型对数据的理解。有时候我们会控制模型对数据的训练。总之我们会使用epoch和batch_size来控制训练的节奏。接下来我们就用代码来实现一下。 44.3 TensorDataset与DataLoader 要使用小批量随机梯度下降我们就需要对数据进行采样、分割等操作。在PyTorch中操作数据所需要使用的模块是torch.utils其中utils.data类下面有大量用来执行数据预处理的工具。在MBSGD中我们需要将数据划分为许多组特征张量对应标签的形式因此最开始我们要将数据的特征张量与标签打包成一个对象。之前我们提到过深度学习中的特征张量维度很少是二维因此其特征张量与标签几乎总是分开的不像机器学习中标签常常出现在特征矩阵的最后一列或第一列。在我们之前的例子中我们是单独生成了标签与特征张量所以也需要合并如果你的数据本来就是特征张量与标签在同一个张量中那你可以跳过这一步。
合并张量与标签我们所使用的类是utils.data.TensorDataset这个功能类似于python中的zip可以将最外面的维度一致的tensor进行打包也就是将第一个维度一致的tensor进行打包。我们来看一下
import torch
from torch.util.data import TensorDataseta torch.randn(500, 2, 3) # 相当于五百个二维数据 - 表格
b torch.randn(500, 3, 4, 5) # 相当于五百个三维数据 - 图像
c torch.randn(500, 1) # 标签#被合并的对象第一维度上的值相等
TensorDataset(a, b, c)[0]# 查看数据
for x in TensorDataset(b,c):#generatorprint(x)break
# output :
(tensor([[[ 0.3641, 1.1461, 1.3315, -0.6803, -0.1573],[-0.3813, 0.0569, 1.4741, -0.2237, 0.4374],[ 0.4338, 0.7315, -0.2749, 0.0160, -0.2451],[-0.5867, -0.5889, 1.8905, -0.7718, -1.7899]],[[-0.4048, 0.7898, 0.3773, 0.7166, 0.0490],[-0.9121, -0.0489, -0.8179, -1.8548, -0.3418],[ 0.0873, 0.3071, -0.9272, 1.4546, -0.8360],[ 1.2235, 1.2197, -0.5222, 0.2297, -0.8180]],[[ 0.4578, -2.0396, -0.1589, -0.3033, -0.6102],[ 1.1299, 0.8919, -0.5627, 0.4364, -0.2321],[ 0.1634, 1.4667, -0.7651, -0.6503, 0.0228],[ 0.8123, 0.9057, 1.3573, -0.3826, 0.2580]]]), tensor([-0.6932]))当我们将数据打包成一个对象之后我们需要使用划分小批量的功能DataLoader。DataLoader是处理训练前专用的功能它可以接受任意形式的数组、张量作为输入并把他们一次性转换为神经网络可以接入的tensor。
from torch.utils.data import DataLoader
bs 120 # 每批次多少数据量
dataset DataLoader(data, batch_size bs, shuffle True, # 划分小批量之前随机打乱数据drop_last True # 需不需要舍弃最后一个batch)for i in dataset:print(i[0].shape)
# output :
torch.Size([120, 3, 4, 5])
torch.Size([120, 3, 4, 5])
torch.Size([120, 3, 4, 5])
torch.Size([120, 3, 4, 5])# 一共有多少个batch
len(dataset)
# output :
4# 展示里面全部的数据
len(dataset.dataset)
# output :
500# 单个样本
dataset.dataset[0]
# output :
(tensor([[[ 0.3641, 1.1461, 1.3315, -0.6803, -0.1573],[-0.3813, 0.0569, 1.4741, -0.2237, 0.4374],[ 0.4338, 0.7315, -0.2749, 0.0160, -0.2451],[-0.5867, -0.5889, 1.8905, -0.7718, -1.7899]],[[-0.4048, 0.7898, 0.3773, 0.7166, 0.0490],[-0.9121, -0.0489, -0.8179, -1.8548, -0.3418],[ 0.0873, 0.3071, -0.9272, 1.4546, -0.8360],[ 1.2235, 1.2197, -0.5222, 0.2297, -0.8180]],[[ 0.4578, -2.0396, -0.1589, -0.3033, -0.6102],[ 1.1299, 0.8919, -0.5627, 0.4364, -0.2321],[ 0.1634, 1.4667, -0.7651, -0.6503, 0.0228],[ 0.8123, 0.9057, 1.3573, -0.3826, 0.2580]]]),tensor([-0.6932]))dataset.dataset[0][1]
# output :
tensor([-0.6932])# 查看现有的batch_size
dataset.batch_size
# output :
12045 在MINST-FASHION上实现神经网络的学习流程
本节课我们讲解了神经网络使用小批量随机梯度下降进行迭代的流程现在我们要整合本节课中所有的代码实现一个完整的训练流程。首先要梳理一下整个流程
设置步长 l r lr lr动量值 g a m m a gamma gamma迭代次数 e p o c h s epochs epochs b a t c h _ s i z e batch\_size batch_size等信息如果需要设置初始权重 w 0 w_0 w0导入数据将数据切分成 b a t c h e s batches batches定义神经网络架构定义损失函数 L ( w ) L(w) L(w)如果需要的话将损失函数调整成凸函数以便求解最小值定义所使用的优化算法开始在epoches和batch上循环执行优化算法 调整数据结构确定数据能够在神经网络、损失函数和优化算法中顺利运行完成向前传播计算初始损失利用反向传播在损失函数 L ( w ) L(w) L(w)上对每一个 w w w求偏导数迭代当前权重清空本轮梯度完成模型进度与效果监控 输出结果 45.1 导库设置各种初始值
import torch
from torch import nn
from torch import optim
from torch.nn import functional as F
from torch.utils.data import TensorDataset # 将特征与标签合并到同一个对象中
from torch.utils.data import DataLoader # 帮助我们进行小批量的分割# 确定数据、确定优先需要设置的值
lr 0.15
gamma 0
epochs 10
bs 12845.2 导入数据分割小批量
以往我们的做法是
# torch.manual_seed(420)
# X torch.rand((50000,20),dtypetorch.float32) * 100
# y torch.randint(low0,high3,size(50000,1),dtypetorch.float32)
# data TensorDataset(X,y)
# data_withbatch DataLoader(data,batch_sizebs, shuffle True)这次我们要使用PyTorch中自带的数据MINST-FATION。 import torchvision
import torchvision.transforms as transforms# 初次运行时下载需要等待较长时间
mnist torchvision.datasets.FashionMNIST(root C:\Pythonwork\DEEP LEARNING\WEEK 3\Datasets\FashionMNIST,train True,download True,transform transforms.ToTensor()
)1. mnist torchvision.datasets.FashionMNIST(...)这行代码通过 torchvision.datasets.FashionMNIST 创建了一个 FashionMNIST 数据集对象并将其赋值给变量 mnist。FashionMNIST 是一个标准的图像分类数据集包含 70,000 张 28×28 的灰度图像分为 10 个类别如 T 恤、裤子、连衣裙等。2. 参数解释
root C:\Pythonwork\DEEP LEARNING\WEEK 3\Datasets\FashionMNISTroot 参数指定了数据集存储的路径。如果数据集已经下载到该路径下则直接加载如果没有下载则会自动下载到该路径。注意路径字符串中使用了双反斜杠 \\这是因为反斜杠 \ 在 Python 中是转义字符需要用双反斜杠来表示普通的文件路径分隔符。train Truetrain 参数用于指定加载的是训练集True还是测试集False。在这里trainTrue 表示加载的是训练集FashionMNIST 的训练集包含 60,000 张图像。download Truedownload 参数用于指定是否自动下载数据集。如果设置为 True当指定路径下没有数据集时程序会自动从网络上下载数据集并保存到 root 指定的路径。如果设置为 False则不会自动下载如果数据集不存在程序会报错。transform transforms.ToTensor()transform 参数用于指定对数据集中的图像进行何种预处理操作。在这里使用了 transforms.ToTensor()它会将图像从 PIL 图像格式或 NumPy 数组格式转换为 PyTorch 的 Tensor 格式。转换后的图像数据范围会从 [0, 255] 转换为 [0.0, 1.0]并且将图像的维度从 (H, W, C)高度、宽度、通道数转换为 (C, H, W)通道数、高度、宽度这是 PyTorch 中张量的标准格式。
len(mnist)
# output :
60000# 查看特征张量
mnist.data.shape
# output :
torch.Size([60000, 28, 28])# 查看标签的类别
mnist.classes
# output :
[T-shirt/top,Trouser,Pullover,Dress,Coat,Sandal,Shirt,Sneaker,Bag,Ankle boot]# 查看图像的模样
import matplotlib.pyplot import plt
plt.imshow(mnist[0][0].view((28,28)).numpy()) # imageshow# 分割batch
batchdata DataLoader(mnist, batch_size bs, shuffle True)# 查看会放入进行迭代的数据结构
for x,y in batchdata:print(x.shape)print(y.shape)break
# output :
torch.Size([128, 1, 28, 28]) # 每个batch128条数据
torch.Size([128]) # 对应128个分类标签# 特征的数目一般是第一维之外的所有维度相乘的数
input_ mnist.data[0].numel()
# 分类的数目
output_ len(mnist.targets.unique())45.3 定义神经网络的架构
class Model(nn.Module):def __init__(self, in_features, out_features):super().__init__()self.linear1 nn.Linear(in_features, 128, bias False)self.output nn.Linear(128, out_features, bias False)def forward(self, x):# -1表示该维度的大小由其他维度的大小和总元素个数自动确定。它是一个占位符用于自动计算该维度的大小。x x.view(-1, 28*28)sigma1 torch.relu(self.linear1(x))z2 self.output(sigma1)sigma2 F.log_softmax(z2, dim 1)return sigma245.4 定义训练函数
def fit(net, batchdata, lr, epochs, gamma):# 定义损失函数criterion nn.NLLLoss() # 定义优化算法opt optim.SGD(net.parameters(), lr lr, momentum gamma)correct 0 # 用于记录正确分类的样本数量samples 0 # 用于记录处理的总样本数量for epoch in range(epochs):for batch_idx, (x, y) in enumerate(batchdata):y y.view(x.shape[0])sigma net.forward(x)loss criterion(sigma, y)loss.backward()opt.step()opt.zero_grad()# 求解准确率yhat torch.max(sigma, 1)[1]torch.max(sigma, 1)
sigma通常是一个二维张量表示模型对每个样本的类别预测分数或概率。
1表示沿着第二个维度即每一行查找最大值。对于分类任务每一行代表一个样本的类别预测分数。
返回值是一个元组第一个元素每一行的最大值即每个样本的最高预测分数。第二个元素每一行最大值的索引即每个样本的预测类别
correct torch.sum(yhat y)samples x.shape[0]if (batch_idx1) % 125 0 or batch_idx len(batchdata)-1:print(Epoch{}:[{}/{}({:.0f}%)],Loss:{:.6f},Accuracy:{:.3f}).format(epoch1,samples,len(batchdata.dataset) * epochs,100 * samples / (batchdata.dataset) * epochs,loss.data.item()float(correct * 100 / samples))45.5 进行训练与评估
# 实例化神经网络调用优化算法需要的参数
torch.manual_seed(420)
net Model(in_features input_, out_features output_)
fit(net, batchdata, lr lr, epochs epochs, gamma gamma)
# output :
Epoch1:[16000/600000(3%)],Loss:0.727888,Accuracy:68.819
Epoch1:[32000/600000(5%)],Loss:0.513726,Accuracy:73.550
Epoch1:[48000/600000(8%)],Loss:0.459364,Accuracy:76.173
Epoch1:[60000/600000(10%)],Loss:0.563562,Accuracy:77.373
Epoch2:[76000/600000(13%)],Loss:0.433777,Accuracy:78.687
Epoch2:[92000/600000(15%)],Loss:0.363204,Accuracy:79.634
Epoch2:[108000/600000(18%)],Loss:0.443800,Accuracy:80.304
Epoch2:[120000/600000(20%)],Loss:0.442278,Accuracy:80.723
Epoch3:[136000/600000(23%)],Loss:0.543707,Accuracy:81.285
Epoch3:[152000/600000(25%)],Loss:0.354620,Accuracy:81.691
Epoch3:[168000/600000(28%)],Loss:0.526626,Accuracy:82.088
Epoch3:[180000/600000(30%)],Loss:0.411618,Accuracy:82.367
Epoch4:[196000/600000(33%)],Loss:0.350448,Accuracy:82.678
Epoch4:[212000/600000(35%)],Loss:0.345022,Accuracy:83.008
Epoch4:[228000/600000(38%)],Loss:0.474553,Accuracy:83.263
Epoch4:[240000/600000(40%)],Loss:0.324190,Accuracy:83.479
Epoch5:[256000/600000(43%)],Loss:0.317327,Accuracy:83.738
Epoch5:[272000/600000(45%)],Loss:0.369660,Accuracy:83.957
Epoch5:[288000/600000(48%)],Loss:0.341690,Accuracy:84.139
Epoch5:[300000/600000(50%)],Loss:0.547992,Accuracy:84.282
Epoch6:[316000/600000(53%)],Loss:0.292443,Accuracy:84.497
Epoch6:[332000/600000(55%)],Loss:0.276111,Accuracy:84.649
Epoch6:[348000/600000(58%)],Loss:0.318102,Accuracy:84.797
Epoch6:[360000/600000(60%)],Loss:0.308750,Accuracy:84.874
Epoch7:[376000/600000(63%)],Loss:0.261769,Accuracy:85.029
Epoch7:[392000/600000(65%)],Loss:0.467604,Accuracy:85.163
Epoch7:[408000/600000(68%)],Loss:0.374349,Accuracy:85.302
Epoch7:[420000/600000(70%)],Loss:0.374845,Accuracy:85.384
Epoch8:[436000/600000(73%)],Loss:0.292379,Accuracy:85.498
Epoch8:[452000/600000(75%)],Loss:0.274055,Accuracy:85.624
Epoch8:[468000/600000(78%)],Loss:0.326614,Accuracy:85.734
Epoch8:[480000/600000(80%)],Loss:0.377757,Accuracy:85.793
Epoch9:[496000/600000(83%)],Loss:0.345958,Accuracy:85.884
Epoch9:[512000/600000(85%)],Loss:0.334107,Accuracy:85.993
Epoch9:[528000/600000(88%)],Loss:0.161249,Accuracy:86.080
Epoch9:[540000/600000(90%)],Loss:0.305421,Accuracy:86.151
Epoch10:[556000/600000(93%)],Loss:0.309134,Accuracy:86.249
Epoch10:[572000/600000(95%)],Loss:0.341859,Accuracy:86.330
Epoch10:[588000/600000(98%)],Loss:0.308504,Accuracy:86.419
Epoch10:[600000/600000(100%)],Loss:0.214857,Accuracy:86.484