b站的用纸笔训练神经网络【matlab与python实现】
我的工作
之前在b站上看到小蛮大佬做的一期用纸笔训练神经网络的视频,关于正向传递和反向传播这一块受益匪浅,但是视频中也存在一些公式以及绘图错误的地方,所以尝试复现了一些代码来更清晰的展现整个过程,目前只提供matlab与python版本的代码(PS:该博客只对视频中提到的内容做一些浅显的梳理,便于初学者理解)。
基本思路
神经网络正向传递和反向传播的过程可以看成下图所示
正向传递:左边3×4维的矩阵经过一个黑盒后会得到右边3×2维的矩阵
- 一开始的时候,我们经过黑盒得到的3×2维的矩阵不一定如我们所愿是实际的目标结果Y,我们需要借助已知的目标结果Y对黑盒进行调整
- 输入3×4维矩阵X,经过黑盒生成3×2维矩阵y(这里是y,不是目标矩阵Y),这样在不断的更新迭代后,黑盒表现的很好了
- 那么当我们再次输入3×4维矩阵X,输出的预测矩阵y就会和目标矩阵Y相差无几,那么对黑盒进行调整的过程,就是反向传播过程
反向传播:右边3×2维的矩阵对黑盒进行调整的过程
黑盒是什么
黑盒包括经过的各种神经元,通过一些列矩阵相乘、激活函数等操作,最终由softmax得到输出结果。
这里引用视频中的一张图,红色方框内可以看成黑盒的部分
其中的紫色线段、蓝色线段、黑色线段,分别对应三个权重w1、w2、w3(这三个权重初始时是随机生成的)。比如紫色线段(对应w1),左边是四个输入(x1、x2、x3、x4),右边是三个神经元输出(s1、s2、s3),所以w1的维度就是4×3,以此类推。
所以神经网络的正向传递可以看成是一系列的矩阵相乘的过程
前面提到,三个权重在一开始是随机生成的,那么反向传递的调整过程,就是对这三个权重进行调整,利用预测的y与实际的Y的差值,即Loss,通过Loss分别对w进行求导(视频里用的是链式法则来解决),得到w的调整量g(w1、w2、w3对应g1、g2、g3),反向传递完后,原来的权重w减去对应的g(当然这个g一般会乘上学习率以及转置)即可得到更新后的w。
总结来说就是反向传播就是更新w的过程。
g1、g2、g3用链式法则求导的公式我会在代码中给出,就用matlab的代码来说一下吧,需要注意 * 和 .* 的区别,*是矩阵乘法,需要前一个数组的列与后一个数组的行相等,而 .*是需要两个矩阵维度完全相等的,是矩阵对应位置相乘
MATLAB源码
建议用matlab来debug矩阵变化的各个过程,比较方便清晰
clc,clear,close all
%% 训练样本
X=[1,1,0,0;
0,0,1,1;
1,0,0,1];
Y=[1,0; %实际值
0,1;
1,0];
[Inx,Iny]=size(X);%输入矩阵的维数
[Outx,Outy]=size(Y);%输出矩阵的维数
Hid_wide=3;%隐藏层节点维度
D=100; %损失初始值
a=0.1;%学习率
times=1;
res=0.001;%容差
Loss=inf; %预测值-实际值
%% 循环训练权重
while D>res %大于容差则一直循环
% disp(times);
disp(D);
%第一层
if times==1 %第一次随机生成
W1=rand(Iny,Hid_wide);%第一次循环随机生成权重W1
end
S1=X*W1;
%S1节点经过激活函数sigmod
Z1=sigmoid(S1);
%第二层
if times==1
W2=rand(Hid_wide,Outx);%第一次循环随机生成权重W2
end
S2=Z1*W2;
%S2节点经过激活函数
Z2=sigmoid(S2);
%输出层
if times==1
W3=rand(Outx,Outy);
end
Q=Z2*W3;
%输出层节点经过激活函数
y=sigmoid(Q); %预测值
%计算损失值
sum=0;
for m=1:Outx
for n=1:Outy
sum=sum+(y(m,n)-Y(m,n))^2;
end
end
if D>sum
D=sum;
end
%% 反向传递过程
%loss对W3求导
g3=(y-Y)'*Z2;
%loss对W2求导
g2=(((y-Y)*W3').*(sigmoid(S2).*(1-sigmoid(S2))))'*Z1;
%loss对W1求导
g1=((((y-Y)*W3').*(sigmoid(S2).*(1-sigmoid(S2))))*W2'.*(sigmoid(S1).*(1-sigmoid(S1))))'*X;
%% 更新权重
W1=W1-a*g1';
W2=W2-a*g2';
W3=W3-a*g3';
Loss(times)=D; %记录每次的损失值
times=times+1;
end
plot(Loss);%打印损失降低过程
disp('调整后的W1');
disp(W1);
disp('调整后的W2');
disp(W2);
disp('调整后的W3');
disp(W3);
disp('真实Y值');
disp(Y);
disp('训练的Y值');
disp(y);
%% 激活函数sigmod
function result=sigmoid(A)
result=inf;
[A1,A2]=size(A);
for i=1:A1
for j=1:A2
result(i,j)=1/(1+exp(-A(i,j)));
end
end
end
Python源码
刚入门python,原谅我大量不熟练的操作,数组计算部分相比matlab确实太累了
"""
作者:猪脚三父
日期:2022年02月01日
"""import numpy as np
import matplotlib.pyplot as plt # 画图用的包defsigmoid(A):# 激活函数sigmod
A1 = np.size(A,0)
A2 = np.size(A,1)
result =[[0.0for col inrange(A2)]for row inrange(A1)]# 初始化一个A1*A2维度的列表
result = np.array(result)# 转为数组for i inrange(A1):for j inrange(A2):
result[i, j]=1/(1+ np.exp(-1* A[i, j]))return result
if __name__ =='__main__':
X =[[1,1,0,0],[0,0,1,1],[1,0,0,1]]
Y=[[1,0],[0,1],[1,0]]
X = np.array(X)# 转成array格式
Y = np.array(Y)# 转成array格式
Inx = np.size(X,0)# 输入矩阵的维数
Iny = np.size(X,1)# 输入矩阵的维数
Outx = np.size(Y,0)# 输出矩阵的维数
Outy = np.size(Y,1)# 输出矩阵的维数
Hid_wide =3# 隐藏层节点维度
D =100# 损失初始值
a =0.1# 学习率
times =1
res =0.001# 容差
Loss =[]while D > res:# 大于容差则一直循环# 第一层if times ==1:# 第一次随机生成
W1 = np.random.random((Iny, Hid_wide))# 第一次循环随机生成权重W1
S1 = np.dot(X, W1)
Z1 = sigmoid(S1)# S1节点经过激活函数sigmod# 第二层if times ==1:# 第一次随机生成
W2 = np.random.random((Hid_wide, Outx))# 第一次循环随机生成权重W2
S2 = np.dot(Z1, W2)
Z2 = sigmoid(S2)# S2节点经过激活函数sigmodif times ==1:# 第一次随机生成
W3 = np.random.random((Outx, Outy))# 第一次循环随机生成权重W3
Q = np.dot(Z2, W3)# 输出层节点经过激活函数
y = sigmoid(Q)# 预测值# 计算损失值sum=0for m inrange(Outx):for n inrange(Outy):sum=sum+(y[m, n]- Y[m, n])**2if D >sum:
D =sum# 反向传递过程# loss对W3求导
g3 = np.dot(np.transpose(y - Y), Z2)# loss对W2求导
tmp2 = np.dot(y - Y, np.transpose(W3))
tmp3 = np.multiply(sigmoid(S2),1- sigmoid(S2))
tmp1 = np.multiply(tmp2, tmp3)
g2 = np.dot(np.transpose(tmp1), Z1)# loss对W1求导
tmp6 = np.multiply(sigmoid(S1),1- sigmoid(S1))
tmp5 = np.dot(tmp1, np.transpose(W2))
tmp4 = np.multiply(tmp5, tmp6)
g1 = np.dot(np.transpose(tmp4), X)# 更新权重
W1 = W1 - a * np.transpose(g1)
W2 = W2 - a * np.transpose(g2)
W3 = W3 - a * np.transpose(g3)
Loss.append(D)# 记录每次的损失值
times = times +1# 打印数据 & 画图print('调整后的W1')print(W1)print('调整后的W2')print(W2)print('调整后的W3')print(W3)print('真实Y值')print(Y)print('训练的Y值')print(y)
t =range(len(Loss))
plt.figure(dpi=100, figsize=(12,6))# 指定图像分辨率和画板大小
plt.fill_between(t, Loss, color="skyblue", alpha=0.3)
plt.plot(t, Loss, color="blue")# 多勾勒一层蓝边
plt.xlabel('迭代次数')# x轴上的名字
plt.ylabel('Loss')# y轴上的名字
plt.rcParams['font.sans-serif']=['SimHei']# 不加不能显示中文
plt.rcParams['axes.unicode_minus']=False# 不加不能显示中文
plt.show()# 打印图像
版权归原作者 猪脚三父 所有, 如有侵权,请联系我们删除。