DeepLearning-BadNeuralNetwork

概述

  在今年参加的MCM/ICM的时候,需要利用已知样本来预测一条曲线,一开始打算用神经网络来完成,但是在利用神经网络进行预测的时候,出现了一个问题,就是产生的曲线是一条水平线,完全没有拟合上。当时解决的方法是将输入样本进行了归一化,然后输出结果时候再进行反归一化,这样预处理之后,问题得以解决。由于一直也没有找到归一化可以解决问题的原因,现在我对于这个问题产生了强烈的兴趣,想把这个问题想清楚。

问题复现

  当时想预测的是一条不规则的曲线,使用95%的样本点作为训练集,选择5%的样本点作为测试集,当时选择的LSTM模型进行的拟合,具体的网络模型实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class LSTMpred(nn.Module):

def __init__(self,input_size,hidden_dim):
super(LSTMpred,self).__init__()
self.input_dim = input_size
self.hidden_dim = hidden_dim
self.lstm = nn.LSTM(input_size,hidden_dim)
self.hidden2out = nn.Linear(hidden_dim,1)
self.hidden = self.init_hidden()

def init_hidden(self):
return (Variable(torch.zeros(1, 1, self.hidden_dim)),
Variable(torch.zeros(1, 1, self.hidden_dim)))

def forward(self,seq):
lstm_out, self.hidden = self.lstm(
seq.view(len(seq), 1, -1), self.hidden)
outdat = self.hidden2out(lstm_out.view(len(seq),-1))
return outdat

  但是实际的训练结果却很差,完全是一条直线。结果如下:
photo1
  一开始我判断是LSTM模型复杂,样本点过少的原因,所以迁移到Matlab环境下重新定义了一个简单的网络,并加大了样本数目,进行网络训练,结果还是与一开始的情况类似,只是水平线的位置稍有改变,在进行归一化之后,问题才有了改善。
  现在利用sklearn.neural_network来把这个问题复现一下:
  目标函数是$y=2*(x-1)^2+2$,为了减低训练难度,直接采用函数上的点作为训练样本,先不进行归一化处理,选择样本点数目为700,即x=range(0,700),原始网络定义如下:

1
clf = MLPRegressor(solver='lbfgs', alpha=1e-5,hidden_layer_sizes=(20, 5, 2), random_state=1)

  果然这个问题就出现了,图中第一个图为原始样本点的输出,第二张图为训练集和测试集预测的结果,第三章图为两者的对比,可以明显的发现,得到的网络输出结果为一条水平线,网络并没有达到预期结果。
              photo2

实验数据

数据

  针对这种训练不好的情况,我进行了一些测试,因为参数初始化具有一定的随机性,所以每个模型进行了100次输出,测试模型即此情况出现的次数统计如下,小标中分数为异常情况出现的次数:

test1-30/100

soler: ‘lbfgs’ \

func: ‘relu’ \

alpha: ‘1e-5’ \

hidder_layer:(20,5,2) \

random_state: 1 \

num of sample:700 \

test2-40/100

soler: ‘lbfgs’ \

func: ‘relu’ \

alpha: ‘1e-5’ \

hidder_layer:(20,5,2) \

random_state: 1 \

num of sample:7000 \

test3-100/100

soler: ‘sgd’ \

func: ‘relu’ \

alpha: ‘1e-5’ \

hidder_layer:(20,5,2) \

random_state: 1 \

learning_rate_init = 0.1 \

num of sample:700 \

test4-100/100

soler: ‘sgd’ \

func: ‘relu’ \

alpha: ‘1e-5’ \

hidder_layer:(20,5,2) \

random_state: 1 \

learning_rate_init = 0.001 \

num of sample:700 \

test5-100/100

soler: ‘sgd’ \

func: ‘relu’ \

alpha: ‘1e-5’ \

hidder_layer:(20,5,2) \

random_state: 1 \

learning_rate_init = 0.001 \

num of sample:7000 \

test6-100/100

soler: ‘sgd’ \

func: ‘relu’ \

alpha: ‘1e-5’ \

hidder_layer:(20,5,2) \

random_state: 1 \

learning_rate_init = 0.1 \

num of sample:7000 \

test7-100/100

soler: ‘sgd’ \

func: ‘relu’ \

alpha: ‘1e-5’ \

hidder_layer:(20,5,2) \

random_state: 1 \

learning_rate = constant \

num of sample:7000 \

test8-41/100

soler: ‘sgd’ \

func: ‘relu’ \

alpha: ‘1e-5’ \

hidder_layer:(20,5,2) \

random_state: 1 \

learning_rate_init = 0.001 \

num of sample:7000 \

test9-32/100

soler: ‘lbfgs’ \

func: ‘relu’ \

alpha: ‘1e-5’ \

hidder_layer:(10,2) \

random_state: 1 \

num of sample:700 \

test10-34/100

soler: ‘lbfgs’ \

func: ‘relu’ \

alpha: ‘1e-5’ \

hidder_layer:(10,2) \

random_state: 1 \

num of sample:7000 \

test11-100/100

soler: ‘lbfgs’ \

func: ‘tanh’ \

alpha: ‘1e-5’ \

hidder_layer:(10,2) \

random_state: 1 \

num of sample:700 \

test12-0/100(liner)

soler: ‘lbfgs’ \

func: ‘identity’ \

alpha: ‘1e-5’ \

hidder_layer:(10,2) \

random_state: 1 \

num of sample:700 \

test13-0/100(liner)

soler: ‘lbfgs’ \

func: ‘identity’ \

alpha: ‘1e-5’ \

hidder_layer:(10,5,2) \

random_state: 1 \

num of sample:700 \

test14-100/100

soler: ‘’ \

func: ‘Sigmoid’ \

alpha: ‘1e-5’ \

hidder_layer:(10,5,2) \

random_state: 1 \

num of sample:700 \

可视化

  从test1中选择训练结果较好的情况展示如下:
                photo3
  从test12中选择线性结果展示如下:
                photo3

结果分析

结果描述

  从结果上来看,对于这个问题来说,影响结果的主要因素是优化方法的选择以及激活函数的选择。
  当选择的优化方法为Newton方法的时候,训练结果较好的概率较大;当选择的为梯度下降方法的时候,无论学习率为0.001还是0.1,训练的结果基本都为异常情况,效果不理想。对于激活函数来说,当选择的为’relu’时,训练效果最佳,当选择为’tanh’或’Sigmoid’时,异常情况出现最多,当选择为‘identity’时,训练的结果均为线性模型。

结果分析

  从优化方法的角度来说,Newton方法的确比梯度下降方法收敛快,所以使用Newton方法效果较好符合预期。
  首先需要说明的是,在sklearn官网文档中说明,‘activation’参数设置的是隐层感知机的激活函数,而非输出层的激活函数。从激活函数的角度来说,因为’identity’函数即为$y=x$,所以其应用到多层感知器中,感知器只学习到了线性关系,并且输入只有一个,所以最后的模型为线性模型。而’tanh’或’Sigmoid’使用的使用情况应该为二分类输出,在Ng的课中也提到过,在二分类中使用‘Sigmoid’,而在多分类中,输出会使用‘softmax’。需要注意的是,即使在二分类中,隐层感知机一般也会采用’relu’,所以中间层采用这两个函数训练结果不理想也是大概率事件。而使用’relu’函数作为隐层感知机的激活函数基本是比较好的选择,但是也只有部分实验训练出较好的结果。
  而MLPRegressor类选择的输出层的激活函数为’identity’。官方描述如下:

Class MLPRegressor implements a multi-layer perceptron (MLP) that trains using backpropagation with no activation function in the output layer, which can also be seen as using the identity function as activation function. Therefore, it uses the square error as the loss function, and the output is a set of continuous values.

  在隐层激活函数选择为’relu’的时候,观察输出为水平线的情况,可以发现水平线一般都与曲线交于曲线中间处,显然这个位置的损失函数虽然不是最优解,但却比水平线在其他位置的损失函数要小,所以这个位置应该是一个局部最优解的情况。造成这种情况发生的原因应该是初始点的位置没有选择恰当。
  综合以上分析,我认为输出结果为水平线的异常情况出现的原因有两个,第一是由于没有为隐层选择恰当的激活函数,所以没有有效的学习到样本;第二是由于神经网络存在局部最优解的或者鞍点的情况,初始点选择不理想的时候,使得Newton方法或者梯度下降方法没法继续有效迭代,陷入局部最优解,导致异常。

小结

  通过这次试验发现,想训练好一个用于拟合曲线的神经网络也并非想象中的那么容易,各项超参数的选择以及网络单元的选择也应该仔细考虑。最近在学习一门深度学习的课程,老师也提到现在一般不会有这样的网络去进行函数的拟合,较小样本的神经网络没有太大的意义。
  其实本来想再推导下损失函数的表达式,这样对于第二个原因会更有说服力,这个坑日后来填。