こんにちは。
キャスレーコンサルティングSI(システム・インテグレーション)部の杉です。

前回の続きです。
chainerを動かして画像を学習・テストさせるところまで目指していきたいと思います。

現時点で人工知能(AI)と呼ぶのはおこがましいですが、
画像を認識させる過程を実際にchainerを使うことで理解していきましょう。

利用した画像データについて

まず、機械学習を行う上での課題としては、

  • 画像のサンプルをありったけ用意する
  • 計算スピードを向上させる

ということが挙げられます。
機械学習の専門家であれば、GPUを購入して(大体5万円前後で買えるようです)、計算速度を上げて認識するプログラムを実行します。
場合によっては数日間動かしっぱなしで学習させることも珍しくありません。

今回はchainerで試しにウサギの種別判別プログラムを組んでみました。
(限定的な種別ですが)

因みに、他にもDeepLearning用のライブラリはいくつかあって、GoogleのTensorFlowが有名です。
chainerを使った理由としては、コーディングがシンプルにできるのと、導入時の敷居が高くなかったためです。
(とはいえ、厄介な点もあるのですが、そこは今後のアップデートを待って、、、)

横道に逸れてしまいましたが本題に入ります。

用意した画像

  •  特定の種類のウサギ以外の画像 ・・・ 4300枚
  •  特定の種類のウサギの画像 ・・・ 400枚ずつ

ウサギの画像は4種類で

  1. ネザーランドドワーフ
  2. アンゴラ(フワフワなやつ)
  3. ロップイヤー(耳が垂れてるやつ)
  4. ライオンラビット(タテガミのような毛があるやつ)

を100個の画像から回転・輝度を変化させて水増ししてます。
それ以外の種類のウサギを0のフォルダに入れてます。
(レッキスとかホトとかダッチとか、、、)

今回はchainerを試してみることが第一目標なので、画像数を上げるまでは手が回りませんでした。
本当なら全体的なデータであれば少なくとも2~3万は必要でしょう。

これで準備は完了です。
ということで後述の学習・テストコードを実行したのですが、
chainerで読み込める形に正規化する箇所でコケてしまいました。
うまく動かないので調べてみると、どうやら画像のサイズを統一しなければいけなかったみたいです。
(ここでは、読み込んだ画像を50×50に変換しました。32×32とか色々あるらしいですが、
よく分からなかったので50でやってます)

pythonソースコード

  • train_tech_blog_sample.py(今回のウサギ判別用の学習・テストコード)
  • changeImage_tech_blog_sample.py(画像50×50のリサイズ用コード)
  • readDataフォルダ
    • 1~4フォルダ配下に画像を400枚ずつ(最後の50枚はテスト用)
    • 0には特定のウサギ以外の画像を入れている
  • resizedData
    • readDataを50×50にリサイズした画像データを0~4フォルダ配下にそのまま配置
ネザーランドドワーフのフォルダ(▼こんな感じで50×50のサイズにしてchainerに突っ込む)

機械学習_8_画像_ネザラ

画像50×50のリサイズ用コード(changeImage_tech_blog_sample.py)

# -*- coding:utf-8 -*-

from PIL import Image
import os

# 画像の読込み
def readImg(imgName, index):
#画像が保存されてるディレクトリへのpath
     imgNames = os.listdir("./readData/" + str(index))
     try:
         # pathは上で設定したフォルダと同じ場所
         img_src = Image.open("./readData/" + str(index) + "/" + imgName)
     except:
         # imageファイル以外を読み込んだ場合
         print("{} is not image file!".format(imgName))
         img_src = 1
     return img_src

# 各フォルダの画像を50×50にリサイズする
for i in range(0,5):
    imgNames = os.listdir("./readData/" + str(i))
    for imgName in imgNames:
        img_src = readImg(imgName, i)
        if img_src == 1:continue
        else:
            # 50×50にリサイズする
            resizedImg = img_src.resize((50,50))
            resizedImg.save("./resizedData/" + str(i) + "/" + "resized_"+imgName)
            print(imgName+" is resized!")

学習とテスト(train_tech_blog_sample.py)


# -*- coding: utf-8 -*-
#!/usr/bin/env python
import argparse
import cPickle

import numpy as np
import six
import cv2
import os
import six.moves.cPickle as pickle

import chainer
from chainer import computational_graph as c
from chainer import cuda
import chainer.functions as F
from chainer import optimizers

parser = argparse.ArgumentParser(description='Training Program')
parser.add_argument('--gpu', '-g', default=-1, type=int,
                    help='GPU ID (negative value indicates CPU)')
parser.add_argument('--model', '-m', default=0, help ='Trained Model')
args = parser.parse_args()

# 1回の学習で使う画像の枚数
batchNumber = 50
# 繰り返しの回数
epochNumber = 10

# 学習用、テスト用のデータセット
x_train = []
x_test = []
y_train = []
y_test = []

# 画像データ
rabit_num = 4300 # ウサギの画像枚数
other_num = 400 # ウサギ以外の画像枚数
test_num = 50 # テスト用の画像枚数

# 画像を読み込んで学習・テスト用の配列にデータを詰める
for i in range(0,5):
    # 画像ファイルの読み込み
    images = os.listdir('./resizedData/'+str(i))

    if i == 0:
        # ウサギ以外の画像枚数
        image_num = rabit_num
    else:
    # ウサギの画像枚数
    image_num = rabit_num

    for j in range(len(images)):
        if j == image_num:
            break

if images[j].find('.png') > 0 or images[j].find('.jpg') > 0 or images[j].find('.jpeg') > 0:

# 最後のtest_num枚はテストデータとして活用する
if image_num - j > test_num:
x_train.append(cv2.imread('./resizedData/'+str(i)+'/'+images[j]))
            y_train.append(i)
        else:
            x_test.append(cv2.imread('./resizedData/'+str(i)+'/'+images[j]))
            y_test.append(i)

# chainerで読み込める形に正規化してnumpy.arrayに変換
x_train = np.array(x_train).astype(np.float32).reshape((len(x_train), 3, 50, 50)) / 255
y_train = np.array(y_train).astype(np.int32)
x_test = np.array(x_test).astype(np.float32).reshape((len(x_test), 3, 50, 50)) / 255
y_test = np.array(y_test).astype(np.int32)

# --- モデルの設定 ---
if args.model != 0:
    if args.gpu >= 0:
        cuda.init(args.gpu)
    model = pickle.load(open(args.model,'rb'))
else:
    model = chainer.FunctionSet(conv1= F.Convolution2D(3, 16, 5, pad=2),
                                conv2= F.Convolution2D(16, 32, 5, pad=2),
                                l3= F.Linear(6272, 256),
                                l4= F.Linear(256, 5))
    if args.gpu >= 0:
        cuda.init(args.gpu)
        model.to_gpu()

# 伝播の設定
def forward(x_data, y_data, train=True):
    x, t = chainer.Variable(x_data, volatile=not train), chainer.Variable(y_data, volatile=not train)
    h = F.max_pooling_2d(F.relu(model.conv1(x)), ksize = 5, stride = 2, pad =2)
    h = F.max_pooling_2d(F.relu(model.conv2(h)), ksize = 5, stride = 2, pad =2)
    h = F.dropout(F.relu(model.l3(h)), train=train)
    y = model.l4(h)

    return F.softmax_cross_entropy(y, t), F.accuracy(y, t)

# オプティマイザーの設定
optimizer = optimizers.Adam()
optimizer.setup(model.collect_parameters())

# 学習とテスト
for epoch in six.moves.range(1, epochNumber + 1):
    print('epoch', epoch)
    trainImageNumber = len(y_train)
    testImageNumber = len(y_test)

    perm = np.random.permutation(trainImageNumber)

    # 学習
    sumAccuracy = 0
    sumLoss = 0
    for i in six.moves.range(0, trainImageNumber, batchNumber):
        x_batch = x_train[perm[i:i + batchNumber]]
        y_batch = y_train[perm[i:i + batchNumber]]

        if args.gpu >= 0:
            x_batch = cuda.to_gpu(x_batch)
            y_batch = cuda.to_gpu(y_batch)

        optimizer.zero_grads()
        loss, acc = forward(x_batch, y_batch)
        loss.backward()
        optimizer.update()

        if epoch == 1 and i == 0:
            with open("graph.dot", "w") as o:
                o.write(c.build_computational_graph((loss, )).dump())
            with open("graph.wo_split.dot", "w") as o:
                g = c.build_computational_graph((loss, ),
                o.write(g.dump())
            print('graph generated')

        sumLoss += float(loss.data) * len(y_batch)
        sumAccuracy += float(acc.data) * len(y_batch)

    print('train mean loss={}, accuracy={}'.format(
        sumLoss / trainImageNumber, sumAccuracy / trainImageNumber))

    # テスト
    sumAccuracy = 0
    sumLoss = 0
    for i in six.moves.range(0, testImageNumber, batchNumber):
        x_batch = x_test[i:i + batchNumber]
        y_batch = y_test[i:i + batchNumber]
        if args.gpu >= 0:
            x_batch = cuda.to_gpu(x_batch)
            y_batch = cuda.to_gpu(y_batch)

        loss, acc = forward(x_batch, y_batch, train=False)

        sumLoss += float(loss.data) * len(y_batch)
        sumAccuracy += float(acc.data) * len(y_batch)

    print('test mean loss={}, accuracy={}'.format(
        sumLoss / testImageNumber, sumAccuracy / testImageNumber))

    #学習後にモデルを保存
    pickle.dump(model, open('model'+str(epoch), 'wb'),-1)

import cPickle でエラーが発生する場合は、


import sys
sys.path.append('C:\Python27\Lib\site-packages')
import cPickle

とすればエラーが回避できるはず。。。

前述のとおり、フォルダ0にはウサギ以外の画像を、フォルダ1~4にはウサギの画像を入れておきます。
(事前に50×50にサイズを変換しておくこと)
大まかな処理の流れは、

  1.  フォルダ内の画像を読み込んでテスト用・学習用の配列に入れる
  2.  正規化してarray型に詰める(chainerで読み込ませるため)
  3.  畳み込み2層によるニューラルネットワークの実装
  4.  学習とテストを実行する

となっています。

3に関しては、今回は2層で行っていますが、
別の階層で実行するとどのくらい精度が上がるか?までは検証していません。。。
3層にするとCPUでは最早使い物にならなそうですが。そこは次回以降の課題ですね。

実行結果

機械学習_6_chainer実行結果

精度としては微妙ですね。正答率が低いです。。。
テスト5回目で53%行ったあとに50%まで下がっています。

課題としては、やはり前述のとおり、

  • 全体的に画像数が少ない
  • 2層ニューラルネットワークが最適ではないのと、その辺の考察が不十分

が大きいかなと思います。
※GPUで実行できていないのも課題

現在の画像認識は、既に『人間の域を超えている』とまで言われているそうなので、
まだまだ道のりは遠いようですね。

次回は画像数を増やして、ニューラルネットワークについてもう少し深く見ていきましょう。
また、学習結果をプロットで分かりやすく可視化させてみたいですね。
どのような画像を間違えたか?などなど(やらなきゃいけないことが多い。。。。)

次回はこれを起点にして正答率を上げることを目標にしていきます。