キカベン
機械学習でより便利な世の中へ
G検定対策
お問い合わせ
   

YOLOv5で転移学習をGoogle ColabのGPUを使ってやってみる

thumb image

YOLOv5で転移学習をやってみる」と同様のことをGoogle ColabのGPUを使ってやってみる。

ブラウザーはGoogle Chromeを勧める。SafariだとTensorBoardが動作しない。それ以外は問題ないが。

1. Google Colabのノートブックにアクセスする🔝

Google Colabを使うにはグーグルのアカウントが必要。無料なので作っても損はない。

アカウントがあれば、https://colab.research.google.comにアクセスするだけで使える。

あとは、New notebookをクリックすると新しいノートブックが開く。

Google Driveにセーブされるので後でわかりやすいようにファイル名を指定する。

星のマークをクリックするとお気に入りフォルダーに入るので必要ならクリックしておく。

ノートブックの使い方は、基本的にはJupyter Notebookの仕様になっているが細かい違いはある。必要に応じて検索すればなんとかなる程度なので気にせず進める。

2. GPUを使えるようにする🔝

GPUを使うには、これを最初にやっておく必要がある。後でやるとせっかく設定した環境が使えなくて無駄になる。

まず、Notebook settingsEditメニューから選ぶ。

そしてGPUを選択する。

忘れずにSaveする。

3. Google Colabのフォルダを確認🔝

Jupyterセルの中で現在のファルダーを確認するために!pwdとコマンドを打つ。

!pwd

(感嘆符、ビックリマーク、エクスクラメーションマーク)はシェルで命令実行する際に頭につける。しかし、piplsなどの基本的なコマンドではつける必要はない。よって、下記のように実行してもOK。

pwd

MacOSならCommand+Enter、WindowsならControl+Enterで実行する。Shift+Enterだと実行して次のセルに移る。

現在のフォルダが/contentであることがわかる。

今回はYOLOv5という名のフォルダを作ることにした。以下のコマンドを実行する。

mkdir YOLOv5

そして、そこに移動するコマンドを実行する。

cd YOLOv5

これで、現在フォルダが/content/YOLOv5になっているはず。不安なら、pwdで再確認。

4. Open Imagesからダウンロード🔝

まず、Open Imagesのデータセットをダウンロードするためのライブラリをインストールするために以下を実行する。

 pip install openimages

次に、犬と猫のデータセットを500画像ずつ(合計100画像)ダウンロードします。

以下のコマンドの実行には!が必要。

!oi_download_dataset --base_dir download --csv_dir download --labels Cat Dog --format darknet --limit 500

YOLOv5で使いやすいように、フォーマットでダークネットを指定します(–format darknet)。

上記のコマンドを実行して、ダウンロードが終わるとdownloadフォルダーにファイルがたくさんあるのが確認できる。

また、これによって、darknet_obj_names.txtというファイルが作られている。中身はこんな感じ。

よって、インデックスで言うと、順番で0が猫、1が犬になる。

ls -al download/cat/imagesを実行すると猫の画像ファイルがあるのがわかる。

同様に、ls -al /download/dog/imagesを実行すれば犬の画像ファイルが確認できる。

上記のフォルダの構成のままではYOLOv5の訓練では使えないので、訓練用にデータセットを格納するためのフォルダを作る。

5. YOLOv5の訓練用のフォルダ作成🔝

YOLOv5では下図のようなフォルダ構成を使う。

folder structure

方法はいろいろありますが、今回はPythonで次のようにフォルダを作る。セルに書き込んで実行する。

import os

if not os.path.exists('data'):
    for folder in ['images', 'labels']:
        for split in ['train', 'val', 'test']:
            os.makedirs(f'data/{folder}/{split}')

上記のフォルダへ、画像と正解データを振り分けていけばYOLOv5の訓練で使うことができる。

6. 画像ファイル名の重複チェック🔝

念のためファイル名に重複がないかチェックしておく。

フォルダを指定してファイル名をsetに入れて取り出す。

import glob

def get_filenames(folder):
    filenames = set()
    
    for path in glob.glob(os.path.join(folder, '*.jpg')):
        # pathを分解してファイル名だけを取り出す
        filename = os.path.split(path)[-1]        
        filenames.add(filename)
        
    return filenames

# 犬と猫の画像ファイル名のセット
dog_images = get_filenames('download/dog/images')
cat_images = get_filenames('download/cat/images')

ファイル名の重複をチェックする。

# 同じ名前のファイル名をチェック
duplicates = dog_images & cat_images

print(duplicates)

ここで、{'1417eccd5854e04a.jpg', '0dcd8cc4b35a93b4.jpg', '0838125199f2caa7.jpg'}がプリントされた。

3つのファイル名が犬と猫のフォルダで重複している。

数が少ないでの目視で画像をチェックする。なお、Google Colab環境ではPIL.Imageを直接オープンできないので、一旦NumPyフォーマットにしてからmatplotlibを使って画像を表示する。

from PIL import Image
import matplotlib.pyplot as plt
import numpy as np


# 同じファイル名の画像をcatとdogのフォルダから表示してみる
for file in duplicates:
    for animal in ['cat', 'dog']:
        image = Image.open(f'download/{animal}/images/{file}')
        plt.imshow(np.array(image))
        plt.show()
yolov5 cat
yolov5 cat
yolov5 cat

同じ猫の画像がdogフォルダに紛れ込んでいた。

理由は分からないが、追求しても仕方ないので取り除こう。

dog_images -= duplicates

print(len(dog_images))

497とプリントされて3つのファイル名が犬画像ファイル名のセットから取り除かれたのが確認できた。

7. データセットを振り分ける🔝

犬と猫のデータを訓練用、バリデーション、テスト用に振り分ける。

画像データは特に規則性もなく、ファイルのリストをシャッフルする必要はないが、一応やっておく。

import numpy as np

dog_images = np.array(list(dog_images))
cat_images = np.array(list(cat_images))

# 乱数シードを固定して再現性を確保
np.random.seed(42)
np.random.shuffle(dog_images)
np.random.shuffle(cat_images)

下記のコードはちょっと長いが、画像と正解データのファイルを振り分けてコピーしているだけ。

import shutil

def split_dataset(animal, image_names, train_size, val_size):
    for i, image_name in enumerate(image_names):
        # 正解データのファイル名
        label_name = image_name.replace('.jpg', '.txt')
        
        # train, val, testを区分けする
        if i < train_size:
            split = 'train'
        elif i < train_size + val_size:
            split = 'val'
        else:
            split = 'test'
        
        # データのコピー元
        source_image_path = f'download/{animal}/images/{image_name}'
        source_label_path = f'download/{animal}/darknet/{label_name}'

        # データのコピー先
        target_image_folder = f'data/images/{split}'
        target_label_folder = f'data/labels/{split}'

        # コピーする
        shutil.copy(source_image_path, target_image_folder)
        shutil.copy(source_label_path, target_label_folder)

# 猫
split_dataset('cat', cat_images, train_size=400, val_size=50)

# 犬は3つ足りないので1つずつ減らす
split_dataset('dog', dog_images, train_size=399, val_size=49) 

これでデータの振り分けは出来たが、次に正解データの中身を理解しておこう。

8. 正解データの中身🔝

ちなみに、正解データ(*.txt)の中身はこんな感じ。

1 0.36750000000000005 0.6512604999999999 0.46125000000000005 0.402427
1 0.806875 0.31792699999999996 0.24625000000000008 0.294118

最初は0か1で、猫か犬かを表している。

次の4つの数値はでBounding Boxの中央のx値とy値、幅wと高さhを画像のサイズに対して相対的に(0~1)で表している。

試しに訓練用のデータで表示してみる。ここでも画像を表示するためにmatplotlibを使う。

from PIL import Image, ImageDraw
import matplotlib.pyplot as plt
import numpy as np


def show_bbox(image_path):
    # image_pathのフォルダ名と拡張子を変更してラベルファイルのパスを作る
    label_path = image_path.replace('/images/', '/labels/').replace('.jpg', '.txt')

    # 画像を開き、描画ようにImageDrawを作る
    image = Image.open(image_path)
    draw = ImageDraw.Draw(image)

    with open(label_path, 'r') as f:
        for line in f.readlines():
            # 一行ごとに処理する
            label, x, y, w, h = line.split(' ')

            # 文字から数値に変換
            x = float(x)
            y = float(y)
            w = float(w)
            h = float(h)

            # 中央位置と幅と高さ => 左上、右下位置
            W, H = image.size
            x1 = (x - w/2) * W
            y1 = (y - h/2) * H
            x2 = (x + w/2) * W
            y2 = (y + h/2) * H

            # BoundingBoxを赤線で囲む
            draw.rectangle((x1, y1, x2, y2), outline=(255, 0, 0), width=5)
            
    plt.imshow(np.array(image))
    plt.show()
    

show_bbox('data/images/train/00a1ab47b5439e8c.jpg')

こんな感じになる。

9. 転移学習の準備🔝

データセットの準備ができたので、転移学習の準備に入る。

まず、YOLOv5のレポジトリをクローンしてから、必要なライブラリをインストールする。

すでにYOLOv5が現在フォルダになっているはずだが、一応確認してほしい。

YOLOv5のフォルダ内で以下を実行する。gitを実行するには!gitとする必要がある。

!git clone https://github.com/ultralytics/yolov5

そして依存ライブラリをインストールする。

pip install -U -r yolov5/requirements.txt

私の環境では以下のエラーが出た。

要するに、PIL, matplotlib, mpl_toolkits, numpy, pandasをインストールし直したのでリスタートしろとのこと。素直に従い、「RESTART RUNTIME」をクリックする。

リスタートすると現在フォルダが/contentに戻ってしまうので、再びYOLOv5に戻る。

cd YOLOv5

そしてもう一度依存ライブラリのインストールを実行。

pip install -U -r yolov5/requirements.txt

今度は問題なく完了。


今回も小さいモデルであるyolov5sを使う。

10. バックボーンを固定する🔝

次に、YOLOv5のモデルのウェイトを固定して転移学習中に変更されないようにする。

このセクションは、Transfer Learning with Frozen Layersを参考にしている。

Google Colab環境でファイルを見るには左にあるフォルダのアイコンをクリックする。

ダブルクリックすればファイルを開けることができる。

yolov5/models/yolov5s.yamlを見ると最初の10個の層が特徴量を取り出すバックボーン(Backbone)になっている。

# YOLOv5 backbone 
 backbone: 
   # [from, number, module, args] 
   [[-1, 1, Focus, [64, 3]],  # 0-P1/2 
    [-1, 1, Conv, [128, 3, 2]],  # 1-P2/4 
    [-1, 3, BottleneckCSP, [128]], 
    [-1, 1, Conv, [256, 3, 2]],  # 3-P3/8 
    [-1, 9, BottleneckCSP, [256]], 
    [-1, 1, Conv, [512, 3, 2]],  # 5-P4/16 
    [-1, 9, BottleneckCSP, [512]], 
    [-1, 1, Conv, [1024, 3, 2]],  # 7-P5/32 
    [-1, 1, SPP, [1024, [5, 9, 13]]], 
    [-1, 3, BottleneckCSP, [1024, False]],  # 9 
   ] 
  
 # YOLOv5 head 
 head: 
   [[-1, 1, Conv, [512, 1, 1]], 
    [-1, 1, nn.Upsample, [None, 2, 'nearest']], 
    [[-1, 6], 1, Concat, [1]],  # cat backbone P4 
    [-1, 3, BottleneckCSP, [512, False]],  # 13 
  
    [-1, 1, Conv, [256, 1, 1]], 
    [-1, 1, nn.Upsample, [None, 2, 'nearest']], 
    [[-1, 4], 1, Concat, [1]],  # cat backbone P3 
    [-1, 3, BottleneckCSP, [256, False]],  # 17 (P3/8-small) 
  
    [-1, 1, Conv, [256, 3, 2]], 
    [[-1, 14], 1, Concat, [1]],  # cat head P4 
    [-1, 3, BottleneckCSP, [512, False]],  # 20 (P4/16-medium) 
  
    [-1, 1, Conv, [512, 3, 2]], 
    [[-1, 10], 1, Concat, [1]],  # cat head P5 
    [-1, 3, BottleneckCSP, [1024, False]],  # 23 (P5/32-large) 
  
    [[17, 20, 23], 1, Detect, [nc, anchors]],  # Detect(P3, P4, P5) 
   ] 

以前は、この10層を固定するために、yolov5/train.pyを直接変更していたが、今は--freeze 10と指定して実行するだけで良くなっている。

これだけで、最初の10層のウェイトが固定され訓練中に変更されなくなります。

11. 設定YAMLファイルを作る🔝

訓練する際に必要な設定YAMLファイルを作ります。

Google Colab環境では、YOLOv5のフォルダが表示されているところを右クリックするとメニューが出てくる。

New fileを選んでファイルをYOLOv5フォルダの中にcats_and_dogs.yamlというファイルを作成します。

# データセットの場所
train: data/images/train
val:   data/images/val
test:  data/images/test

# クラスの数(nc = number of classes)
nc: 2

# クラスの名前(0が猫、1が犬)
names: ['cat', 'dog']

ファイルのセーブはMacOSならCommand+S、WindowsならControl+S。

これで訓練の準備ができた。

12. 訓練を実行する🔝

以下のようにして訓練を実行する。

ここでも!が必要。また、--freeze 10を忘れなく。

!python yolov5/train.py --data cats_and_dogs.yaml --weights yolov5s.pt --freeze 10 --epochs 100 --batch 16

バッチサイズはデフォルトの16にしました。以前、CPUでやった時は4だったのでだいぶ速くなるはず。

エポックは100も必要ないが、そのくらいあれば十分。

出力を見るとGPUが使われているのが分かる。

13. Tensorboardを使う🔝

リアルタイムではないが、TensorBoardを使うこともできる。

やり方は簡単。以下のマジック命令を実行するだけ。ちなみにSafariブラウザーでは起動しなかった。ChromeはOK。

%load_ext tensorboard
%tensorboard --logdir yolov5/runs

以下のように表示される。

14. 訓練後のモデル評価🔝

訓練が終わると、runs/train/exp/weightsにモデルのウェイトのファイルが二つ格納されている。

  • best.pt 一番成績が良かったモデル
  • last.pt 一番最後のエポックのモデル

ちなみにruns/train/exp/weightsexpexperiment(実験)の略で、訓練を実行するたびに新しいフォルダ (exp2、 exp3、..)と自動で作られる。

では、これらのモデルのウェイトを使ってテストのデータセットで評価をする。

!python yolov5/val.py --data cats_and_dogs.yaml --weights yolov5/runs/train/exp/weights/best.pt

以下のような結果が出た、以前より性能が増しているようにも見える。

Class Images Labels P     R     mAP@.5 mAP@.5:.95:
  all     99    113 0.823 0.735 0.847  0.612
  cat     99     54 0.889 0.741 0.87   0.61
  dog     99     59 0.758 0.729 0.823  0.614

参照【物体検出】mAP (mean Average Precision)とは? その目的と計算方法を解説します



コメントを残す