EnsekiTT Blog

EnsekiTTが書くブログです。

とりあえず取得したオタクの顔700枚で転移学習せずにGoogLeNetを目線検出に使ってみた話

つまりなにしたの?

前回画面上に5*7点の注視点を用意してそれぞれをガン見しているオタクこと私のキャプチャをノートPC据え付けのWebカメラで撮影した。
今回はこれを使ってGoogLeNetにどこを見ている画像なのかを判別してもらおうと思う。
ただ、今回は動作確認用に各ポイント20枚ずつというかなり少ないデータセットで動かしてみたのが今日のはなし。

f:id:ensekitt:20171203221715j:plain

*1

一昨日やり始めたらこんな記事が出ていた

shiropen.com
しゅごい。こういうのやりたい…

どんな手順でやるの?(前回から再掲)

1, データセット作成アプリを作る
2, 自分の顔でデータセットを作る→(苦行すぎるので一旦すくなめで実行)
ensekitt.hatenablog.com

3, 性能の良い画像解析系のCNNを流用(転移学習)して構築する→(今日はこころ転移学習無しでやった)
4, 色んな所を見ている顔のデータを学習する→(辛抱強く待つ)
5, 実際に稼働させて目線の座標がざっくり分かったら嬉しい

ディレクトリごとにわけられた多くの画像を読み込む方法

qiita.com
が同じような方法でデータセットを作っていたので、参考に写経させていただきつつ
LabeledImageDatasetを使うことにした
chainer.datasets.LabeledImageDataset — Chainer 3.2.0 documentation

from chainer.datasets import LabeledImageDataset
from itertools import chain
# 画像フォルダ
IMG_DIR = 'datas'
# 各注視点ごとのフォルダ
dnames = glob.glob('{}/*'.format(IMG_DIR))
# キャプチャのパス
fnames = [glob.glob('{}/*.jpg'.format(d)) for d in dnames
          if not os.path.exists('{}/ignore'.format(d))]
fnames = list(chain.from_iterable(fnames))

labels = [os.path.basename(os.path.dirname(fn)) for fn in fnames]
dnames = [os.path.basename(d) for d in dnames
          if not os.path.exists('{}/ignore'.format(d))]
labels = [dnames.index(l) for l in labels]

d = LabeledImageDataset(list(zip(fnames, labels)))

データセットの加工

さっきの記事を参考にデータセットを加工してみることにした。
今度はTransformDatasetを使った。
chainer.datasets.TransformDataset — Chainer 3.2.0 documentation

from chainer.datasets import TransformDataset
from PIL import Image

width, height = 224, 224

# 平均画像を用意する(これはCaffe Model Zooのモデルのときと変えてないの失敗した…
mean_image = np.ndarray((3, 224, 224), dtype=np.float32)
mean_image[0] = 103.939
mean_image[1] = 116.779
mean_image[2] = 123.68

# 各データに行う変換
def transform(inputs):
    img, label = inputs
    img = img[:3, ...]
    img = img.astype(np.uint8)
    img = Image.fromarray(img.transpose(1, 2, 0))
    img = img.resize((width, height), Image.BICUBIC)
    img = np.asarray(img).transpose(2, 0, 1)
    img = img - mean_image
    return img, label

# 変換付きデータセットにする
td = TransformDataset(d, transform)

学習の設定

バッチサイズ50で5000epoch回してみた。
TrainとValidは8対2で分けた。
もちろん全然データセットとしては少ないけど、進むことまでは確認したかったのでとりあえずこれで実施する。

def train(train, test):
    model = L.Classifier(GoogLeNet())
    dev = 0
    if dev >= 0:
        chainer.cuda.get_device(dev).use()
        model.to_gpu()

    optimizer = chainer.optimizers.Adam()
    optimizer.setup(model)

    train_iter = chainer.iterators.SerialIterator(train, 50)
    test_iter = chainer.iterators.SerialIterator(test, 50,repeat=False, shuffle=False)

    epoch = 5000
    updater = training.StandardUpdater(train_iter, optimizer, device=dev)
    trainer = training.Trainer(updater, (epoch, 'epoch'), out="result")
    
    # Evaluator
    trainer.extend(extensions.Evaluator(test_iter, model, device=dev))

    # LogReport
    trainer.extend(extensions.LogReport())

    # PrintReport
    trainer.extend(extensions.PrintReport( entries=['epoch', 'main/loss', 'main/accuracy','validation/main/loss', 'validation/main/accuracy', 'elapsed_time' ]))

    print("run")
    trainer.run()

コードはこちら

github.com

一旦結果

epoch       main/loss   main/accuracy  validation/main/loss  validation/main/accuracy  elapsed_time
4995        0.108707    0.973333       6.63872               0.299291                  15884.8       
4996        0.096381    0.978182       5.98058               0.374752                  15887.8       
4997        0.0417148   0.991667       6.02118               0.36766                   15891.1       
4998        0.0102915   0.998333       6.42415               0.333475                  15894.3       
4999        0.00949544  0.996667       6.19799               0.381418                  15897.6       
5000        0.00411291  0.998182       6.61703               0.368085                  15900.7       

完全に過学習気味なので、とりあえずデータセットを増やしていけば良さそう。
もう少しデータを増やしたら平均画像もこれ用に更新しようと思う。

クリエイティブ・コモンズ・ライセンス
この 作品 は クリエイティブ・コモンズ 表示 4.0 国際 ライセンスの下に提供されています。