当前位置:首页 > 每日看点 > 正文内容

为什么我的神经网络输出永远一样?

卡卷网9个月前 (02-22)每日看点138

看了一下代码,确实有点东西嗷,自己写神经网络,自己写MSE损失函数,自己写梯度回传。(甚至连矩阵计算都不用,直接手写矩阵行元素的计算,好险不是矩阵元素两重循环计算。)

简单对代码梳理了一下,整个模型可以分为两个部分:

前向传播部分:

  • 压缩: 为什么我的神经网络输出永远一样?  第1张 ,具体为 为什么我的神经网络输出永远一样?  第1张
  • 解压: 为什么我的神经网络输出永远一样?  第1张 ,具体为 为什么我的神经网络输出永远一样?  第1张
  • 损失函数:为什么我的神经网络输出永远一样?  第1张

反向传播/.参数更新部分:

  • 梯度计算:为什么我的神经网络输出永远一样?  第1张 等计算,通过导数的链式法则等。
  • 参数更新: 为什么我的神经网络输出永远一样?  第7张 ,对相关参数进行更新。

输出的矩阵为什么是大都趋于0.5,我猜测,主要是因为数据生成采用的是np.random.random函数,这个函数会以均匀分布的方式生成(0,1)之间的随机数,所以矩阵中每一个元素的均值就是0.5,与此同时,在实现的代码中,是计算完所有的样本再进行梯度下降,很大概率是朝着0.5的方向下降。

那么这个问题就来了在修改好下降方向以后,为什么模型仍然不会学习得到输入 为什么我的神经网络输出永远一样?  第1张 是什么,输出 为什么我的神经网络输出永远一样?  第1张 就是什么的额能力呢?大概是当前模型的能力不足以学会更多的东西(参数较小)。


关于代码的一些吐槽

整个的计算效率很低,运行题主源代码都快运行了一个小时了。

可以加速计算的一个点是:当前选择的是对每一个数据进行计算

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 做矩阵计算。那么接下来的任务就是将梯度求导这一块也改成矩阵计算,为了方便看客们的理解,在这里,做一个跟上述任务类似的求导过程计算就完事了:

首先,我们的预测是 为什么我的神经网络输出永远一样?  第1张 其中, 为什么我的神经网络输出永远一样?  第1张 就是我们的输出,假设维度是 为什么我的神经网络输出永远一样?  第1张 ,而 为什么我的神经网络输出永远一样?  第1张 就是我们的输入,维度是 为什么我的神经网络输出永远一样?  第1张为什么我的神经网络输出永远一样?  第1张 是我们的矩阵参数,维度是 为什么我的神经网络输出永远一样?  第1张为什么我的神经网络输出永远一样?  第1张 是我们的偏置,维度是 为什么我的神经网络输出永远一样?  第1张 ,相当于每一行的每一个元素都加上同一个偏置。

我们的损失函数有 为什么我的神经网络输出永远一样?  第1张 ,其实就是预测和真实值之间每一个元素做一个平方再求和,所以也可以写成 为什么我的神经网络输出永远一样?  第1张

那么,接下来,我们想知道,当前的损失函数数值,如何得到我们的参数 为什么我的神经网络输出永远一样?  第1张 的梯度呢?在这里就只讨论可微分的情况。因为直接计算矩阵的微分比较困难,但是我们可以先计算其中一个,比如 为什么我的神经网络输出永远一样?  第1张 ,即计算 为什么我的神经网络输出永远一样?  第1张 有:

【数学数字比较多,防止伤害眼睛,可跳过】

为什么我的神经网络输出永远一样?  第1张 其中为什么我的神经网络输出永远一样?  第1张 ,所以拆开来有:

为什么我的神经网络输出永远一样?  第1张

其中,当 为什么我的神经网络输出永远一样?  第1张 的时候,该导数为 为什么我的神经网络输出永远一样?  第1张 ,其余为0。

所以原式可转化为 为什么我的神经网络输出永远一样?  第1张 ,其中 为什么我的神经网络输出永远一样?  第1张为什么我的神经网络输出永远一样?  第1张 为1,其余为0。

所以就有 为什么我的神经网络输出永远一样?  第1张 ,最后矩阵形式为 为什么我的神经网络输出永远一样?  第1张

类似地,如果对参数 为什么我的神经网络输出永远一样?  第1张 ,也有上述的关系有 为什么我的神经网络输出永远一样?  第1张 ,实际上就是对列求和,所向量形式有 为什么我的神经网络输出永远一样?  第1张

【结束】

总之,通过上述的方式,我们能够将梯度计算通过矩阵的形式进行计算,对于原问题来说,我们就有:

为什么我的神经网络输出永远一样?  第37张代码如下:

# 矩阵梯度更新 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附近的存在,但是损失函数此时已经不随着训练而继续下降了,一定程度说明当前的模型的性能比较差,可能无法达到题主的目的。

扫描二维码推送至手机访问。

版权声明:本文由卡卷网发布,如需转载请注明出处。

本文链接:https://www.kajuan.net/ttnews/2025/02/11092.html

分享给朋友:

相关文章

AI普及:让人类更聪明还是更愚蠢?

AI普及:让人类更聪明还是更愚蠢?

人工智能真的会替代人类吗?如果你询问人工智能这个问题,它通常会用“情感模块”作为标准答案来回复你——人工智能因为缺失情感模块,所以暂时无法替代人类的情感、创造力和想象力。是否这意味着,人工智能一旦拥有了情感模块,就会超越正态曲线最中段的大部...

百度收录又开始“作”了,用“快速抓取”替代“快速收录”

百度收录又开始“作”了,用“快速抓取”替代“快速收录”

作为国内不得不用的知名搜索引擎“百度”,对于网站收录的规则,又有新的升级。以前有个快速收录,我们可以通过WordPress插件,免费快速提交给百度。但是现在这个功能没了,取而代之的是“快速抓取”,如下图想要使用此功能,需要加入“VIP俱乐部...

有什么音乐软件可以全部免费下载歌曲?

有什么音乐软件可以全部免费下载歌曲?

作为音乐发烧友,我几乎把市面上所有的发烧碟、试音碟,全都给收藏,下载下来了!音质都是无损的,品质特别高,有5.1环绕的、有DTS的、有中文的、有英文的。大家可以看看这个音乐目录,大概有30万张专辑。有需要下载软件的朋友,可以双击屏幕,然后搓...

天涯论坛关闭后,除了知乎,大家都在逛什么?

天涯论坛关闭后,除了知乎,大家都在逛什么?

天涯神贴合集完整版,给大家整理好了!那年大学,打开天涯,感觉打开了一片新天地,里面什么样的人都有,有大神也有蛇神,比某乎好太多了,可惜后面关了很多年前,天涯社区曾出现了不少深受欢迎的帖子,成功地预言了许多形势和事件。这些帖子因此被冠以“天涯...

你是如何在不依靠工资收入的情况下赚到一万元的?

你是如何在不依靠工资收入的情况下赚到一万元的?

分享几个路子稳,门槛低,变现快,适合年轻人的靠谱的副业。绝对不是送外卖、滴滴、搬砖等等的苦力活,这几个副业都是能是性价比极高,还能让你快速成长的工作。想通过副业实现暴富、立马月入过万的同学,建议速速划走。今天撇哥就给大家分享100+个靠谱赚...

马云也搞不明白:为什么现在用户偏爱微信支付,而不是支付宝?

这题我会,我教马云一招。你直接把你那破比支付宝的代码全删了,重新写一个。打开支付宝直接就是一个大大的支付码,然后右上角按一下就是扫一扫。你要是还想保留你的其他那些乱七八糟的功能,麻烦将他们全部做到下拉菜单里。你这么设计我不说你能干死微信,但...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。