本文将介绍如何使用 Keras + Tensorflow 创建卷积神经网络,并对其进行训练,以使得车辆保持在两条白线之间。
2017 年 2 月 2 日更新 - 感谢 Hacker News 的评论,我更新了这个文档,使用更多的机器学习最佳实践。
这里是一个树莓派(Raspberry Pi)控制遥控车,使用自动驾驶仪在本文档跟随驱动之间的线路。 看到 Donkey 资料库的指示,建立自己的车。
import os
import urllib.request
import pickle
%matplotlib inline
import matplotlib
from matplotlib.pyplot import imshow
数据集由 ~7900 个图像和手动开车时收集的转向角组成。大约三分之二的图像与线之间的汽车。另外三分之一的车开始偏离航线,并且驶回线路之间。
#downlaod driving data (450Mb)
data_url = 'https://s3.amazonaws.com/donkey_resources/indoor_lanes.pkl'
file_path, headers = urllib.request.urlretrieve(data_url)
print(file_path)
路径是:
/tmp/tmpjjuhirpf
数据集由 2 个 pickled 数组组成。X 是图像阵列,Y 是相应转向角度的阵列。
#extract data
with open(file_path, 'rb') as f:
X, Y = pickle.load(f)
print('X.shape: ', X.shape)
print('Y.shape: ', Y.shape)
imshow(X[0])
结果:
X.shape: (7892, 120, 160, 3)
Y.shape: (7892,)
在这里,我们将洗牌(shuffle)我们的数据,并将数据分成三部分。训练数据将用于训练我们的驾驶模型,使用验证数据避免过度拟合模型,测试数据用于测试我们的模型是否学到了什么。
import numpy as np
#shuffle both X and Y the same way
def unison_shuffled_copies(X, Y):
assert len(X) == len(Y)
p = np.random.permutation(len(X))
return X[p], Y[p]
shuffled_X, shuffled_Y = unison_shuffled_copies(X,Y)
len(shuffled_X)
输出:7892
test_cutoff = int(len(X) * .8) # 80% of data used for training
val_cutoff = test_cutoff + int(len(X) * .1) # 10% of data used for validation and test data
train_X, train_Y = shuffled_X[:test_cutoff], shuffled_Y[:test_cutoff]
val_X, val_Y = shuffled_X[test_cutoff:val_cutoff], shuffled_Y[test_cutoff:val_cutoff]
test_X, test_Y = shuffled_X[val_cutoff:], shuffled_Y[val_cutoff:]
len(train_X) + len(val_X) + len(test_X)
输出:7892
为了加倍我们的训练数据并防止转向偏差,我们翻转每个图像和转向角并将其添加到数据集中。还有其他的方法来增加使用翻译和假阴影驾驶数据,但我没有使用这些自动驾驶仪。
X_flipped = np.array([np.fliplr(i) for i in train_X])
Y_flipped = np.array([-i for i in train_Y])
train_X = np.concatenate([train_X, X_flipped])
train_Y = np.concatenate([train_Y, Y_flipped])
len(train_X)
Out[7]:
结果:12626
这种驾驶模式将是一个端到端的神经网络,接受图像阵列作为输入,并输出-90(左)和90(右)之间的转向角。 要做到这一点,我们将使用一个完全连接图层的3层卷积网络。该模型基于 Otavio 的 Carputer,但不产生油门值输出,不使用过去的转向值作为模型的输入,并且使用较少的卷积层。
from keras.models import Model, load_model
from keras.layers import Input, Convolution2D, MaxPooling2D, Activation, Dropout, Flatten, Dense
使用 TensorFlow 后端。
img_in = Input(shape=(120, 160, 3), name='img_in')
angle_in = Input(shape=(1,), name='angle_in')
x = Convolution2D(8, 3, 3)(img_in)
x = Activation('relu')(x)
x = MaxPooling2D(pool_size=(2, 2))(x)
x = Convolution2D(16, 3, 3)(x)
x = Activation('relu')(x)
x = MaxPooling2D(pool_size=(2, 2))(x)
x = Convolution2D(32, 3, 3)(x)
x = Activation('relu')(x)
x = MaxPooling2D(pool_size=(2, 2))(x)
merged = Flatten()(x)
x = Dense(256)(merged)
x = Activation('linear')(x)
x = Dropout(.2)(x)
angle_out = Dense(1, name='angle_out')(x)
model = Model(input=[img_in], output=[angle_out])
model.compile(optimizer='adam', loss='mean_squared_error')
model.summary()
输出:
____________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
====================================================================================================
img_in (InputLayer) (None, 120, 160, 3) 0
____________________________________________________________________________________________________
convolution2d_1 (Convolution2D) (None, 118, 158, 8) 224 img_in[0][0]
____________________________________________________________________________________________________
activation_1 (Activation) (None, 118, 158, 8) 0 convolution2d_1[0][0]
____________________________________________________________________________________________________
maxpooling2d_1 (MaxPooling2D) (None, 59, 79, 8) 0 activation_1[0][0]
____________________________________________________________________________________________________
convolution2d_2 (Convolution2D) (None, 57, 77, 16) 1168 maxpooling2d_1[0][0]
____________________________________________________________________________________________________
activation_2 (Activation) (None, 57, 77, 16) 0 convolution2d_2[0][0]
____________________________________________________________________________________________________
maxpooling2d_2 (MaxPooling2D) (None, 28, 38, 16) 0 activation_2[0][0]
____________________________________________________________________________________________________
convolution2d_3 (Convolution2D) (None, 26, 36, 32) 4640 maxpooling2d_2[0][0]
____________________________________________________________________________________________________
activation_3 (Activation) (None, 26, 36, 32) 0 convolution2d_3[0][0]
____________________________________________________________________________________________________
maxpooling2d_3 (MaxPooling2D) (None, 13, 18, 32) 0 activation_3[0][0]
____________________________________________________________________________________________________
flatten_1 (Flatten) (None, 7488) 0 maxpooling2d_3[0][0]
____________________________________________________________________________________________________
dense_1 (Dense) (None, 256) 1917184 flatten_1[0][0]
____________________________________________________________________________________________________
activation_4 (Activation) (None, 256) 0 dense_1[0][0]
____________________________________________________________________________________________________
dropout_1 (Dropout) (None, 256) 0 activation_4[0][0]
____________________________________________________________________________________________________
angle_out (Dense) (None, 1) 257 dropout_1[0][0]
====================================================================================================
Total params: 1,923,473
Trainable params: 1,923,473
Non-trainable params: 0
____________________________________________________________________________________________________
我已经学会了很难的方法,即使这一切都是完美的,如果你没有正确地训练,你的自动驾驶仪将无法工作。我遇到的最大的问题是过度适应模型,以至于在很少的情况下都不能正常工作。 这里是 2 个 Keras回调,将节省您的时间。
警告 - 如果仅使用CPU,则需要很长时间(3小时)
我在没有 CUDA 兼容 GPU 的 Dell XPS 笔记本电脑上运行,因此速度非常慢,并且冻结。为了加速训练,您可以使用带有 GPU 的 EC2 实例。Keras 和 Tensorflow 已经加载了几个实例图像。
import os
from keras import callbacks
model_path = os.path.expanduser('~/best_autopilot.hdf5')
#Save the model after each epoch if the validation loss improved.
save_best = callbacks.ModelCheckpoint(model_path, monitor='val_loss', verbose=1,
save_best_only=True, mode='min')
#stop training if the validation loss doesn't improve for 5 consecutive epochs.
early_stop = callbacks.EarlyStopping(monitor='val_loss', min_delta=0, patience=5,
verbose=0, mode='auto')
callbacks_list = [save_best, early_stop]
对于这个笔记本,我只会训练模型 4 个时代(epochs)。
model.fit(train_X, train_Y, batch_size=64, nb_epoch=4, validation_data=(val_X, val_Y), callbacks=callbacks_list)
输出:
Train on 12626 samples, validate on 789 samples
Epoch 1/4
12608/12626 [============================>.] - ETA: 0s - loss: 821.4849 00000: val_loss improved from inf to 516.59291, saving model to /home/wroscoe/best_autopilot.hdf5
12626/12626 [==============================] - 384s - loss: 822.5694 - val_loss: 516.5929
Epoch 2/4
12608/12626 [============================>.] - ETA: 0s - loss: 430.3060 00001: val_loss improved from 516.59291 to 384.87241, saving model to /home/wroscoe/best_autopilot.hdf5
12626/12626 [==============================] - 400s - loss: 430.4499 - val_loss: 384.8724
Epoch 3/4
12608/12626 [============================>.] - ETA: 0s - loss: 348.3565 00002: val_loss improved from 384.87241 to 321.23424, saving model to /home/wroscoe/best_autopilot.hdf5
12626/12626 [==============================] - 328s - loss: 348.1003 - val_loss: 321.2342
Epoch 4/4
12608/12626 [============================>.] - ETA: 0s - loss: 304.5375 00003: val_loss did not improve
12626/12626 [==============================] - 327s - loss: 304.4417 - val_loss: 326.8560
Out[11]:
我们可以通过绘制预测值和实际值来检查我们的模型预测是否合理。第一个图表显示我们的测试数据中存在一个学习的关系(在训练期间模型没有看到)。
import pandas as pd
model = load_model(model_path)
test_P = model.predict(test_X)
test_P = test_P.reshape((test_P.shape[0],))
df = pd.DataFrame({'predicted':test_P, 'actual':test_Y})
ax = df.plot.scatter('predicted', 'actual')
#ax.set_ylabel("steering angle")
第二张图,使用包含训练数据的非混洗(unshuffled)数据,来显示预测角度紧跟实际转向角度。
P = model.predict(X[:700])
#predict outputs nested arrays so we need to reshape to plot.
P = P.reshape((P.shape[0],))
ax = pd.DataFrame({'predicted':P, 'actual':Y[:700]}).plot()
ax.set_ylabel("steering angle")
输出:
改善模型,这个模型是纯粹(navie)的,因为它不使用过去的值来帮助预测未来。我们可以通过将过去的转向角度作为模型的输入来进行试验,添加一个递归层,或者只是改变卷积层的结构。
添加更多数据,随着我们添加更多驾驶数据,此模型将会得到改进。 预测油门,输出目前自动驾驶仪只能转向并保持恒定的速度。一个更复杂的模型将加速在直路上,并在路缘之前放缓。
观光\评论区