自動運転AIプログラムの仕組みと作り方

※サンプル・コード掲載

概要

この記事では、自動車の自動運転プロジェクトの構築方法について説明していきます。

主要なタスクはシュミレーター内のレーストラック上で自動車を走らせ、ディープラーニングを用いて人間の行動様式を模倣させることです。

これは非常に興味深い問題です。

それは、シミュレーション上ですべてのシナリオを運転することは不可能なため、ディープラーニングは一般的な交通ルールを学習する必要があるからです。

私たちはディープラーニングモデルを使用している間、非常に注意深くなる必要がありました。

なぜなら、それらがオーバーフィットしてしまう傾向を持っているからです。

オーバーフィットはモデルがトレーニングモデル自体に対して非常に敏感で、モデルが新しい初見のデータを一般化できない状態を指します。

オーバーフィットを避ける一つの方法はデータをたくさん収集することです。

典型的な畳み込みニューラルネットワークは100万にも及ぶパラメータを有し、これらのパラメータをチューニングするには、無相関データの何百万ものトレーニング例が必要であり、それらはいつでもできるわけではなく、時に到底手の及ばないコストが掛かるケースもあります。

私たちの自動車の例では、異なった天気、明るさ、交通状態、道路状態での運転が必要となります。

オーバーフィッティングを回避する1つの方法は、オーギュメントすることです。

オーギュメンテーションとは、小さなデータセットから新しいトレーニングデータを生成するプロセスを指します。

私たちは各画像から、数千の新しいトレーニング例を生成したため、それら全てのデータをディスク上に生成し保管することは不可能でした。

そのため、データをそのファイルから読み込むためにKerasジェネレーターを活用し、その場でオーギュメント、それを用いてモデルのトレーニングを行いました。

私たちは左右のカメラからの画像を活用することで、リカバリーをシュミレートするための追加のトレーニング画像を生成することができました。

Kerasジェネレーターは学習の最初段階で、モデルが高い確率で低いステアリング・アングルにデータをドロップするように設定されています。

これによりゼロアングルでの運転に対するバイアスのポテンシャルを排除することができます。

画像のオーギュメントパイプラインの設定の後、モデルをトレーニングすることができます。

トレーニングは学習率0.0001のシンプルな学習プログラムを用いて行われました。

トレーニング後、モデルは最初のトラック上で自動車を数時間にわたって運転することができるようになり、2番目のトラックの汎用化も行いました。

すべてのトレーニングは、PS4のコントローラーを用いて約4周を一方向運転したデータに基づいています。

そのモデルはトレーニングではトラック2を見ていませんでしたが、画像オーギュメント(反転、暗く、シフトなど)とすべてのカメラ(全方向)からのデータを使用し、そのモデルは一般的な運転ルールを学習して、その学習を他のトラックに適用することができました。

※それらの結果は、以前作成したTitan X GPUマシーン上で得られたものです。異なるパフォーマンスのコンピュータでは、パフォーマンスが異なることにご注意下さい。

1. Augmentation

オーギュメンテーションは、データから可能な限り多くのデータを引き出すのに役立ちます。

私たちは以下のデータオーギュメンテーションテクニックを用いて追加のデータを生成します。

オーギュメンテーションは入手したトレーニングデータを操作し、より多くのトレーニングデータ例を生成するテクニックです。

このテクニックは少ないデータから有力な分類機を発展させるのに使用されてきました。

参考:The Keras Blog

しかしながら、オーギュメンテーションはニューラルネットワークの適用に非常に特定的です。

1-1. 明るさの調整

昼夜の状態をシュミレートするために明るさの変更を行います。

最初にHSVに変換した画像を用いて、Vチャンネルのスケールアップもしくはダウン、そしてRGBチャンネルに変換し直すことによって、異なった明るさの画像を生成します。

def augment_brightness_camera_images(image):
    image1 = cv2.cvtColor(image、cv2.COLOR_RGB2HSV)
    image1 = np.array(image1、dtype = np.float64)
    random_bright = .5 + np.random.uniform()
    image1 [:、:、 2] = image1 [:、:、2] * random_bright 
    image1 [:、:、2] [image1 [:、:、2]> 255] = 255 
    image1 = np.array(image1、dtype = np.uint8)
    image1 = cv2.cvtColor(image1、cv2.COLOR_HSV2RGB)
    return image1
自動運転AI

1-2. 左右のカメラ画像の使用

左右のカメラ画像を使用し、車の横に外れる影響とリカバリーをシュミレーションします。

左のカメラに0.25の微量のアングルを追加し、右のカメラから0.25のアングルを取り除きます。

これは、中央に動くためには左のカメラは右に動かなければならず、右のカメラは左に動かなくてはいけないということです。

自動運転用の左右のカメラ画像

1-3. 水平および垂直シフト

カメラ画像を水平方向に移動させて、自動車が道路上の異なった位置にいる時の影響をシュミレートし、ステアリング角度へのシフトに対応するオフセットを追加します。

ピクセルシフトあたり0.004のステアリング角度を右側に追加し、同様に坂の登り下りの影響をシュミレートするため、ランダムに画像を垂直方向にシフトします。

def trans_image(image,steer,trans_range):
    # Translation
    tr_x = trans_range*np.random.uniform()-trans_range/2
    steer_ang = steer + tr_x/trans_range*2*.2
    tr_y = 40*np.random.uniform()-40/2
    #tr_y = 0
    Trans_M = np.float32([[1,0,tr_x],[0,1,tr_y]])
    image_tr = cv2.warpAffine(image,Trans_M,(cols,rows))
    
    return image_tr,steer_ang
車載カメラ画像

1-4. シャドウの調整

次のオーギュメンテーションは、画像全体にランダムで影を投影する陰影のオーギュメンテーションです。

これは、ランダムに選択された箇所と画像の片側(ランダムに選択された)の全てに陰影をつけることで実行されます。

コードは以下のとおりです。

def add_random_shadow(image):
    top_y = 320*np.random.uniform()
    top_x = 0
    bot_x = 160
    bot_y = 320*np.random.uniform()
    image_hls = cv2.cvtColor(image,cv2.COLOR_RGB2HLS)
    shadow_mask = 0*image_hls[:,:,1]
    X_m = np.mgrid[0:image.shape[0],0:image.shape[1]][0]
    Y_m = np.mgrid[0:image.shape[0],0:image.shape[1]][1]
shadow_mask[((X_m-top_x)*(bot_y-top_y) -(bot_x - top_x)*(Y_m-top_y) >=0)]=1
    #random_bright = .25+.7*np.random.uniform()
    if np.random.randint(2)==1:
        random_bright = .5
        cond1 = shadow_mask==1
        cond0 = shadow_mask==0
        if np.random.randint(2)==1:
            image_hls[:,:,1][cond1] = image_hls[:,:,1][cond1]*random_bright
        else:
            image_hls[:,:,1][cond0] = image_hls[:,:,1][cond0]*random_bright    
    image = cv2.cvtColor(image_hls,cv2.COLOR_HLS2RGB)
return image
自動運転用カメラ

1-5. 反転

上記の変換に加えて、画像をランダムに反転し、また予測された角度のサインを変更することで逆方向の運転のシュミレートを行います。

2. 前処理

上記のようなオーギュメントの後、画像の上部1/5をトリミングし、水平に除去します。

また、下部の25ピクセルもトリミングし自動車のボンネットも除去します。

元々の自動車の画像の上部1/3は除去されましたが、その後、自動車が坂を登り下りしている状況を含むように1/5変更されました。

次に64 X 64画像サイズに縮尺を変更します。

オーギュメンテーション後、オーギュメントされた画像は以下のようになります。

それらの画像はKerasのジェネレーターによって生成され、一つの画像から無制限に画像を生成することができます。

KerasのLambdaレイヤーを用いてインテンシティを-.5と.5の間で正常化しました。

new_size_col,new_size_row = 64, 64
def preprocessImage(image):
    shape = image.shape
    # note: numpy arrays are (row, col)!
    image = image[math.floor(shape[0]/5):shape[0]-25, 0:shape[1]]
    image = cv2.resize(image,(new_size_col,new_size_row),         interpolation=cv2.INTER_AREA)    
    #image = image/255.-.5
    return image
def preprocess_image_file_train(line_data):
    i_lrc = np.random.randint(3)
    if (i_lrc == 0):
        path_file = line_data['left'][0].strip()
        shift_ang = .25
    if (i_lrc == 1):
        path_file = line_data['center'][0].strip()
        shift_ang = 0.
    if (i_lrc == 2):
        path_file = line_data['right'][0].strip()
        shift_ang = -.25
    y_steer = line_data['steer_sm'][0] + shift_ang
    image = cv2.imread(path_file)
    image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)
    image,y_steer,tr_x = trans_image(image,y_steer,100)
    image = augment_brightness_camera_images(image)
    image = preprocessImage(image)
    image = np.array(image)
    ind_flip = np.random.randint(2)
    if ind_flip==0:
        image = cv2.flip(image,1)
        y_steer = -y_steer
    
    return image,y_steer

3. サブ・サンプリング用のKerasジェネレーター

データが限られているため、同じ画像から数百のトレーニングデータ・サンプルを生成しています。

すべての画像シミュレーションをメモリ内に保存することは不可能です。

そのため私たちはKeraのジェネレーター機能を活用し、データセットに表示される可能性が低い、低いアングルを持つような画像をサンプリングします。

これは、運転に対するバイアスをもつモデルによって発生するかもしれない問題を緩和します。

下のパネルは一つの画像から生成された複数のトレーニング・サンプルを示しています。

‘pr_threshold’変数は小さなアングルを持つデータがドロップされるかどうかを決定する閾値です。

自動運転用カメラ画像
def generate_train_from_PD_batch(data,batch_size = 32):
    
    batch_images = np.zeros((batch_size, new_size_row, new_size_col, 3))
    batch_steering = np.zeros(batch_size)
    while 1:
        for i_batch in range(batch_size):
            i_line = np.random.randint(len(data))
            line_data = data.iloc[[i_line]].reset_index()
            
            keep_pr = 0
            #x,y = preprocess_image_file_train(line_data)
            while keep_pr == 0:
                x,y = preprocess_image_file_train(line_data)
                pr_unif = np.random
                if abs(y).1:
                    pr_val = np.random.uniform()
                    if pr_val>pr_threshold:
                        keep_pr = 1
                else:
                    keep_pr = 1
            
            #x = x.reshape(1, x.shape[0], x.shape[1], x.shape[2])
            #y = np.array([[y]])
            batch_images[i_batch] = x
            batch_steering[i_batch] = y
        yield batch_images, batch_steering

4. モデル・アーキテクチャとトレーニング

自動運転用トレーニング・モデル

トレーニングデータ用に上記のモデル・アーキテクチャを実装しました。

最初のレイヤーは3-1X1フィルター、これは画像の色空間を変換する効果を持っています。

研究では、色空間は様々なアプリケーションにより適しているということが発表されています。

最適な色空間がわからないため、3つの1X1フィルターを用いてモデルに最適な色空間を選択します。

これは32、64、128フィルターからなる3X3サイズの3つの畳み込みブロックに続きます。

それらの畳み込みレイヤーは3つの完全結合されたレイヤーに続きます。

全ての畳み込みブロックとそれにつづく2つの完全結合されたレイヤーは、活性化関数として指数関数的なrelu(ELU)を持ちます。

私は滑らかなアングルの遷移を作るためにleaky reluを選びました。

トレーニング

私は8エポックで256のバッチサイズを持つkerasを用いてモデルをトレーニングし、それぞれのエポックで、20000画像を生成しました。

pr_thresholdから始め、1のような小さなアングルを持つデータがドロップされるチャンス、そしてエポック後にそれを反復回数で割ることによって確率を減少させました。

全体のトレーニングは5分ほどでしたが、それが正しいアーキテクチャとパラメータにたどり着くまでには20時間以上もかかりました。

以下の抜粋はトレーニングの結果を表しています。

自動運転車のトレーニング

5. モデルのパフォーマンス

以下の動画は収集されたオリジナルデータであるトラック1におけるアルゴリズムのパフォーマンスを示しています。

その自動車は数時間運転することができたので、次にカメラの解像度、動画サイズもしくはトラックのいずれかが変更された場合を見ていきましょう。

5-1. ある画像サイズから別の画像サイズへの一般化

以下の動画はある画像サイズから別のサイズへの一般化を示しています。

私は同じプレトレーニングされたモデルを使用し、全ての画像サイズを試し、そしてディープラーニング・ニューラルネットワークは全ての画像サイズで運転が可能であることを発見しました。

5-2. ある画像サイズから別の解像度への一般化

以下の動画はある画像解像度から別の解像度への一般化を示しています。

私は同じプレトレーニングされたモデルを用いて、全ての解像度を試し、ディープラーニング・ニューラルネットワークは全ての解像度で運転が可能であることを発見しました。

また、様々な画像サイズと画像解像度の組み合わせを試した結果、トラック1では全ての画像解像度とサイズの組み合わせにおいて、このディープラーニング・アルゴリズムで自動車を運転することができました。

5-3. あるトラックから別のトラックへの一般化

下の動画はあるトラックから別のトラックへの一般化を示しています。

それはおそらくディープラーニング・アルゴリズムにとって最も厳しいテストでした。

2番目のトラックでは、より多くの右折とUターンが存在し、暗く、そして坂道もありました。

それら全ては元のトラックでは無かったものでしたが、これら全てのエフェクトは画像オーギュメントを介して含まれていました。

6. 今後の方向性

このプロジェクトは今後まだまだ続きます。

今後、以下を試していきたいと考えています。

6-1

プレトレーニングされたモデルのパフォーマンスがどれくらいか、正しいモデルと比較して確認する。

6-2

トラック固有の動作を学習するために並列ネットワークを含める。

今回、その場でトレーニングを行うためにアジェイルトレーナーを実装しました。

理由はリカバリーデータのレコーディングの代わりに、自動車にミスを犯させ、必要に応じてトレーニングを行うためです。

これは非常に有用なツールですが、本当に必要になるまで使用は避けるのがベストです。

私は過去の制御理論の知識に基づいてこのアプローチを選択しませんでした。

それらのスキームはエラーがゼロであるかどうかを学習せず、一般的な運転ルールを学習する代わりに、トレーニングデータ自体の特徴を学習する可能性があります。

これは適応制御における動的な豊かさに類似しています。

適応制御におけるパラメータのアップデートは必要性に基づいており、他のコントロールタスクを一般化しません。

良い選択肢はその場で更新される非線形モデルを使用し、別のタスクに移動すると消去されることです。

6-3

もう一つの興味深い試みは現実世界のデータにおいてこのモデルがどのくらいよく機能するかをテストすることです。

6-4

前方に偽の自動車を配置する、閉鎖するなどのような追加のオーギュメンテーションスキームを試す。

7. 考察

これはおそらく私が行った中で最も変わったプロジェクトであり、このプロジェクトは私が持っていたディープラーニングに関する知識への挑戦でした。

一般的には多くのデータでトレーニングを行うと結果的にパフォーマンスが向上しますが。

しかし、今回のケースでは、10エポックを超えた時にはいつでも、単純にその自動車はそのトラックを走り去っていました。

全ての画像オーギュメントが合理的に見えるものの、それらがアプリオリであると思っていませんでした。

原文

https://chatbotslife.com/using-augmentation-to-mimic-human-driving-496b569760a9

チャットボットライフとの提携により、翻訳し掲載しています。
チャットボットライフとは、最新のボット、AI、NLP、ツール等を扱うメディアです。