大纲

  • 一、基本原理
  • 1.神经元
  • 2.输入层
  • 3.隐藏层
  • 4.输出层
  • 5.权重更新
  • 二、仿真
  • 三、Matlab代码


一、基本原理

三层前向神经网络应该是神经网络最原始、最简单的网络结构了,顾名思义,它只包含了三层结构——输入层、隐藏层、输出层。其基本原理很简单,主要是加权求和,再加个激活函数,运算的知识点没有超过初中数学。

1.神经元

人工神经网络是收到人脑结构启发的一种算法。人脑神经元通过突触两两相连,将信息传递给下一个树突,树突将得到的数据经过处理(激活),继续传递给下一个神经元。受到这样结构的启发,人们提出了人工神经元结构来模拟真实神经元的工作过程,如下图所示。

单层前向神经网络 多层前向神经网络模型_机器学习

假设一个神经元前面连接了d个神经元,该神经元接受的输入是分别来自前一层神经元的输出x1…xd。每个输入对该神经元作用的大小不同,因此每个输入对应不同的权重w1…wd,经过加权求和,这个神经元所接受的输入总量为:w1*x1 + w2*x2 + … + wd*xd。当然,神经元也不会就将这个值全盘输出给下一层,而是有筛选性地输出,就像我们的大脑如果接受大量信息而不经过筛选,那迟早得累死。这个筛选的标准就是一个激活函数,以前比较常用tanh、sigmoid函数,现在实际应用中ReLu更常用。

单层前向神经网络 多层前向神经网络模型_matlab_02

如果没有激活函数,整个网络相当于一个线性函数,即输出为输入x的加权求和,并不能起到分类的作用。引入激活函数其实就是引入了非线性因素。

2.输入层

给定n个训练样本X,每个训练样本维度为d,对应标签T。举个实际的例子,用Matlab生成5类不同高斯分布的点,各两百个,每个点的维度为2,即横坐标和纵坐标。

单层前向神经网络 多层前向神经网络模型_权重_03


那么,神经网络的输入层需要d+1个节点,多出来一个节点输入1作为样本的偏置。这里的x上标i表示第i个样本,下标j表示第j个节点。如下图所示,因为样本x的维度是2,分别输入每一个样本的每一维度的值,再加上一个偏置1.

单层前向神经网络 多层前向神经网络模型_matlab_04

3.隐藏层

隐藏节点对应上图蓝色的点。z = W·x,隐藏层的输入是输入层的加权,权重矩阵Wh的第i行对应输入层对隐藏层第i个节点的权重。隐藏层的输入经过tanh激活得到输出a,f(z)=a。

单层前向神经网络 多层前向神经网络模型_单层前向神经网络_05

4.输出层

隐藏层的输出a同样经过对应的权重加和,就设这个加和为u吧,然后同样经过一个激活函数sigmoid得到预测值y。

单层前向神经网络 多层前向神经网络模型_单层前向神经网络_06

单层前向神经网络 多层前向神经网络模型_单层前向神经网络_07


sigmoid函数图像如上,中间原点对应值为0.5,两边无限趋近于0和1。sigmoid作为激活函数,那么神经元输出的值区间是(0,1)。越接近于1,表示预测概率越大;越接近0,表示预测结果越小。分类结果选取概率最大的y所对应类别标签。

5.权重更新

采用反向传播,公式太多,还是另外再开一坑吧。

二、仿真

单层前向神经网络 多层前向神经网络模型_权重_03


本次仿真共1000个样本点,取600个作为训练集,400个作为验证集,共有5类样本。采用100样本的小批量下降方法,分别对不同学习速率和不同隐藏节点个数的训练准确率作了比较。结果显示节点个数为10、学习速率为0.01时效果较好。

单层前向神经网络 多层前向神经网络模型_权重_09

采用随机梯度下降的方法,每一次的迭代速度快,但是收敛慢且震动大。采用批梯度下降的算法震动最小,但是迭代时间长,迭代1000次左右收敛。100样本小批量梯度下降震荡程度适中,收敛速度最快,差不多训练400次就收敛了,最高准确率在97%左右。下面分别是随机梯度、100小批量、批梯度下降运行结果对比。

单层前向神经网络 多层前向神经网络模型_机器学习_10

单层前向神经网络 多层前向神经网络模型_神经网络_11


单层前向神经网络 多层前向神经网络模型_单层前向神经网络_12

三、Matlab代码

参考网上的代码,自己有做了进一步修改

Sigma = [1, 0; 0, 1];
mu1 = [1, -1];
x1 = mvnrnd(mu1, Sigma, 200);
mu2 = [5, -4];
x2 = mvnrnd(mu2, Sigma, 200);
mu3 = [1, 4];
x3 = mvnrnd(mu3, Sigma, 200);
mu4 = [6, 4];
x4 = mvnrnd(mu4, Sigma, 200);
mu5 = [7, 0.0];
x5 = mvnrnd(mu5, Sigma, 200);
%---------------------main---------------------%
%读入数据

X_train = [x1(1:120,:);x2(1:120,:);x3(1:120,:);x4(1:120,:);x5(1:120,:)];
X_test = [x1(121:200,:);x2(121:200,:);x3(121:200,:);x4(121:200,:);x5(121:200,:)];


t = zeros(600,5);
t(1:120,1) = 1;
t(121:240,2) = 1;
t(241:360,3) = 1;
t(361:480,4) = 1;
t(481:600,5) = 1;

tt = zeros(400,5);
tt(1:80, 1) = 1;
tt(81:160, 2) = 1;
tt(161:240, 3) = 1;
tt(241:320, 4) = 1;
tt(321:400, 5) = 1;

param.Nx = 600; %训练数
param.Nd = 3;   %输入x的维度
param.Ny = 5;

param.Nh = 10;     %隐藏节点个数
param.eta = 0.01;   %权重更新率
param.theta = 1;   %停止阈值

%初始化
Wh = 0.2*(rand(param.Nd,param.Nh)-0.5);
Wy = 0.2*(rand(param.Nh,param.Ny)-0.5);
w{1} = Wh;
w{2} = Wy;

param.batch_size = 100;

%[J, z, yh] = forward(X_train, w, t, param);

%批梯度下降
[ w1, count1, data1, acc] = batchBP( X_train, X_test, w, t, tt, param );
%单样本方式更新权重
%[w2, count2, data2] = singleBP( X_train, X_test, z, yh, w, t, tt, param );
%绘图
n1 = numel(find(data1 > 0));
data1 = data1(1:n1);
acc = acc(1:n1);
xx1 = 1:n1;
figure(1);
plot(xx1,data1);
xlabel('迭代次数')
ylabel('目标函数loss')
figure(2);
plot(xx1, acc);
xlabel('迭代次数')
ylabel('正确率')

function [J,z,yh] = forward(x, w, t, Nx)
%x为输入样本,w为网络权重,t为输出真值,param为网络参数
%J为损失函数,z为最后一层输出,yh为隐藏层输出
%Nh = param.Nh;
%Nx = param.Nx;
%Nd = param.Nd;
%Ny = param.Ny;

Wh = w{1};
Wy = w{2};

neth = x * Wh;
yh = tanh(neth);

netj = yh * Wy;
z = sigmoid(netj);

J = zeros(Nx,1);
for k = 1:Nx
    J(k) = 0.5 * sum((z(k,:) - t(k,:)).^2);
%     J(k) = 0;
%     for i = 1:Ny
%         J(k) = 0.5 * (z(k,i) - t(k,i))^2 + J(k);
%     end
end
end

function [ w, q, data, acc] = batchBP( x, x_t, w, t, tt, param )
%BATCHBP 此处显示有关此函数的摘要
%   批量BP算法
Nh = param.Nh;
%Nx = param.Nx;
Nx = param.batch_size;
Nd = param.Nd;
Ny = param.Ny;
eta = param.eta;
theta = param.theta;

flag = 0;
% res = [0 0 0];
% resid = 1;
data = zeros(30000,1);
acc = zeros(30000,1);
q = 0;  %迭代次数 q = count


while(flag == 0)
    %根据次数划分每一次的训练数据
    m = mod(q, 6);
    x_batch = x(1+m*100: (m+1)*100, :);
    t_batch = t(1+m*100: (m+1)*100, :);
    
    Wh = w{1};
    Wy = w{2};
    [ J, z, yh ] = forward( x_batch, w, t_batch, Nx );
    sj = zeros(Nx,Ny);
    sh = zeros(Nx,Nh);
    deltaj = zeros(Nh,Ny);
    deltah = zeros(Nd,Nh);
    for k = 1:Nx
        for j = 1:Ny
            sj(k,j) = z(k,j)*(1-z(k,j))*(t_batch(k,j)-z(k,j));
            for h = 1:Nh
                deltaj(h,j) = eta*sj(k,j)*yh(k,h) + deltaj(h,j);
            end
        end
        for h = 1:Nh
            sh(k,h) = (1-yh(k,h)^2)*(Wy(h,:)*sj(k,:)');
            for i = 1:Nd
                deltah(i,h) = eta*sh(k,h)*x_batch(k,i) + deltah(i,h);
            end
        end
    end
    
    Wy = Wy + deltaj;
    Wh = Wh + deltah;
    w{1} = Wh;
    w{2} = Wy;
    %[ J, z, yh ] = forward( x, w, t, param );
    JJ = sum(abs(J));
    [ out ] = test( w, x_t, tt );
    q = q + 1;
    data(q) = JJ;
    acc(q) = out;
    
%     res(resid) = JJ;
%     resid = resid + 1;
%     if resid == 4
%         resid = 1;
%     end
    if JJ < theta || isnan(JJ) || q==3000 %(round(res(1),3) == round(res(2),3) && round(res(1),3) == round(res(3),3))
        flag = 1;       
    end
   
    disp(['batch迭代第' num2str(q) '次,正确率为:' num2str(out*100) '%, loss为:' num2str(JJ)]);
    
end
end


 function [ out ] = test( w, X_test, tt )
%TEST 此处显示有关此函数的摘要
%   此处显示详细说明
Wh = w{1};
Wy = w{2};

neth = X_test * Wh;
yh = tanh(neth);

netj = yh * Wy;
z = sigmoid(netj);

[~,I] = max(z,[],2);  %返回每行最大的数所在的列数
[~,It] = max(tt,[],2);
tr = numel(find(I == It));  %判断正确的数目
out = tr/size(X_test,1);

 end

function [ y ] = sigmoid( x )
%SIGMOID 此处显示有关此函数的摘要
%   此处显示详细说明
[a,b] = size(x);
y = zeros(a,b);
for i = 1:a
    for j = 1:b
        y(i,j) = 1/(1+exp(-x(i,j)));
    end
end
end