0%

神经网络——数值分析问题的最后杀手锏

我刚接触神经网络的那会它还没有像现在那么火热,当时我对它的效果很不屑,因为它在小样本的时候效果很差,但是到了研究生阶段再次遇到它的时候我对它有了新的认识,包括其内部的算法,有时间的话会另开一文再议,本文为16年我在数学建模中对神经网络算法的理解。

简介

神经网络与之前的模拟退火,遗传算法并称为三大智能算法.但其与后两者的功能完全不同.他所解决的不是优化问题,而是类似于拟合,插值的问题.
虽然其算法理论复杂,但是由于在MATLAB中的易于使用,所以也是处理数值分析问题的最后杀手锏.

nnet

使用

神经网络只是一个一类算法的总称,下面我们演示其中一个最常见,也是最通用的一种—BP神经网络.
例1:使用神经网络做x^2+y^2的插值

  1. 确定自变量为p行q列矩阵,p指实验次数,q指自变量个数:

    1
    2
    3
    x1=[1:3:20]';
    x2=[1:3:20]';
    x=[x1,x2]
  2. 确定因变量为p行r列矩阵,p指实验次数,r指因变量个数:

    1
    y=x1.^2+x2.^2
  3. 拷贝EzBp.m文件到当前目录,调用函数EzBP(x,y):

    1
    [net,is,os]=EzBP(x,y);

    若弹出神经网络的控制台,如:

    nnet_train

    表明成功.

  4. 测试结果.net表示学习完毕的神经网络,is方便我们对测试数据归一化,os方便我们将神经网络的返回的结果反归一化,得到我们的结果.
    现在我们用(11,11)测试一下(正确结果应该是11^2+11^2=242).

    1
    2
    3
    4
    5
    6
    7
    testnum=[11,11]'
    %输入值归一化
    inputNum=mapminmax('apply',testNum,is);
    %放入神经网络,进行计算
    outputNum=net(inputNum);
    %将神经网络的值反归一化
    res=mapminmax('reverse',outputNum,os)

    结果(你的可能跟我不一样):

    1
    2
    res =
    245.5549

函数内部源码

整个函数不难,结合一下流程图自己就能看懂.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
function [net,ps,ts]=EzBP(P,T,x);
% input x个体初始权值和阀值
% input P样本输入(n line,1 col)
% input T样本输出(n line,1 col)

% output net BP神经网络
% output ps 输入值归一化矩阵
% output ts 输出值归一化矩阵

nntwarn off %关闭警告
P=P';
T=T';
%数据预处理--归一化处理
[P,ps]=mapminmax(P);
[T,ts]=mapminmax(T);
[pr,pc]=size(P);
[tr,tc]=size(T);

%设置隐藏神经元个数,一般设置2*inputNum+1
inputNum=pr;
outputNum=tr;
hiddenNum=2*inputNum+1;

%新建一个神经网络对象,这里采用tansig的激励算子,这个算子对非线性的插值计算效果较好
net=newff(minmax(P),[hiddenNum,outputNum],{'tansig','tansig'}); %隐含层 输出层

%设置神经网络训练的结束条件
net.trainParam.epochs=1e5;
net.trainParam.goal=1e-5;
net.trainParam.lr=0.05;
net.trainParam.show=10;

if nargin==3
net.trainParam.showwindow=false;
w1num=inputNum*hiddenNum;
w2num=outputNum*hiddenNum;
w1=x(1:w1num);
B1=x(w1num+1:w1num+hiddenNum);
w2=x(w1num+hiddenNum+1:w1num+hiddenNum+w2num);
B2=x(w1num+hiddenNum+w2num+1:w1num+hiddenNum+w2num+outputNum);
net.iw{1,1}=reshape(w1,hiddenNum,inputNum);
net.lw{2,1}=reshape(w2,outputNum,hiddenNum);
net.b{1}=reshape(B1,hiddenNum,1);
net.b{2}=reshape(B2,outputNum,1);
end
%训练
net=train(net,P,T);
end

优化技巧

神经网络的效果的好坏由(1)所选用的训练数据(2)神经元个数,(3)激励算子所决定.下面介绍一些简单的优化方法.

归一化,取典型值

这是最简单的一种,归一化在EzBP函数里已经默认提供,说一下去典型值的意思:

举个例子,我们要拟合y=2*x,给的训练数据是x=1:10,y=2*x.那我把1.5放进去,出来的值很可能就是3.0±0.1的值,很靠谱吧?
而我把100放进去,呵呵,那就不晓得是什么离谱的值了.现在知道什么叫典型的意思了吧.一般的,我们把每一变量的最大值和最低值放到网络中学习,而选用一部分中间的值作为验证.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function [train,test]=ChooseData(data)
feature=[];
for eachCow=data
[maxNum,maxPos]=max(eachCow);
[minNum,minPos]=min(eachCow);
feature=union(feature,maxPos);
feature=union(feature,minPos);
end
dataNum=size(data,1);
rand_=randperm(dataNum);
needToChoose=floor(dataNum*0.9)
train=union(feature,rand_(1:needToChoose));
test=rand_(needToChoose:end);
train=data(train,:);
test=data(test,:);
end

交叉验证

交叉验证是在训练数据比较少的情况下,增加训练数据的好方法.这里也给出简单的操作函数CvBP(x,y[,n]).n为可选参数,n越大训练数据会变得更多.但也不意味着训练结果会更好(过分学习的情况).

demo.m:

1
2
3
4
5
6
7
8
9
10
11
12
x1=[1:3:20]';
x2=[1:3:20]';
x=[x1,x2];

y=x1.^2+x2.^2;

net=CvBP(x,y,10);

testNum=[11,11]';

%放入神经网络,进行计算
outputNum=net(testNum)

CvBP.m:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
function perfectNet=CvBP(P,T,num)
pdata=P';
tdata=T';

pTrain=P;
tTrain=T;

n=10;
%% 交叉验证
mse_max=10e30;
desiredInput=[];
desiredOutput=[];

if nargin==2
num=5;
end
indices = crossvalind('Kfold',length(pTrain),num);
for i = 1:num
perfp=[];
disp(['The result of ',num2str(i),'/',num2str(num)])
test = (indices == i); trainA = ~test;
pCvTrain=pTrain(trainA,:);
tCvTrain=tTrain(trainA,:);
pCvTest=pTrain(test,:);
tCvTest=tTrain(test,:);
pCvTrain=pCvTrain';
tCvTrain=tCvTrain';
pCvTest= pCvTest';
tCvTest= tCvTest';

nett=feedforwardnet(n);
% net.trainParam.epochs=100000;
% net.trainParam.show=200;
% net.trainParam.goal=1e-4;
% net=train(net,desired_input,desired_output);
nett=train(nett,pCvTrain,tCvTrain);
testOut=nett(pCvTest);
perf=perform(nett,testOut,tCvTest);
if mse_max>perf
perfectNet=nett;
mse_max=perf;
desiredInput=pCvTrain;
desiredOutput=tCvTrain;
end
end
end

结果:

1
2
outputNum =
245.5549

与遗传算法结合

还记得以前介绍的遗传算法嘛?注意到EzBP的第三个参数了嘛?没错,这就是给遗传算法准备的.其实,在初始化神经网络时,每个神经元都有一个初始的值[-0.5,0.5],如果用遗传算法对这个进行优化,就会对结果造成影响.(实际效果不好).

demo.m:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
x1=[1:3:20]';
x2=[1:3:20]';
x=[x1,x2];

y=x1.^2+x2.^2;

[net,is,os]=GABP(x,y);

testNum=[11,11]';

%输入值归一化
inputNum=mapminmax('apply',testNum,is);
%放入神经网络,进行计算
outputNum=net(inputNum);
%将神经网络的值反归一化
res=mapminmax('reverse',outputNum,os)

GABP.m:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function [net,ps,ts,perf]=GABP(P,T)
inputNum=size(P,2);
outputNum=size(T,2);
P_T=[P T];
[train,test]=ChooseData(P_T);
trainX=train(:,1:inputNum);
trainY=train(:,inputNum+1:end);
testX=test(:,1:inputNum);
testY=test(:,inputNum+1:end);
hiddenNum=2*inputNum+1;
w1num=inputNum*hiddenNum;
w2num=outputNum*hiddenNum;
N=w1num+hiddenNum+w2num+outputNum;

bound=repmat([-0.5 0.5],N,1);
[best,x]=EzGA(bound,@fun,20,{trainX,trainY,testX,testY});
[net,ps,ts,perf]=EzBP(trainX,trainY,x,testX,testY);
end

总结

神经网络算法是一套历史悠久也比较成熟的机器学习算法.是对付数值分析问题的最后杀手锏.我们一般比较常用的一种神经网络是BP神经网络.本文也以BP神经网络的使用及其优化进行了详细讲解,同时提供了可调用的函数原型.
但想要得到较好效果,还建议学习其他的一些神经网络,了解其各自优势.但由于这一类算法普遍稳定性较差,如果不是万不得已,或是效果超群,不建议使用.同时,若要使用此算法,请务必抽出一部分样本用来检验(严禁拿结果直接来学习,然后返回结果!!)

其算法优劣分析如下(笔者认为):
优点:

  1. 所适用的类型广泛.如果优化的好,几乎可以解决比赛中遇到的各种问题.e.g.插值,拟合,聚类.
  2. 对非线性数据的拟合效果优异.实在没看出数据有啥规律,神经网络至少算是一种解决方法.

缺点:

  1. 需要大量的样本数据

源程序: https://github.com/Anemone95/matlab-nnet