b站的用纸笔训练神经网络【matlab与python实现】

b站的用纸笔训练神经网络【matlab与python实现】

我的工作

之前在b站上看到小蛮大佬做的一期用纸笔训练神经网络的视频,关于正向传递和反向传播这一块受益匪浅,但是视频中也存在一些公式以及绘图错误的地方,所以尝试复现了一些代码来更清晰的展现整个过程,目前只提供matlab与python版本的代码(PS:该博客只对视频中提到的内容做一些浅显的梳理,便于初学者理解)。

视频链接:https://www.bilibili.com/video/BV1R64y187yt/

基本思路

神经网络正向传递和反向传播的过程可以看成下图所示
正向传递:左边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  # 画图用的包


def sigmoid(A):  # 激活函数sigmod
    A1 = np.size(A, 0)
    A2 = np.size(A, 1)
    result = [[0.0 for col in range(A2)] for row in range(A1)]  # 初始化一个A1*A2维度的列表
    result = np.array(result)  # 转为数组
    for i in range(A1):
        for j in range(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节点经过激活函数sigmod

        if times == 1:  # 第一次随机生成
            W3 = np.random.random((Outx, Outy))  # 第一次循环随机生成权重W3
        Q = np.dot(Z2, W3)

        # 输出层节点经过激活函数
        y = sigmoid(Q)  # 预测值

        # 计算损失值
        sum = 0
        for m in range(Outx):
            for n in range(Outy):
                sum = sum + (y[m, n] - Y[m, n]) ** 2
        if 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()  # 打印图像

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>