隐藏单元的设计原则(代码篇)|机器学习你会遇到的“坑”

隐藏单元的设计原则(代码篇)|机器学习你会遇到的“坑”

在上一节的代码篇中,我们已经对keras的功能做了简单的介绍,我们已经可以很快的搭建出一个简单前馈神经网络,但上一节的目的主要是让大家更快的熟悉keras的基本功能,并未将模型应用到具体的数据上,我们本文会利用统计学习的一些数据,将神经网络与统计学习的一些模型做简单粗糙的对比。

首先,我们可以将神经网络应用在《过拟合问题》中使用的糖尿病数据上,它是具备10个特征的回归数据,我们可以用简单的线性回归,ridge regression, LASSO,bayesian regression,SVR等模型去学习,很难确定这些方法的效果最好可以达到多少,因为特征提取加上学习器内部的参数,我们会面临非常庞大的组合问题,所以要准确对比是较为繁琐的一项工程,在这里我们只做粗略的对比,我们引用《如何进行特征选择》一节中提到的包裹法的结果:

from sklearn.feature_selection import RFECV

import seaborn as sns

import matplotlib.pyplot as plt

import numpy as np

from sklearn.linear_model import LinearRegression

from sklearn.model_selection import KFold

data=datasets.load_diabetes()

X=data['data']

y=data['target']

lr=LinearRegression()

scorer='neg_mean_squared_error'

rfecv = RFECV(estimator=lr, step=1, cv=KFold(5),

scoring=scorer)

rfecv.fit(X, y)

sns.set(style='darkgrid')

plt.xlabel("Number of features selected")

plt.ylabel("Cross validation score (MSE)")

plt.plot(range(1, len(rfecv.grid_scores_) + 1), -rfecv.grid_scores_,'r',label="Optimal number of features : %d" % rfecv.n_features_)

plt.legend()

plt.show()

隐藏单元的设计原则(代码篇)|机器学习你会遇到的“坑”

如图,包裹法的学习器为简单线性回归,在对数据进行递归剔除的时候,发现特征为6的时候效果最好,MSE为2946.88。

我们为此类回归数据构建简单的一个包含两个隐层的神经网络,并将其结构打印出来:

from keras import initializers

from keras.models import Model

from keras.layers import Input,Dense,Activation

rn=initializers.RandomNormal(mean=0,stddev=1,seed=42)

inputs=Input(shape=(10,))

x=Dense(128,kernel_initializer=rn)(inputs)

x=Activation('sigmoid')(x)

x=Dense(32,kernel_initializer=rn)(x)

x=Activation('sigmoid')(x)

out=Dense(1,kernel_initializer=rn)(x)

out=Activation('linear')(out)

model=Model(inputs=inputs,outputs=out)

print(model.summary())

______________________________________________________

Layer (type) Output Shape Param #

======================================================

input_25 (InputLayer) (None, 10) 0

______________________________________________________

dense_56 (Dense) (None, 128) 1408

______________________________________________________

activation_52 (Activation) (None, 128) 0

______________________________________________________

dense_57 (Dense) (None, 32) 4128

______________________________________________________

activation_53 (Activation) (None, 32) 0

______________________________________________________

dense_58 (Dense) (None, 1) 33

______________________________________________________

activation_54 (Activation) (None, 1) 0

======================================================

Total params: 5,569

Trainable params: 5,569

Non-trainable params: 0

______________________________________________________

根据打印结果,一共有5569个参数,这里面涉及到参数数量的计算方法,熟悉神经网络结构的前提下,我们可以飞快的计算出,10*128+128+128*32+32+32*1+1=5569。

其中,我们在隐层使用了sigmoid函数作为激活函数,第一层有128个神经元,第二层有32个神经元,神经元数目的设计并没有什么特殊的道理。输出层则是一个神经元,使用了线性单元作为激活函数,本质上就是未采用激活函数。接下来,我们开始编译并训练模型:

from keras import losses,optimizers,metrics

loss=losses.mean_squared_error

performance=metrics.mean_squared_error

optimizer=optimizers.Adagrad()

model.compile(loss=loss,optimizer=optimizer,metrics=[performance])

his=model.fit(X,Y,batch_size=128,epochs=50,verbose=1,validation_split=0.3,shuffle=True)

我们使用MSE作为Loss和性能度量,Adagrad作为优化算法,同时:

  • 设置batch为128,代表着每个iteration利用128个样本来更新梯度;

  • 设置validation_split为0.3,代表着我们在训练开始前就划分出了0.3的样本作为验证集;

  • 设置epochs为50,表示我们会对整个数据集进行50次迭代;

  • 设置verbose为1,表示显示更新日志,将会出现:

Train on 309 samples, validate on 133 samples

Epoch 1/5

309/309 [==============================] - 0s 1ms/step - loss: 27615.7395 - mean_squared_error: 27615.7395 - val_loss: 29259.6711 - val_mean_squared_error: 29259.6711

Epoch 2/5

309/309 [==============================] - 0s 38us/step - loss: 26909.9610 - mean_squared_error: 26909.9610 - val_loss: 28663.5950 - val_mean_squared_error: 28663.5950

Epoch 3/5

309/309 [==============================] - 0s 30us/step - loss: 26295.9708 - mean_squared_error: 26295.9708 - val_loss: 27765.8976 - val_mean_squared_error: 27765.8976

Epoch 4/5

309/309 [==============================] - 0s 35us/step - loss: 25396.5153 - mean_squared_error: 25396.5153 - val_loss: 26754.5670 - val_mean_squared_error: 26754.5670

Epoch 5/5

309/309 [==============================] - 0s 42us/step - loss: 24493.1804 - mean_squared_error: 24493.1804 - val_loss: 25994.9439 - val_mean_squared_error: 25994.9439

.......

这里,只截取前五次的结果,因为数据量少,每个epochs是瞬间完成的,我们往往看不到进度条,但每一步我们都会获得训练集上的Loss和度量以及测试集上的Loss和度量。

我们将每一步的信息保存到了his变量中,就可以知道训练过程中Loss和性能度量的变化,在这个例子中,Loss和性能度量是一样的:

import seaborn as sns

import matplotlib.pyplot as plt

sns.set(style='darkgrid')

plt.plot(range(50),his.history['val_loss'],label='val_loss')

plt.plot(range(50),his.history['loss'],label='trian_loss')

plt.title('ANN Loss')

plt.legend()

plt.show()

隐藏单元的设计原则(代码篇)|机器学习你会遇到的“坑”

如图,在50次对整体数据的迭代中,可以发现训练集和测试集的Loss下降都非常快。

特别需要注意的是,从图中还可以看出,这样的Loss好像并没有收敛的趋势,这可能代表着训练的失败。我们一方面可以设置epochs来观察其后续是否会收敛,但这样做很可能是徒劳的,因为这个神经网络参数并不多,也不深,优化起来是非常容易的,没有很快的收敛就是失败。另一方面,根据《理解梯度下降》中的知识,我们需要改变我们的优化算法,使用更简单的添加动量的随机梯度下降,而非自适应学习率算法,因为Adagrad的缺点就是很可能在一开始学习率降得太低,使得优化缓慢:

.......#其余的保持不变

optimizer=optimizers.SGD()

model.compile(loss=loss,optimizer=optimizer,metrics=[performance])

his=model.fit(X,Y,batch_size=128,epochs=50,verbose=1,validation_split=0.3,shuffle=True)

sns.set(style='darkgrid')

plt.plot(range(50),his.history['val_loss'],label='val_loss')

plt.plot(range(50),his.history['loss'],label='trian_loss')

plt.title('ANN Loss')

plt.legend()

plt.show()

隐藏单元的设计原则(代码篇)|机器学习你会遇到的“坑”

如图,简单的添加动量的SGD在第2次对整体数据做迭代的时候就把Loss降低到了一个很低的水平。

此时,让我们来粗略的对比下两者,在包裹法下的简单线性回归MSE是2946,在这个神经网络中则表现为5000多,这是非常严重的一个问题。鉴于神经网络的参数太多,我们可以很容易想到其发生了过拟合(相比于线性回归),我们可以采取的策略有很多,但在这里为了不引入更多的问题,我们去掉其中含有128个神经元的隐层来达到削减神经网络规模的目的:

inputs=Input(shape=(10,))

x=Dense(32,kernel_initializer=rn)(inputs)

x=Activation('sigmoid')(x)

out=Dense(1,kernel_initializer=rn)(x)

out=Activation('linear')(out)

model=Model(inputs=inputs,outputs=out)

这样,神经网络就只包含了一个隐层,我们继续上述的步骤就可以得到:

隐藏单元的设计原则(代码篇)|机器学习你会遇到的“坑”

如图,大概在30个epochs左右,我们就得到了与线性回归较为接近的MSE。

对于本文采用的数据,似乎神经网络不会超过统计学习给出的结果,哪怕是最简单的线性回归。实则不然,一方面,如果我们仔细的调节参数,神经网络应该会比简单线性回归更加出色,但是对于神经网络,调参是非常困难的,光是层数和神经元数就极难确定,更不用提改变激活函数的种类,我们下一个代码篇中展示不同的激活函数对性能的影响。另一方面,统计学习显示出了其不可替代的作用,它在很多任务中采取更少的资源,花费更少的时间就能得到一个较为满意的结果。

隐藏单元的设计原则(代码篇)|机器学习你会遇到的“坑”

读芯君开扒

课堂TIPS

• 之所以采取回归数据来做示例,原因之一是我曾经收到过一个问题:为什么神经网络不做回归?是不能做回归么?我想这篇文章可以作为一个回答,神经网络当然可以做回归,只是在现在的比赛中大部分都是识别问题,也就是分类问题,对神经网络不了解的人很容易产生错误的印象。

• 我在这里使用了linear形式的输出单元,其实也就是将神经元的输入当作输出,输出单元的设计将在后续的课程中详细介绍,读者可以先行简单记住,回归问题中输出单元一般都采用linear。

• 防止神经网络过拟合的办法有很多,比如Dropout,添加正则化,参数共享,我们将会在后续的课程介绍,此处采取了减少层数的办法,在实际中这样的办法使用的并不多。

隐藏单元的设计原则(代码篇)|机器学习你会遇到的“坑”

作者:唐僧不用海飞丝

如需转载,请后台留言,遵守转载规范

相关推荐