車輛行為克隆

說是行為克隆,聽著高大上,其實(shí)原理很簡單。掛載在車蓋上的攝像頭拍攝一張圖片,網(wǎng)絡(luò)對(duì)其進(jìn)行回歸預(yù)測,輸出車輪轉(zhuǎn)向角。最終目標(biāo)就是使模擬器中的小車可以自己開,不能超出車道線。

訓(xùn)練數(shù)據(jù)就是好多好多張車輛運(yùn)行中攝像頭拍攝的圖片,而label就是各個(gè)圖片對(duì)應(yīng)的車輪轉(zhuǎn)向角,為[-25,25]中的值。

原理都很簡單,主要是我第一次是用的keras搭建的網(wǎng)絡(luò),并且使用generator來生成訓(xùn)練數(shù)據(jù),主要記錄這些內(nèi)容。

網(wǎng)絡(luò)結(jié)構(gòu)

使用的是NVIDIA發(fā)表的一篇端到端自動(dòng)駕駛論文中的網(wǎng)絡(luò)結(jié)構(gòu),網(wǎng)絡(luò)功能很強(qiáng)大,對(duì)于這個(gè)小項(xiàng)目綽綽有余了。網(wǎng)絡(luò)為五層卷積后接四層全連接,因?yàn)橹活A(yù)測轉(zhuǎn)向角,所以輸出只有一個(gè)神經(jīng)元。另外,在第一層卷積之前有兩個(gè)預(yù)處理步驟:1.因?yàn)閿z像頭拍攝的范圍還是挺大的,對(duì)于圖像中天空這一部分是不需要的,所以給它裁掉。還有攝像頭還會(huì)拍到一點(diǎn)車蓋,這個(gè)給它裁掉。輸入圖像是(160,320,3),我看了下,上面裁60行,下面裁20行就可以。2.之后對(duì)輸入normalize一下,這里只是簡單的除以255后減去0.5而已。

from keras.models import Sequential
from keras.layers import Flatten, Dense, Lambda, Dropout
from keras.layers.convolutional import Conv2D, Cropping2D
from keras.layers.pooling import MaxPooling2D

conv_trainable = True

model = Sequential()
model.add(Cropping2D(cropping=((60,20),(0,0)), input_shape=(160,320,3)))
model.add(Lambda(lambda x: x / 255.0 - 0.5))

model.add(Conv2D(24,(5,5),strides=(2,2),activation='relu', trainable=conv_trainable))
model.add(Conv2D(36,(5,5),strides=(2,2),activation='relu', trainable=conv_trainable))
model.add(Conv2D(48,(5,5),strides=(2,2),activation='relu', trainable=conv_trainable))
model.add(Conv2D(64,(3,3),activation='relu', trainable=conv_trainable))
model.add(Conv2D(64,(3,3),activation='relu', trainable=conv_trainable))

model.add(Flatten())
# 我訓(xùn)練的epoch不多,還沒過擬合,所以沒用dropout
# model.add(Dropout(0.5))
model.add(Dense(100,activation='relu'))
# model.add(Dropout(0.6))
model.add(Dense(50,activation='relu'))
model.add(Dense(10,activation='relu'))
model.add(Dense(1))

model.compile(loss='mse', optimizer='adam')
model.summary()

對(duì)于結(jié)構(gòu)這么簡單的網(wǎng)絡(luò),使用keras可是太方便了!

訓(xùn)練數(shù)據(jù)

訓(xùn)練數(shù)據(jù)是好多好多圖片,shape為(160,320,3)。所有圖片的信息都存在一個(gè)csv文件中,csv文件的每一行包括車上三個(gè)攝像頭拍攝圖片的地址(中,左,右),和此時(shí)車輪的轉(zhuǎn)向角。

在訓(xùn)練之前先要做個(gè)data augment。我只是對(duì)每張圖片都翻轉(zhuǎn)一下。翻轉(zhuǎn)后要記錄新的圖片地址和取了負(fù)值的轉(zhuǎn)向角,然后更新原來的csv文件。下面代碼完成這個(gè)操作:

import csv
import cv2
import numpy as np

# 本代碼由于augment data,對(duì)于所有圖片左右翻轉(zhuǎn)下
# 方法是通過csv文件中的地址分別讀取圖片,翻轉(zhuǎn)圖片,存儲(chǔ)新圖片,如此循環(huán)
# 在該過程中記錄新圖片地址,最后更新csv文件。
new_csv = []
with open('./driving_log.csv', 'r') as csvfile:
    reader = csv.reader(csvfile)
    # 注意line中所有數(shù)據(jù)都是字符串形式
    for line in reader:
        for i in range(3):
            # 防止地址前后有空格,沒有什么特殊的含義
            path = line[i].strip()
            img = cv2.imread(path)
            img = cv2.flip(img, 1)
            # 翻轉(zhuǎn)后圖片的新名字
            new_path = path.split('.jpg')[0] + '_r.jpg'
            cv2.imwrite(new_path, img)
            line[i] = new_path
        # 因?yàn)榉D(zhuǎn)了,所以左圖右圖互換
        line[1],line[2] = line[2], line[1]
        line[3] = str(-1.0 * float(line[3]))
        # 把新的信息存到列表里,后面更新csv文件
        new_csv.append(line)

# 用open以寫方式打開csv文件需要指定newline=''
# 不然,寫的行與行之間會(huì)多一個(gè)空行
with open('./driving_log.csv', 'a', newline='') as csvfile:
    writer = csv.writer(csvfile)
    writer.writerows(new_csv)

下面代碼讀入csv文件,提取出每一行,shuffle后,分類訓(xùn)練、驗(yàn)證集。

lines = []
with open('./driving_log.csv') as csvfile:
    # reader為一生成器
    reader = csv.reader(csvfile)
    for line in reader:
        lines.append(line)

# sklearn的工具還是很好用的
from sklearn.utils import shuffle
lines = shuffle(lines)

from sklearn.model_selection import train_test_split
# 注意這種分法沒有完全分開同一時(shí)刻左中右三個(gè)攝像頭拍攝的圖片
train_samples, validation_samples = train_test_split(lines, test_size=0.2)

這時(shí)訓(xùn)練數(shù)據(jù)我記得是好五六萬張吧。一次都讀進(jìn)內(nèi)存電腦課吃不消。所以每訓(xùn)練一個(gè)batch再從硬盤中讀一個(gè)batch的圖片。所以這里shuffle的只是圖片的地址,到時(shí)候根據(jù)地址讀圖片就好了。

使用生成器訓(xùn)練網(wǎng)絡(luò)

需要構(gòu)造一個(gè)生成器,在訓(xùn)練時(shí)傳入到model.fit_generator中,生成器每次返回一個(gè)batch的數(shù)據(jù)。

# samples接收的就是保存了地址的列表
# 返回的batch全部在samples中。
def generator(samples, batch_size=32):
    num_samples = len(samples)
    # 要不斷生成數(shù)據(jù),所以無限循環(huán)
    while 1:
        # 每次大循環(huán)完該samples,也就是一個(gè)epoch,都shuffle下整個(gè)數(shù)據(jù)集
        shuffle(samples)
        # 在該for中每次yeild一個(gè)batch數(shù)據(jù)
        for offset in range(0, num_samples, batch_size):
            batch_samples = samples[offset:offset+batch_size]
            images = []
            labels = []

            for batch_sample in batch_samples:
                # 這里使用了左中右三個(gè)攝像頭的圖片
                # 所以yeild的真正的數(shù)據(jù)量為batch*3
                for i in range(3):
                    # stip就是防止地址前后的空格
                    # 讀不到圖片imread可不會(huì)報(bào)錯(cuò)
                    image = cv2.imread(batch_sample[i].strip())
                    images.append(image)
                label = float(batch_sample[3])
                label_left = label + correction
                label_right = label - correction
                labels.extend([label, label_left, label_right])

            X_train = np.array(images)
            y_train = np.array(labels)
            # 這個(gè)shuffle我感覺沒太大用
            yield shuffle(X_train, y_train)

上面只是生成器的定義,下面,要建立訓(xùn)練集和驗(yàn)證集的生成器對(duì)象,分別傳入之前分割好的列表即可:

batch_size = 16
train_generator = generator(train_samples, batch_size=batch_size)
validation_generator = generator(validation_samples, batch_size=batch_size)

這兩個(gè)生成器就是用于model.fit_generator()中的了。

訓(xùn)練!

直接調(diào)用model.fit_generator就好:

# 每一個(gè)epoch都保存一個(gè)模型
# 所以設(shè)置model.fit_generator中的epochs參數(shù)為1
# 并在循環(huán)中調(diào)用
# 每次fit結(jié)束后就保持模型。
for i in range(5):
    history_object = model.fit_generator(generator = train_generator,
                                     steps_per_epoch = int(len(train_samples)/batch_size),
                                     epochs = 1,
                                     validation_data = validation_generator,
                                     validation_steps = int(len(validation_samples)/batch_size))

    model.save('./model_%d.h5'%i)

用keras可太方便了,這要是用tensorflow可費(fèi)勁了。

上面說這個(gè)網(wǎng)絡(luò)結(jié)構(gòu)強(qiáng)大是有原因的,一開始我搭建網(wǎng)絡(luò)的時(shí)候忘記指定全連接層的轉(zhuǎn)移函數(shù)了!??!所以這四層全連接就相當(dāng)于一層線性層,但是,就算是這樣,使用第4個(gè)epoch之后得到的模型,小車也能非常穩(wěn)定的開。。。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1、通過CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請(qǐng)求組件 FMDB本地?cái)?shù)據(jù)庫組件 SD...
    陽明AI閱讀 16,214評(píng)論 3 119
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,433評(píng)論 4 61
  • 今天看到報(bào)紙的一則新聞:白領(lǐng)的生存狀態(tài),工作時(shí)間長,強(qiáng)度大,缺乏鍛煉,既是都市生活節(jié)奏和他們工作性質(zhì)所決定的,也是...
    領(lǐng)后小小記事閱讀 468評(píng)論 0 0
  • 因?yàn)榍閼验_了一家咖啡店,雖然自私,但不愿把自己的快樂建立在別人的痛苦上,故愿意坦誠自己的自私。后來每一次的深交都給...
    華美人閱讀 201評(píng)論 0 1
  • 4/18 上午 8:27我:早安,周一,那就新周愉快吧 4/19 下午 2:31我:【鏈接:收縮目標(biāo),才能捕獲更多...
    值得的閱讀 96評(píng)論 0 0

友情鏈接更多精彩內(nèi)容