AirSim 自動駕駛教程--訓練數據
之後的更新會第一時間發布在: https://github.com/EVYang1992/AutonomousDrivingCookbook,歡迎watch 和 fork
Step 1 - 訓練模型
現在我們覺得我們對數據有一點感覺,可以開始設計我們的模型了。在這份文檔中,我們將定義一個網路架構來訓練模型,我們也會討論一些數據轉換來呼應之前我們在上一份文檔中的觀察到的東西。
現在我們開始導入一些庫。
from keras.preprocessing.image import ImageDataGeneratorfrom keras.models import Sequential, Modelfrom keras.layers import Conv2D, MaxPooling2D, Dropout, Flatten, Dense, Lambda, Input, concatenatefrom keras.layers.normalization import BatchNormalizationfrom keras.layers.advanced_activations import ELUfrom keras.optimizers import Adam, SGD, Adamax, Nadamfrom keras.callbacks import ReduceLROnPlateau, ModelCheckpoint, CSVLogger, EarlyStoppingimport keras.backend as Kfrom keras.preprocessing import imagefrom keras_tqdm import TQDMNotebookCallbackimport jsonimport osimport numpy as npimport pandas as pdfrom Generator import DriveDataGeneratorfrom Cooking import checkAndCreateDirimport h5pyfrom PIL import Image, ImageDrawimport mathimport matplotlib.pyplot as plt# << 上一步已經處理好的數據所在的目錄 >>COOKED_DATA_DIR = data_cooked/# << 模型輸出將被存放的目錄 >>MODEL_OUTPUT_DIR = model
我們來讀取從探索數據階段得到的數據集。如果它們不存在,執行Step 0 文檔來生成它們。
train_dataset = h5py.File(os.path.join(COOKED_DATA_DIR, train.h5), r)eval_dataset = h5py.File(os.path.join(COOKED_DATA_DIR, eval.h5), r)test_dataset = h5py.File(os.path.join(COOKED_DATA_DIR, test.h5), r)num_train_examples = train_dataset[image].shape[0]num_eval_examples = eval_dataset[image].shape[0]num_test_examples = test_dataset[image].shape[0]batch_size=32
對於圖像數據,將所有的數據載入到內存太消耗內存了。所幸的是,Keras 有一個稱之為DataGenerators 的概念。一個DataGenerator 其實就是一個將從硬碟一塊塊讀取數據的迭代器。這允許你保持你的CPU 和GPU 一直工作,增加生產力。
我們在數據探索階段已經觀察到一些東西。現在是時候像一些策略來驗證它們:
- 每一張圖片只有一部分是我們感興趣的 - 當批量化的時候,我們能夠移除一部分圖片中我們不感興趣的部分。
- 數據集顯示了垂向翻轉能力 - 當批量化的時候,我們可以繞著Y 軸隨機翻轉一些圖片和標註所以模型有可以進行學習的新的數據。
- 數據集不應該受圖片亮度影響 - 當批量化的時候,我們可以隨機的增加或者移除一些圖片的亮度,所以模型能夠學習到全局的光照強度變化應該被忽略。
- 數據集有一大部分存在零值圖片 - 當批量化的時候,我們可以隨機丟棄一部分轉向角為零的數據,那麼模型訓練時看起來平衡多了。
- 我們需要一些在數據集中為轉向策略的樣本,那麼模型就可以學習怎樣進行比較大的轉向 - 我們在預處理階段就關注這個。
儘管Keras 確實有一些標準的圖片轉換內建函數,然而它們的功能不足以應用在這個應用。舉個例子來說,當我們使用內建的ImageDataGenerator進行horizontal_flip = True 操作,標註沒有同時進行翻轉。 所幸的是,我們能夠僅僅擴展ImageDataGenerator 這個類嵌入我們自己的轉換邏輯。這部分代碼在Generator.py 文件中 - 直接可以用,但是要解釋這部分在這個文檔中就需要很長的時間。
這裡,我們會用下面的參數初始化生成器:
- Zero_Drop_Percentage: 0.9 - 就是說,我們隨機丟棄90% 的label = 0 的數據
- Brighten_Range: 0.4 - 就是說,每張圖片的亮度將被增加40%。計算」亮度「,我們將圖片從RGB 空間轉換為HSV 空間, 放大或縮小 V 值,然後再轉換回RGB 空間。
- ROI: [76,135,0,255] - 就是說,x1, x2, y1, y2 矩形代表了圖片中我們感興趣的部分。
思考訓練 1.1
試著不斷調試這些參數來看看是否你能得到更好的訓練年結果。
data_generator = DriveDataGenerator(rescale=1./255., horizontal_flip=True, brighten_range=0.4)train_generator = data_generator.flow (train_dataset[image], train_dataset[previous_state], train_dataset[label], batch_size=batch_size, zero_drop_percentage=0.95, roi=[76,135,0,255])eval_generator = data_generator.flow (eval_dataset[image], eval_dataset[previous_state], eval_dataset[label], batch_size=batch_size, zero_drop_percentage=0.95, roi=[76,135,0,255])
我們來看一個樣本批次。轉向角在圖片中用紅線表示:
def draw_image_with_label(img, label, prediction=None): theta = label * 0.69 #Steering range for the car is +- 40 degrees -> 0.69 radians line_length = 50 line_thickness = 3 label_line_color = (255, 0, 0) prediction_line_color = (0, 0, 255) pil_image = image.array_to_img(img, K.image_data_format(), scale=True) print(Actual Steering Angle = {0}.format(label)) draw_image = pil_image.copy() image_draw = ImageDraw.Draw(draw_image) first_point = (int(img.shape[1]/2),img.shape[0]) second_point = (int((img.shape[1]/2) + (line_length * math.sin(theta))), int(img.shape[0] - (line_length * math.cos(theta)))) image_draw.line([first_point, second_point], fill=label_line_color, width=line_thickness) if (prediction is not None): print(Predicted Steering Angle = {0}.format(prediction)) print(L1 Error: {0}.format(abs(prediction-label))) theta = prediction * 0.69 second_point = (int((img.shape[1]/2) + (line_length * math.sin(theta))), int(img.shape[0] - (line_length * math.cos(theta)))) image_draw.line([first_point, second_point], fill=prediction_line_color, width=line_thickness) del image_draw plt.imshow(draw_image) plt.show()[sample_batch_train_data, sample_batch_test_data] = next(train_generator)for i in range(0, 3, 1): draw_image_with_label(sample_batch_train_data[0][i], sample_batch_test_data[i])
Actual Steering Angle = [ 0.00389267]
Actual Steering Angle = [-0.15305]
Actual Steering Angle = [ 0.11013433]
然後,我們定義網路層架構。我們將使用一個標準的卷積層/最大池化層的結合來處理圖片(我們不能在本文檔詳細來討論這些層,但是如果你不理解你在做什麼,你肯定應該查看在readme 文本中提及的書籍)。然後,我們將汽車上一個時刻的狀態當作一個額外的特徵加入到全連接層。層的尺寸和優化參數由試驗來確定 - 試著對它們做微調來看看會發生什麼。
image_input_shape = sample_batch_train_data[0].shape[1:]state_input_shape = sample_batch_train_data[1].shape[1:]activation = relu#Create the convolutional stackspic_input = Input(shape=image_input_shape)img_stack = Conv2D(16, (3, 3), name="convolution0", padding=same, activation=activation)(pic_input)img_stack = MaxPooling2D(pool_size=(2,2))(img_stack)img_stack = Conv2D(32, (3, 3), activation=activation, padding=same, name=convolution1)(img_stack)img_stack = MaxPooling2D(pool_size=(2, 2))(img_stack)img_stack = Conv2D(32, (3, 3), activation=activation, padding=same, name=convolution2)(img_stack)img_stack = MaxPooling2D(pool_size=(2, 2))(img_stack)img_stack = Flatten()(img_stack)img_stack = Dropout(0.2)(img_stack)#Inject the state inputstate_input = Input(shape=state_input_shape)merged = concatenate([img_stack, state_input])# Add a few dense layers to finish the modelmerged = Dense(64, activation=activation, name=dense0)(merged)merged = Dropout(0.2)(merged)#merged = Dense(50, activation=activation, name=dense1)(merged)#merged = Dropout(0.2)(merged)merged = Dense(10, activation=activation, name=dense2)(merged)merged = Dropout(0.2)(merged)merged = Dense(1, name=output)(merged)#adam = Adam(lr=0.0001, beta_1=0.9, beta_2=0.999, epsilon=1e-08, decay=0.0)adam = Nadam(lr=0.0001, beta_1=0.9, beta_2=0.999, epsilon=1e-08)model = Model(inputs=[pic_input, state_input], outputs=merged)model.compile(optimizer=adam, loss=mse)
我們看一下模型概覽
model.summary()
這已經是很多參數了! 所幸的是,我們有我們的數據增加策略,所以網路有收斂的機會。試著增加或者移除一些層或者改變他們的寬度,來看看這回對網路中訓練參數的數量有什麼影響。
Keras 的一個很棒的特點是指出回掉函數。這些函數在每一個訓練epoch 中被執行。我們會定義一些回掉函數:
- ReduceLrOnPlateau - 如果模型接近於最小值,學習率太高,那麼模型將圍繞在那個最小值而不達到最小值。這個回掉函數允許我們減小學習率當驗證數據的損失停止增加,允許我們達到一個最優點。
- CsvLogger - 記錄在每一次訓練的模型輸出,這使得我們可以在不必要時用控制台的情況下跟蹤進度。
- ModelCheckpoint - 通常,我們會想要使用那些在驗證數據上有很最小損失的模型。這個回掉函數將在每次驗證數據訓練時損失增加時的模型。
- EarlyStopping - 當驗證損失停止增加時候,我們會想要停止訓練。否則,我們會有過擬合的風險。這個檢測器將檢測驗證損失停止增加的時刻,同時停止訓練過程。
plateau_callback = ReduceLROnPlateau(monitor="val_loss", factor=0.5, patience=3, min_lr=0.0001, verbose=1)checkpoint_filepath = os.path.join(MODEL_OUTPUT_DIR, models, {0}_model.{1}-{2}.h5.format(model, {epoch:02d}, {val_loss:.7f}))checkAndCreateDir(checkpoint_filepath)checkpoint_callback = ModelCheckpoint(checkpoint_filepath, save_best_only=True, verbose=1)csv_callback = CSVLogger(os.path.join(MODEL_OUTPUT_DIR, training_log.csv))early_stopping_callback = EarlyStopping(monitor="val_loss", patience=10, verbose=1)callbacks=[plateau_callback, csv_callback, checkpoint_callback, early_stopping_callback, TQDMNotebookCallback()]
現在是時候訓練模型了!使用預設設置,這個模型在一塊NVidia GTX970 GPU 上訓練時間大約為45分鐘。注意: 有時候模型在驗證損失超過7個epoch 保持不變時候將被阻塞。如果不運行,這個模型應該終止,此時驗證損失在大概0.0003 左右。
[sample_batch_train_data, sample_batch_test_data] = next(train_generator)predictions = model.predict([sample_batch_train_data[0], sample_batch_train_data[1]])for i in range(0, 3, 1): draw_image_with_label(sample_batch_train_data[0][i], sample_batch_test_data[i], predictions[i])
Actual Steering Angle = [-0.035522]
Predicted Steering Angle = [-0.0003692]L1 Error: [ 0.0351528]Actual Steering Angle = [ 0.12993667]
Predicted Steering Angle = [-0.0003692]L1 Error: [ 0.13030587]Actual Steering Angle = [-0.09872733]
Predicted Steering Angle = [-0.0003692]L1 Error: [ 0.09835813]看起來很棒!我們繼續使用AirSim 來進行測試!
推薦閱讀:
TAG:自動駕駛 | 深度學習DeepLearning |