深度学习 Pytorch 深层神经网络
在之前已经学习了三种单层神经网络,分别为实现线性方程的回归网络,实现二分类的逻辑回归(二分类网络),以及实现多分类的softmax回归(多分类网络)。从本节开始,我们将从单层神经网络展开至深层神经网络,并深入理解层、以及层上的各类函数计算对于神经网络的意义。
32 异或门问题
回忆一下,上节我们提到的数据“与门”(andgate
)是一组只有两个特征(x1,x2)的分类数据,当两个特征下的取值都为1
时,分类标签为1
,其他时候分类标签为0
。
x0 | x1 | x2 | andgate |
---|---|---|---|
1 | 0 | 0 | 0 |
1 | 1 | 0 | 0 |
1 | 0 | 1 | 0 |
1 | 1 | 1 | 1 |
之前我们使用tensor
结构与nn.Linear
类在“与门”数据上实现了简单的二分类神经网络(逻辑回归),其中使用tensor
结构定义时,我们自定义了权重向量w
,并巧妙地让逻辑回归的输出结果与真实结果一致,具体代码如下:
import torch
X = torch.tensor([[1,0,0],[1,1,0],[1,0,1],[1,1,1]], dtype = torch.float32)
andgate = torch.tensor([0,0,0,1], dtype = torch.float32)
w = torch.tensor([-0.2,0.15,0.15], dtype = torch.float32)
def LogisticR(X,w):zhat = torch.mv(X,w)sigma = torch.sigmoid(zhat)andhat = torch.tensor([int(x) for x in sigma >= 0.5], dtype=torch.float32)return sigma, andhat
sigma, andhat = LogisticR(X,w)
sigma
# output :
tensor([0.4502, 0.4875, 0.4875, 0.5250])andhat
# output :
tensor([0., 0., 0., 1.])andgate
# output :
tensor([0., 0., 0., 1.])
考虑到与门的数据只有两维,我们可以通过python
中的matploblib
代码将数据可视化,其中,特征 x 1 x_1 x1为横坐标,特征 x 2 x_2 x2为纵坐标,紫色点代表类别0
,红色点代表类别1
。
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style("white")
plt.figure(figsize=(5,3)) #设置画布大小
plt.title("AND GATE",fontsize=16) #设置图像标题
plt.scatter(X[:,1],X[:,2],c=andgate,cmap="rainbow") #绘制散点图
plt.xlim(-1,3) #设置横纵坐标尺寸
plt.ylim(-1,3)
plt.grid(alpha=.4,axis="y") #显示背景中的网格
plt.gca().spines["top"].set_alpha(.0) #让上方和右侧的坐标轴被隐藏
plt.gca().spines["right"].set_alpha(.0);
# output :
在机器学习中,存在一种对数据进行分类的简单方式:就是在两类数据中间绘制一条直线,并规定直线一侧的点属于一类标签,另一侧的点属于另一类标签。
而在这个由 x 1 x_1 x1与 x 2 x_2 x2构成的空间中,二维平面的任意一条线可以被表示为:
x 1 = a x 2 + b x_1=ax_2+b x1=ax2+b
我们将此表达式变换一下:
0 = b − x 1 + a x 2 0 = [ 1 , x 1 , x 2 ] ∗ [ b − 1 a ] 0 = X w 0 = b - x_1 + ax_2 \\ 0 = [1, x_1, x_2] * \begin{bmatrix} b \\ -1 \\ a \end{bmatrix} \\ 0 = Xw 0=b−x1+ax20=[1,x1,x2]∗ b−1a 0=Xw
其中[b, -1, a]
就是我们的权重向量 w w w(默认列向量), X X X就是我们的特征张量, b b b是我们的截距。在一组数据下,给定固定的 w w w和 b b b,这个式子就可以是一条固定直线。在 w w w和 b b b不确定的状况下,这个表达式 X w = 0 Xw=0 Xw=0就可以代表平面上的任意一条直线。在直线上方的点为1
类(红色),在直线下方的点为0
类(紫色)。
如果在 w w w和 b b b固定时,给定一个唯一的 X X X的取值,这个表达式就可以表示一个固定的点。如果任取一个紫色的点 X p X_p Xp就可以被表示为:
X p w = p X_pw=p Xpw=p
由于紫色的点所代表的标签y
是0
,所以我们规定, p < = 0 p<=0 p<=0。
同样的,对于任意一个红色的点 X r X_r Xr而言,我们可以将它表示为:
X r w = r X_rw=r Xrw=r
由于红色点所代表的标签y
是1
,所以我们规定, r > 0 r>0 r>0。
由此,如果我们有新的测试数据 X t X_t Xt,则 X t X_t Xt的标签可以根据以下式子来判定:
y = { 1 , if X i w > 0 0 , if X i w ≤ 0 y = \begin{cases} 1, & \text{if } X_i w > 0 \\ 0, & \text{if } X_i w \leq 0 \end{cases} y={1,0,if Xiw>0if Xiw≤0
在外面只有两个特征的时候,实际上这个式子就是:
y = { 1 , if w 1 x 1 + w 2 x 2 + b > 0 0 , if w 1 x 1 + w 2 x 2 + b ≤ 0 y = \begin{cases} 1, & \text{if } w_1x_1 + w_2x_2 + b > 0 \\ 0, & \text{if } w_1x_1 + w_2x_2 + b \leq 0 \end{cases} y={1,0,if w1x1+w2x2+b>0if w1x1+w2x2+b≤0
而 w 1 x 1 + w 2 x 2 + b w_1x_1+w_2x_2+b w1x1+w2x2+b就是我们在神经网络中表示 z z z的式子,所以这个式子实际上就是:
y = { 1 , if z > 0 0 , if z ≤ 0 y = \begin{cases} 1, & \text{if } z > 0 \\ 0, & \text{if } z \leq 0 \end{cases} y={1,0,if z>0if z≤0
你发现了吗?用线划分的数学表达和我们之前学习的阶跃函数的表示方法一模一样!
在上节中,我们试着使用阶跃函数作为联系函数替代sigmoid
来对与门数据进行分类,最终的结果发现阶跃函数也可以轻松实现与sigmoid
函数作为联系函数时的分类效果。我们现在把代码改写一下:
import torch
X = torch.tensor([[1,0,0],[1,1,0],[1,0,1],[1,1,1]], dtype = torch.float32)
andgate = torch.tensor([0,0,0,1], dtype = torch.float32)
def AND(X):w = torch.tensor([-0.2,0.15, 0.15], dtype = torch.float32)zhat = torch.mv(X,w)andhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32)return andhat
andhat = AND(X)
andhat
# output :
tensor([0., 0., 0., 1.])andgate
# output :
tensor([0., 0., 0., 1.])
实际上,阶跃函数的本质也就是用直线划分点的过程,而这条具有分类功能的直线被我们称为 “决策边界” 。
在机器学习中,任意 分类 算法都可以绘制自己的决策边界,且依赖决策边界来进行分类。
不难看出,既然决策边界的方程就是 z z z的表达式,那在相同的数据 X X X下(即在相同的数据空间中),决策边界具体在哪里就是由我们定义的 w w w决定的。
在之前的代码中,我们定义了 w 1 = 0.15 , w 2 = 0.15 , b = − 0.23 w_1=0.15,w_2=0.15,b=-0.23 w1=0.15,w2=0.15,b=−0.23,这些参数对应的直线就是 0.15 x 1 + 0.15 x 2 − 0.23 0.15x_1+0.15x_2-0.23 0.15x1+0.15x2−0.23。我们可以用以下代码,将这条线绘制到样本点的图像上:
import numpy as np
x = np.arange(-1, 3, 0.5)
plt.plot(x,(0.23-0.15*x)/0.15, color="k", linestyle="--")
# output :
可以看出,这是一条能够将样本点完美分割的直线。这说明,我们所设置的权重和截距绘制出的直线,可以将与门数据中的两类点完美分开。所以对于任意的数据,我们只需要找到适合的 w w w和 b b b就能够确认相应的决策边界,也就可以自由进行分类了。
现在,让我们使用阶跃函数作为线性结果 z z z之后的函数,在其他典型数据上试试看使用决策边界进行分类的方式。
比如下面的 “或门” (OR GATE
),特征一或特征二为1
的时候标签就为1
的数据。
x0 | x1 | x2 | orgate |
---|---|---|---|
1 | 0 | 0 | 0 |
1 | 1 | 0 | 1 |
1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 |
以及 “非与门”(NAND GATE
),特征一和特征二都是1
的时候标签就为0
,其他时候标签则为1
的数据。
x0 | x1 | x2 | andgate |
---|---|---|---|
1 | 0 | 0 | 1 |
1 | 1 | 0 | 1 |
1 | 0 | 1 | 1 |
1 | 1 | 1 | 0 |
用以下代码,可以很容易就实现对这两种数据的拟合:
import torch
X = torch.tensor([[1,0,0],[1,1,0],[1,0,1],[1,1,1]],dtype=torch.float32)
# 定义或门的标签
orgate = torch.tensor([0,1,1,1], dtype = torch.float32)
# 或门的函数
def OR(X):w = torch.tensor([-0.08, 0.15,0.15], dtype = torch.float32)zhat = torch.mv(X,w)yhat = torch.tensor([int(x) for x in zhat > 0], dtype=torch.float32)return yhat
OR(X)
# output :
tensor([0., 1., 1., 1.])
# 绘制直线划分散点的图像
x = np.arange(-1,3,0.5)
plt.figure(figsize=(5,3))
plt.title("OR GATE",fontsize=16)
plt.scatter(X[:,1],X[:,2],c=orgate,cmap="rainbow")
plt.plot(x,(0.08-0.15*x)/0.15,color="k",linestyle="--")
plt.xlim(-1,3)
plt.ylim(-1,3)
plt.grid(alpha=.4,axis="y")
plt.gca().spines["top"].set_alpha(.0)
plt.gca().spines["right"].set_alpha(.0)
# output :
注意:非与门、或门的权重都不同
# 定义与非门的标签
X = torch.tensor([[1,0,0],[1,1,0],[1,0,1],[1,1,1]],dtype=torch.float32)
nandgate = torch.tensor([1,1,1,0], dtype = torch.float32)
def NAND(X):w = torch.tensor([0.23,-0.15,-0.15], dtype = torch.float32) zhat = torch.mv(X,w)yhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32)return yhat
NAND(X)
# output :
tensor([1., 1., 1., 0.])
#绘制直线划分散点的图像
x = np.arange(-1,3,0.5)
plt.figure(figsize=(5,3))
plt.title("NAND GATE",fontsize=16)
plt.scatter(X[:,1],X[:,2],c=nandgate,cmap="rainbow")
plt.plot(x,(0.23-0.15*x)/0.15,color="k",linestyle="--")
plt.xlim(-1,3)
plt.ylim(-1,3)
plt.grid(alpha=.4,axis="y")
plt.gca().spines["top"].set_alpha(.0)
plt.gca().spines["right"].set_alpha(.0)
# output :
可以看到,或门和与非门的情况都可以被很简单地解决。现在来看下面这一组数据:
x0 | x1 | x2 | XOR |
---|---|---|---|
1 | 0 | 0 | 0 |
1 | 1 | 0 | 1 |
1 | 0 | 1 | 1 |
1 | 1 | 1 | 0 |
和之前的数据相比,这组数据的特征没有变化,不过标签 y y y变成了[0, 1, 1, 0]
。这是一组被称为“异或门”(XOR GATE
)的数据,可以看出,当两个特征的取值一致时,标签为0,反之标签则为1。我们同样把这组数据可视化看看:
xorgate = torch.tensor([0,1,1,0], dtype = torch.float32)
plt.figure(figsize=(5,3))
plt.title("XOR GATE",fontsize=16)
plt.scatter(X[:,1],X[:,2],c=xorgate,cmap="rainbow")
plt.xlim(-1,3)
plt.ylim(-1,3)
plt.grid(alpha=.4,axis="y")
plt.gca().spines["top"].set_alpha(.0)
plt.gca().spines["right"].set_alpha(.0)
# output :
问题出现了——没有任意一条直线可以将两类点完美分开,所以无论我们如何调整 w w w和 b b b的取值都无济于事。
这种情况在机器学习中非常常见,而现实中的大部分数据都是无法用直线完美分开的(相似的是,线性回归可以拟合空间中的直线,但大部分变量之间的关系都无法用直线来拟合,这也是线性模型的局限性)。此时我们会需要类似如下的曲线来对数据进行划分:
如何把直线的决策边界变成曲线呢?答案是将单层神经网络变成多层。
来看下面的网格结构:
这是一个多层神经网络,除了输入层和输出层,还多了一层“中间层”。在这个网络中,数据依然是从左侧的输入层进入,特征会分别进入NAND
和OR
两个中间层的神经元,分别获得NAND
函数的结果ynand和OR
函数的结果yor,接着,ynand和yor会继续被输入下一层的神经元AND
,经过AND
函数的处理,成为最终结果y
。让我们来使用代码实现这个结构,来看看这样的结构是否能够解决异或门的问题:
def XOR(X):#输入值:input_1 = X#中间层:sigma_nand = NAND(input_1)sigma_or = OR(input_1)x0 = torch.tensor([[1],[1],[1],[1]],dtype=torch.float32)#输出层:input_2 = torch.cat((x0,sigma_nand.view(4,1),sigma_or.view(4,1)),dim=1)y_and = AND(input_2)#print("NANE:",y_nand)#print("OR:",y_or)return y_and
XOR(X)
# output :
tensor([0., 1., 1., 0.])
可以看到,最终输出的结果和异或门要求的结果是一致的。可见,拥有更多的”层“帮助我们解决了单层神经网络无法处理的非线性问题。叠加了多层的神经网络也被称为”多层神经网络“。多层神经网络是神经网络在深度学习中的基本形态,接下来,我们就来认识多层神经网络。
33 黑箱:深层神经网络的不可解释性
首先从结构上来看,多层神经网络比单层神经网络多出了“中间层”。中间层常常被称为隐藏层(hidden layer
),理论上来说可以有无限层,所以在图像表示中经常被省略。
层数越多,神经网络的模型复杂度越高,一般也认为更深的神经网络可以解决更加复杂的问题。在学习中,通常我们只会设置3~5个隐藏层,但在实际工业场景中会更多。
当数据量够大时,现代神经网络层数越深,效果越好。
在一个神经网络中,更靠近输入层的层级相对于其他层级叫做"上层",更靠近输出层的则相对于其他层级叫做"下层"。若从输入层开始从左向右编号,则输入层为第0层,输出层为最后一层。除了输入层以外,每个神经元中都存在着对数据进行处理的数个函数。在我们的例子异或门(XOR
)中,隐藏层中的函数是NAND
函数和OR
函数(也就是线性回归的加和函数+阶跃函数),输出层上的函数是AND
函数。
对于所有神经元和所有层而言,加和函数的部分都是一致的(都得到结果 ),因此我们需要关注的是加和之外的那部分函数。在隐藏层中这个函数被称为激活函数,符号为 h ( z ) h(z) h(z),在输出层中这个函数只是普通的连接函数,我们定义为是 g ( z ) g(z) g(z)。我们的数据被逐层传递,每个下层的神经元都必须处理上层的神经元中的 h ( z ) h(z) h(z)处理完毕的数据,整个流程本质是一个嵌套计算结果的过程。
实际上对于所有神经元来说,都要实行加和,唯一不同的是加和之后使用的函数。
在神经网络中,任意层上都有至少一个神经元,最上面的是常量神经元,连接常量神经元的箭头上的参数是截距 b b b,剩余的是特征神经元,连接这些神经元的箭头上的参数都是权重 w w w。神经元是从上至下进行编号,需要注意的是,常量神经元与特征神经元是分别编号的。和从0
开始编号的层数不同,神经元是从1
开始编号的。在异或门的例子中,含有1
的偏差神经元是1
号偏差神经元,含有特征 x 1 x_1 x1的神经元则是1
号特征神经元。
除了神经元和网络层,权重、偏差、神经元上的取值也存在编号。这些编号规律分别如下:
这些编号实在很复杂,所以将编号改写为如下:
需要记住的是, z z z是加和之后的结果, σ \sigma σ是经过激活函数的结果,无论是什么激活函数。
w w w和 b b b是位于层与层之间的连线上,所以有箭头。 z z z和 σ \sigma σ是神经元上面的,无箭头。
上标表示的是层的编号,下标表示的是层中的神经元编号。
而 b b b由于每一层之后会有一个新的神经元,所以没有起点,只有终点。
有了这些编号说明,我们就可以用数学公式来表示从输入层传入到第一层隐藏层的信号了。以上一节中说明的XOR
异或门为例子,对于仅有两个特征的单一样本而言,在第一层的第一个特征神经元中获得加和结果的式子可以表示为:
z 1 1 = b → 1 → 1 + x 1 w 1 → 1 0 → 1 + x 2 w 2 → 1 0 → 1 z_1^1 = b_{\rightarrow1}^{\rightarrow1} + x_1 w_{1 \rightarrow 1}^{0 \rightarrow 1} + x_2 w_{2 \rightarrow 1}^{0 \rightarrow 1} z11=b→1→1+x1w1→10→1+x2w2→10→1
而隐藏层中被 h ( z ) h(z) h(z)处理的公式可以写作:
σ 1 1 = h ( z 1 1 ) = h ( b − 1 − 1 + x 1 w 1 → 1 0 → 1 + x 2 w 2 → 1 0 → 1 ) \sigma_1^1 = h(z_1^1) \\ = h(b_{-1}^{-1} + x_1 w_{1 \rightarrow 1}^{0 \rightarrow 1} + x_2 w_{2 \rightarrow 1}^{0 \rightarrow 1}) σ11=h(z11)=h(b−1−1+x1w1→10→1+x2w2→10→1)
根据我们之前写的NAND
函数,这里的 h ( z ) h(z) h(z)为阶跃函数。
现在,我们用矩阵来表示数据从输入层传入到第一层,并在第一层的神经元中被处理成 σ \sigma σ的情况:
Z 1 = W 1 ⋅ X + B 1 \mathbf{Z}^1 = \mathbf{W}^1 \cdot \mathbf{X} + \mathbf{B}^1 Z1=W1⋅X+B1
[ z 1 1 z 2 1 ] = [ w 1 → 1 0 → 1 w 1 → 2 0 → 1 w 2 → 1 0 → 1 w 2 → 2 0 → 1 ] ∗ [ x 1 x 2 ] + [ b → 1 → 1 b → 2 → 1 ] \begin{bmatrix} z_1^1 \\ z_2^1 \end{bmatrix}= \begin{bmatrix} w_{1 \rightarrow 1}^{0 \rightarrow 1} & w_{1 \rightarrow 2}^{0 \rightarrow 1} \\ w_{2 \rightarrow 1}^{0 \rightarrow 1} & w_{2 \rightarrow 2}^{0 \rightarrow 1} \end{bmatrix} * \begin{bmatrix} x_1 \\ x_2 \end{bmatrix} + \begin{bmatrix} b_{\rightarrow1}^{\rightarrow1} \\ b_{\rightarrow2}^{\rightarrow1} \end{bmatrix} [z11z21]=[w1→10→1w2→10→1w1→20→1w2→20→1]∗[x1x2]+[b→1→1b→2→1]
矩阵结构表示为: ( 2 , 1 ) = ( 2 , 2 ) ∗ ( 2 , 1 ) + ( 2 , 1 ) 矩阵结构表示为: (2,1) = (2,2) * (2,1) + (2,1) 矩阵结构表示为:(2,1)=(2,2)∗(2,1)+(2,1)
= [ x 1 w 1 → 1 0 → 1 + x 2 w 1 → 2 0 → 1 x 1 w 2 → 1 0 → 1 + x 2 w 2 → 2 0 → 1 ] + [ b → 1 → 1 b → 2 → 1 ] = \begin{bmatrix} x_1 w_{1 \rightarrow 1}^{0 \rightarrow 1} + x_2 w_{1 \rightarrow 2}^{0 \rightarrow 1} \\ x_1 w_{2 \rightarrow 1}^{0 \rightarrow 1} + x_2 w_{2 \rightarrow 2}^{0 \rightarrow 1} \end{bmatrix} + \begin{bmatrix} b_{\rightarrow 1}^{\rightarrow 1} \\ b_{\rightarrow 2}^{\rightarrow 1} \end{bmatrix} =[x1w1→10→1+x2w1→20→1x1w2→10→1+x2w2→20→1]+[b→1→1b→2→1]
= [ x 1 w 1 → 1 0 → 1 + x 2 w 1 → 2 0 → 1 + b → 1 → 1 x 1 w 2 → 1 0 → 1 + x 2 w 2 → 2 0 → 1 + b → 2 → 1 ] = \begin{bmatrix} x_1 w_{1 \rightarrow 1}^{0 \rightarrow 1} + x_2 w_{1 \rightarrow 2}^{0 \rightarrow 1} + b_{\rightarrow 1}^{\rightarrow 1} \\ x_1 w_{2 \rightarrow 1}^{0 \rightarrow 1} + x_2 w_{2 \rightarrow 2}^{0 \rightarrow 1} + b_{\rightarrow 2}^{\rightarrow 1} \end{bmatrix} =[x1w1→10→1+x2w1→20→1+b→1→1x1w2→10→1+x2w2→20→1+b→2→1]
[ σ 1 1 σ 2 1 ] = [ h ( x 1 w 1 → 1 0 → 1 + x 2 w 1 → 2 0 → 1 + b → 1 → 1 ) h ( x 1 w 2 → 1 0 → 1 + x 2 w 2 → 2 0 → 1 + b → 2 → 1 ) ] \begin{bmatrix} \sigma_1^1 \\ \sigma_2^1 \end{bmatrix}= \begin{bmatrix} h(x_1 w_{1 \rightarrow 1}^{0 \rightarrow 1} + x_2 w_{1 \rightarrow 2}^{0 \rightarrow 1} + b_{\rightarrow1}^{\rightarrow1}) \\ h(x_1 w_{2 \rightarrow 1}^{0 \rightarrow 1} + x_2 w_{2 \rightarrow 2}^{0 \rightarrow 1} + b_{\rightarrow2}^{\rightarrow1}) \end{bmatrix} [σ11σ21]=[h(x1w1→10→1+x2w1→20→1+b→1→1)h(x1w2→10→1+x2w2→20→1+b→2→1)]
相应的从中间层最下面的神经网络会得到的结果是 σ 1 2 \sigma_1^2 σ12(如果是阶跃函数则是直接得到 y y y)。 σ \sigma σ会作为中间层的结果继续传入下一层。如果我们继续向下嵌套,则可以得到:
z 1 2 = b → 1 → 2 + σ 1 1 w 1 → 1 1 → 2 + σ 2 1 w 2 → 1 1 → 2 σ 1 2 = g ( z 1 2 ) σ 1 2 = g ( b → 1 → 2 + σ 1 1 w 1 → 1 1 → 2 + σ 2 1 w 2 → 1 1 → 2 ) z_1^2 = b_{\rightarrow 1}^{\rightarrow 2} + \sigma_1^1 w_{1\rightarrow 1}^{1\rightarrow 2} + \sigma_2^1 w_{2\rightarrow 1}^{1\rightarrow 2} \\ \sigma_1^2 = g(z_1^2) \\ \sigma_1^2 = g(b_{\rightarrow 1}^{\rightarrow 2} + \sigma_1^1 w_{1\rightarrow 1}^{1\rightarrow 2} + \sigma_2^1 w_{2\rightarrow 1}^{1\rightarrow 2}) z12=b→1→2+σ11w1→11→2+σ21w2→11→2σ12=g(z12)σ12=g(b→1→2+σ11w1→11→2+σ21w2→11→2)
由于第二层就已经是输出层了,因此第二层使用的函数是 g ( z ) g(z) g(z),在这里,第二层的表达和第一层几乎一模一样。相信各种编号在这里已经让人感觉到有些头疼了,虽然公式本身并不复杂,但涉及到神经网络不同的层以及每层上的神经元之间的数据流动,公式的编号会让人有所混淆。如果神经网络的层数继续增加,或每一层上神经元数量继续增加,神经网络的嵌套和计算就会变得更加复杂。
在实际中,我们的真实数据可能有超过数百甚至数千个特征,所以真实神经网络的复杂度是非常高,计算非常缓慢的。所以,当神经网络长成如下所示的模样,我们就无法理解中间过程了。我们不知道究竟有多少个系数,如何相互作用产生了我们的预测结果,因此神经网络的过程是一个“黑箱”。
34 探索多层神经网络:层 vs h(z)
在之前的XOR
函数中,我们提出“多层神经网络能够描绘出一条曲线作为决策边界,以此为基础处理单层神经网络无法处理的复杂问题”,这可能让许多人产生了“是增加层数帮助了神经网络”的错觉,实际上并非如此。
在神经网络的隐藏层中,存在两个关键的元素,一个是加和函数 ∑ \sum ∑,另一个是 h ( z ) h(z) h(z)。除了输入层之外,任何层的任何神经元上都会有加和的性质,因为神经元有“多进单出”的性质,可以一次性输入多个信号,但是输出只能有一个,因此输入神经元的信息必须以某种方式进行整合,否则神经元就无法将信息传递下去。
而最容易的整合方式就是加和 ∑ \sum ∑。因此我们可以认为 ∑ \sum ∑是神经元自带的性质,只要增加更多的层,就会有更多的加和。但是 h ( z ) h(z) h(z)的存在却不是如此,即使隐藏层上没有 h ( z ) h(z) h(z)(或 h ( z ) h(z) h(z)是一个恒等函数),神经网络依然可以从第一层走到最后一层。
让我们来试试看,在XOR
中,假设隐藏层上没有 h ( z ) h(z) h(z)的话,会发生什么:
#回忆一下XOR数据的真实标签
xorgate = torch.tensor([0,1,1,0],dtype=torch.float32)def AND(X):w = torch.tensor([-0.2,0.15, 0.15], dtype = torch.float32)zhat = torch.mv(X,w)#下面这一行就是阶跃函数的表达式,注意AND函数是在输出层,所以保留输出层的阶跃函数g(z)andhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32)return andhatdef OR(X):w = torch.tensor([-0.08,0.15,0.15], dtype = torch.float32)zhat = torch.mv(X,w)#注释掉阶跃函数,相当于h(z)是恒等函数#yhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32)return zhatdef NAND(X):w = torch.tensor([0.23,-0.15,-0.15], dtype = torch.float32) zhat = torch.mv(X,w)#注释掉阶跃函数,相当于h(z)是恒等函数#yhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32)return zhatdef XOR(X):#输入值:input_1 = X#中间层:sigma_nand = NAND(input_1)sigma_or = OR(input_1)x0 = torch.tensor([[1],[1],[1],[1]],dtype=torch.float32)#输出层:input_2 = torch.cat((x0,sigma_nand.view(4,1),sigma_or.view(4,1)),dim=1)y_and = AND(input_2)#print("NANE:",y_nand)#print("OR:",y_or)return y_andXOR(X)
# output :
tensor([0., 0., 0., 0.])
很明显,此时XOR
函数的预测结果与真实的xorgate
不一致。当隐藏层的 h ( z ) h(z) h(z)是恒等函数或不存在时,叠加层并不能够解决XOR
这样的非线性问题。从数学上来看,这也非常容易理解。
从输入层到第1层:
Z 1 = W 1 ⋅ X + B 1 Σ 1 = h ( Z 1 ) 因为 h ( z ) 为恒等函数, 所以 Σ 1 = [ σ 1 1 σ 2 1 ] = [ z 1 1 z 2 1 ] \mathbf{Z}^1 = \mathbf{W}^1 \cdot \mathbf{X} + \mathbf{B}^1 \\ \boldsymbol{\Sigma}^1 = h(\mathbf{Z}^1) \\ 因为 h(z) 为恒等函数, \\ 所以 \boldsymbol{\Sigma}^1 = \begin{bmatrix} \sigma_1^1 \\ \sigma_2^1 \end{bmatrix} = \begin{bmatrix} z_1^1 \\ z_2^1 \end{bmatrix} Z1=W1⋅X+B1Σ1=h(Z1)因为h(z)为恒等函数,所以Σ1=[σ11σ21]=[z11z21]
从第1层到输出层:
Z 2 = W 2 ⋅ Σ + B 2 [ z 1 2 ] = [ w 1 → 1 1 → 2 w 1 → 2 1 → 2 ] ∗ [ σ 1 1 σ 2 1 ] + [ b → 1 → 2 ] \mathbf{Z}^2 = \mathbf{W}^2 \cdot \boldsymbol{\Sigma} + \mathbf{B}^2 \\ \begin{bmatrix} z_1^2 \end{bmatrix} = \begin{bmatrix} w_{1\rightarrow 1}^{1\rightarrow 2} & w_{1\rightarrow 2}^{1\rightarrow 2} \end{bmatrix} \ast \begin{bmatrix} \sigma_1^1 \\ \sigma_2^1 \end{bmatrix} + \begin{bmatrix} b_{\rightarrow 1}^{\rightarrow 2} \end{bmatrix} Z2=W2⋅Σ+B2[z12]=[w1→11→2w1→21→2]∗[σ11σ21]+[b→1→2]
由于公式太长 ,不再给 s s s写箭头角标 ,而是直接写普通数字角标:
[ z 1 2 ] = σ 1 1 w 11 2 + σ 2 1 w 12 2 + b 1 2 = z 1 1 w 11 2 + z 2 1 w 12 2 + b 1 2 = ( x 1 w 11 1 + x 2 w 12 1 + b 1 1 ) w 11 2 + ( x 1 w 21 1 + x 2 w 22 1 + b 2 1 ) w 12 2 + b 1 2 = x 1 ( w 11 1 w 11 2 + w 21 1 w 12 2 ) + x 2 ( w 12 1 w 11 2 + w 22 1 w 12 2 ) + b 1 1 w 11 2 + b 2 1 w 12 2 + b 1 2 = x 1 W 1 + x 2 W 2 + B , 其中 W 1 , W 2 和 B 都是常数 \begin{bmatrix} z_1^2 \end{bmatrix} = \sigma_1^1 w_{11}^2 + \sigma_2^1 w_{12}^2 + b_1^2 \\ = z_1^1 w_{11}^2 + z_2^1 w_{12}^2 + b_1^2 \\ = (x_1 w_{11}^1 + x_2 w_{12}^1 + b_1^1) w_{11}^2 + (x_1 w_{21}^1 + x_2 w_{22}^1 + b_2^1) w_{12}^2 + b_1^2 \\ = x_1 (w_{11}^1 w_{11}^2 + w_{21}^1 w_{12}^2) + x_2 (w_{12}^1 w_{11}^2 + w_{22}^1 w_{12}^2) + b_1^1 w_{11}^2 + b_2^1 w_{12}^2 + b_1^2 \\ = x_1 W_1 + x_2 W_2 + B, \text{其中 } W_1, W_2 \text{ 和 } B \text{ 都是常数} [z12]=σ11w112+σ21w122+b12=z11w112+z21w122+b12=(x1w111+x2w121+b11)w112+(x1w211+x2w221+b21)w122+b12=x1(w111w112+w211w122)+x2(w121w112+w221w122)+b11w112+b21w122+b12=x1W1+x2W2+B,其中 W1,W2 和 B 都是常数
不难发现,最终从输出层输出的结果和第一层的输出结果 x 1 w 11 1 + x 2 w 12 1 + b 1 1 x_1w_{11}^1+x_2w_{12}^1+b_1^1 x1w111+x2w121+b11是类似的,只不过是乘以特征 x 1 , x 2 x_1,x_2 x1,x2的具体数值不同。在没有 h ( z ) h(z) h(z)时,在层中流动的数据被做了仿射变换(affine transformation
),仿射变换后得到的依然是一个线性方程,而这样的方程不能解决非线性问题。可见,”层”本身不是神经网络解决非线性问题的关键,层上的 h ( z ) h(z) h(z)才是。从上面的例子和数学公式中可以看出,如果 h ( z ) h(z) h(z)是线性函数,或不存在,那增加再多的层也没有用。
那是不是任意非线性函数作为 h ( z ) h(z) h(z)都可以解决问题呢?让我们来试试看,在XOR
例子中如果不使用阶跃函数,而使用sigmoid
函数作为 h ( z ) h(z) h(z),会发生什么。
def AND(X):w = torch.tensor([-0.2,0.15, 0.15], dtype = torch.float32)zhat = torch.mv(X,w)#下面这一行就是阶跃函数的表达式,注意AND函数是在输出层,所以保留输出层的阶跃函数g(z)andhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32)return andhatdef OR(X):w = torch.tensor([-0.08,0.15,0.15], dtype = torch.float32)zhat = torch.mv(X,w)#h(z), 使用sigmoid函数sigma = torch.sigmoid(zhat)return sigmadef NAND(X):w = torch.tensor([0.23,-0.15,-0.15], dtype = torch.float32) zhat = torch.mv(X,w)#h(z), 使用sigmoid函数sigma = torch.sigmoid(zhat)return sigmadef XOR(X):#输入值:input_1 = X#中间层:sigma_nand = NAND(input_1)sigma_or = OR(input_1)x0 = torch.tensor([[1],[1],[1],[1]],dtype=torch.float32)#输出层:input_2 = torch.cat((x0,sigma_nand.view(4,1),sigma_or.view(4,1)),dim=1)y_and = AND(input_2)#print("NANE:",y_nand)#print("OR:",y_or)return y_andXOR(X)
# output :
tensor([0., 0., 0., 0.])
可以发现,如果将 h ( z ) h(z) h(z)换成sigmoid
函数,XOR
结构的神经网络同样会失效!可见,即便是使用了,也不一定能够解决曲线分类的问题。在不适合的非线性函数加持下,神经网络的层数再多也无法起效。所以, h ( z ) h(z) h(z)是真正能够让神经网络算法“活起来”的关键,没有搭配合适 的神经网络结构是无用的,而 h ( z ) h(z) h(z)正是神经网络中最关键的概念之一激活函数(activation function)。
35 激活函数
在人工神经网络的神经元上,根据一组输入定义该神经元的输出结果的函数,就是激活函数。激活函数一般都是非线性函数,它出现在神经网络中除了输入层以外的每层的每个神经元上。
经过前面的介绍与铺垫,到这里相信大家已经充分理解激活函数的作用了。神经网络中可用的激活函数多达数十种(详情可以在激活函数的维基百科中找到,但机器学习中常用的激活函数只有恒等函数(identity function
),阶跃函数(sign
),sigmoid
函数,ReLU
,tanh
,softmax
这六种,其中Softmax
与恒等函数几乎不会出现在隐藏层上,Sign
、Tanh
几乎不会出现在输出层上,ReLU
与Sigmoid
则是两种层都会出现,并且应用广泛。
在这里,我们将总结性声明一下输出层的g(z)与隐藏层的h(z)之间的区别,以帮助大家获得更深的理解:
-
虽然都是激活函数,但隐藏层和输出层上的激活函数作用是完全不一样的。输出层的激活函数 g ( z ) g(z) g(z)是为了让神经网络能够输出不同类型的标签而存在的。其中恒等函数用于回归,
sigmoid
函数用于二分类,softmax
用于多分类。换句说, g ( z ) g(z) g(z)仅仅与输出结果的表现形式有关,与神经网络的效果无关,也因此它可以使用线性的恒等函数。但隐藏层的激活函数就不同了,如我们之前尝试的XOR
,隐藏层上的激活函数 h ( z ) h(z) h(z)的选择会影响神经网络的效果,而线性的 h ( z ) h(z) h(z)是会让神经网络的结构失效的。 -
在同一个神经网络中, g ( z ) g(z) g(z)与 h ( z ) h(z) h(z)可以是不同的,并且在大多数运行回归和多分类的神经网络时,他们也的确是不同的。每层上 h ( z ) h(z) h(z)可以是不同的,但是同一层上的激活函数必须一致。
之前我们曾经尝试过以下几种情况:
隐藏层 h ( z ) h(z) h(z) | 输出层 g ( z ) g(z) g(z) | XOR网络是否有效 |
---|---|---|
阶跃函数 | 阶跃函数 | 有效 |
恒等函数 | 阶跃函数 | 无效 |
sigmoid函数 | 阶跃函数 | 无效 |
阶跃函数 | sigmoid函数 | ? |
现在我们来试试看,隐藏层上的 h ( z ) h(z) h(z)是阶跃函数,而输出层的 g ( z ) g(z) g(z)是sigmoid
的情况。如果XOR
网络依然有效,就证明了 g ( z ) g(z) g(z)的变化对神经网络结果输出无影响。反之,则说明 g ( z ) g(z) g(z)也影响神经网络输出结果。
#如果g(z)是sigmoid函数,而h(z)是阶跃函数#输出层,以0.5为sigmoid的阈值
def AND(X):w = torch.tensor([-0.2,0.15, 0.15], dtype = torch.float32)zhat = torch.mv(X,w)sigma = torch.sigmoid(zhat)andhat = torch.tensor([int(x) for x in sigma >= 0.5],dtype=torch.float32)return andhat#隐藏层,OR与NAND都使用阶跃函数作为h(z)
def OR(X):w = torch.tensor([-0.08,0.15,0.15], dtype = torch.float32)zhat = torch.mv(X,w)yhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32)return yhatdef NAND(X):w = torch.tensor([0.23,-0.15,-0.15], dtype = torch.float32) zhat = torch.mv(X,w)yhat = torch.tensor([int(x) for x in zhat >= 0],dtype=torch.float32)return yhatdef XOR(X):#输入值:input_1 = X#中间层:sigma_nand = NAND(input_1)sigma_or = OR(input_1)x0 = torch.tensor([[1],[1],[1],[1]],dtype=torch.float32)#输出层:input_2 = torch.cat((x0,sigma_nand.view(4,1),sigma_or.view(4,1)),dim=1)y_and = AND(input_2)#print("NANE:",y_nand)#print("OR:",y_or)return y_andXOR(X)
# output :
tensor([0., 1., 1., 0.])
从结果可以看出,只要隐藏层的 h ( z ) h(z) h(z)是阶跃函数,XOR
网络就一定能有效,这与输出层 g ( z ) g(z) g(z)是什么函数完全无关。
从这里开始,若没有特别说明,当我们提到“激活函数”时,特指隐藏层上的激活函数
。当需要表达输出层上的激活函数时,我们需称其为“输出层激活函数”(out_activation
)。
隐藏层 h ( z ) h(z) h(z) | 输出层 g ( z ) g(z) g(z) | XOR网络是否有效 |
---|---|---|
阶跃函数 | 阶跃函数 | 有效 |
恒等函数 | 阶跃函数 | 无效 |
sigmoid函数 | 阶跃函数 | 无效 |
阶跃函数 | sigmoid函数 | 有效 |
36 从0实现深度神经网络的正向传播
学到这里,我们已经学完了一个普通深度神经网络全部的基本元素——用来构筑神经网络的结构的层与激活函数,输入神经网络的数据(特征、权重、截距),并且我们了解从左向右的过程是神经网络的正向传播(也叫做前向传播,或者向前传播)。在过去的课程中我们所学习的内容都是在torch.nn
这个模块下,现在我们就使用封装好的torch.nn
模块来实现一个完整、多层的神经网络的正向传播。
假设我们有500
条数据,20
个特征,标签为3
分类。我们现在要实现一个三层神经网络,这个神经网络的架构如下:第一层有13
个神经元,第二层有8
个神经元,第三层是输出层。其中,第一层的激活函数是relu
,第二层是sigmoid
。我们要如何实现它呢?来看代码:
# 继承nn.Module类完成正向传播
import torch
import torch.nn as nn
from torch.nn import functional as F# 确定数据
# 控制X和y的随机性
torch.manual_seed(420)
X = torch.rand((500, 20), dtype = torch.float32)
y = torch.randint(low = 0, high = 3, size = (500, 1), dtype = torch.float32)# 继承nn.Modules类来定义神经网络的架构
class Model(nn.Module):# init : 定义类本身,__init__函数是在类被实例化的瞬间就会执行的函数# in_features为特征,out_features为标签def __init__(self,in_features = 10, out_features = 2):# 用父类替换现在的类# 子类只默认继承除了父类的init函数之外的所有函数,如果要继承父类的init函数,必须使用super关键字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)# __init__之外的函数,是在__init__被执行完毕后,就可以被调用的函数def forward(self, x):# 输入数据,会自动生成w和bz1 = self.linear1(x)sigma1 = torch.relu(z1)z2 = self.linear2(sigma1)sigma2 = torch.sigmoid(z2)z3 = self.output(sigma2)# 因为我们输入的是二维张量,dim=1就表示要对每一行都进行softmax计算sigma3 = F.softmax(z3, dim = 1)return sigma3input_ = X.shape[1] # 特征的数目
output_ = len(y.unique()) # 分类的数目# 实例化神经网络
# 控制每次实例化后w和b的随机性
torch.manual_seed(420)
net = Model(in_features = input_, out_features = output_)
# 在这一瞬间,所有的层就已经被实例化好了,所有随机的w和b也都被建立好了
# 前向传播
net(X) # 等同于net.forward(X)
# output :
tensor([[0.4140, 0.3496, 0.2365],[0.4210, 0.3454, 0.2336],[0.4011, 0.3635, 0.2355],...,[0.4196, 0.3452, 0.2352],[0.4153, 0.3455, 0.2392],[0.4153, 0.3442, 0.2405]], grad_fn=<SoftmaxBackward0>)# 查看输出结果的结构
net.forward(X).shape
# output : 对应五百个数据的三个类别的概率
torch.Size([500, 3])# 查看每一层上的权重w和截距b的形状
net.linear.weight.shape
# output :
torch.Size([13, 20])net.linear2.weight.shape
# output :
torch.Size([8, 13])net.output.weight.shape
# output :
torch.Size([3, 8])
每一层的权重矩阵都是反过来的结构:第一个数表示当前层的神经元个数,第二个数表示前一层的神经元个数。
比如说我们的X结构是(500,20),对于多层神经网络来说,输入的X会自动的转换成(20,500)的结构,并且w会自动的放在前面和X相乘。
第一层: w (13,20) * (20,500) -> (13,500)
第二层: w (8, 13) * (13,500) -> (8,500)
第三层:w(3, 8) * (8, 500) -> (3,500) -> (500,3)
最后softmax输出的时候,又会把行和列给调过来,变成(500,3)
# 查看截距形状
net.linear1.bias.shape
# output : 因为第一层有13个神经元
torch.Size([13])
通过使用super函数,我们的神经网络模型从nn.Module那里继承了哪些有用的属性和方法呢?首先,如果不加super函数,神经网络的向前传播是无法运行的:
# 注释掉super函数就会报错:
AttributeError: cannot assign module before Module.__init__() call
nn.Module
类的定义代码有一千多行,其__init__函数中有许多复杂的内容需要被继承,因此super
函数一定不能漏下。
我们从nn.Module
中继承了这些有用的方法:
# 属性的继承
net.training # 是否用于训练# 方法的继承
net.cuda()net.cpu()net.apply() # 对__init__中的所有对象(全部层)都执行同样的操作
# 比如,令所有线性层的初始权重w都为0
def initial_0(m):print(m)if type(m) == nn.Linear:m.weight.data.fill_(0)print(m.weight)
net.apply(initial_0)# 一个特殊的方法
net.parameters() # 一个迭代器,我们可以通过循环的方式查看里面究竟是什么内容
for param in net.parameters():print(param)
相关文章:
深度学习 Pytorch 深层神经网络
在之前已经学习了三种单层神经网络,分别为实现线性方程的回归网络,实现二分类的逻辑回归(二分类网络),以及实现多分类的softmax回归(多分类网络)。从本节开始,我们将从单层神经网络展…...
【python】三帧差法实现运动目标检测
三帧差法是一种常用的运动目标检测方法,它通过比较连续三帧图像之间的差异来检测运动物体。这种方法尤其适用于背景变化较小的场景。 目录 1 方案 2 实践 ① 代码 ② 效果图 1 方案 具体步骤如下: ① 读取视频流:使用cv2.VideoCapture()…...
机器人抓取与操作经典规划算法(深蓝)——2
1 经典规划算法 位姿估计:(1)相机系位姿 (2)机器人系位姿 抓取位姿:(1)抓取位姿计算 (2)抓取评估和优化 路径规划:(1)笛卡…...
WGCLOUD服务器资源监控软件使用笔记 - Token is error是什么错误
[wgcloud-agent]2025/01/30 10:41:30 WgcloudAgent.go:90: 主机监控信息上报server开始 [wgcloud-agent]2025/01/30 10:41:30 WgcloudAgent.go:99: 主机监控信息上报server返回信息: {"result":"Token is error"} 这个错误是因为agent配置的wgToken和serv…...
在排序数组中查找元素的第一个和最后一个位置(力扣)
一.题目介绍 二.题目解析 使用二分进行查找 2.1处理边界情况 如果数组为空,直接返回 [-1, -1],因为无法找到目标值。 int[] ret new int[2]; ret[0] ret[1] -1; if (nums.length 0) return ret; 2.2查找左端点(目标值开始位置&#…...
Kafka的消息协议
引言 在学习MQTT消息协议的时候我常常思考kafka的消息协议是什么,怎么保证消息的可靠性和高性能传输的,接下来我们一同探究一下 Kafka 在不同的使用场景和组件交互中用到了多种协议,以下为你详细介绍: 内部通信协议 Kafka 使用…...
Vue 3 30天精进之旅:Day 09 - 组合式API
在Vue 3中,组合式API(Composition API)是一个引入的新特性,它为开发者提供了一种更灵活的方式来构建和组织组件。与传统的选项API相比,组合式API更注重逻辑的复用和逻辑的组合,让我们更容易处理大型应用中的…...
Day28(补)-【AI思考】-AI会不会考虑自己的需求?
文章目录 AI会不会考虑自己的需求?一、**技术本质:深度≠理解**二、**传播机制:热搜如何制造幻觉**三、**伦理考量:为何必须"撇清"**关键结论 AI会不会考虑自己的需求? 让思想碎片重焕生机的灵魂:…...
JavaScript 进阶(下)
原型 what 首先,构造函数通过原型分配的函数是所有对象所 共享的。 然后,JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象 这个对象可以挂载函数,对象实…...
selenium自动化测试框架——面试题整理
目录 1. 什么是 Selenium?它的工作原理是什么? 2. Selenium 主要组件 3. 常见 WebDriver 驱动 4. Selenium 如何驱动浏览器? 5. WebDriver 协议是什么? 6. Page Object 模式与 Page Factory 7. 如何判断元素是否可见&#x…...
CF1098F Ж-function
【题意】 给你一个字符串 s s s,每次询问给你 l , r l, r l,r,让你输出 s s s l , r sss_{l,r} sssl,r中 ∑ i 1 r − l 1 L C P ( s s i , s s 1 ) \sum_{i1}^{r-l1}LCP(ss_i,ss_1) ∑i1r−l1LCP(ssi,ss1)。 【思路】 和前一道题一样&#…...
数据库备份、主从、集群等配置
数据库备份、主从、集群等配置 1 MySQL1.1 docker安装MySQL1.2 主从复制1.2.1 主节点配置1.2.2 从节点配置1.2.3 创建用于主从同步的用户1.2.4 开启主从同步1.2.4 主从同步验证 1.3 主从切换1.3.1 主节点设置只读(在192.168.1.151上操作)1.3.2 检查主从数…...
电脑要使用cuda需要进行什么配置
在电脑上使用CUDA(NVIDIA的并行计算平台和API),需要进行以下配置和准备: 1. 检查NVIDIA显卡支持 确保你的电脑拥有支持CUDA的NVIDIA显卡。 可以在NVIDIA官方CUDA支持显卡列表中查看显卡型号是否支持CUDA。 2. 安装NVIDIA显卡驱动…...
【Unity3D】实现横版2D游戏——攀爬绳索(简易版)
目录 GeneRope.cs 场景绳索生成类 HeroColliderController.cs 控制角色与单向平台是否忽略碰撞 HeroClampController.cs 控制角色攀爬 OnTriggerEnter2D方法 OnTriggerStay2D方法 OnTriggerExit2D方法 Update方法 开始攀爬 结束攀爬 Sensor_HeroKnight.cs 角色触发器…...
JS 时间格式大全(含大量示例)
在 JS 中,处理时间和日期是常见的需求。无论是展示当前时间、格式化日期字符串,还是进行时间计算,JavaScript 都提供了丰富的 API 来满足这些需求。本文将详细介绍如何使用 JavaScript 生成各种时间格式,从基础到高级,…...
opencv裁剪视频区域
import cv2 # 打开视频文件 video_path input.mp4 cap cv2.VideoCapture(video_path) # 获取视频的帧率、宽度和高度 fps int(cap.get(cv2.CAP_PROP_FPS)) width int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # 定义裁剪区…...
C++ deque(1)
1.deque介绍 deque的扩容不像vector那样麻烦 直接新开一个buffer 不用重新开空间再把数据全部移过去 deque本质上是一个指针数组和vector<vector>不一样,vector<vector>本质上是一个vector对象数组!并且vector<vector>的buffer是不一…...
MapReduce概述
目录 1. MapReduce概述2. MapReduce的功能2.1 数据划分和计算任务调度2.2 数据/代码互定位2.3 系统优化2.4 出错检测和恢复 3. MapReduce处理流程4. MapReduce编程基础参考 1. MapReduce概述 MapReduce是面向大数据并行处理的计算模型、框架和平台: 1. 基于集群的高性能并行…...
DOM操作中childNodes与children的差异及封装方案
引言 在JavaScript的DOM操作中,childNodes和children是开发者常用的属性,但它们在浏览器中的行为差异可能导致兼容性问题。尤其是在处理空白符(如换行符\n)时,某些浏览器(如Chrome和Edge)会将空…...
在线课堂小程序设计与实现(LW+源码+讲解)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...
分布式系统架构怎么搭建?
分布式系统架构 互联网企业的业务飞速发展,促使系统架构不断变化。总体来说,系统架构大致经历了单体应用架构—垂直应用架构—分布式架构—SOA架构—微服务架构的演变,很多互联网企业的系统架构已经向服务化网格(Service Mesh&am…...
大模型知识蒸馏技术(2)——蒸馏技术发展简史
版权声明 本文原创作者:谷哥的小弟作者博客地址:http://blog.csdn.net/lfdfhl2006年模型压缩研究 知识蒸馏的早期思想可以追溯到2006年,当时Geoffrey Hinton等人在模型压缩领域进行了开创性研究。尽管当时深度学习尚未像今天这样广泛普及,但Hinton的研究已经为知识迁移和模…...
C++初阶 -- 泛型编程(函数模板、类模板)入门
目录 一、泛型编程 1.1 介绍 二、函数模板 2.1 函数模板的概念 2.2 函数模板的格式 2.3 函数模板的原理 2.4 函数模板的实例化 2.4.1 隐式实例化 2.4.2 显式实例化 2.5 模板参数的匹配原则 三、类模板 3.1 类模板的使用格式 3.2 类模板的实例化 一、泛型编程 在有…...
[EAI-027] RDT-1B: a Diffusion Foundation Model for Bimanual Manipulation
Paper Card 论文标题:RDT-1B: a Diffusion Foundation Model for Bimanual Manipulation 论文作者:Songming Liu, Lingxuan Wu, Bangguo Li, Hengkai Tan, Huayu Chen, Zhengyi Wang, Ke Xu, Hang Su, Jun Zhu 论文链接:https://arxiv.org/ab…...
MongoDB常见的运维工具总结介绍
MongoDB 提供了一些强大的运维工具,帮助管理员进行数据库监控、备份、恢复、性能优化等操作。以下是一些常见的 MongoDB 运维工具及其功能介绍: 1. MongoDB Atlas 功能:MongoDB Atlas 是 MongoDB 官方的云托管数据库服务,它提供…...
dify实现原理分析-rag-数据检索的实现
数据检索的总体执行步骤 数据检索总体步骤如下: #mermaid-svg-YCRNdSE7T1d0Etyj {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-YCRNdSE7T1d0Etyj .error-icon{fill:#552222;}#mermaid-svg-YCRNdSE7T1d…...
20个整流电路及仿真实验汇总
0、 前言 以下是关于“20个整流电路及仿真实验汇总”的前言部分: 在现代电力电子技术领域,整流电路作为将交流电(AC)转换为直流电(DC)的关键电路,广泛应用于各类电源设计、信号处理以及电力电子设备中。整流电路不仅能够为电子设备提供稳定的直流电源,还在电力传输、…...
CVE-2020-0796永恒之蓝2.0(漏洞复现)
目录 前言 产生原因 影响范围 漏洞复现 复现环境 复现步骤 防御措施 总结 前言 在网络安全的战场上,漏洞一直是攻防双方关注的焦点。CVE-2020-0796,这个被称为 “永恒之蓝 2.0” 的漏洞,一度引起了广泛的关注与担忧。它究竟是怎样的…...
爬虫基础(一)HTTP协议 :请求与响应
前言 爬虫需要基础知识,HTTP协议只是个开始,除此之外还有很多,我们慢慢来记录。 今天的HTTP协议,会有助于我们更好的了解网络。 一、什么是HTTP协议 (1)定义 HTTP(超文本传输协议ÿ…...
拼车(1094)
1094. 拼车 - 力扣(LeetCode) 解法: class Solution { public:bool carPooling(vector<vector<int>>& trips, int capacity) {uint32_t passenger_cnt 0;//将原数据按照from排序auto func_0 [](vector<int> & …...
Ubuntu全面卸载mysql
如果你已经看到whereis mysql输出了与MySQL相关的路径,说明MySQL仍然存在于系统中。要卸载MySQL,可以按照以下步骤操作,确保完全删除所有相关的文件和配置: 1. 停止MySQL服务 首先,停止MySQL服务: sudo …...
@Inject @Qualifier @Named
Inject Qualifier Named 在依赖注入(DI)中,Inject、Qualifier 和 Named 是用于管理对象创建和绑定的关键注解。以下是它们的用途、依赖配置和代码示例的详细说明: 1. 注解的作用 Inject:标记需要注入的构造函数、字段…...
OpenHarmonyOS 3.2 编译生成的hap和app文件的名称如何配置追加版本号?
找了一圈发现官方的文档都是最新的,3.2很多API都不支持,比如获取OhosAppContext,通过OhosAppContext来获取应用版本号,最终是通过读取app.json5的文件内容来读取版本号,最终修改entry下的hvigorfile.ts如下,…...
初始化mysql报错cannot open shared object file: No such file or directory
报错展示 我在初始化msyql的时候报错:mysqld: error while loading shared libraries: libaio.so.1: cannot open shared object file: No such file or directory 解读: libaio包的作用是为了支持同步I/O。对于数据库之类的系统特别重要,因此…...
Vue.js组件开发-实现全屏背景图片滑动切换特效
使用 Vue 实现全屏背景图片滑动切换特效的详细步骤、代码、注释和使用说明。 步骤 创建 Vue 项目:使用 Vue CLI 创建一个新的 Vue 项目。准备图片资源:准备好要用于背景切换的图片,并将它们放在项目的合适目录下。编写 HTML 结构࿱…...
AI在自动化测试中的伦理挑战
在软件测试领域,人工智能(AI)已经不再是遥不可及的未来技术,而是正在深刻影响着测试过程的现实力量。尤其是在自动化测试领域,AI通过加速测试脚本生成、自动化缺陷检测、测试数据生成等功能,极大提升了测试…...
FreeRTOS的任务创建和删除
1,任务创建和删除的API函数 任务的创建和删除本质就是调用FreeRTOS的API函数 动态创建任务: 任务的任务控制块以及任务的栈空间所需的内存,均由 FreeRTOS 从 FreeRTOS 管理的堆中分配。 静态创建任务: 任务的任务控制块以及任务的…...
【C++】STL容器使用与实现详解:vector
文章目录 Ⅰ. vector的介绍和使用一、vector的介绍二、vector的使用 (只列出比较重要的,其他的需要时查文档)1. vector的定义2. vector iterator(迭代器)的使用3. vector 容量问题4. vector 增删查改5. 正确释放 vecto…...
【Block总结】动态蛇形卷积,专注于细长和弯曲的局部结构|即插即用
论文信息 标题: Dynamic Snake Convolution based on Topological Geometric Constraints for Tubular Structure Segmentation 作者: 戚耀磊、何宇霆、戚晓明、张媛、杨冠羽 会议: 2023 IEEE/CVF International Conference on Computer Vision (ICCV) 发表时间: 2023年10月…...
物业管理软件引领社区智能化转型提升服务效率与居民生活质量
内容概要 物业管理软件的出现,标志着社区管理方式的一场革命,它不仅仅是一个工具,更是推动智能化转型的关键助力。通过高效的管理功能,物业管理软件在优化服务流程的同时,也提升了居民的生活质量和社区的整体发展活力…...
论文阅读(十三):复杂表型关联的贝叶斯、基于系统的多层次分析:从解释到决策
1.论文链接:Bayesian, Systems-based, Multilevel Analysis of Associations for Complex Phenotypes: from Interpretation to Decision 摘要: 遗传关联研究(GAS)报告的结果相对稀缺,促使许多研究方向。尽管关联概念…...
线性调整器——耗能型调整器
线性调整器又称线性电压调节器,以下是关于它的介绍: 基本工作原理 线性调整器的基本电路如图1.1(a)所示,晶体管Q1(工作于线性状态,或非开关状态)构成一个连接直流源V和输出端V。的可调电气电阻,直流源V由60Hz隔离变压器(电气隔离和整流&#…...
梯度提升用于高效的分类与回归
使用 决策树(Decision Tree) 实现 梯度提升(Gradient Boosting) 主要是模拟 GBDT(Gradient Boosting Decision Trees) 的原理,即: 第一棵树拟合原始数据计算残差(负梯度…...
使用Ollama和Open WebUI快速玩转大模型:简单快捷的尝试各种llm大模型,比如DeepSeek r1
Ollama本身就是非常优秀的大模型管理和推理组件,再使用Open WebUI更加如虎添翼! Ollama快速使用指南 安装Ollama Windows下安装 下载Windows版Ollama软件:Release v0.5.7 ollama/ollama GitHub 下载ollama-windows-amd64.zip这个文件即可…...
2025年1月个人工作生活总结
本文为 2025年1月工作生活总结。 研发编码 使用sqlite3命令行查询表数据 可以直接使用sqlite3查询数据表,不需进入命令行模式。示例如下: sqlite3 database_name.db "SELECT * FROM table_name;"linux shell使用read超时一例 先前有个编译…...
Windows环境安装nvm,并使用nvm管理nodejs版本教程
目录 1.nvm安装步骤 2.验证nvm是否安装成功 3.查看本地可以安装的所有版本 4.安装特定nodejs版本 5.配置nvm镜像 6.使用特定nodejs版本 7.给nodejs配置镜像和环境变量 8.查看本地安装的所有版本(* 表示当前版本) 9.卸载指定版本的nodejs 前端开发中,不…...
C++中常用的排序方法之——冒泡排序
成长路上不孤单😊😊😊😊😊😊 【14后😊///计算机爱好者😊///持续分享所学😊///如有需要欢迎收藏转发///😊】 今日分享关于C中常用的排序方法之——冒泡排序的…...
SQL进阶实战技巧:如何分析浏览到下单各步骤转化率及流失用户数?
目录 0 问题描述 1 数据准备 2 问题分析 3 问题拓展 3.1 跳出率计算 3.2 计算从浏览商品到支付订单的不同路径的用户数,并按照用户数降序排列。 往期精彩 0 问题描述 统计从浏览商品到最终下单的各个步骤的用户数和流失用户数,并计算转化率 用户表结构和…...
NLP模型大对比:Transformer >Seq2Seq > LSTM > RNN > n-gram
结论 Transformer 大于 传统的Seq2Seq 大于 LSTM 大于 RNN 大于 传统的n-gram n-gram VS Transformer 我们可以用一个 图书馆查询 的类比来解释它们的差异: 一、核心差异对比 维度n-gram 模型Transformer工作方式固定窗口的"近视观察员"全局关联的&q…...
接口技术-第5次作业
目录 作业内容 解答 一、填空题 二、综合题 1.采用AD570通过82C55A与CPU接口,82C55A的端口地址为300H~303H,完成用查询方式采集250个数据,送到2000H开始的存储单元存储。绘制电路连接图(AD570的4种主要信号线都要标出)。 2…...