为什么我的神经网络输出永远一样?
作者:卡卷网发布时间:2025-02-22 17:48浏览数量:59次评论数量:0次
看了一下代码,确实有点东西嗷,自己写神经网络,自己写MSE损失函数,自己写梯度回传。(甚至连矩阵计算都不用,直接手写矩阵行元素的计算,好险不是矩阵元素两重循环计算。)
简单对代码梳理了一下,整个模型可以分为两个部分:
前向传播部分:
- 压缩:
,具体为
,
- 解压:
,具体为
,
- 损失函数:
。
反向传播/.参数更新部分:
- 梯度计算:
等计算,通过导数的链式法则等。
- 参数更新:
,对相关参数进行更新。
输出的矩阵为什么是大都趋于0.5,我猜测,主要是因为数据生成采用的是np.random.random函数,这个函数会以均匀分布的方式生成(0,1)之间的随机数,所以矩阵中每一个元素的均值就是0.5,与此同时,在实现的代码中,是计算完所有的样本再进行梯度下降,很大概率是朝着0.5的方向下降。
那么这个问题就来了在修改好下降方向以后,为什么模型仍然不会学习得到输入 是什么,输出
就是什么的额能力呢?大概是当前模型的能力不足以学会更多的东西(参数较小)。
关于代码的一些吐槽:
整个的计算效率很低,运行题主源代码都快运行了一个小时了。
可以加速计算的一个点是:当前选择的是对每一个数据进行计算
for i in range(len(data)):
pass
这里可以使用batch加速计算一下,不然整个计算速度就太慢了。但是为了能够适配batch,又需要将相关的计算从for计算改成矩阵计算,比如
原始的前向传播,对每一行进行计算:
alpha=np.zeros(8)
belta=np.zeros(8)
delta=np.zeros(8)
for i in range(8):
alpha += x_input[i] * w_matrix_input[0, i]
belta += x_input[i] * w_matrix_input[1, i]
delta += x_input[i] * w_matrix_input[2, i]
alpha += b_array_input[0]
belta += b_array_input[1]
delta += b_array_input[2]
sigmoid_alpha = sigmoid(alpha)
sigmoid_belta = sigmoid(belta)
sigmoid_delta = sigmoid(delta)
hidden_layer_output=np.array([sigmoid_alpha,sigmoid_belta,sigmoid_delta])
output = np.zeros((8,8))
line = np.zeros(8)
for i in range(8):
line=sigmoid_alpha*w_matrix_output[i,0]+sigmoid_belta*w_matrix_output[i,1]+sigmoid_delta*w_matrix_output[i, 2]+b_array_output[i]
output[i]=line
output=sigmoid(output)
return output,hidden_layer_output
新的前向传播:
C = w_matrix_input@x_input + b_array_input.reshape(-1,1)
hidden_layer_output = sigmoid(C)
D = w_matrix_output@hidden_layer_output + b_array_output.reshape(-1,1)
output=sigmoid(D)
return output,hidden_layer_output
总之先把整个代码能够精简一点,可能才能看出更加多的问题吧
PS:可以先整个深度学习框架,不然这看着不太容易理解。
Numpy下的矩阵计算
由于原代码采用了大量地for循环和向量计算,这实际上造成了时间上的浪费,毕竟现代的计算方法矩阵计算已经很方便了,所以在这里,对原来的代码进行改进,使其能够利用矩阵的方式来计算。
对于前向传播来说,很简单,在上面也介绍了,可以直接在代码使用 A@B 做矩阵计算。那么接下来的任务就是将梯度求导这一块也改成矩阵计算,为了方便看客们的理解,在这里,做一个跟上述任务类似的求导过程计算就完事了:
首先,我们的预测是 其中,
就是我们的输出,假设维度是
,而
就是我们的输入,维度是
,
是我们的矩阵参数,维度是
,
是我们的偏置,维度是
,相当于每一行的每一个元素都加上同一个偏置。
我们的损失函数有 ,其实就是预测和真实值之间每一个元素做一个平方再求和,所以也可以写成
。
那么,接下来,我们想知道,当前的损失函数数值,如何得到我们的参数 的梯度呢?在这里就只讨论可微分的情况。因为直接计算矩阵的微分比较困难,但是我们可以先计算其中一个,比如
,即计算
有:
【数学数字比较多,防止伤害眼睛,可跳过】
其中
,所以拆开来有:
其中,当 的时候,该导数为
,其余为0。
所以原式可转化为 ,其中
当
为1,其余为0。
所以就有 ,最后矩阵形式为
。
类似地,如果对参数 ,也有上述的关系有
,实际上就是对列求和,所向量形式有
。
【结束】
总之,通过上述的方式,我们能够将梯度计算通过矩阵的形式进行计算,对于原问题来说,我们就有:
代码如下:
# 矩阵梯度更新
def gradiengt_decent(output, input, w2, hidden_output):
Derror_Dw2 = ((output - input) * output *(1-output)) @ hidden_output.T
Derror_Db2 = np.sum((output - input) * output *(1-output), axis=1)
Derror_Dw1 = ((w2.T @ (((output - input) * output *(1-output)))) * hidden_output * (1 - hidden_output)) @ input.T
Derror_Db1 = np.sum(((w2.T @ (((output - input) * output *(1-output)))) * hidden_output * (1 - hidden_output)), axis=1)
return Derror_Dw1,Derror_Dw2,Derror_Db1,Derror_Db2
PS:因为原代码没有用到W2,大概率是有问题的。
至此,基于numpy下的矩阵计算就修改完了。
深度学习框架
通过上述的矩阵导数计算可以知道,手动计算一个参数的梯度求导很简单,但是计算效率低;计算一个矩阵的梯度就有难度,但是计算效率高。如果能够有一个自动求导微分的工具,那该多好啊。这也是当前的深度学习发展很关键的一部分——自动微分机制。
接下来,将采用当前主流的深度学习框架Pytorch来实现,同时验证文章上述梯度计算的正确性。
首先是定义网络以及前向传播:
import torch
from torch import nn
class SAE(nn.Module):
def __init__(self) -> None:
super().__init__()
# 参数初始化
self.W1 = nn.parameter.Parameter(torch.zeros(3, 8, dtype=torch.double))
self.b1 = nn.parameter.Parameter(torch.zeros(3, dtype=torch.double))
self.sigmoid = nn.Sigmoid()
self.W2 = nn.parameter.Parameter(torch.zeros(8, 3, dtype=torch.double))
self.b2 = nn.parameter.Parameter(torch.zeros(8, dtype=torch.double))
def forward(self, X):
hidden_matrix = self.sigmoid(self.W1.matmul(X) + self.b1.reshape(-1, 1))
output_matrix = self.sigmoid(self.W2.matmul(hidden_matrix) + self.b2.reshape(-1, 1))
return output_matrix
然后有训练过程:
from torch import optim
Net = SAE()
# Net(torch.tensor(data[0], dtype=torch.float))
# 开始训练
optimizer = optim.SGD(Net.parameters(), lr=0.05)
Loss_func = nn.MSELoss(reduction="sum")
epochs = 128
for _ in range(epochs):
lenght = data.shape[0]
Loss = 0
for i in range(lenght):
X = torch.tensor(data[i])
X_hat = Net(X)
loss = Loss_func(X_hat, X)/2
optimizer.zero_grad()
loss.backward()
optimizer.step()
Loss += loss
print(f"{_+1} loss:{Loss/lenght}")
为了验证上述梯度回传计算的正确性,使用这样的代码来验证,已经将矩阵都以全0的方式初始化:
input_data = data[0]
# 基于Numpy的方法
w1, b1, w2, b2= W1, B1, W2, B2
output, hidden_layer_output=forward_propagation(input_data,w1,b1,w2,b2)
dw1, dw2, db1, db2 = gradiengt_decent(output,input_data,w2,hidden_layer_output)
# 基于Pytoch的方法
Net = SAE()
X = torch.DoubleTensor(data[0])
X_hat = Net(X)
loss = Loss_func(X_hat, X)/2
optimizer.zero_grad()
loss.backward()
# 比较
print(Net.W1.grad - dw1)
print(Net.W2.grad - dw2)
print(Net.b1.grad - db1)
print(Net.b2.grad - db2)
结果有:
tensor([[0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0.]], dtype=torch.float64)
tensor([[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]], dtype=torch.float64)
tensor([0., 0., 0.], dtype=torch.float64)
tensor([ 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, -2.7756e-17,
2.7756e-17, 0.0000e+00, 0.0000e+00], dtype=torch.float64)
说明上述的计算基本正确,可能存在一些精度上的误差。
至此,我们就可以开始分析,题主所说的“神经网络输出都一样这个问题”。
首先先看题主的任务,可能是某种矩阵压缩的任务,将矩阵从(8,8)的形式压缩到(3,8)的形式,甚至可能是8个8维度的行向量,压缩成3个8维度的行向量以探求向量之间的关系。
如果是后者,从线性代数的秩来看,是比较难做到的,一般来说,随机出来的矩阵是一个满秩矩阵,很难存在可以压缩的信息,即从8x8压缩到3x8的形式。
但是至少,通过实验结果,以及可能的分析,可以看到,题主的任务,首先在正确的实现下并不是0.5附近徘徊,在训练了1000次以后有:
1000 loss:2.3343
Net(torch.tensor(data[0]).cuda()).cpu().detach().numpy()
array([[0.51172373, 0.56241952, 0.55800087, 0.47174427, 0.56678227,
0.40300673, 0.44959767, 0.52739377],
[0.48013683, 0.40617486, 0.41255567, 0.53892418, 0.39989175,
0.63824329, 0.57131044, 0.45713635],
[0.52591279, 0.61318884, 0.60572179, 0.45605942, 0.62052586,
0.33946687, 0.41771344, 0.553189 ],
[0.50419805, 0.51642133, 0.51534773, 0.49460804, 0.51748356,
0.47785564, 0.48926727, 0.50795945],
[0.52419569, 0.5990492 , 0.5926021 , 0.46454659, 0.60539538,
0.36360818, 0.43164683, 0.54750019],
[0.52763252, 0.60983392, 0.6027865 , 0.46186592, 0.61676286,
0.35126886, 0.42565517, 0.55329581],
[0.51289696, 0.5519445 , 0.54853156, 0.48214023, 0.55531696,
0.42882915, 0.46505109, 0.52494809],
[0.39894411, 0.12901825, 0.14456692, 0.68272197, 0.1150574 ,
0.94388547, 0.80557175, 0.29499562]])
可以看到部分数值有0.9和0.29不在0.5附近的存在,但是损失函数此时已经不随着训练而继续下降了,一定程度说明当前的模型的性能比较差,可能无法达到题主的目的。
免责声明:本文由卡卷网编辑并发布,但不代表本站的观点和立场,只提供分享给大家。
相关推荐

你 发表评论:
欢迎