第三部分 第一个模型:一个隐层结构的传统神经网络
这一部分让我们从代码开始:
# add to kfkd.py
from lasagne import layers
from lasagne.updates import nesterov_momentum
from nolearn.lasagne import NeuralNet
net1 = NeuralNet(
layers=[ # three layers: one hidden layer
(‘input‘, layers.InputLayer),
(‘hidden‘, layers.DenseLayer),
(‘output‘, layers.DenseLayer),
],
# layer parameters:
input_shape=(None, 9216), # 96x96 input pixels per batch
hidden_num_units=100, # number of units in hidden layer
output_nonlinearity=None, # output layer uses identity function
output_num_units=30, # 30 target values
# optimization method:
update=nesterov_momentum,
update_learning_rate=0.01,
update_momentum=0.9,
regression=True, # flag to indicate we‘re dealing with regression problem
max_epochs=400, # we want to train this many epochs
verbose=1,
)
X, y = load()
net1.fit(X, y)
在这个网络中,我们使用了一些参数来初始化,让我们从头梳理:
layers=[ # three layers: one hidden layer
(‘input‘, layers.InputLayer),
(‘hidden‘, layers.DenseLayer),
(‘output‘, layers.DenseLayer),
],
# layer parameters:
input_shape=(None, 9216), # 96x96 input pixels per batch
hidden_num_units=100, # number of units in hidden layer
output_nonlinearity=None, # output layer uses identity function
output_num_units=30, # 30 target values
这里我们定义了input层,hidden层,还有output层。在layers参数下,我们定义了每一层的类型,以及层与层之间的顺序。input_shape , hidden_num_units ,output_nonlinearity ,output_num_units 分别指定了每一层的参数,这些参数通过前缀指定相应的层,如input_shape制定了input层。之所以采用这种稍显奇怪的方式来制定参数,是为了更好的兼容scikit-learn从而更好的利用其优势。
我们将输入的第一个维度设置为None,相当于设定了可变的batch size。
将输出层非线性显示的设置为None。也就是,输出层单元的激活方式是线性的,即隐藏层激活值的线性组合。
Denselayer的默认非线性特性曲线是rectifier,即max(0,x),当先最流行的激活方程。通过不明确的制定隐藏层的激活方程,也就制定了rectifier作为隐层的激活方程。
神经网络的权重初始化为某一个区间的均匀分布值,Lasagne已经选好了这个区间。【区间选取参考】
还有一些没有介绍的参数,这些参数都以update开头用来表示更新方程(或最优化方法)的参数。更新方程将会在每个batch后更新神经网络的权重。我们使用nesterov_momentum梯度下降这种最优化方法来完成这个工作。Lasagne还为我们实现了一些
其他的方法诸如adagrad和rmsprop。我们选择nesterov_momentum是因为这种方法已经在大量的问题上被证明有效。
# optimization method:
update=nesterov_momentum,
update_learning_rate=0.01,
update_momentum=0.9,
update_learning_rate定义了梯度下降更新权重的步长。我们稍后讨论学习率和momentum参数,现在的话,这种健全的默认值已经足够了。
上图是不同的最优化方法的对比(animation by Alec Radford)。星标位置为全局最优值。注意不添加“势”的随机梯度下降是收敛最慢的,我们在教程中从头到尾都是用Nesterov加速过的梯度下降。
最后两行加载了数据,然后用数据训练了我们的第一个神经网络。
X, y = load()
net1.fit(X, y)
运行这两行会输出一个表格,每次完成一个epoch就输出一行。每一行里,我们可以看到当前的训练损失和验证损失(最小二乘损失),以及两者的比率。NeuroNet将会自动把输入数据X分成训练集和测试集,用20%的数据作验证。(比率可以通过参数eval_size=0.2调整)
$ python kfkd.py
...
InputLayer (None, 9216) produces 9216 outputs
DenseLayer (None, 100) produces 100 outputs
DenseLayer (None, 30) produces 30 outputs
Epoch | Train loss | Valid loss | Train / Val
--------|--------------|--------------|----------------
1 | 0.105418 | 0.031085 | 3.391261
2 | 0.020353 | 0.019294 | 1.054894
3 | 0.016118 | 0.016918 | 0.952734
4 | 0.014187 | 0.015550 | 0.912363
5 | 0.013329 | 0.014791 | 0.901199
...
200 | 0.003250 | 0.004150 | 0.783282
201 | 0.003242 | 0.004141 | 0.782850
202 | 0.003234 | 0.004133 | 0.782305
203 | 0.003225 | 0.004126 | 0.781746
204 | 0.003217 | 0.004118 | 0.781239
205 | 0.003209 | 0.004110 | 0.780738
...
395 | 0.002259 | 0.003269 | 0.690925
396 | 0.002256 | 0.003264 | 0.691164
397 | 0.002254 | 0.003264 | 0.690485
398 | 0.002249 | 0.003259 | 0.690303
399 | 0.002247 | 0.003260 | 0.689252
400 | 0.002244 | 0.003255 | 0.689606
在相对较快的GPU上训练,我们能够在1分钟之内完成400个epoch的训练。注意测试损失会一直减小。
现在我们有了一个很好的结果了么?我们看到测试误差是0.0032,和竞赛基准比试一下。注意我们将目标除以了48以将其缩放到-1到1之间,也就是说,要是想计算均方误差和排行榜的结果比较,必须把我们上面得到的0.003255还原到原来的尺度。
>>> import numpy as np
>>> np.sqrt(0.003255) * 48
2.7385251505144153
这个值应该可以代表我们的成绩了。当然,这得假设测试集合的数据和训练集合的数据符合相同的分布,但事实却并非如此。
第四部分 测试刚刚的网络
我们刚刚训练的net1对象已经保存了训练时打印在控制台桌面中的记录,我们可以获取这个记录通过train_history_相关属性,让我们画出这两个曲线。
train_loss = np.array([i["train_loss"] for i in net1.train_history_])
valid_loss = np.array([i["valid_loss"] for i in net1.train_history_])
pyplot.plot(train_loss, linewidth=3, label="train")
pyplot.plot(valid_loss, linewidth=3, label="valid")
pyplot.grid()
pyplot.legend()
pyplot.xlabel("epoch")
pyplot.ylabel("loss")
pyplot.ylim(1e-3, 1e-2)
pyplot.yscale("log")
pyplot.show()
我们能够看到我们的网络过拟合了,但是结果还不赖。事实上,我们找不到验证错误开始上升的点,所以那种通常用来避免过拟合的early stopping方法在现在还没有什么用处。注意我们没有采用任何正则化手段,除了选择节点比较少的隐层——这可以让过拟合保持在可控范围内。
那么网络的预测结果是什么样的呢?让我们选择一些样例来看一看。
def plot_sample(x, y, axis):
img = x.reshape(96, 96)
axis.imshow(img, cmap=‘gray‘)
axis.scatter(y[0::2] * 48 + 48, y[1::2] * 48 + 48, marker=‘x‘, s=10)
X, _ = load(test=True)
y_pred = net1.predict(X)
fig = pyplot.figure(figsize=(6, 6))
fig.subplots_adjust(
left=0, right=1, bottom=0, top=1, hspace=0.05, wspace=0.05)
for i in range(16):
ax = fig.add_subplot(4, 4, i + 1, xticks=[], yticks=[])
plot_sample(X[i], y_pred[i], ax)
pyplot.show()
第一个模型预测的结果(从测试集抽出了16个样例)
预测结果看起来还不错,但是有点时候还是有一点偏。让我们试着做的更好一些。
To be continue…