深度学习 Pytorch 神经网络的学习
本节将从梯度下降法向外拓展,介绍更常用的优化算法,实现神经网络的学习和迭代。在本节课结束将完整实现一个神经网络训练的全流程。
对于像神经网络这样的复杂模型,可能会有数百个 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_{i=1}^{m} y_{i(k=j)} \ln \sigma_i \quad \text{其中} \quad \sigma = softmax(z), \quad z = WX \\ SSE = \frac{1}{m} \sum_{i=1}^{m} (z_i - \hat{z}_i)^2 \quad \text{其中} \quad z = Xw CELoss=−i=1∑myi(k=j)lnσi其中σ=softmax(z),z=WXSSE=m1i=1∑m(zi−z^i)2其中z=Xw
不难发现,任意损失函数中总是包含特征张量 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)。
对于任意向量来说,其大小和方向都是依赖于向量元素具体的值而确定。比如向量(1,2)的方向就是从原点(0,0)指向点(1,2)的方向,大小就是 1 2 + 2 2 \sqrt{1^2+2^2} 12+22(向量的大小也叫模长),梯度向量的大小和方向也是如此计算。梯度向量中的具体元素就是各个自变量的偏导数,这些偏导数的具体值必须依赖于当前所在坐标点的值进行计算。 所以:
- 梯度的大小和方向对每个坐标点而言是独一无二的。坐标点一旦变化,梯度向量的方向和大小也会变化。
- 每走到一个新的点,读取该点的坐标并带入梯度向量的表达式进行计算,就可以得到当前点对应的梯度向量的方向和大小。
而我们现在的坐标空间是由损失函数 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_{(t+1)} = w_{(t)} - \frac{\partial L}{\partial w} w(t+1)=w(t)−∂w∂L
为了控制每次走的距离的大小,我们将步长 η \eta η(又叫做学习率)加入公式,就可以将上面的式子改写为:
w ( t + 1 ) = w ( t ) − η ∂ L ∂ w w_{(t+1)} = w_{(t)} - \eta \frac{\partial L}{\partial w} w(t+1)=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_{i=1}^{m} (y_i \cdot \ln(\sigma_i) + (1 - y_i) \cdot \ln(1 - \sigma_i)) \\ &= -\sum_{i=1}^{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=−i=1∑m(yi⋅ln(σi)+(1−yi)⋅ln(1−σi))=−i=1∑m(yi⋅ln(1+e−Xiw1)+(1−yi)⋅ln(1−1+e−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_{i=1}^{m} \left( y_i \cdot \ln(\sigma_i^{(2)}) + (1 - y_i) \cdot \ln(1 - \sigma_i^{(2)}) \right) \\ = -\sum_{i=1}^{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=−i=1∑m(yi⋅ln(σi(2))+(1−yi)⋅ln(1−σi(2)))=−i=1∑m(yi⋅ln(1+e−σi(1)w(1→2)1)+(1−yi)⋅ln(1−1+e−σ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_{i=1}^{m}(y_i \cdot \ln(\sigma_i^{(2)}) + (1 - y_i) \cdot \ln(1 - \sigma_i^{(2)})) \\ = -\sum_{i=1}^{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=−i=1∑m(yi⋅ln(σi(2))+(1−yi)⋅ln(1−σi(2)))=−i=1∑m(yi⋅ln(1+e−1+e−Xiw(0→1)w(1→2)11)+(1−yi)⋅ln(1−1+e−1+e−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} 假设有函数u=h(z),z=f(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_{i=1}^{m}(y_i * \ln(\sigma_i) + (1 - y_i) * \ln(1 - \sigma_i))\right)}{\partial \sigma} \\ = \sum_{i=1}^{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(σ)=∂σ∂(−∑i=1m(yi∗ln(σi)+(1−yi)∗ln(1−σi)))=i=1∑m∂σ∂(−(yi∗ln(σi)+(1−yi)∗ln(1−σi)))求导不影响加和符号,因此暂时不看加和符号:=−(y∗σ1+(1−y)∗1−σ1∗(−1))=−(σy+1−σ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∂1+e−z1=∂z∂(1+e−z)−1=−1⋅(1+e−z)−2⋅e−z⋅(−1)=(1+e−z)2e−z=(1+e−z)21+e−z−1=(1+e−z)21+e−z−(1+e−z)21=1+e−z1−(1+e−z)21=1+e−z1(1−1+e−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#这里返回的是在函数y=x**2上,x=1时的导数值。
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_graph=True)
backward
求解出的结果的结构与对应的权重矩阵的结构一模一样,因为一个权重就对应了一个偏导数。
唯一需要说明的点是,在使用autograd
的时候,我们强调了requires_grad
的用法,但在定义打包好的类以及使用loss.backward()
的时候,我们却没有给任何数据定义requires_grad=True
。这是因为:
-
当使用nn.Module继承后的类进行正向传播时,我们的权重 w w w是自动生成的,在生成时就被自动设置为允许计算梯度(requires_grad=True),所以不需要我们自己去设置
-
同时,观察我们的反向传播过程:
不难发现,我们的特征张量 X X X与真实标签 y y y都不在反向传播的过程当中,但是 X X X与 y y y其实都是损失函数计算需要用的值,在计算图上,这些值都位于叶子节点上,我们在定义损失函数时,并没有告诉损失函数哪些值是自变量,哪些是常数,那
backward
函数是怎么判断具体求解哪个对象的梯度的呢?其实就是靠
requires_grad
。首先backward
值会识别叶子节点,不在叶子上的变量是不会被backward
考虑的。对于全部叶子节点来说,只有属性requires_grad=True
的节点,才会被计算。在设置 X X X与 y y y时,我们都没有写requires_grad
参数,也就是默认让“允许求解梯度”这个选项为False
,所以backward
在计算的时候就只会计算关于 w w w的部分。当然,我们也可以将 X X X和 y y y或者任何除了权重以及截距的量的
requires_grad
打开,一旦我们设置为True
,backward
就会在帮助我们计算 的导数的同时,也帮我们计算以 X X X或 y y y为自变量的导数。在正常的梯度下降和反向传播过程中,我们是不需要这些导数的,因此我们一律不去管requires_grad
的设置,就让它默认为False
,以节约计算资源。当然,如果你的 w w w是自己设置的,千万记得一定要设置requires_grad=True
。
43 移动坐标点
43.1 走出第一步
有了大小和方向,接下来就可以开始走出我们的第一步了。来看权重的迭代公式:
w ( t + 1 ) = w ( t ) − η ∂ L ( w ) ∂ w \mathbf{w}_{(t+1)} = \mathbf{w}_{(t)} - \eta \frac{\partial L(\mathbf{w})}{\partial \mathbf{w}} w(t+1)=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}_{(t+1)} = \mathbf{w}_{(t)} + v_{(t)} v(t)=γv(t−1)−η∂w∂Lw(t+1)=w(t)+v(t)
在第一步中,没有历史梯度方向,因此第一步的真实方向就是起始点梯度的反方向, v 0 = 0 v_0=0 v0=0。其中 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 descent,SGD
)。
mini-batch SGD
的优势是算法不会轻易陷入局部最优,由于每次梯度向量的方向都会发生巨大变化,因此一旦有机会,算法就能够跳出局部最优,走向全局最优(当然也有可能是跳出一个局部最优,走向另一个局部最优)。不过缺点是,需要的迭代次数变得不明。如果最开始就在全局最优的范围内,那可能只需要非常少的迭代次数就收敛,但是如果最开始落入了局部最优的范围,或全局最优与局部最优的差异很小,那可能需要花很长的时间、经过很多次迭代才能够收敛,毕竟不断改变的方向会让迭代的路线变得曲折。
从整体来看,为了mini-batch SGD
这“不会轻易被局部最优困住”的优点,我们在神经网络中使用它作为优化算法(或优化算法的基础)。当然,还有另一个流传更广、更为认知的理由支持我们使用mini-batch SGD
:mini-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所需要的迭代次数n=NBm
在深度学习中,我们常常定义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 :
120
45 在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 = 128
45.2 导入数据,分割小批量
以往我们的做法是:
# torch.manual_seed(420)
# X = torch.rand((50000,20),dtype=torch.float32) * 100
# y = torch.randint(low=0,high=3,size=(50000,1),dtype=torch.float32)
# data = TensorDataset(X,y)
# data_withbatch = DataLoader(data,batch_size=bs, 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\FashionMNIST'root 参数指定了数据集存储的路径。如果数据集已经下载到该路径下,则直接加载;如果没有下载,则会自动下载到该路径。注意:路径字符串中使用了双反斜杠 \\,这是因为反斜杠 \ 在 Python 中是转义字符,需要用双反斜杠来表示普通的文件路径分隔符。train = Truetrain 参数用于指定加载的是训练集(True)还是测试集(False)。在这里,train=True 表示加载的是训练集,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 sigma2
45.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_idx+1) % 125 == 0 or batch_idx == len(batchdata)-1:print('Epoch{}:[{}/{}({:.0f}%)],Loss:{:.6f},Accuracy:{:.3f}').format(epoch+1,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
相关文章:
深度学习 Pytorch 神经网络的学习
本节将从梯度下降法向外拓展,介绍更常用的优化算法,实现神经网络的学习和迭代。在本节课结束将完整实现一个神经网络训练的全流程。 对于像神经网络这样的复杂模型,可能会有数百个 w w w的存在,同时如果我们使用的是像交叉熵这样…...
【机器学习】自定义数据集 使用pytorch框架实现逻辑回归并保存模型,然后保存模型后再加载模型进行预测,对预测结果计算精确度和召回率及F1分数
一、使用pytorch框架实现逻辑回归 1. 数据部分: 首先自定义了一个简单的数据集,特征 X 是 100 个随机样本,每个样本一个特征,目标值 y 基于线性关系并添加了噪声。将 numpy 数组转换为 PyTorch 张量,方便后续在模型中…...
深入理解linux中的文件(上)
1.前置知识: (1)文章 内容 属性 (2)访问文件之前,都必须打开它(打开文件,等价于把文件加载到内存中) 如果不打开文件,文件就在磁盘中 (3&…...
分布式微服务系统架构第90集:现代化金融核心系统
#1.1 深化数字化转型,核心面临新挑战 1、架构侧:无法敏捷协同数字金融经营模式转型。 2、需求侧:业务需求传导低效始终困扰金融机构。 3、开发侧:创新产品上市速度低于期望。 4、运维侧:传统面向资源型监控体系难以支撑…...
Vue简介
目录 Vue是什么?为什么要使用Vue?Vue的三种加载方式拓展:什么是渐进式框架? Vue是什么? Vue是一套用于构建用户界面的渐进式 JavaScript (主张最少)框架 ,开发者只需关注视图层。另一方面,当与…...
【Linux】从零开始:编写你的第一个Linux进度条小程序
Linux相关知识点可以通过点击以下链接进行学习一起加油!初识指令指令进阶权限管理yum包管理与vim编辑器GCC/G编译器make与Makefile自动化构建GDB调试器与Git版本控制工具 🌈个人主页:是店小二呀 🌈C语言专栏:C语言 &am…...
FFmpeg工具使用基础
一、FFmpeg工具介绍 FFmpeg命令行工具主要包括以下几个部分: ffmpeg:编解码工具ffprobe:多媒体分析器ffplay:简单的音视频播放器这些工具共同构成了FFmpeg的核心功能,支持各种音视频格式的处理和转换 二、在Ubuntu18.04上安装FFmpeg工具 1、sudo apt-upda…...
数据库管理-第287期 Oracle DB 23.7新特性一览(20250124)
数据库管理287期 2025-01-24 数据库管理-第287期 Oracle DB 23.7新特性一览(20250124)1 AI向量搜索:算术和聚合运算2 更改Compatible至23.6.0,以使用23.6或更高版本中的新AI向量搜索功能3 Cloud Developer包4 DBMS_DEVELOPER.GET_…...
快速提升网站收录:利用网站FAQ页面
本文转自:百万收录网 原文链接:https://www.baiwanshoulu.com/48.html 利用网站FAQ(FrequentlyAskedQuestions,常见问题解答)页面是快速提升网站收录的有效策略之一。以下是一些具体的方法和建议,以帮助你…...
AtCoder Beginner Contest 391(ABCDE)
A - Lucky Direction 翻译: 给你一个字符串 D,代表八个方向(北、东、西、南、东北、西北、东南、西南)之一。方向与其代表字符串之间的对应关系如下。 北: N东: E西: W南: S东…...
解决Django非ORM模型提示初始化request问题
提问 Django在DRF时候自定义显示一些非model的字段提示TypeError: Field.__init__() got an unexpected keyword argument request 解答1 错误提示 TypeError: Field.__init__() got an unexpected keyword argument request 显示在创建序列化器实例时,传递了一个…...
【机器学习】自定义数据集 使用scikit-learn中svm的包实现svm分类
一、支持向量机(support vector machines. ,SVM)概念 1. SVM 绪论 支持向量机(SVM)的核心思想是找到一个最优的超平面,将不同类别的数据点分开。SVM 的关键特点包括: ① 分类与回归: SVM 可以用于分类&a…...
hunyuan 混元学习
使用了5个subset,也是用了text-image和text-video进行训练的 也是进行了复杂的视频选择。同movie gen. 也进行了模型切断,用拉普拉斯算子找到最清晰的一帧作为训练的起始 训练了不同的模型去选择数据,比如用Dover去选择美观度比较好的数据,…...
.Net / C# 繁体中文 与 简体中文 互相转换, 支持地方特色词汇
版本号 Nuget 搜索 “OpenCCNET”, 注意别找错, 好多库的名字都差不多 支持 “繁,简” 的互相转换, 支持多个地区常用词汇的转换, 还支持 日文的新旧转换. OpenCC 在 .Net 中的实现 https://github.com/CosineG/OpenCC.NET <PackageReference Include"OpenCCNET"…...
仿真设计|基于51单片机的温湿度、一氧化碳、甲醛检测报警系统
目录 具体实现功能 设计介绍 51单片机简介 资料内容 仿真实现(protues8.7) 程序(Keil5) 全部内容 资料获取 具体实现功能 (1)温湿度传感器、CO传感器、甲醛传感器实时检测温湿度值、CO值和甲醛值进…...
Vue- 组件通信2
一、props props 是使用频率最高的一种通信方式,常用于:父 ↔ 子 若 父传子:属性值是非函数;若 子传父:属性值是函数。 Props: 指的是传递给子组件的属性。子组件通过 props 接收数据。单向数据流: 数据通过 props…...
春晚舞台上的人形机器人:科技与文化的奇妙融合
文章目录 人形机器人Unitree H1的“硬核”实力传统文化与现代科技的创新融合网友热议与文化共鸣未来展望:科技与文化的更多可能结语 2025 年央视春晚的舞台,无疑是全球华人目光聚焦的焦点。就在这个盛大的舞台上,一场名为《秧BOT》的创意融合…...
向上调整算法(详解)c++
算法流程: 与⽗结点的权值作⽐较,如果⽐它⼤,就与⽗亲交换; 交换完之后,重复 1 操作,直到⽐⽗亲⼩,或者换到根节点的位置 这里为什么插入85完后合法? 我们插入一个85,…...
Python之Excel操作 - 写入数据
我们将使用 openpyxl 库,它是一个功能强大且易于使用的库,专门用于处理 Excel 文件。 1. 安装 openpyxl 首先,你需要安装 openpyxl 库。你可以使用 pip 命令进行安装: pip install openpyxl创建一个文件 example.xlsxÿ…...
MYSQL--一条SQL执行的流程,分析MYSQL的架构
文章目录 第一步建立连接第二部解析 SQL第三步执行 sql预处理优化阶段执行阶段索引下推 执行一条select 语句中间会发生什么? 这个是对 mysql 架构的深入理解。 select * from product where id 1;对于mysql的架构分层: mysql 架构分成了 Server 层和存储引擎层&a…...
【开源免费】基于Vue和SpringBoot的流浪宠物管理系统(附论文)
本文项目编号 T 182 ,文末自助获取源码 \color{red}{T182,文末自助获取源码} T182,文末自助获取源码 目录 一、系统介绍二、数据库设计三、配套教程3.1 启动教程3.2 讲解视频3.3 二次开发教程 四、功能截图五、文案资料5.1 选题背景5.2 国内…...
您与此网站之间建立的连接不安全
网站建立好后,用360浏览器打开后地址栏有一个灰色小锁打着红色叉点击后显示“您与此网站之间建立的连接不安全”“请勿在此网站上输入任何敏感信息(例如密码或信用卡信息),因为攻击者可能会盗取这些信息。” 出现这个提示的主要原…...
kamailio-ACC模块介绍【kamailio6.0. X】
Acc 模块 作者 Jiri Kuthan iptel.org jiriiptel.org Bogdan-Andrei Iancu Voice Sistem SRL bogdanvoice-system.ro Ramona-Elena Modroiu rosdev.ro ramonarosdev.ro 编辑 Bogdan-Andrei Iancu Voice Sistem SRL bogdanvoice-system.ro Sven Knoblich 1&1 Internet …...
图漾相机——Sample_V1示例程序
文章目录 1.SDK支持的平台类型1.1 Windows 平台1.2 Linux平台 2.SDK基本知识2.1 SDK目录结构2.2 设备组件简介2.3 设备组件属性2.4 设备的帧数据管理机制2.5 SDK中的坐标系变换 3.Sample_V1示例程序3.1 DeviceStorage3.2 DumpCalibInfo3.3 NetStatistic3.4 SimpleView_SaveLoad…...
谈谈你所了解的AR技术吧!
深入探讨 AR 技术的原理与应用 在科技飞速发展的今天,AR(增强现实)技术已经悄然改变了我们与周围世界互动的方式。你是否曾想象过如何能够通过手机屏幕与虚拟物体进行实时互动?在这篇文章中,我们将深入探讨AR技术的原…...
C++计算特定随机操作后序列元素乘积的期望
有一个长度为 n n n的序列 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an。初始序列的所有元素均为 0 0 0。再给定正整数 m m m、 c c c和 ( n − m 1 ) (n-m1) (n−m1)个正整数 b 1 , b 2 , . . . , b n − m 1 b_1,b_2,...,b_{n-m1} b1,b2,...,bn−m1…...
【机器学习理论】朴素贝叶斯网络
基础知识: 先验概率:对某个事件发生的概率的估计。可以是基于历史数据的估计,可以由专家知识得出等等。一般是单独事件概率。 后验概率:指某件事已经发生,计算事情发生是由某个因素引起的概率。一般是一个条件概率。 …...
NPM 使用介绍
NPM 使用介绍 引言 NPM(Node Package Manager)是Node.js生态系统中的一个核心工具,用于管理JavaScript项目的依赖包。无论是开发一个小型脚本还是构建大型应用程序,NPM都能极大地提高开发效率。本文将详细介绍NPM的使用方法,包括安装、配置、依赖管理、包发布等,帮助您…...
langchain 实现多智能体多轮对话
这里写目录标题 工具定义模型选择graph节点函数定义graph 运行 工具定义 import random from typing import Annotated, Literalfrom langchain_core.tools import tool from langchain_core.tools.base import InjectedToolCallId from langgraph.prebuilt import InjectedSt…...
网络攻防实战指北专栏讲解大纲与网络安全法
专栏 本专栏为网络攻防实战指北,大纲如下所示 进度:目前已更完准备篇、HTML基础 计划:所谓基础不牢,地动山摇。所以下一步将持续更新基础篇内容 讲解信息安全时,结合《中华人民共和国网络安全法》(以下简…...
四、jQuery笔记
(一)jQuery概述 jQuery本身是js的一个轻量级的库,封装了一个对象jQuery,jquery的所有语法都在jQuery对象中 浏览器不认识jquery,只渲染html、css和js代码,需要先导入jQuery文件,官网下载即可 jQuery中文说明文档:https://hemin.cn/jq/ (二)jQuery要点 1、jQuery对象 …...
解锁微服务:五大进阶业务场景深度剖析
目录 医疗行业:智能诊疗的加速引擎 电商领域:数据依赖的破局之道 金融行业:运维可观测性的提升之路 物流行业:智慧物流的创新架构 综合业务:服务依赖的优化策略 医疗行业:智能诊疗的加速引擎 在医疗行业迈…...
C++:虚函数与多态性习题2
题目内容: 编写程序,声明抽象基类Shape,由它派生出3个派生类:Circle、Rectangle、Triangle,用虚函数分别计算图形面积,并求它们的和。要求用基类指针数组,使它每一个元素指向一个派生类对象。 …...
开源软件协议介绍
一、可以闭源使用/不具传染性的协议 允许商业使用和分发 1、BSD:详细介绍 2、LGPL许可证:详细介绍 3、MPL2.0:详细介绍 二、具有传染性/使用后需要开源自身软件的协议 不建议商业使用 1、GPL许可证:详细介绍...
MapReduce简单应用(一)——WordCount
目录 1. 执行过程1.1 分割1.2 Map1.3 Combine1.4 Reduce 2. 代码和结果2.1 pom.xml中依赖配置2.2 工具类util2.3 WordCount2.4 结果 参考 1. 执行过程 假设WordCount的两个输入文本text1.txt和text2.txt如下。 Hello World Bye WorldHello Hadoop Bye Hadoop1.1 分割 将每个文…...
【HarmonyOS之旅】基于ArkTS开发(三) -> 兼容JS的类Web开发(三)
目录 1 -> 生命周期 1.1 -> 应用生命周期 1.2 -> 页面生命周期 2 -> 资源限定与访问 2.1 -> 资源限定词 2.2 -> 资源限定词的命名要求 2.3 -> 限定词与设备状态的匹配规则 2.4 -> 引用JS模块内resources资源 3 -> 多语言支持 3.1 -> 定…...
(9) 上:学习与验证 linux 里的 epoll 对象里的 EPOLLIN、 EPOLLHUP 与 EPOLLRDHUP 的不同
(1)经过之前的学习。俺认为结论是这样的,因为三次握手到四次挥手,到 RST 报文,都是 tcp 连接上收到了报文,这都属于读事件。所以: EPOLLIN : 包含了读事件, FIN 报文的正常四次挥手、…...
Avalonia与QtQuick的简单对比
这个是Avalonia开发的示例应用程序(官方入门示例)(Avalonia 11.1.0 .Net 9.0) 刚启动时,内存占用150M左右,稍等一会儿后,内存占用降低到77M左右,CPU占用一直都在,我i9-…...
WebForms DataList 深入解析
WebForms DataList 深入解析 引言 在Web开发领域,控件是构建用户界面(UI)的核心组件。ASP.NET WebForms框架提供了丰富的控件,其中DataList控件是一个灵活且强大的数据绑定控件。本文将深入探讨WebForms DataList控件的功能、用法以及在实际开发中的应用。 DataList控件…...
jetson编译torchvision出现 No such file or directory: ‘:/usr/local/cuda/bin/nvcc‘
文章目录 1. 完整报错2. 解决方法 1. 完整报错 jetson编译torchvision,执行python3 setup.py install --user遇到报错 running build_ext error: [Errno 2] No such file or directory: :/usr/local/cuda/bin/nvcc完整报错信息如下: (pytorch) nxnx-desktop:~/Do…...
《苍穹外卖》项目学习记录-Day10订单状态定时处理
利用Cron表达式生成器生成Cron表达式 1.处理超时订单 查询订单表把超时的订单查询出来,也就是订单的状态为待付款,下单的时间已经超过了15分钟。 //select * from orders where status ? and order_time < (当前时间 - 15分钟) 遍历集合把数据库…...
“新月智能武器系统”CIWS,开启智能武器的新纪元
新月人物传记:人物传记之新月篇-CSDN博客 相关文章链接:星际战争模拟系统:新月的编程之道-CSDN博客 新月智能护甲系统CMIA--未来战场的守护者-CSDN博客 “新月之智”智能战术头盔系统(CITHS)-CSDN博客 目录 智能武…...
FPGA| 使用Quartus II报错Top-level design entity ““ is undefined
1、使用FPGA准备点亮LED测试下板子,发现这个报错Error (12007): Top-level design entity "LEDLED" is undefined 工程如上图 报错如下图 2、分析到原因是因为工程名称和顶层模块里面的module名称不一样导致 解决办法:修改module名称和顶层模…...
如何实现滑动列表功能
文章目录 1 概念介绍2 使用方法3 示例代码 我们在上一章回中介绍了沉浸式状态栏相关的内容,本章回中将介绍SliverList组件.闲话休提,让我们一起Talk Flutter吧。 1 概念介绍 我们在这里介绍的SliverList组件是一种列表类组件,类似我们之前介…...
数据结构:优先级队列—堆
一、优先级队列 1、优先级队列概念 优先级队列,听名字我们就知道他是一种队列,队列在前面我们已经学习过了,它是一种先进先出的数据结构,但是在特殊的情况下,我们我们队列中元素是带有一定优先级的,它需要…...
SpringCloud系列教程:微服务的未来(十八)雪崩问题、服务保护方案、Sentinel快速入门
前言 在分布式系统中,雪崩效应(Avalanche Effect)是一种常见的故障现象,通常发生在系统中某个组件出现故障时,导致其他组件级联失败,最终引发整个系统的崩溃。为了有效应对雪崩效应,服务保护方…...
Java小白入门教程:Object
目录 一、定义 二、作用 三、使用场景 四、语法以及示例 1、创建Object类型的对象 2、使用 toString()方法 3、使用 equals()方法 4、使用 hashCode()方法 5、使用 getClass()方法 6、使用 clone()方法 7、使用 finalize()方法 一、定义 在Java中, object…...
ubuntu 更新24LTS中断导致“系统出错且无法恢复,请联系系统管理员”
22LTS to 24LTS 更新过程中手jian把更新程序controlC导致的。 解决 目前企图完成更新来恢复,重启后有软件包冲突,sudo apt upgrade报冲突。无法进行。 将原来source.list重新 sudo dpkg --configure -a sudo apt install -f 这些都不管用。还是显示gno…...
【单细胞第二节:单细胞示例数据分析-GSE218208】
GSE218208 1.创建Seurat对象 #untar(“GSE218208_RAW.tar”) rm(list ls()) a data.table::fread("GSM6736629_10x-PBMC-1_ds0.1974_CountMatrix.tsv.gz",data.table F) a[1:4,1:4] library(tidyverse) a$alias:gene str_split(a$alias:gene,":",si…...
ComfyUI安装调用DeepSeek——DeepSeek多模态之图形模型安装问题解决(ComfyUI-Janus-Pro)
ComfyUI 的 Janus-Pro 节点,一个统一的多模态理解和生成框架。 试用: https://huggingface.co/spaces/deepseek-ai/Janus-1.3B https://huggingface.co/spaces/deepseek-ai/Janus-Pro-7B https://huggingface.co/spaces/deepseek-ai/JanusFlow-1.3B 安装…...