前馈人工神经网络是基础的神经网络,这一编开始实现。当然在实现前肯定要先说一下原理,不然有代码也看不懂,还不如没代码。
我们先来约定一下矩阵的表示,类似Xrc或Xr1c1或X1_2或Xr1_1或X1c1,也就是一个矩阵的表示分三部分,这三部分按序排列,第一部分是矩阵的名称,用大写字母表示,第二、三部分分别是矩阵的行数和列数,若是固定的数字,则前面没有小写字母。
这里做的人工神经网络是三层的,第一层是输入层X1i(一个一行i列的矩阵,其实就是一个行矢量),第二层是隐藏层H1h(一个一行h列的矩阵,也是个行矢量),第三层是输出层O1o(类似前面两个)。输入层与隐藏层之间用Wih(一个i行h列的矩阵)连接,隐藏层与输出层间用Who矩阵连接。
单个人工神经的模式如下(网上的图):
可以看到它是把前面的输入与权值w合到一起然后刺激神经元获得一个输出。
前馈人工神经网络的图(也是网上的):
层与层之间都是把前一层的输出与权值矩阵作用作为本层的输入,然后再经过激励函数得到本层的输出
所以一个前馈神经网络第k层到第k+1层间的
输入
I1k1=I1k*Wkk1
输出
O1k1=gk(I1k1)
Wkk1是第k层到第k+1层的连接权值矩阵;
gk是刺激神经元的激励函数,一般用logsig函数,在输出层也可以用线性函数来获得超过1的输出。
下面开始真正的了。
第一步 训练网络(得到的结果是权值)
1.计算输出:
输入层:
输入是 X1i
输出是 O1i=X1i
隐藏层:
输入是 X1h=X1i*Wih+B1h (B1h是隐藏层偏置)
输出是 O1h=gh(X1h) (这里的gh激励函数用logsig)
输出层:
输入是 X1o=O1h*Who+B1o (B1o是输出层偏置)
输出是 O1o=go(X1o) (这里的go函数用logsig或线性函数)
所以期望的输出Y1o与最终的输出O1o差值为
D1o=Y1o-O1o
当O1o与Y1o越接近,则预测的越好,所以我们用
error=D1o.square()
来判断什么时候达到训练效果。(资料中还要除以2,但是我想偷懒)error越小越接近期望的结果。
好了,上面得到了一次输出,我们还要把误差传回去修正权值矩阵以便下次得到的输出更接近期望的输出。BP方法(误差回传)是用梯度下降来修正权值矩阵,步骤与上面的差不多,只是把gh函数换成了gh函数的导数gh1。并且要乘梯度
2.误差回传
输出层:
残差输入是 D1o
残差输出是 E1o=D1o.hadamard(gh1(X1o))
隐藏层:
残差输入是 D1h=E1o*Who.transpose() (这里要转置一下,不然无法相乘)
残差输出是 E1h=D1h.hadamard(gh1(X1h))
输出层:
没有残差输入
没有残差输出
3.修改权值
输入层到隐藏层:
Wih+=rate*O1i.transpose().kronecker(E1h) (rate为学习速率,一般取0.6到0.8)
B1h+=rate*E1h
隐藏层到输出层:
Who+=rate*O1h.transpose().kronecker(E1o)
B1o+=rate*E1o
第二步 识别
其实这个过程我们已经见过啦,就是第一步的第1小步:计算输出,得到的O1o就是识别结果。
好了,下面是全部代码(不包括matrix.h,已经在前面文章包含了。当然,为了便于理解,我没有优化代码):
#pragma once
#include"stdafx.h"
#include"matrix.h"
#include"time.h"
#define T 8
#define I 3
#define H 5
#define O 1
//double normalized_random(){ return (2.0*(double)rand() / RAND_MAX) - 1; }
template<int row, int col>t_matrix<row, col>gh(const t_matrix<row, col>&M)
{
return logsig(M);
}
template<int row, int col>t_matrix<row, col>gh1(const t_matrix<row, col>&M)
{
return logsig1(M);
}
template<int row, int col>t_matrix<row, col>go(const t_matrix<row, col>&M)
{
return logsig(M);
}
typedef t_matrix<1, I>t_i;
typedef t_matrix<1, H> t_h;
typedef t_matrix<1, O> t_o;
typedef t_i t_is[T];
typedef t_o t_os[T];
typedef t_matrix<I, H> t_ih;
typedef t_matrix<H, H> t_hh;
typedef t_matrix<H, O> t_ho;
void train(const t_is&xs, const t_os&ys, double rate, double err_limit, t_ih&Wih, t_h&B1h, t_ho&Who, t_o&B1o)
{
int times = 0;
srand(time(0));
Wih = t_ih::random(-1, 1);
B1h = t_h::random(-1, 1);
Who = t_ho::random(-1, 1);
B1o = t_o::random(-1, 1);
while (true)
{
times++;
double error = 0;
for (int t = 0; t < T; t++)
{
const t_i&X1i = xs[t];
const t_o&Y1o = ys[t];
const t_i&O1i = X1i;
auto X1h = X1i*Wih + B1h;
auto O1h = gh(X1h);
auto X1o = O1h*Who + B1o;
auto O1o = go(X1o);
auto D1o = Y1o - O1o;
auto E1o = D1o.hadamard(gh1(X1o));
auto D1h = E1o*Who.transpose();
auto E1h = D1h.hadamard(gh1(X1h));
error += D1o.square();
Wih += rate*O1i.transpose().kronecker(E1h);
B1h += rate*E1h;
Who += rate*O1h.transpose().kronecker(E1o);
B1o += rate*E1o;
}
printf("循环次数:%d,误差:%g\n", times, error);
if (error < err_limit)return;
}
}
void calculate(const t_i&x, const t_ih&Wih, const t_h&B1h, const t_ho&Who, const t_o&B1o, t_o&y)
{
const t_i&X1i = x;
const t_i&O1i = X1i;
auto X1h = X1i*Wih + B1h;
auto O1h = gh(X1h);
auto X1o = O1h*Who + B1o;
auto O1o = go(X1o);
y = O1o;
}
int recognize(const t_os&ys, const t_o&y, double err_limit)
{
if (T <= 0)return -1;
int index = -1;
double err = (ys[0] - y).square(), maxerr;
maxerr = err;
if (maxerr < err_limit)
index = 0;
for (int i = 1; i < T; i++)
{
err = (ys[i] - y).square();
if (maxerr > err)
{
maxerr = err;
if (maxerr < err_limit)
index = i;
}
}
return index;
}
int main()
{
t_is xs = { { 0, 0, 0 }, { 0, 0, 1 }, { 0, 1, 0 }, { 0, 1, 1 }, { 1, 0, 0 }, { 1, 0, 1 }, { 1, 1, 0 }, { 1, 1, 1 } };
t_os ys = { { 0.1 }, { 0.2 }, { 0.3 }, { 0.4 }, { 0.5 }, { 0.6 }, { 0.7 }, { 0.8 } };
t_i x = { 1, 0, 1 };
t_ih wih;
t_h b1h;
t_ho who;
t_o b1o;
train(xs, ys, 0.5, 0.0001, wih, b1h, who, b1o);
printf("训练完成\n");
t_o y;
calculate(x, wih, b1h, who, b1o, y);
printf("计算完成,值:\n");
for (int i = 0; i < O; i++)
printf("%g ", y.data[0][i]);
int index = recognize(ys, y, 0.01);
printf("\n识别完成,值:\n");
if (index >= 0)
{
printf("[");
for (int i = 0; i < O; i++)
printf("%g ", ys[index].data[0][i]);
printf("]");
printf("\n原输入(");
for (int i = 0; i < I; i++)
printf("%g ", xs[index].data[0][i]);
printf(")");
}
getchar();
return 0;
}