dl学习笔记:(7)完整神经网络流程

完整神经网络流程

  • 反向传播
    • 链式求导
  • 代码实现反向传播
  • 动量法Momentum
  • 开始迭代
    • 为什么选择小批量
    • TensorDataset与DataLoader

反向传播

由于本节的公式比较多,所以如果哪里写错了漏写了,还请帮忙指出以便进行改正,谢谢。
在前面的章节已经介绍过梯度下降的两个关键:1.方向 2.步长,下面我们来讲解反向传播的原理。在介绍反向传播的原理之前,我们先看一下,如果没有这个方法应该如何进行求导工作,这样就能体现出反向传播的作用和厉害之处。
下面我们先用单层神经网络来举一个例子,下面是计算的流程图:
在这里插入图片描述
∂ L o s s ∂ w , 其中 \frac{\partial Loss}{\partial w}, \quad \text{其中} wLoss,其中 L o s s = − ∑ i = 1 m ( y i ⋅ ln ⁡ ( σ i ) + ( 1 − y i ) ⋅ ln ⁡ ( 1 − σ i ) ) Loss = -\sum_{i=1}^{m} \left( y_i \cdot \ln(\sigma_i) + (1 - y_i) \cdot \ln(1 - \sigma_i) \right) Loss=i=1m(yiln(σi)+(1yi)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 ) ) -\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) i=1m(yiln(1+eXiw1)+(1yi)ln(11+eXiw1)),可以看出式子由于多个函数的嵌套已经变得很复杂了,所以下面我们就不进行对w的求导操作了。
下面我们继续看一个双层的神经网络:
在这里插入图片描述
∂ L o s s ∂ w ( 1 → 2 ) , 其中一样的 \frac{\partial Loss}{\partial w^{(1 \rightarrow 2)}}, \quad \text{其中一样的} w(12)Loss,其中一样的 L o s s = − ∑ i = 1 m ( y i ⋅ ln ⁡ ( σ i ( 2 ) ) + ( 1 − y i ) ⋅ ln ⁡ ( 1 − σ i ( 2 ) ) ) Loss = -\sum_{i=1}^{m} \left( y_i \cdot \ln(\sigma_i^{(2)}) + (1 - y_i) \cdot \ln(1 - \sigma_i^{(2)}) \right) Loss=i=1m(yiln(σi(2))+(1yi)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 ) ) ) = -\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) =i=1m(yiln(1+eσi(1)w(12)1)+(1yi)ln(11+eσi(1)w(12)1))
可以看到这里和前面基本一致,但是当我们继续向前求导式子就会愈发复杂:
∂ L o s s ∂ w ( 0 → 1 ) , 其中 \frac{\partial Loss}{\partial w^{(0 \rightarrow 1)}}, \quad \text{其中} w(01)Loss,其中 L o s s = − ∑ i = 1 m ( y i ⋅ ln ⁡ ( σ i ( 2 ) ) + ( 1 − y i ) ⋅ ln ⁡ ( 1 − σ i ( 2 ) ) ) Loss = -\sum_{i=1}^{m} \left( y_i \cdot \ln(\sigma_i^{(2)}) + (1 - y_i) \cdot \ln(1 - \sigma_i^{(2)}) \right) Loss=i=1m(yiln(σi(2))+(1yi)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 ) ) ) = - \sum_{i=1}^{m} \left( y_i \ln \left( \frac{1}{1 + e^{-\frac{1}{1 + e^{-X_i w^{(0 \rightarrow 1)}}} w^{(1 \rightarrow 2)}} } \right) + (1 - y_i) \ln \left( 1 - \frac{1}{1 + e^{-\frac{1}{1 + e^{-X_i w^{(0 \rightarrow 1)}}} w^{(1 \rightarrow 2)}} } \right) \right) =i=1m(yiln(1+e1+eXiw(01)1w(12)1)+(1yi)ln(11+e1+eXiw(01)1w(12)1))
我们现在就可以看到当函数进行层层嵌套之后,式子就会变得非常复杂,是很不利于我们进行求导的操作的,并且这还只是一个双层的简单神经网络,可以想象当我们后面遇到更加复杂的网络结构的时候,式子就会变得超出能理解和求导的范围了。所以求导过程的复杂的确一直都是神经网络的难题,直到1986年由Rumelhart、Williams和“神经网络之父”Hinton提出的反向传播算法才得到较好的解决。

链式求导

下面我们具体来看一下反向传播算法是如何解决这个问题的:
在高等数学中我们都学过,假设有一个复合函数y = f(g(x)),链式法则告诉我们,复合函数的导数是外层函数的导数与内层函数的导数的乘积,即: d y d x = d f d u ⋅ d u d x \frac{dy}{dx} = \frac{df}{du} \cdot \frac{du}{dx} dxdy=dudfdxdu。我们利用这个规则进行求导如下:
∂ Loss ∂ w ( 1 → 2 ) = ∂ L ( σ ) ∂ σ ⋅ ∂ σ ( z ) ∂ z ⋅ ∂ z ( w ) ∂ w \frac{\partial \text{Loss}}{\partial w^{(1 \rightarrow 2)}} = \frac{\partial L(\sigma)}{\partial \sigma} \cdot \frac{\partial \sigma(z)}{\partial z} \cdot \frac{\partial z(w)}{\partial w} w(12)Loss=σL(σ)zσ(z)wz(w)
∂ L ( σ ) ∂ σ = ∂ ( − ∑ i = 1 m ( y i ⋅ ln ⁡ ( σ i ) + ( 1 − y i ) ⋅ ln ⁡ ( 1 − σ i ) ) ) ∂ σ \frac{\partial L(\sigma)}{\partial \sigma} = \frac{\partial \left( -\sum_{i=1}^{m} \left( y_i \cdot \ln(\sigma_i) + (1 - y_i) \cdot \ln(1 - \sigma_i) \right) \right)}{\partial \sigma} σL(σ)=σ(i=1m(yiln(σi)+(1yi)ln(1σi)))

= ∑ i = 1 m ∂ ( − ( y i ⋅ ln ⁡ ( σ i ) + ( 1 − y i ) ⋅ ln ⁡ ( 1 − σ i ) ) ) ∂ σ = \sum_{i=1}^{m} \frac{\partial \left( -(y_i \cdot \ln(\sigma_i) + (1 - y_i) \cdot \ln(1 - \sigma_i)) \right)}{\partial \sigma} =i=1mσ((yiln(σi)+(1yi)ln(1σi)))

= − ( y ⋅ 1 σ + ( 1 − y ) ⋅ 1 1 − σ ⋅ ( − 1 ) ) = -(y \cdot \frac{1}{\sigma} + (1 - y) \cdot \frac{1}{1 - \sigma} \cdot (-1)) =(yσ1+(1y)1σ1(1))

= − ( y σ + y − 1 1 − σ ) = -( \frac{y}{\sigma} + \frac{y - 1}{1 - \sigma} ) =(σy+1σy1)

= − y ( 1 − σ ) + ( y − 1 ) σ σ ( 1 − σ ) = - \frac{y(1 - \sigma) + (y - 1) \sigma}{\sigma(1 - \sigma)} =σ(1σ)y(1σ)+(y1)σ

= − y σ − y σ + y σ − σ σ ( 1 − σ ) = - \frac{y \sigma - y \sigma + y \sigma - \sigma}{\sigma(1 - \sigma)} =σ(1σ)yσyσ+yσσ

= σ − y σ ( 1 − σ ) = \frac{\sigma - y}{\sigma(1 - \sigma)} =σ(1σ)σy
其他部分也是同理得到:
∂ σ ( z ) ∂ z = ∂ 1 1 + e − z ∂ z \frac{\partial \sigma(z)}{\partial z} = \frac{\partial \frac{1}{1 + e^{-z}}}{\partial z} zσ(z)=z1+ez1

= ∂ ( 1 + e − z ) − 1 ∂ z = \frac{\partial (1 + e^{-z})^{-1}}{\partial z} =z(1+ez)1

= − 1 ⋅ ( 1 + e − z ) − 2 ⋅ e − z ⋅ ( − 1 ) = -1 \cdot (1 + e^{-z})^{-2} \cdot e^{-z} \cdot (-1) =1(1+ez)2ez(1)

= e − z ( 1 + e − z ) 2 = \frac{e^{-z}}{(1 + e^{-z})^2} =(1+ez)2ez

= 1 + e − z − 1 ( 1 + e − z ) 2 = \frac{1 + e^{-z} - 1}{(1 + e^{-z})^2} =(1+ez)21+ez1

= 1 ( 1 + e − z ) ⋅ ( 1 − 1 ( 1 + e − z ) ) = \frac{1}{(1 + e^{-z})} \cdot \left( 1 - \frac{1}{(1 + e^{-z})} \right) =(1+ez)1(1(1+ez)1)

= σ ( 1 − σ ) = \sigma(1 - \sigma) =σ(1σ)
最后一部分:
∂ z ( w ) ∂ w = ∂ σ ( 1 ) w ∂ w \frac{\partial z(w)}{\partial w} = \frac{\partial \sigma^{(1)}w}{\partial w} wz(w)=wσ(1)w
将三块分别带入链式求导得到:
∂ Loss ∂ w ( 1 → 2 ) = ∂ L ( σ ) ∂ σ ⋅ ∂ σ ( z ) ∂ z ⋅ ∂ z ( w ) ∂ w \frac{\partial \text{Loss}}{\partial w^{(1 \rightarrow 2)}} = \frac{\partial L(\sigma)}{\partial \sigma} \cdot \frac{\partial \sigma(z)}{\partial z} \cdot \frac{\partial z(w)}{\partial w} w(12)Loss=σL(σ)zσ(z)wz(w)

= σ ( 2 ) − y σ 2 ( 1 − σ ( 2 ) ) ⋅ σ ( 2 ) ⋅ ( 1 − σ ( 2 ) ) ⋅ σ ( 1 ) = \frac{\sigma^{(2)} - y}{\sigma^{2} (1 - \sigma^{(2)})} \cdot \sigma^{(2)} \cdot (1 - \sigma^{(2)}) \cdot \sigma^{(1)} =σ2(1σ(2))σ(2)yσ(2)(1σ(2))σ(1)

= σ ( 1 ) ⋅ ( σ ( 2 ) − y ) = \sigma^{(1)} \cdot (\sigma^{(2)} - y) =σ(1)(σ(2)y)

下面我们可以继续向前传播:
∂ Loss ∂ w ( 0 → 1 ) = ∂ L ( σ ) ∂ σ ( 2 ) ⋅ ∂ σ ( z ) ∂ z ( 2 ) ⋅ ∂ z ( w ) ∂ σ ( 1 ) ⋅ ∂ σ ( z ) ∂ z ( 1 ) ⋅ ∂ z ( w ) ∂ w ( 0 → 1 ) \frac{\partial \text{Loss}}{\partial w^{(0 \rightarrow 1)}} = \frac{\partial L(\sigma)}{\partial \sigma^{(2)}} \cdot \frac{\partial \sigma(z)}{\partial z^{(2)}} \cdot \frac{\partial z(w)}{\partial \sigma^{(1)}} \cdot \frac{\partial \sigma(z)}{\partial z^{(1)}} \cdot \frac{\partial z(w)}{\partial w^{(0 \rightarrow 1)}} w(01)Loss=σ(2)L(σ)z(2)σ(z)σ(1)z(w)z(1)σ(z)w(01)z(w)
我们可以发现其中有好几项在前面的求导过程中已经求解完了,所以这里直接带入即可

= ( σ ( 2 ) − y ) ⋅ ∂ z ( σ ) ∂ σ ( 1 ) ⋅ ∂ z ( z ) ∂ z ( 1 ) ⋅ ∂ z ( w ) ∂ w ( 0 → 1 ) = (\sigma^{(2)} - y) \cdot \frac{\partial z(\sigma)}{\partial \sigma^{(1)}} \cdot \frac{\partial z(z)}{\partial z^{(1)}} \cdot \frac{\partial z(w)}{\partial w^{(0 \rightarrow 1)}} =(σ(2)y)σ(1)z(σ)z(1)z(z)w(01)z(w)

= ( σ ( 2 ) − y ) ⋅ w 1 → 2 ⋅ ( σ ( 1 ) ( 1 − σ ( 1 ) ) ) ⋅ X = (\sigma^{(2)} - y) \cdot w^{1 \rightarrow 2} \cdot \left( \sigma^{(1)} \left( 1 - \sigma^{(1)} \right) \right) \cdot X =(σ(2)y)w12(σ(1)(1σ(1)))X

代码实现反向传播

至于这里如何使用代码实现,在前面的章节已经具体介绍过了,所以这里就再复习一遍:
任务和架构:3分类,500个样本,20个特征,共3层,第一层13个神经元,第二层8个神经元
首先还是先导入库:

import torch
import torch.nn as nn
from torch.nn import functional as F

下一步是确定数据:

torch.manual_seed(250)
X = torch.rand((500,20),dtype=torch.float32) * 100
y = torch.randint(low=0,high=3,size=(500,1),dtype=torch.float32)

定义model类:

class Model(nn.Module):
    def __init__(self,in_features=10,out_features=2):
        super(Model,self).__init__() 
        self.linear1 = nn.Linear(in_features,13,bias=False) 
        self.linear2 = nn.Linear(13,8,bias=False)
        self.output = nn.Linear(8,out_features,bias=True)     
    def forward(self, x):
        sigma1 = torch.relu(self.linear1(x))
        sigma2 = torch.sigmoid(self.linear2(sigma1))
        zhat = self.output(sigma2)
        return zhat

确定参数:

input = X.shape[1]
output = len(y.unique())

实例化:

torch.manual_seed(250)
net = model(in_features=input,out_features=output)

前向传播:

zhat = net.forward(X)
zhat

结果如下:
在这里插入图片描述
定义损失函数:

criterion = nn.CrossEntropyLoss()
loss = criterion(zhat,y.reshape(500).long())
loss

结果如下:
在这里插入图片描述
这里有一个小坑,这里的交叉熵损失函数只接受一维张量,并且要求标签必须是整型,所以需要添加reshape和long的操作
反向传播过程:

net.linear1.weight.grad
loss.backward()
net.linear1.weight.grad

在这里插入图片描述
我们可以看到反向传播前是查看不到梯度的,只有在backward之后才行

动量法Momentum

动量法的基本原理:
动量法通过引入“动量”的概念来解决这一问题,类似于物理中的动量:前一时刻的梯度信息会在更新中保留下来,影响当前的更新。这样,优化算法不仅依赖于当前的梯度,还“记住”之前的梯度,从而能够在更新过程中累积梯度的“惯性”,加速收敛。
动量法的核心思想是利用过去的梯度信息来加速当前的梯度更新。如果梯度在某一方向上稳定,动量会将更新步骤“加速”并朝着这个方向移动。而如果梯度在某一方向上发生变化,动量会帮助减小这种变化,避免过多的震荡。
具体来说,动量法有以下几个优点:

  1. 加速收敛:尤其是在坡度比较平缓的方向上,动量法能够加速收敛,因为它结合了过去的梯度信息。
  2. 减少震荡:在梯度方向上具有震荡的情况下,动量法通过加权平均来减少震荡,使得优化更加稳定。
  3. 适应不同的局部极小值:动量法能够帮助优化算法越过某些局部极小值,尤其在非凸优化问题中,它有助于找到更好的解。
    转化为公式:
    v ( t ) = γ v ( t − 1 ) − η ∂ L ∂ w v_{(t)} = \gamma v_{(t-1)} - \eta \frac{\partial L}{\partial w} v(t)=γv(t1)ηwL
    w ( t + 1 ) = w ( t ) + v ( t ) w_{(t+1)} = w_{(t)} + v_{(t)} w(t+1)=w(t)+v(t)

在这里插入图片描述
我们很容易可以在pytorch中简单实现一下动量法的过程:
先定义参数和超参数

lr = 0.1
gamma = 0.9
dw = net.linear1.weight.grad
w = net.linear1.weight.data
v = torch.zeros(dw.shape[0],dw.shape[1])

进行迭代:

v = gamma * v - lr * dw
w -= v
w

我们运行几轮之后就可以得到迭代过的结果,可以发现迭代过程的确比原来快很多:
在这里插入图片描述
当然,pytorch也有内置的动量法实现:
在这里插入图片描述
在之前的章节中已经介绍过pytorch框架的各个模块,动量法就在优化算法模块optim中。
为了实现优化算法,首先我们需要导入optim模块,其他的步骤和前面基本一致:

import torch.optim as optim

启动:

opt = optim.SGD(net.parameters() , lr=lr , momentum = gamma)
zhat = net.forward(X) 
loss = criterion(zhat,y.reshape(500).long())
loss.backward() 
opt.step() 
opt.zero_grad() 
print(loss)
print(net.linear1.weight.data[0][:10])

需要解释的是我们在进行一轮梯度下降之后,为了不浪费存储空间,并且我们一般也不会去查看梯度下降的历史记录,我们可以将梯度清空opt.zero_grad() 。其余的步骤和前面基本一致,就不再重复了,迭代几轮之后的结果如下:
在这里插入图片描述
现在只是进行了一轮梯度下降,下一步就是循环这个迭代过程。

开始迭代

为什么选择小批量

在我们的前面的代码中,都是将所有的特征矩阵x传入,但是在实际的深度学习工作中,我们所面临的数据量都是大量的高维数据,如果每次进行梯度下降都要对所有的矩阵进行求导,那么将会非常耗费计算资源。所以下面我来介绍小批量随机梯度下降(mini-batch stochastic gradient descent,简写为mini-batch SGD)。小批量梯度下降和传统的梯度下降的迭代流程基本一致,唯一不同的地方在于迭代使用的数据,小批量采用的方法是每次迭代前都对整体的样本进行采样,形成一个批次(batch),以便减少样本量和计算量。你可能会问每次只选出一批样本,模型能学到东西吗或者效果真的比全部样本都学好吗?
为什么会选择mini-batch SGD作为神经网络的入门级优化算法呢?比起传统梯度下降,mini-batch SGD更可能找到全局最小值。
在最小化损失函数 L(w) 时,目标是找到函数的最小值。然而,函数可能有不同类型的最小值:局部极小值和全局最小值。
传统梯度下降是每次迭代时都使用全部数据的梯度下降,所以每次使用的数据是一致的,因此梯度向量的方向和大小都只受到权重的影响,所以梯度方向的变化相对较小,很多时候看起来梯度甚至是指向一个方向。这样带来的优势是可以使用较大的步长,快速迭代直到找到最小值。但是缺点也很明显,由于梯度方向不容易发生巨大变化,所以一旦在迭代过程中落入局部最优的范围,传统梯度下降就很难跳出局部最优,再去寻找全局最优解了。
而mini-batch SGD在每次迭代前都会随机抽取一批数据,所以每次迭代时带入梯度向量表达式的数据是不同的,梯度的方向同时受到系数 和带入的训练数据的影响,因此每次迭代时梯度向量的方向都会发生较大变化。所以优点就是不会轻易陷入局部最优,但是相对的缺点就是需要的迭代次数变得不明。所以对于mini-batch SGD而言,它的梯度下降路线看起来往往是曲折的折线。极端情况下,当我们每次随机选取的批量中只有一个样本时,梯度下降的迭代轨迹就会变得异常不稳定。我们称这样的梯度下降为随机梯度下降(stochastic gradient descent,SGD)。
下图展示了三种梯度下降方法的不同:
在这里插入图片描述
所以在mini-batch SGD中,我们选择的批量batch含有的样本数被称为batch_size,批量尺寸,而一个epoch代表对所有训练数据进行一次完整的迭代,完成一个epoch所需要的迭代次数等于总样本量除以batch_size。

TensorDataset与DataLoader

下面我们用代码来实现分批的操作,这就需要介绍一下另外两个工具TensorDataset与DataLoader。
在这里插入图片描述
我们再次搬出这张图片可以发现,左边有一个专门用来做预处理工作的模块叫做utils,下面就有负责导入和处理的TensorDataset与DataLoader。想要实现小批量随机梯度下降,我们就需要对数据进行采样、分割等操作。通常的来说,特征张量与标签几乎总是分开的,所以我们需要将数据划分为许多组特征张量+对应标签的形式,要将数据的特征张量与标签打包成一个对象。而合并张量与标签,我们所使用的类就是utils.data.TensorDataset,负责将最外面的维度一致的tensor进行打包,下面用代码展示:

import torch
from torch.utils.data import TensorDataset
a = torch.randn(500,2,3)
b = torch.randn(500,3,4,5)
c = torch.randn(500,1)
TensorDataset(a,b,c)[0]

结果如下:
在这里插入图片描述
我们可以通过结果看出来,TensorDataset将abc中各拿了一份元素并合并起来了,例如这里第一部分是属于a的23矩阵,第二部分是属于b的34*5的矩阵,第三部分是属于c的一维张量。需要注意的是如果只输入函数,返回的是迭代器,结果如下:
在这里插入图片描述
也可以通过循环的方式遍历迭代器,方法如下:
在这里插入图片描述
另外需要注意的是这个函数必须要求第一个维度一致,否则就会出现报错,如下:在这里插入图片描述
如果我们用同样的方法加上dataloader,可以发现输出只不过把最后的元组形式变成列表:
在这里插入图片描述
下面介绍一下dataloader的几个参数的使用:

dataset = DataLoader(data
           , batch_size=100
           , shuffle=True
           , drop_last = True)

在这里插入图片描述
这里我们定义了一个500*2的矩阵,batch_size就是每一个batch分多大,这里我们取100个。shuffle的含义是是否需要每次都打乱随机抽取,如果这里是false的话,就会按照顺序依次分好,例如[1,100],[100,200]这样依次排下去。drop_last代表是否丢掉最后那个除不尽的小批次。
同样的这里返回的依然是一个迭代器,我们还是可以通过循环打印出来里面的内容:

dataset = DataLoader(data
           , batch_size=100
           , shuffle=True
           , drop_last = True)
for i in dataset:
    print(i[0].shape)

在这里插入图片描述
可以发现500份已经被我们分成了五个100份的张量了。
最后我们梳理一下整个流程:
1)设置步长 ,动量值 ,迭代次数 ,batch_size等信息,(如果需要)设置初始权重
2)导入数据,将数据切分成batches
3)定义神经网络架构
4)定义损失函数 ,如果需要的话,将损失函数调整成凸函数,以便求解最小值
5)定义所使用的优化算法
6)开始在epoches和batch上循环,执行优化算法:
6.1)调整数据结构,确定数据能够在神经网络、损失函数和优化算法中顺利运行
6.2)完成向前传播,计算初始损失
6.3)利用反向传播,在损失函数上求偏导数
6.4)迭代当前权重
6.5)清空本轮梯度
6.6)完成模型进度与效果监控
7)输出结果

在这一节中只是介绍了TensorDataset与DataLoader的常用用法,下一节会在fashion-minist数据集做一个具体的展示,以上就是本篇文章所有内容,谢谢大家看到这里!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/959493.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【esp32-uniapp】uniapp小程序篇02——引入组件库

一、引入组件库(可自行选择其他组件库) 接下来介绍colorUI、uview plus的安装,其他的安装可自行查找教程 1.colorUI weilanwl/coloruicss: 鲜亮的高饱和色彩,专注视觉的小程序组件库 下载之后解压,将\coloruicss-ma…

CNN-BiLSTM卷积双向长短期记忆神经网络时间序列预测(Matlab完整源码和数据)

CNN-BiLSTM卷积双向长短期记忆神经网络时间序列预测(Matlab完整源码和数据) 目录 CNN-BiLSTM卷积双向长短期记忆神经网络时间序列预测(Matlab完整源码和数据)预测效果基本介绍 CNN-BiLSTM卷积双向长短期记忆神经网络时间序列预测一…

算法中的移动窗帘——C++滑动窗口算法详解

1. 滑动窗口简介 滑动窗口是一种在算法中常用的技巧,主要用来处理具有连续性的子数组或子序列问题。通过滑动窗口,可以在一维数组或字符串上维护一个固定或可变长度的窗口,逐步移动窗口,避免重复计算,从而提升效率。常…

MFC结构体数据文件读写实例

程序功能将结构体内数组数据写入文件和读出 2Dlg.h中代码: typedef struct Student {int nNum[1000];float fScore;CString sss;}stu; class CMy2Dlg : public CDialog { // Construction public:CMy2Dlg(CWnd* pParent NULL); // standard constructorstu stu1; ... } 2Dl…

数据结构:二叉树—面试题(一)

目录 1、相同的树 2、另一棵树的子树 3、翻转二叉树 4、平衡二叉树 5、对称二叉树 6、二叉树遍历 7、二叉树的分层遍历 1、相同的树 习题链接https://leetcode.cn/problems/same-tree/description/ 描述: 给你两棵二叉树的根节点 p 和 q ,编写一…

绘制决策树尝试2 内含添加环境变量步骤

目录 step1 ai码 ai改 step2 下面就是环境配置问题 “ExecutableNotFound: failed to execute WindowsPath(‘dot’), make sure the Graphviz executables are on your systems’ PATH” dot -v愣是没有​编辑 graphviz安装指导 对于Windows用户: 对于Lin…

基于迁移学习的ResNet50模型实现石榴病害数据集多分类图片预测

完整源码项目包获取→点击文章末尾名片! 番石榴病害数据集 背景描述 番石榴 (Psidium guajava) 是南亚的主要作物,尤其是在孟加拉国。它富含维生素 C 和纤维,支持区域经济和营养。不幸的是,番石榴生产受到降…

C++17 命名空间的新特性:简化与优化的典范

文章目录 1. 简化的嵌套命名空间1.1 背景与问题1.2 C17的解决方案1.3 实际应用场景1.4 注意事项 2. 声明多个名称的using声明2.1 背景与问题2.2 C17的解决方案2.3 实际应用场景2.4 注意事项 3. 属性命名空间的简化3.1 背景与问题3.2 C17的解决方案3.3 实际应用场景3.4 注意事项…

【JavaEE】-- 计算机是如何工作的

文章目录 1. 冯诺依曼体系(VonNeumann Architecture)2. CPU 基本工作流程2.1 寄存器(Register)和 内存(RAM)2.2 控制单元 CU(ControlUnit)2.3 指令(Instruction) 3. 操作系统(OperatingSystem)3.1 操作系统的定位3.2 什么是进程/任务(Process…

消融效果

消融效果是模拟物体逐渐从屏幕上消失或溶解的过程,它通常利用噪声纹理实现,使物体按照某种规则逐渐透明或完全不可见。这种效果常用于: 角色死亡、传送场景、 魔法消失,比如燃烧、消失等 1、基本原理 通过对比噪声纹理值与消融进…

java后端之事务管理

Transactional注解:作用于业务层的方法、类、接口上,将当前方法交给spring进行事务管理,执行前开启事务,成功执行则提交事务,执行异常回滚事务 spring事务管理日志: 默认情况下,只有出现Runti…

【Paper Tips】随记2-word版快速删除某字符

写paper时随心记录一些对自己有用的skills与tips。 文章目录 一、待解决问题1.1 问题描述1.2 解决方法 二、方法详述2.1 必要说明2.2 应用步骤2.2.1 CtrlH一键全文替换2.2.2 录制宏 三、疑问四、总结 一、待解决问题 1.1 问题描述 word中粘贴部分文字时,格式不对时…

vite环境变量处理

环境变量: 会根据当前代码环境产生值的变化的变量就叫做环境变量 代码环境: 开发环境测试环境预发布环境灰度环境生产环境 举例: 百度地图 SDK,小程序的SDK APP_KEY: 测试环境和生产环境还有开发环境是不一样的key 开发环境: 110 生产环境:111 测试环境: 112 我们去请求第三…

使用JavaScript实现猜数字小功能

引言: 在学习编程的过程中,通过实际的小项目来巩固知识是非常有效的方法。今天,我们将使用 JavaScript 来实现一个简单的猜数字游戏。这个游戏不仅能让我们熟悉 JavaScript 的基本语法,还能锻炼我们的逻辑思维能力。 游戏规则 …

数据结构——概念与时间空间复杂度

目录 前言 一相关概念 1什么是数据结构 2什么是算法 二算法效率 1如何衡量算法效率的好坏 2算法的复杂度 三时间复杂度 1时间复杂度表示 2计算时间复杂度 2.1题一 2.2题二 2.3题三 2.4题四 2.5题五 2.6题六 2.7题七 2.8题八 四空间复杂度 1题一 2题二 3…

【转帖】eclipse-24-09版本后,怎么还原原来版本的搜索功能

【1】原贴地址:eclipse - 怎么还原原来版本的搜索功能_eclipse打开类型搜索类功能失效-CSDN博客 https://blog.csdn.net/sinat_32238399/article/details/145113105 【2】原文如下: 更新eclipse-24-09版本后之后,新的搜索功能(CT…

Django基础之ORM

一.前言 上一节简单的讲了一下orm,主要还是做个了解,这一节将和大家介绍更加细致的orm,以及他们的用法,到最后再和大家说一下cookie和session,就结束了全部的django基础部分 二.orm的基本操作 1.settings.py&#x…

单片机-STM32 IIC通信(OLED屏幕)(十一)

一、屏幕的分类 1、LED屏幕: 由无数个发光的LED灯珠按照一定的顺序排列而成,当需要显示内容的时候,点亮相关的LED灯即可,市场占有率很高,主要是用于户外,广告屏幕,成本低。 LED屏是一种用发光…

纯css实现div宽度可调整

<!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>纯css实现div尺寸可调整</title><style…

国产编辑器EverEdit - 输出窗口

1 输出窗口 1.1 应用场景 输出窗口可以显示用户执行某些操作的结果&#xff0c;主要包括&#xff1a; 查找类&#xff1a;查找全部&#xff0c;筛选等待操作&#xff0c;可以把查找结果打印到输出窗口中&#xff1b; 程序类&#xff1a;在执行外部程序时(如&#xff1a;命令窗…