当前位置: 首页 > news >正文

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数据集做一个具体的展示,以上就是本篇文章所有内容,谢谢大家看到这里!

相关文章:

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

完整神经网络流程 反向传播链式求导 代码实现反向传播动量法Momentum开始迭代为什么选择小批量TensorDataset与DataLoader 反向传播 由于本节的公式比较多,所以如果哪里写错了漏写了,还请帮忙指出以便进行改正,谢谢。 在前面的章节已经介绍过…...

三分钟简单了解一些HTML的标签和语法_01

1.图片建议建立一个文件夹如下图所示 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"keywords"><title>魔神羽落</title><style>.testone{background-color: #ff53e…...

C# 中使用Hash用于密码加密

通过一定的哈希算法&#xff08;典型的有MD5&#xff0c;SHA-1等&#xff09;&#xff0c;将一段较长的数据映射为较短小的数据&#xff0c;这段小数据就是大数据的哈希值。他最大的特点就是唯一性&#xff0c;一旦大数据发生了变化&#xff0c;哪怕是一个微小的变化&#xff0…...

【C++高并发服务器WebServer】-2:exec函数簇、进程控制

本文目录 一、exec函数簇介绍二、exec函数簇 一、exec函数簇介绍 exec 函数族的作用是根据指定的文件名找到可执行文件&#xff0c;并用它来取代调用进程的内容&#xff0c;换句话说&#xff0c;就是在调用进程内部执行一个可执行文件。 exec函数族的函数执行成功后不会返回&…...

Linux将目录挂载到另一个目录,类似软硬链接,并通过fstab实现

格式 <源> <目> none bind 0 0例如 /data/var/lib/docker /var/lib/docker none bind 0 0参数使用制表符tab间隔 效果 rootDebian12:~# cat /etc/fstab | grep -v ^# /dev/mapper/Debian12--vg-root / ext4 erro…...

【C++模板】:如何判断自定义类型是否实现某个函数

一、引子 偶尔我们会面对这样的尴尬的场景&#xff0c;我们需要显示的去判断在某个自定义类型中&#xff0c;是否已经提供了我们期待的API接口&#xff0c;以避免产生“莫须有”的错误。阁下该如何破解此问题&#xff01; 这里&#xff0c;直接给出一种通用的方法&#xff0c;…...

关于CAN(FD)转以太网详细介绍

一、功能描述 CANFD 完全向下兼容 CAN &#xff0c;以下统称 CAN(FD) 。 SG-CAN(FD)NET-210 是一款用来把 CANFD 总线数据转为网口数据的设备。 网口支持 TCP Sever 、 TCP Client 、 UDP Sever 、 UDP Client 四种模式。 可以通过软件配置和 Web 网页配置。 两路…...

GPU算力平台|在GPU算力平台部署MedicalGPT医疗大模型的应用教程

文章目录 一、GPU算力服务平台云端GPU算力平台 二、平台账号注册流程MedicalGPT医疗大模型的部署MedicalGPT医疗大模型概述MedicalGPT部署步骤 一、GPU算力服务平台 云端GPU算力平台 云端GPU算力平台专为GPU加速计算设计&#xff0c;是一个高性能计算中心&#xff0c;广泛应用…...

【ChatGPT】意义空间与语义运动定律 —— AI 世界的神秘法则

作者介绍 斯蒂芬•沃尔弗拉姆&#xff08;Stephen Wolfram&#xff09; 计算机科学家、数学家和理论物理学家&#xff0c;当今科学和技术领域重要的革新者之一。他创造了在全世界备受推崇的软件系统——Mathematica、Wolfram|Alpha和Wolfram语言。 在 ChatGPT 内部&#xff0c;…...

Harbor 部署教程

Harbor 部署教程 一.背景二.遇到的问题及解决办法1.服务无法启动,相关的容器不断重启 三.操作步骤1. 创建工作目录并进入2. 配置 Docker 守护进程3. 重启 Docker 服务4. 下载 Harbor 离线安装包和 Docker Compose5. 安装 Docker Compose6. 解压 Harbor 安装包7. 配置 Harbor8. …...

hive 自动检测、自动重启、记录检测日志、自动清理日志

最终效果 定时检测hive运行状态&#xff0c;进程不存在或者进程存在但是不监听端口的hiveserver2&#xff0c;自动重新拉起每次检测脚本执行的日志都会保存在log目录下.check文件&#xff0c;每一个月一个文件每月15日&#xff0c;删除2月前的检测日志开启hive自带日志输出后&…...

C++类和对象下详细指南

C类和对象下详细指南 1. 初始化列表与构造函数 1.1 初始化列表概述 初始化列表在C中用于初始化对象的成员变量&#xff0c;特别是当你需要在对象构造时就明确成员变量的值时。通过初始化列表&#xff0c;成员变量的初始化可以在进入构造函数体之前完成。这不仅可以提升性能&…...

【算法C++】构造回文字符串问题

问题描述 小C手中有一个由小写字母组成的字符串 s。她希望构造另一个字符串 t&#xff0c;并且这个字符串需要满足以下几个条件&#xff1a; t 由小写字母组成&#xff0c;且长度与 s 相同。t 是回文字符串&#xff0c;即从左到右与从右到左读取相同。t 的字典序要小于 s&…...

基于java线程池和EasyExcel实现数据异步导入

基于java线程池和EasyExcel实现数据异步导入 2.代码实现 2.1 controller层 PostMapping("import")public void importExcel(MultipartFile file) throws IOException {importService.importExcelAsync(file);}2.2 service层 Resource private SalariesListener sa…...

使用Layout三行布局(SemiDesign)

tips&#xff1a;Semi Design网址 &#xff1a;Semi Design 1、安装Semi # 使用 npm npm i douyinfe/semi-ui# 使用 yarn yarn add douyinfe/semi-ui# 使用 pnpm pnpm add douyinfe/semi-ui 2、引入Layout布局 import { Layout } from douyinfe/semi-ui;3、开始实现三行布局…...

缓存之美:万文详解 Caffeine 实现原理(下)

上篇文章&#xff1a;缓存之美&#xff1a;万文详解 Caffeine 实现原理&#xff08;上&#xff09; getIfPresent 现在我们对 put 方法有了基本了解&#xff0c;现在我们继续深入 getIfPresent 方法&#xff1a; public class TestReadSourceCode {Testpublic void doRead() …...

PHP防伪溯源一体化管理系统小程序

&#x1f50d; 防伪溯源一体化管理系统&#xff0c;品质之光&#xff0c;根源之锁 &#x1f680; 引领防伪技术革命&#xff0c;重塑品牌信任基石 我们自豪地站在防伪技术的前沿&#xff0c;为您呈现基于ThinkPHP和Uniapp精心锻造的多平台&#xff08;微信小程序、H5网页&…...

STM32——LCD

一、引脚配置 查看引脚 将上述引脚都设置为GPIO_Output 二、导入驱动文件 将 LCD 驱动的 Inc 以及 Src 中的 fonts.h,lcd.h 和 lcd.c 导入到自己工程的驱动文件中。 当然&#xff0c;后面 lcd 的驱动学习可以和 IMX6U 一块学。 三、LCD函数 void LCD_Clear(u16 Color); 功能…...

洛谷刷题1-3

比较巧妙&#xff0c;求最小公倍数&#xff0c;看多少个数一次循环&#xff0c;直接求解就好了&#xff0c;N的数量级比较大&#xff0c;一层循环也会超时&#xff0c;也用了点双指针的想法&#xff08;归并排序&#xff09; 这里很大的问题&#xff0c;主要是cin输入的时候遇到…...

Facebook 元宇宙与全球文化交流的新趋势

随着科技的快速发展&#xff0c;虚拟现实与增强现实技术逐渐成为全球社交平台的重要组成部分。Facebook&#xff08;现改名为 Meta&#xff09;率先将目光投向了元宇宙这一新兴领域&#xff0c;致力于打造一个超越传统社交媒体的虚拟空间&#xff0c;成为全球文化交流的新平台。…...

结构化布线系统分为六个子系统

文章目录 前言系统介绍1. 工作区子系统 (Work Area Subsystem)2. 水平布线子系统 (Horizontal Cabling Subsystem)3. 管理子系统 (Administration Subsystem)4. 干线&#xff08;垂直&#xff09;子系统 (Backbone Cabling Subsystem)5. 设备间子系统 (Equipment Room Subsyste…...

亿坊软件前端命名规范

在前端开发中&#xff0c;文件命名的重要性不言而喻。由于历史原因和个人习惯&#xff0c;不同的开发者在命名DOM结构、图片和CSS文件时&#xff0c;可能会产生不一致的情况。这不仅会导致维护成本增加&#xff0c;还会降低团队协作效率。因此&#xff0c;制定一套统一的命名规…...

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

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

【Qt】: QPointer详解

考古&#xff1a; 《Qt智能指针之QScopedPointer》 QPointer是Qt框架中的一个智能指针类&#xff0c;用于安全地管理QObject派生对象的指针。它的主要功能是自动将指针置为nullptr&#xff0c;当它所指向的QObject对象被销毁时。这在Qt应用程序中非常有用&#xff0c;因为QObj…...

15_业务系统基类

创建脚本 SystemRoot.cs 因为 业务系统基类的子类 会涉及资源加载服务层ResSvc.cs 和 音乐播放服务层AudioSvc.cs 所以在业务系统基类 提取引用资源加载服务层ResSvc.cs 和 音乐播放服务层AudioSvc.cs 并调用单例初始化 using UnityEngine; // 功能 : 业务系统基类 public c…...

C++中explicit关键字的介绍和使用详解

某一天突然发现Qt自动生成的类文件的构造函数前边都有explicit关键字&#xff0c;但是explicit关键字什么意思呐&#xff1f; 在C中&#xff0c;explicit是一个关键字&#xff0c;主要用于修饰构造函数或转换运算符&#xff0c;其作用是防止隐式类型转换或隐式构造行为。 下面…...

动态内存管理

本章重点 1.为什么存在动态内存分配 2.动态内存函数的介绍 3.malloc free calloc realloc 4.常见的动态内存错误 一.为什么存在动态内存分配 二.动态内存函数的介绍 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include &…...

Java 中的各种锁详解

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/literature?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;…...

进制之间转换

「 一、十进制 二进制 」 1.十进制转二进制&#xff1a;一直除以2直到商为0&#xff0c;再反向取余数。 例&#xff1a;13&#xff08;十进制&#xff09;转1101&#xff08;二进制&#xff09; 2.二进制转十进制:最后一位数开始是2^0&#xff0c;然后一直按照指数递增的方式…...

微信小程序获取位置服务

wx.getLocation({type: gcj02,success(res) {wx.log(定位成功);},fail(err) {wx.log(定位失败, err);wx.showModal({content: 请打开手机和小程序中的定位服务,success: (modRes) > {if (modRes.confirm) {wx.openSetting({success(setRes) {if (setRes.authSetting[scope.u…...

数据结构——实验八·学生管理系统

嗨~~欢迎来到Tubishu的博客&#x1f338;如果你也是一名在校大学生&#xff0c;正在寻找各种编程资源&#xff0c;那么你就来对地方啦&#x1f31f; Tubishu是一名计算机本科生&#xff0c;会不定期整理和分享学习中的优质资源&#xff0c;希望能为你的编程之路添砖加瓦⭐&…...

Linux应用编程(五)USB应用开发-libusb库

一、基础知识 1. USB接口是什么&#xff1f; USB接口&#xff08;Universal Serial Bus&#xff09;是一种通用串行总线&#xff0c;广泛使用的接口标准&#xff0c;主要用于连接计算机与外围设备&#xff08;如键盘、鼠标、打印机、存储设备等&#xff09;之间的数据传输和电…...

Swift语言探索:Sequence与Collection的详细解读

在Swift编程语言中&#xff0c;Sequence和Collection是两个非常重要的协议&#xff0c;它们定义了遍历和访问元素集合的方式。理解这两个协议不仅有助于我们更好地掌握Swift的集合类型&#xff0c;还能让我们在编写代码时更加灵活和高效。本文将详细解读Sequence与Collection&a…...

解锁C# EF/EF Core:从入门到进阶的技术飞跃

一、EF/EF Core 初相识 在.NET 开发的广阔天地中&#xff0c;Entity Framework (EF) 及其轻量级、可扩展、跨平台的版本 Entity Framework Core (EF Core)&#xff0c;犹如两颗璀璨的明星&#xff0c;照亮了数据访问层开发的道路。它们是开源的对象关系映射器&#xff08;ORM&…...

大模型搜广推?对算法工作的影响

大模型与传统应用结合的性质 长期看是一种范式革新。算力和模型定义的边界发生变化&#xff0c;选择生成式AI或大模型发展方向时&#xff0c;会不断发现新的增长曲线&#xff0c;目前在不断被验证。 现阶段大模型确实带来了增量信息&#xff0c;但推荐过程仍在原有流程基础上…...

【教程】最好的pytorch教程

文章目录 Materials for the Learn PyTorch for Deep Learning: Zero to Mastery course from: https://github.com/mrdbourke/pytorch-deep-learning/blob/main/01_pytorch_workflow.ipynb...

基于java线程池和EasyExcel实现异步导出

基于java线程池和EasyExcel实现异步导出 1.controller层 GetMapping("export") public void exportExcel(HttpServletResponse response) throws IOException, InterruptedException {exportService.exportExcel(response); }2. service public void exportExcel(H…...

vue3组件传值具体使用

问&#xff1a; left.vue文件调用接口获取了后端返回的urlLink字段&#xff0c;我该怎么传递给总的父组件index.vue中&#xff0c;我需要点击父组件的一个按钮来触发跳转&#xff1f; 回答&#xff1a; 在 Vue 3 中使用 TypeScript 和 setup 语法糖时&#xff0c;可以通过 e…...

《CPython Internals》阅读笔记:p336-p352

《CPython Internals》学习第 17天&#xff0c;p336-p352 总结&#xff0c;总计 17 页。 一、技术总结 1.GDB GDB 是 GNU Dbugger 的缩写。 (1)安装 sudo apt install gdb(2)创建 .gdbinit 文件 touch ~/.gdbinitvim ~/.gdbinit(3)配置 .gdbinit 文件 add-auto-load-saf…...

WPF基础 | WPF 常用控件实战:Button、TextBox 等的基础应用

WPF基础 | WPF 常用控件实战&#xff1a;Button、TextBox 等的基础应用 一、前言二、Button 控件基础2.1 Button 的基本定义与显示2.2 按钮样式设置2.3 按钮大小与布局 三、Button 的交互功能3.1 点击事件处理3.2 鼠标悬停与离开效果3.3 按钮禁用与启用 四、TextBox 控件基础4.…...

Langchain+讯飞星火大模型Spark Max调用

1、安装langchain #安装langchain环境 pip install langchain0.3.3 openai -i https://mirrors.aliyun.com/pypi/simple #灵积模型服务 pip install dashscope -i https://mirrors.aliyun.com/pypi/simple #安装第三方集成,就是各种大语言模型 pip install langchain-comm…...

2025年华为云一键快速部署幻兽帕鲁联机服务器教程

华为云如何快速部署幻兽帕鲁联机服务器&#xff1f;为了方便新手玩家搭建专属游戏联机服务器&#xff0c;华为云推出了云游戏专场&#xff0c;无需专业技术&#xff0c;新手小白也能一键快速部署幻兽帕鲁联机服务器&#xff01; 华为云快速部署幻兽帕鲁联机服务器教程如下&…...

Langchain+文心一言调用

import osfrom langchain_community.llms import QianfanLLMEndpointos.environ["QIANFAN_AK"] "" os.environ["QIANFAN_SK"] ""llm_wenxin QianfanLLMEndpoint()res llm_wenxin.invoke("中国国庆日是哪一天?") print(…...

C++AVL树(一)详解

文章目录 AVL树概念AVL树的插入平衡因子的更新旋转的规则左单旋右单旋抽象的情况h0h1h 2h 3 AVL树 概念 AVL树是一棵平衡二叉查找树&#xff0c;AVL树是空树&#xff0c;保证左右子树都是AVL树&#xff0c;AVL树要求高度差的绝对值不超过1&#xff0c;因为最好情况是1&#…...

ceph新增节点,OSD设备,标签管理(二)

一、访问客户端集群方式 方式一: 使用cephadm shell交互式配置 [rootceph141 ~]# cephadm shell # 注意&#xff0c;此命令会启动一个新的容器&#xff0c;运行玩后会退出&#xff01; Inferring fsid c153209c-d8a0-11ef-a0ed-bdb84668ed01 Inferring config /var/lib/ce…...

c++学习第七天

创作过程中难免有不足&#xff0c;若您发现本文内容有误&#xff0c;恳请不吝赐教。 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考。 一、const成员函数 //Date.h#pragma once#include<iostream> using namespace std;class Date { public:Date…...

F/V/F/I频率脉冲信号转换器

F/V/F/I频率脉冲信号转换器 概述&#xff1a;捷晟达科技的JSD TFA-1001系列是一进一出频率脉冲信号转换器(F/V转换器),该频率转换器是将频率脉冲信号(方波、正弦波、锯齿波)转换成国际标准的模拟量电压(电流)信号,并远距离无失真传送到控制室(如:PLC,DCS,AD,PC采集系统)产品的输…...

SQLServer中DBCC INPUTBUFFER显示从客户端发送到 SQL Server 实例的最后一个语句

SQLServer中DBCC INPUTBUFFER显示从客户端发送到 SQL Server 实例的最后一个语句 1、本文内容 语法参数结果集权限示例 适用于&#xff1a; SQL ServerAzure SQL 数据库Azure SQL 托管实例 显示从客户端发送到 SQL Server 实例的最后一个语句。 2、语法 DBCC INPUTBUFFE…...

Mysql面试题----MySQL中CHAR和VARCHAR的区别

存储方式 CHAR&#xff1a;是一种固定长度的字符串类型。它会按照定义的长度来分配存储空间&#xff0c;无论实际存储的字符串长度是多少。例如&#xff0c;定义一个 CHAR (10) 的列&#xff0c;如果存储的值是 ‘ab’&#xff0c;那么它仍然会占用 10 个字符的存储空间&#…...

github汉化

本文主要讲述了github如何汉化的方法。 目录 问题描述汉化步骤1.打开github&#xff0c;搜索github-chinese2.打开项目&#xff0c;打开README.md3.下载安装脚本管理器3.1 在README.md中往下滑动&#xff0c;找到浏览器与脚本管理器3.2 选择浏览器对应的脚本管理器3.2.1 点击去…...