神经网络的简单实现
引言
神经网络可能是听起来最高端的机器学习算法,本文将介绍简单的神经网络原理,并给出C++的参考代码
神经网络的原理
神经网络的基本原理
神经网络一般用于根据训练数据集 { x i , y i } i = 1 n \{x_i,y_i\}_{i=1}^n {xi,yi}i=1n 对于测试数据集 { x i ′ } \{x_i'\} {xi′} 进行预测的问题,这里的 x i , y i , x i ′ x_i,y_i,x_i' xi,yi,xi′ 通常是都向量,但为了表述方便,本文仅考虑 y i y_i yi 为一维向量的情形
一般来说,神经网络由若干个神经元构成,神经元分成若干层,第一层是输入层,神经元的个数与 x i x_i xi 的维数相同,最后一层是输出层,与 y i y_i yi 的维数相同,中间若干层可以进行自定义。除了输入层外,每一层的每一个神经元都根据前一层的神经元计算得到,确定这里所谓的“计算”是如何计算,就是神经网络在训练过程中要解决的问题。
单层神经网络
我们先来介绍一个最简单的神经网络——只包含输入层和输出层的神经网络
设训练数据集中的某一数据为
x
=
(
x
1
,
x
2
,
⋯
 
,
x
n
)
,
y
x=(x_1,x_2,\cdots,x_n),y
x=(x1,x2,⋯,xn),y
假定
x
x
x 与
y
y
y 之间有一个客观存在的关系式
y
=
f
(
x
)
y=f(x)
y=f(x)
那么我们就要通过训练来不断逼近这个
f
f
f ,我们最容易想到的是线性表达式,即
f
(
x
)
=
∑
i
=
1
n
w
i
x
i
f(x)=\sum_{i=1}^nw_ix_i
f(x)=i=1∑nwixi
其中
w
i
w_i
wi 是常数,我们称之为权重,那么问题就转化为了调整权重的问题,我们不妨先随机确定权重,然后设置一个损失函数来衡量这样的权重下预测值和实际值相差多少,根据损失函数来确定权重的变化量。损失函数可以使用预测值和实际值差的平方
E
k
=
(
f
(
x
)
−
y
)
2
E_k=(f(x)-y)^2
Ek=(f(x)−y)2
(如果对多个数据进行训练的话可以使用均方误差)
下面我们用梯度下降法进行迭代,计算
E
k
E_k
Ek 的导向量
∂
E
k
∂
w
=
(
∂
E
k
∂
w
1
,
∂
E
k
∂
w
2
,
⋯
 
,
∂
E
k
∂
w
n
)
\frac{\partial E_k}{\partial w}=\left(\frac{\partial E_k}{\partial w_1},\frac{\partial E_k}{\partial w_2},\cdots,\frac{\partial E_k}{\partial w_n}\right)
∂w∂Ek=(∂w1∂Ek,∂w2∂Ek,⋯,∂wn∂Ek)
其中
∂
E
k
∂
w
i
=
∂
∂
w
i
(
∑
i
=
1
n
w
i
x
i
−
y
)
2
=
2
(
∑
i
=
1
n
w
i
x
i
−
y
)
x
i
\frac{\partial E_k}{\partial w_i}= \frac{\partial}{\partial w_i}\left(\sum_{i=1}^nw_ix_i-y\right)^2= 2\left(\sum_{i=1}^nw_ix_i-y\right)x_i
∂wi∂Ek=∂wi∂(i=1∑nwixi−y)2=2(i=1∑nwixi−y)xi
对
w
w
w 进行迭代
w
:
=
w
−
η
∂
E
k
∂
w
w:=w-\eta \frac{\partial E_k}{\partial w}
w:=w−η∂w∂Ek
其中
η
\eta
η 是学习率,是根据具体问题进行设置的参数
这样经过若干次迭代, w w w 的值将会趋于稳定
细心地读者不难发现,这不就是一个多元线性回归吗,还真是,接下来我们让这个神经网络变得复杂一点,就不那么“线性”了
多层全连接神经网络
我们在输入层和输出层之间添加若干隐含层,每一层包含的神经元对应的值由前一层神经元的值线性表出,换言之,每一个非输入层的神经元都对应了一个权重向量 w w w ,维数与前一层神经元个数相同,该神经元对应的值即为 w w w 与前一层神经元的内积
这样显得“高端”多了,但是,此时输出层的值仍可以被输入层线性表出,因此这种看似复杂的神经网络本质上与多元线性回归无异,因为最终得到的表达式仍是线性的
为了让结果变成非线性的,我们引入激活函数
s
i
g
m
o
i
d
(
x
)
=
1
1
+
e
−
x
sigmoid(x)=\frac{1}{1+e^{-x}}
sigmoid(x)=1+e−x1
在隐含层神经元的每一次计算中,都作用激活函数,即以
s
i
g
m
o
i
d
(
∑
i
=
1
n
w
i
x
i
)
sigmoid\left(\sum_{i=1}^nw_ix_i\right)
sigmoid(∑i=1nwixi) 替代
∑
i
=
1
n
w
i
x
i
\sum_{i=1}^nw_ix_i
∑i=1nwixi
用类似的方法计算偏导数进行迭代即可,只不过这里的偏导数比前者要复杂的多,在实际应用是可以从前(输入层)向后(输出层)运用链式法则进行计算,当然,也可以从后向前进行递归式计算,不过需要记忆化以免重复计算
神经网络的C++实现
下面是笔者写的多层全连接神经网络的参考代码,以a+b为例,这可能是相当复杂的a+b了
#include <bits/stdc++.h>
#define rep(i,a,b) for(ll i=a;i<=b;i++)
#define all(i,a) for(ll i=0;i<=(ll)a.size()-1;i++)
#define debug(x) cout<<" "<<(#x)<<":"<<x<<endl
using namespace std;
typedef long long ll;
//tensor
typedef vector<double> tensor;
tensor operator += (tensor &a,const tensor &b){rep(i,0,(ll)a.size()-1)a[i]+=b[i];return a;}
tensor operator -= (tensor &a,const tensor &b){rep(i,0,(ll)a.size()-1)a[i]-=b[i];return a;}
tensor operator *= (tensor &a,const tensor &b){rep(i,0,(ll)a.size()-1)a[i]*=b[i];return a;}
tensor operator /= (tensor &a,const tensor &b){rep(i,0,(ll)a.size()-1)a[i]/=b[i];return a;}
tensor operator + (const tensor &a,const tensor &b){tensor c=a;return c+=b;}
tensor operator - (const tensor &a,const tensor &b){tensor c=a;return c-=b;}
tensor operator * (const tensor &a,const tensor &b){tensor c=a;return c*=b;}
tensor operator / (const tensor &a,const tensor &b){tensor c=a;return c/=b;}
tensor operator += (tensor &a,const double &b){rep(i,0,(ll)a.size()-1)a[i]+=b;return a;}
tensor operator -= (tensor &a,const double &b){rep(i,0,(ll)a.size()-1)a[i]-=b;return a;}
tensor operator *= (tensor &a,const double &b){rep(i,0,(ll)a.size()-1)a[i]*=b;return a;}
tensor operator /= (tensor &a,const double &b){rep(i,0,(ll)a.size()-1)a[i]/=b;return a;}
tensor operator + (const tensor &a,const double b){tensor c=a;return c+=b;}
tensor operator - (const tensor &a,const double b){tensor c=a;return c-=b;}
tensor operator * (const tensor &a,const double b){tensor c=a;return c*=b;}
tensor operator / (const tensor &a,const double b){tensor c=a;return c/=b;}
tensor operator + (const double b,const tensor &a){tensor c=a;return c+=b;}
tensor operator - (const double b,const tensor &a){return tensor(a.size(),b)-a;}
tensor operator * (const double b,const tensor &a){tensor c=a;return c*=b;}
tensor operator / (const double b,const tensor &a){return tensor(a.size(),b)/a;}
tensor operator - (const tensor &a){tensor c=a;rep(i,0,(ll)c.size()-1)c[i]=-c[i];return c;}
double Dot(const tensor &a,const tensor &b){double ans=0;rep(i,0,(ll)a.size()-1)ans+=a[i]*b[i];return ans;}
istream &operator >>(istream &in,tensor &a){double x;in>>x;a.push_back(x);return in;}
ostream &operator <<(ostream &out,const tensor &a){
out<<'(';
rep(i,0,(ll)a.size()-1){
out<<a[i];
if(i+1!=(ll)a.size())out<<',';
}
out<<')';
return out;
}
class Sigmoid{//激活函数:Sigmoid函数
public:
double cal(double x){return 1.0/(1+exp(-x));}
double diff(double x){return cal(x)*(1-cal(x));}
}actFun;
class Neuron{//神经元
public:
ll input_index;//输入层标记 -1表示非输入层
ll act;//是否作用激活函数
double val;
tensor w;//权值向量
void show(){//输出神经元相关信息
if(input_index>=0)cout<<"x"<<input_index;
else for(auto &i:w)cout<<i<<" ";
}
};
typedef vector<Neuron> Layer;//层
void input_Layer(Layer &A,ll sz){//初始化输入层
A.resize(sz);
rep(i,0,sz-1)A[i].input_index=i,A[i].act=0;
}
void connect(Layer &A,Layer &B,ll sz){//初始化全连接层
B.resize(sz);
rep(i,0,sz-1){
B[i].input_index=-1;
B[i].w.resize(A.size());
B[i].act=(i!=sz-1);
}
}
void initialize_layer(Layer &A){//随机初始化权值
for(auto &e:A){
for(auto &x:e.w)x=(rand()-12345)*1.0/10000;
}
}
class Net{//神经网络
public:
vector<Layer> ly;//层
vector<vector<tensor>> dw;//梯度下降中的偏导数
vector<tensor> df;//临时变量
void build(vector<ll> num){//初始化神经网络
ly.resize(num.size());
input_Layer(ly[0],num[0]);
rep(i,1,(ll)num.size()-1)connect(ly[i-1],ly[i],num[i]);
for(auto &A:ly)initialize_layer(A);
dw.resize(num.size());
all(i,dw){
dw[i].resize(num[i]);
all(j,dw[i])dw[i][j].resize(i==0?1:num[i-1]);
}
df.resize(num.size());
all(i,df)df[i].resize(num[i]);
}
void show(){//输出神经网络所有神经元
all(i,ly){
cout<<" Layer: "<<i+1<<endl;
for(auto &e:ly[i]){
cout<<" ";
e.show();
cout<<endl;
}
}
}
double predict(const tensor &x){//预测
all(i,ly){
all(j,ly[i]){
if(i==0)ly[i][j].val=x[j];
else{
ly[i][j].val=0;
all(k,ly[i-1])ly[i][j].val+=ly[i][j].w[k]*ly[i-1][k].val;
if(ly[i][j].act)ly[i][j].val=actFun.cal(ly[i][j].val);
}
}
}
return ly[(ll)ly.size()-1][0].val;
}
void calculate_diff(ll I,ll J,ll K,const tensor &x,const double y){
rep(i,I,(ll)ly.size()-1){
all(j,ly[i]){
if(i==I)df[i][j]=j==J?ly[i-1][K].val:0;
else df[i][j]=Dot(ly[i][j].w,df[i-1]);
if(ly[i][j].act)df[i][j]*=actFun.diff(ly[i][j].val);
}
}
dw[I][J][K]=df[(ll)ly.size()-1][0];
}
void train(const tensor &x,const double y,double eta){//训练
double y_=predict(x);
all(i,dw)all(j,dw[i])all(k,dw[i][j])if(ly[i][j].input_index==-1)calculate_diff(i,j,k,x,y);
all(i,dw)all(j,dw[i])if(ly[i][j].input_index==-1)ly[i][j].w-=dw[i][j]*(eta*(y_-y));
}
}net;
//例:y=x0+x1
double test(tensor x){
return x[0]+x[1];
}
int main(){
//初始化神经网络
net.build({2,2,1});
//生成随机数据集
tensor x[1005];
rep(i,1,1000)x[i]={rand()*1.0/1000,rand()*1.0/1000};
//训练
rep(it,1,100000){
ll i=rand()%1000+1;
net.train(x[i],test(x[i]),0.0001);
}
//输出神经网络信息
net.show();
//手动测试a+b
double a,b;
while(cin>>a>>b){
cout<<net.predict({a,b})<<endl;
}
return 0;
}