画像処理とか機械学習とか

画像処理や機械学習関連の事について気まぐれで書いていきます。歩行者検出関係が多いと思います。ハリネズミもたまに出現します。

chainerのimagenetサンプルで好きな画像サイズで入力する方法

chainerのサンプルにあるimagenetは,1000クラスの大規模画像分類用に設計されたネットワークである,Network In Network や,AlexNet, GoogLeNetなどを使うことが出来ます。

入力画像のサイズは256×256となっており,train_imagenet.pyの学習プログラムの内部でランダムにクリッピングをすることで,学習データを増やし,多少のズレに頑健なネットワークの学習が可能となっています。

このサンプルソースを使って,自分で用意した学習データを使って学習させたい場合,入力画像のサイズや,アスペクト比が違うこともあると思います。

そういう場合に、どこを変更すれば簡単に動くようになるかを紹介したいと思います。

まず、train_imagenet.pyのプログラムから
PreprocessedDatasetクラスの変更部分がこちらです。

class PreprocessedDataset(chainer.dataset.DatasetMixin):

    def __init__(self, path, root, mean, crop_size_x, crop_size_y random=True):
        self.base = chainer.datasets.LabeledImageDataset(path, root)
    self.mean = mean
        self.crop_size_x = crop_size_x
        self.crop_size_y = crop_size_y
        self.random = random

    def __len__(self):
        return len(self.base)

    def get_example(self, i):
        # It reads the i-th image/label pair and return a preprocessed image.
        # It applies following preprocesses:
        #     - Cropping (random or center rectangular)
        #     - Random flip
        #     - Scaling to [0, 1] value
        crop_size_x = self.crop_size_x
        crop_size_y = self.crop_size_y

        image, label = self.base[i]
        _, h, w = image.shape

        if self.random:
            # Randomly crop a region and flip the image
            top = random.randint(0, h - crop_size_y - 1)
            left = random.randint(0, w - crop_size_x - 1)
            if random.randint(0, 1):
                image = image[:, :, ::-1]
        else:
            # Crop the center
            top = (h - crop_size_y) // 2
            left = (w - crop_size_x) // 2
        bottom = top + crop_size_y
        right = left + crop_size_x

        image = image[:, top:bottom, left:right]
        image -= self.mean[:, top:bottom, left:right]
        image /= 255
        return image, label

このクラスは、初期化を行う部分と,データセットを読み込む際にデータをクリッピングする操作が含まれています。
元のプログラムは正方形の256×256ピクセルの画像が入力される前提となっているため,縦長や横長の画像に対応するために
crop_sizeにcrop_size_xとcrop_size_yを加え、修正しています。

次はTestModeEvaluatorクラスです。
まず、meanファイルとデータセット読み込みの部分で、先ほどcrop_size_xとcrop_size_yを加えた部分の修正をこちらでも行います。
model.insizeは、モデルファイルの中に書かれている入力画像のサイズを取得している部分です。
縦横違うサイズの場合は変更、もしくは追加する必要があります。

 # Load the datasets and mean file
    mean = np.load(args.mean)
    train = PreprocessedDataset(args.train, args.root, mean, model.insize_x, model.insize_y)
    val = PreprocessedDataset(args.val, args.root, mean, model.insize_x, model.insize_y False)

次にモデルファイルです。
今回は例としてNetwork In Networkを用います。

import math

import chainer
import chainer.functions as F
import chainer.links as L


class NIN(chainer.Chain):

    """Network-in-Network example model."""

    insize_x = 227 #横の入力画像サイズ(クリッピング後のサイズ)
    insize_y = 227 #縦の入力画像サイズ(クリッピング後のサイズ)

    #入力画像サイズに合わせて、畳み込みなどのフィルタサイズを変更してください
    def __init__(self):
        w = math.sqrt(2)  # MSRA scaling
        super(NIN, self).__init__(
            mlpconv1=L.MLPConvolution2D(
                None, (96, 96, 96), 11, stride=4, wscale=w),
            mlpconv2=L.MLPConvolution2D(
                None, (256, 256, 256), 5, pad=2, wscale=w),
            mlpconv3=L.MLPConvolution2D(
                None, (384, 384, 384), 3, pad=1, wscale=w),
            mlpconv4=L.MLPConvolution2D(
                None, (1024, 1024, 1000), 3, pad=1, wscale=w),
        )
        self.train = True

    def __call__(self, x, t):
        h = F.max_pooling_2d(F.relu(self.mlpconv1(x)), 3, stride=2)
        h = F.max_pooling_2d(F.relu(self.mlpconv2(h)), 3, stride=2)
        h = F.max_pooling_2d(F.relu(self.mlpconv3(h)), 3, stride=2)
        h = self.mlpconv4(F.dropout(h, train=self.train))
        h = F.reshape(F.average_pooling_2d(h, 6), (x.data.shape[0], 1000))

        loss = F.softmax_cross_entropy(h, t)
        chainer.report({'loss': loss, 'accuracy': F.accuracy(h, t)}, self)
return loss

Softmaxって何をしてるの?

ニューラルネットワークの出力は例えばニューロンが一つの場合は以下のようになります。

f:id:hiro2o2:20160203215759p:plain

f:id:hiro2o2:20160203220113p:plain

  • 各ノードの出力

まず、それぞれの入力xに重みwを掛け合わせ、全て足します。そして、閾値θを引いた式を、活性化関数に入力した結果が出力yとなります。活性化関数には、様々種類があり古いものではtanhやsigmoid関数などが使われてきました。

しかし、勾配消失問題というニューラルネットワークの層が深くなるにつれて、学習の際に勾配が0に近くなり、低い層で誤差が消失する問題がありました。
この問題に対応するために開発されたのが、ReLU(rectified linear unit)やMaxOutという活性化関数です。これらの手法では誤差消失問題を起こさないため、深い層のネットワークでも学習が可能となり、現在多くのネットワークで採用されています。

  • Softmaxとは

このような活性化関数を通して出力される値は、使用する活性化関数によっては負の値が出てきたりと、そのままでは扱いづらいです。そのため、この出力を確率に変換する式がSoftmaxになります。

以下の式がSoftmaxの式で、複数のノードの出力yiがある場合の各出力結果の確率piは以下の式で表すことができます。expで出力結果を正の値へ変換し、全ての出力のexpの和で割る事で、確率に変換する事ができます。
f:id:hiro2o2:20160721013200p:plain

f:id:hiro2o2:20160721013204p:plain

ディープラーニングのネットワークの出力の最後にほぼこのSoftmaxが付いていると思いますが、上記のように、ニューラルネットワークの出力結果を確率に変換することで、各クラスの確率を表現しています。

Windowsでchainerを使って画像を説明する文章を自動生成するサンプルを実行する方法

qiita.com

今回はこちらの記事で紹介されている、畳み込みニューラルネットワーク+時系列ディープラーニングを用いる手法を紹介します。

上記記事のサンプルでは、画像とそれを説明する文章データを学習させ、入力画像を説明する文章を自動生成するネットワークを実装されたと言うことで、実際に動かす方法を日本語で説明を加えて紹介します。

・概要図(画像中で単語と文字と表記がブレてますが、同じものを指してます)
f:id:hiro2o2:20160701170213p:plain

実際にdsannoさんが実装されたプログラムが
GitHub - dsanno/chainer-image-captionこちらになります。
動作に必要なのが

  1. Chainerをインストール済みのPC
  2. 学習データ(ダウンロード
  3. Caffeの学習済みネットワーク(ダウンロード

の3つになります。このサンプルでは画像のベクトル化に畳み込みニューラルネットワークはCaffeの学習済みモデルを用い、文章を自動生成する部分に時系列ディープラーニングを用いて、時系列ディープラーニングの部分を学習させます。

  • 文章生成モデルの学習

まず、ダウンロードしたデータセットを解凍し、dataset.json と vgg_feats.matをサンプルのルートフォルダにコピーします。
サンプルのルートフォルダでCtrl+右クリックを押して、コマンドウィンドウをここで開くを選択し、コマンドウィンドウを開きます。

$ python src/convert_dataset.py dataset.json dataset.pkl

コマンドウィンドウで上記コマンドを実行し、データセットを変換します。

そして、下記コマンドで学習を行い、文章を生成するためのモデルを学習します。
GPUを使用するので-g 0 をつけています。

$ python src/train.py -g 0 -s dataset.pkl -i vgg_feats.mat -o model/caption_gen
  • ダウンロードしたCNNの準備

画像を扱うために、ダウンロードしたCaffeモデルをpklに変換するために以下のコマンドを実行します。

$ python src/convert_caffemodel_to_pkl.py VGG_ILSVRC_19_layers.caffemodel vgg19.pkl
  • 画像から文章を自動生成

以下のコマンドを実行すると、画像からキャプションを付けるサンプルが実行できます。

$ python src/generate_caption.py -s dataset.pkl -i vgg19.pkl -m model/caption_gen_0010.model -l image/list.txt



以下に、サンプルのtrain.pyにソースコード理解の為にコメントを加えた物を載せます。
python初心者なので、間違っている部分もあるかもしれませんが、参考までに。

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

import argparse
import cPickle as pickle
import json
import numpy as np
import scipy.io
import random

#chainerライブラリの読み込み
import chainer
from chainer import cuda, Variable, optimizers, serializers, functions as F
from chainer.functions.evaluation import accuracy

#net.pyファイルに定義したネットワークの読み込み
from net import ImageCaption

import time

#--------------------------------------------------------------------------#
#   引数の設定                                                             #
#--------------------------------------------------------------------------#
parser = argparse.ArgumentParser(description='Train image caption model')
parser.add_argument('--gpu', '-g', default=-1, type=int,
                    help='GPU ID (negative value indicates CPU)')
parser.add_argument('--sentence', '-s', required=True, type=str,
                    help='input sentences dataset file path')
parser.add_argument('--image', '-i', required=True, type=str,
                    help='input images file path')
parser.add_argument('--model', '-m', default=None, type=str,
                    help='input model and state file path without extension')
parser.add_argument('--output', '-o', required=True, type=str,
                    help='output model and state file path without extension')
parser.add_argument('--iter', default=100, type=int,
                    help='output model and state file path without extension')
args = parser.parse_args()

gpu_device = None
args = parser.parse_args()
xp = np

#gpuを使用する際
if args.gpu >= 0:
    cuda.check_cuda_available()
    gpu_device = args.gpu
    cuda.get_device(gpu_device).use()
    xp = cuda.cupy

#--------------------------------------------------------------------------#
#   データセットの設定                                                    #
#--------------------------------------------------------------------------#

#言語のデータセットを読み込む
with open(args.sentence, 'rb') as f:
    sentence_dataset = pickle.load(f)
    
#画像データセットをmatファイルから読み込む
image_dataset = scipy.io.loadmat(args.image)
images = image_dataset['feats'].transpose((1, 0))

#読み込んだデータセットへの名前をわかりやすく変更
train_image_ids = sentence_dataset['images']['train']
train_sentences = sentence_dataset['sentences']['train']
test_image_ids = sentence_dataset['images']['test']
test_sentences = sentence_dataset['sentences']['test']
word_ids = sentence_dataset['word_ids']
feature_num = images.shape[1]

#隠れ層の数
hidden_num = 512

#バッチサイズ
batch_size = 128

#読み込んだデータセットの単語数を表示
print 'word count: ', len(word_ids)

#画像にキャプションをつけるネットワークのインスタンス化(単語数, 画像の次元数, 隠れ層の数)
caption_net = ImageCaption(len(word_ids), feature_num, hidden_num)
if gpu_device is not None:
    caption_net.to_gpu(gpu_device)
#最適化手法の設定(ここではAdam)
optimizer = optimizers.Adam()
optimizer.setup(caption_net)

#引数で初期化にモデルが与えられていたら、モデルを読み込む
#(本プログラムでは、CNNにCaffeの学習済みモデルを使用しているため、必須)
if args.model is not None:
    serializers.load_hdf5(args.model + '.model', caption_net)
    serializers.load_hdf5(args.model + '.state', optimizer)

#文字コード関係
bos = word_ids['<S>']
eos = word_ids['</S>']
unknown = word_ids['<UNK>']

#--------------------------------------------------------------------------#
#   関数の定義                                                          #
#--------------------------------------------------------------------------#

    #----------------------------------------------------------------------#
    #   学習データの順番をランダムに入れ替える                             #
    #----------------------------------------------------------------------#
def random_batches(image_groups, sentence_groups):
    batches = []

    #引数で与えられた画像群と文章群の要素を代入しながら要素分繰り返す
    for image_ids, sentences in zip(image_groups, sentence_groups):
        #文章の長さ
        length = len(sentences)
        #indexは0からlengthまでのint32型の整数が入った配列
        index = np.arange(length, dtype=np.int32)
        #indexの配列をランダムにシャッフル
        np.random.shuffle(index)

        #0から値をbatch_sizeだけlengthまで増やしながらnに代入し、繰り返す
        for n in range(0, length, batch_size):
            #batch_indexは、シャッフル後のindex中のn~(n+batch_size-1)までのインデックスをコピーしたもの
            batch_index = index[n:n + batch_size]
            #batch_indexのimage_idsとsentencesをbatchesに追加
            batches.append((image_ids[batch_index], sentences[batch_index]))

    #batchesをさらにシャッフルする
    random.shuffle(batches)
    return batches


    #----------------------------------------------------------------------#
    #   image_idsとsentencesからグループを作成                             #
    #----------------------------------------------------------------------#
def make_groups(image_ids, sentences, train=True):
    #学習の場合
    if train:
        #境界を固定値で設定
        boundaries = [1, 6, 11, 16, 21, 31, 41, 51]
    else:
        #境界を1~40までの整数値で設定
        boundaries = range(1, 41)

    sentence_groups = []
    image_groups = []

    #zipは、複数オブジェクトを同時にループで回す際に使用する
    #boundariesのbeginが0番目~最後から-1番目の値、endが1番目~最後の値
    for begin, end in zip(boundaries[:-1], boundaries[1:]):

        #beginからendまでのリストに対してlambda関数を適用し、それをsumする
        #begin=1, end=6なら、[1,2...5,6]の各値(sentences[1]の長さ~sentences[6]の長さを足す)=size
        size = sum(map(lambda x: len(sentences[x]), range(begin, end)))

        #size×(end+1)の行列を作る 要素は文章の終わりの記号eosで初期化
        sub_sentences = np.full((size, end + 1), eos, dtype=np.int32)
        #文章の始めの記号bosを設定
        sub_sentences[:, 0] = bos

        #sub_image_idsにsize分のゼロ行列を作成
        sub_image_ids = np.zeros((size,), dtype=np.int32)
        offset = 0

        #現在注目しているbeginとendの配列の要素を一つずつnに代入し、要素数分ループ
        for n in range(begin, end):
            #sentence[n]の長さ
            length = len(sentences[n])
            #長さが0より大きい場合
            if length > 0:
                #sub_sentencesに今注目しているsentence[n]を代入
                sub_sentences[offset:offset + length, 1:n + 1] = sentences[n]
                #sub_image_idsに今注目しているimage_ids[n]を代入
                sub_image_ids[offset:offset + length] = image_ids[n]
            #代入した文字数分だけoffset(代入の開始位置)をずらす    
            offset += length

        #現在のsub_sentencesをグループとしてsentence_groupsに追加    
        sentence_groups.append(sub_sentences)
        #現在のsub_image_idsをグループとしてimage_groupsに追加
        image_groups.append(sub_image_ids)
        
    return image_groups, sentence_groups

    #----------------------------------------------------------------------#
    #   順伝播の操作                                                       #
    #----------------------------------------------------------------------#
def forward(net, image_batch, sentence_batch, train=True):
    #chainerで扱える型(Variable型)へ配列を変換
    images = Variable(xp.asarray(image_batch), volatile=not train)
    
    n, sentence_length = sentence_batch.shape

    #imagesでnetを初期化
    net.initialize(images, train=train)

    #変数を初期化
    loss = 0
    acc = 0
    size = 0

    #i=0~sentence_batchの長さだけ繰り返し
    for i in range(sentence_length - 1):
        #sentence_batch配列で要素[:, i]がeosでなければ1, eosなら0を返し,targetは1,0が入った配列になる
        target = xp.where(xp.asarray(sentence_batch[:, i]) != eos, 1, 0).astype(np.float32)
        #現在のターゲットが全てeosの場合
        if (target == 0).all():
            break

        #ターゲットに文章が入っている場合
        #入力(文章)
        x = Variable(xp.asarray(sentence_batch[:, i]), volatile=not train)
        #1時刻先の入力
        t = Variable(xp.asarray(sentence_batch[:, i + 1]), volatile=not train)
        #LSTMで予測した次の単語確率の配列
        y = net(x, train=train)
        #最も高い確率のインデックスを取り出す
        y_max_index = xp.argmax(y.data, axis=1)

        # mask = 縦がtargetの長さ分、横が1次元の配列を作成
        # これをy.dataの横の長さ分繰り返す 
        mask = target.reshape((len(target), 1)).repeat(y.data.shape[1], axis=1)

        #予測した単語確率の配列 × maskをVariable型に変換したもの
        y = y * Variable(mask, volatile=not train)

        #損失を計算(予測したy, 真の次の時刻の単語t)
        loss += F.softmax_cross_entropy(y, t)
        #精度を計算
        acc += xp.sum((y_max_index == t.data) * target)
        #現在のターゲットの総単語数
        size += xp.sum(target)
        
    return loss / size, float(acc) / size, float(size)

    #----------------------------------------------------------------------#
    #   学習                                                               #
    #----------------------------------------------------------------------#
def train(epoch_num):
    #画像と単語の学習データグループを作成
    image_groups, sentence_groups = make_groups(train_image_ids, train_sentences)
    #画像と単語のテストデータグループを作成
    test_image_groups, test_sentence_groups = make_groups(test_image_ids, test_sentences, train=False)

    #epoch_num分繰り返す
    for epoch in range(epoch_num):

        #学習データグループからランダムでバッチを作成
        batches = random_batches(image_groups, sentence_groups)

        #変数を初期化
        sum_loss = 0
        sum_acc = 0
        sum_size = 0

        #バッチのサイズを計算
        batch_num = len(batches)

        #enumerateでインデックス付きで要素を取得し、batchesの最後までループ
        for i, (image_id_batch, sentence_batch) in enumerate(batches):

            #順伝播させる
            loss, acc, size = forward(caption_net, images[image_id_batch], sentence_batch)

            #chainerのライブラリを使用して誤差逆伝播させ、最適化手法でパラメータを更新
            optimizer.zero_grads()
            loss.backward()
            loss.unchain_backward()
            optimizer.update()

            #sentenceの長さを取得
            sentence_length = sentence_batch.shape[1]
            sum_loss += float(loss.data) * size
            sum_acc += acc * size
            sum_size += size

            #500回繰り返す毎に学習状況を出力
            if (i + 1) % 500 == 0:
                print '{} / {} loss: {} accuracy: {}'.format(i + 1, batch_num, sum_loss / sum_size, sum_acc / sum_size)

        #バッチの最後まで学習が終わったら、現在のepochでの学習状況を出力        
        print 'epoch: {} done'.format(epoch + 1)
        print 'train loss: {} accuracy: {}'.format(sum_loss / sum_size, sum_acc / sum_size)
        sum_loss = 0
        sum_acc = 0
        sum_size = 0

        #テストのループ image_idsにテスト画像、sentencesに文章をテストグループから代入しながら繰り返す
        for image_ids, sentences in zip(test_image_groups, test_sentence_groups):

            if len(sentences) == 0:
                continue
            size = len(sentences)

            # i=0~文章の長さまで,バッチサイズ分増やしながら繰り返す
            for i in range(0, size, batch_size):
                image_id_batch = image_ids[i:i + batch_size]
                sentence_batch = sentences[i:i + batch_size]

                #順伝播させる
                loss, acc, size = forward(caption_net, images[image_id_batch], sentence_batch, train=False)
                sentence_length = sentence_batch.shape[1]

                #誤差と精度を計算
                sum_loss += float(loss.data) * size
                sum_acc += acc * size
                sum_size += size

        #現在のパラメータにおける、テストでの精度を出力        
        print 'test loss: {} accuracy: {}'.format(sum_loss / sum_size, sum_acc / sum_size)

        #現在のepochでの学習済みモデルを出力
        serializers.save_hdf5(args.output + '_{0:04d}.model'.format(epoch), caption_net)
        serializers.save_hdf5(args.output + '_{0:04d}.state'.format(epoch), optimizer)

#メイン
train(args.iter)



※参考文献:Pythonによるデータ分析入門 ―NumPy、pandasを使ったデータ処理

リカレントニューラルネットワークとは?(RNN)

画像認識や音声認識などで用いられる一般的なDNNの手法は、ある時刻での推定を独立して行うことになります。しかし、動画から状態を認識したり、音声の意味を理解するためには、独立した識別だけでは十分であるとは言えません。そのため、前後の時系列情報を扱うことができるリカレントニューラルネットワーク(回帰型ニューラルネットワーク:RNN)という手法が提案されました。
f:id:hiro2o2:20160617231159p:plain

上の図のように、リカレントニューラルネットワークは前の時刻の中間層を次の時刻の入力と合わせて学習に用いることで、時系列情報を考慮したネットワーク構造となっています。

リカレントニューラルネットワークは、時系列情報を保持したネットワークで、展開するとDNNを時間方向につなげた大きなネットワークとみなすことができます。
そのため、リカレントニューラルネットワークの学習にもDNNと同様に誤差逆伝播法を用いることができます。特にリカレントニューラルネットワークでの学習は back-propagation through time法と呼ばれています。

RNNを実際に適応する例として、言語の予測が挙げられます。「今日は雲ひとつない晴天だ」という文章の最後の方の「晴天」を予測したいとすると、その前の文章の「雲ひとつない」という情報から関連付けて「晴天」を予測できます。これは、「晴天」という単語がこの文章だけから導くことが可能で、他の文章を必要としないためRNNが過去の学習結果を利用することができるからです。
このように単純な場合の学習がRNNで出来るのですが、長期の依存関係にある物の予測は難しく、学習ができないという問題があるそうです。

chainerのimagenetサンプルをopencvを使って動画に適用するサンプルコード

Windows環境にchainerが入っている前提で説明します。
特に難しい処理をしている訳ではないので、コードをなるべく書かずに気軽に試したい方向けです。

まず、opencvを普段使っていない方はPCにダウンロードしてください。

http://opencv.org/

上記リンクのOpenCV for Windowsをダウンロードし、exeファイルを実行して好きな場所に解凍してください。C直下などがおすすめです。
そして、解凍したフォルダを開き、「opencv」→「build」→「python」→「2.7」→「x64」→「cv2.pyd」というファイルをコピーします。
コピーしたcv2.pydを、「Python27」→「Lib」→「site-packages」の中に入れてください。Python27のフォルダはPythonをインストールした場所にあります。(基本はCドライブ)

これでpythonopencvが使えるようになりました。
動画への拡張には https://github.com/shi3z/chainer_imagenet_tools こちらのinspection.pyのコードをベースに作成しました。

画像を引数で渡す形式から、動画のパスを指定して実行する形式に変更しています。
サンプルプログラムでは、動画の1フレーム中の一つの注目領域だけをCNNに入力しているので、用途によって注目領域をループでずらして使うなどしたら良いと思います。

以下サンプルプログラム

#!/usr/bin/env python
# coding: UTF-8
"""Example code of learning a large scale convnet from ILSVRC2012 dataset.

Prerequisite: To run this example, crop the center of training and
validation images and scale them to 256x256, and make two lists of space-
separated CSV whose first column is full path to image and second column is
zero-origin label (this format is same as that used by Caffe's ImageDataLayer).

"""
from __future__ import print_function
import argparse
import datetime
import json
import multiprocessing
import random
import sys
import threading
import time

import numpy as np
from PIL import Image


import six
import cPickle as pickle
from six.moves import queue

import chainer
import matplotlib.pyplot as plt
import numpy as np
import math
import chainer.functions as F
import chainer.links as L
from chainer.links import caffe
from matplotlib.ticker import * 
from chainer import serializers
from chainer import cuda

import cv2
import time


parser = argparse.ArgumentParser(
    description='Image inspection using chainer')
parser.add_argument('--model','-m',default='model', help='Path to model file')
parser.add_argument('--mean', default='mean.npy',
                    help='Path to the mean file (computed by compute_mean.py)')
parser.add_argument('--gpu', '-g', default=-1, type=int,
                    help='GPU ID (negative value indicates CPU)')
args = parser.parse_args()
if args.gpu >= 0:
    cuda.check_cuda_available()
xp = cuda.cupy if args.gpu >= 0 else np

#データの拡張を行う
def read_image(input_img, center=False, flip=False):
  image = np.asarray(input_img).transpose(2, 0, 1)
  if center:
    top = left = cropwidth / 2
  else:
    top = random.randint(0, cropwidth - 1)
    left = random.randint(0, cropwidth - 1)
  bottom = model.insize + top
  right = model.insize + left
  image = image[:, top:bottom, left:right].astype(np.float32)
  image -= mean_image[:, top:bottom, left:right]
  image /= 255
  if flip and random.randint(0, 1) == 0:
    return image[:, :, ::-1]
  else:
    return image

import nin

mean_image = pickle.load(open(args.mean, 'rb'))


model = nin.NIN()
serializers.load_npz(args.model, model)
cropwidth = 256 - model.insize
if args.gpu >= 0:
    cuda.get_device(args.gpu).use()
    model.to_gpu()
else:
    model.to_cpu()



def predict(net, x):
    h = F.max_pooling_2d(F.relu(net.mlpconv1(x)), 3, stride=2)
    h = F.max_pooling_2d(F.relu(net.mlpconv2(h)), 3, stride=2)
    h = F.max_pooling_2d(F.relu(net.mlpconv3(h)), 3, stride=2)
    h = net.mlpconv4(F.dropout(h, train=net.train))
    h = F.reshape(F.average_pooling_2d(h, 6), (x.data.shape[0], 1000))
    return F.softmax(h)

#動画読み込み
cap = cv2.VideoCapture('./movie.avi')
fps = cap.get(cv2.CAP_PROP_FPS)

#動画中のCNNに入力する注目領域(用途に合わせてループ内でラスタスキャン等を行う)
x = 0
y = 0
h = 256
w = 256

#動画を開いている間の処理
while(cap.isOpened()):
 
 #1フレーム取り出す
 ret, frame = cap.read()

 #注目領域を切り出す
 dst = frame[y:y+h, x:x+w]

 #opencvの画像形式からPILの形式へ変換
 cv_rgb = dst[::-1, :, ::-1].copy()
 CV2PIL_img = Image.fromarray(cv_rgb)

 #データ拡張の操作
 img = read_image(CV2PIL_img)

 #CNNへ渡す形式へ変換
 x = np.ndarray(
         (1, 3, model.insize, model.insize), dtype=np.float32)
 x[0]=img
 x = chainer.Variable(np.asarray(x), volatile='on')

 score = predict(model,x)
 categories = np.loadtxt("labels.txt", str, delimiter="\t")

 top_k = 20
 prediction = zip(score.data[0].tolist(), categories)
 prediction.sort(cmp=lambda x, y: cmp(x[0], y[0]), reverse=True)

 for rank, (score, name) in enumerate(prediction[:top_k], start=1):
     if rank == 1:
       #ランクが1の場合の処理を書く
       print('#%d | %s | %4.1f%%' % (rank, name, score * 100))
       if name == '設定したラベル':
         #ランクが1で指定のラベルの場合の処理を書く
           
     


cap.release()

Windowsでchainerを使った畳み込みニューラルネットワークを用いたハリネズミの種類識別をやってみる

今回は独自のデータセットを用いてハリネズミの種類を分類したいと思います。
ハリネズミには何種類か色の種類があるのはご存知でしょうか?
今回はこのハリネズミ分類器をディープラーニングを用いて自動生成させたいと思います。

f:id:hiro2o2:20160423145221p:plain

1.アルビノ

f:id:hiro2o2:20160423145241p:plain

2.ノーマル

f:id:hiro2o2:20160423145255p:plain

3.パイド

f:id:hiro2o2:20160423145311p:plain

4.シナモン

 今回はこの4種類のハリネズミの分類をディープラーニングを用いて行いたいと思います。データセットはグーグル画像検索で拾ってきたデータをもとにリサイズ、切り抜き等の操作を行い、1~4までのカラーのハリネズミ画像をすべて90枚程度ずつの計366枚集めました。

f:id:hiro2o2:20160423150136p:plain

             作成した学習データの一部

(※画像はグーグル画像検索の結果から引用していますので、もし不都合があればすぐに消します。)

  • 学習の準備
    学習データ、テストデータのあるフォルダへのパスが書いたファイルを用意します。

    https://github.com/shi3z/chainer_imagenet_tools
    上記リンクの開発者さんのmake_train.pyを利用してtrain.txtとtest.txtを作成しました。ありがとございます。

  • 学習データの正規化を行う
    chainerのimagenetのサンプルコード内にcompute_mean.pyというコードがあるので、これを用いて平均画像を作ります。平均画像を用いる事で学習画像を正規化し、画像の輝度値をある一定の値域以内に収めるという処理を挟みます。

    >$ python compute_mean.py train.txt

  • 学習スタート!

    >$ python train_imagenet.py train.txt test.txt -g 0 > log.txt

    を実行するとgpuで学習が始まり、ログがlog.txtに書き出されます。
    学習データが少ないので、epoch 100まで回しました。学習の結果が以下のように収束していることが分かります。errorは誤答率で、学習の最終段階でも約1割程度間違えが起きてしまうようです。

    f:id:hiro2o2:20160423151255p:plain

  • 学習したモデルを用いた識別を行う
    学習が終わると、modelという学習済みのモデルのファイルが出来上がります。この学習済みモデルを用いて実際に識別してみましょう。識別のコードもhttps://github.com/shi3z/chainer_imagenet_tools
    こちらのinspection.pyというコードをお借りします。

    識別する画像は私の愛ハリのマロンの画像を用います。
    マロンはノーマルのハリネズミなので、識別結果がノーマルと出ればokです。

    画像1:顔のアップ

    f:id:hiro2o2:20160423151655p:plain
    識別結果
    f:id:hiro2o2:20160423151928p:plain
    尤も高いのがノーマルでその次がパイドとなっており、正しく識別できてます。

  画像2:おなか辺りのアップ

  f:id:hiro2o2:20160423151651p:plain

   識別結果

  f:id:hiro2o2:20160423152241p:plain
  この画像もノーマルが最も高く正解しています。

 

  画像3:横腹のアップ

  f:id:hiro2o2:20160423151653p:plain
  識別結果

  f:id:hiro2o2:20160423152459p:plain
  3回目にしてパイドが一番高い結果となり、間違いました。
  しかし、かなり良い精度で見分けることが出来るのではないでしょうか?
  

 

ちなみに、アルビノ、シナモン、パイドのハリネズミ画像の識別結果

f:id:hiro2o2:20160423153145p:plain

f:id:hiro2o2:20160423153538p:plain

 

f:id:hiro2o2:20160423153147p:plain

f:id:hiro2o2:20160423153705p:plain

f:id:hiro2o2:20160423153149p:plain

f:id:hiro2o2:20160423153749p:plain

それぞれ、テストデータの画像ですが、正しく識別できています。

 学習済みのモデルを配布しますので、もし試してみたいマニアな方はダウンロードしてみてください。inspection.pyを使って好きなハリネズミ画像を分類できます。

http://fast-uploader.com/file/7016949787728/
また、実際に識別する際にはこれに加え、labels.txtというテキストファイルにラベル(アルビノ、ノーマル、パイド、シナモン)を書いたもの必要です。

Windowsで超便利なディープラーニングのライブラリのchainerを使う方法

研究でも少しディープラーニングを扱っておきたかったので今回はディープラーニングを簡単に実装して動かすためのライブラリであるchainerを用いる方法を載せたいと思います。

公式ではWindowsに非対応ですが、mnistのサンプルとimagenetの学習まで独自のデータセットを用いてgpuで動かせたのでubuntuじゃなくても動かせます。(とりあえずは)

環境
f:id:hiro2o2:20160415160710p:plain:w300
日本の企業が提供しているDNN用のライブラリ
ドキュメントは英語のみの提供と日本人に厳しいS仕様
http://docs.chainer.org/en/stable/index.html

OS:Windows7
GPUNVIDIA TITAN X
Python2.7(64bitバージョン)
コンパイラMicrosoft Visual C++ Compiler for Python2.7

GPUを使う場合(cuDNNはダウンロードに会員登録が必要)
CUDA Toolkit, cuDNN

  • chainerをインストールするまで

Pythonコンパイラをインストール後、GPUを使う場合はCUDA Toolkitをインストールし、所定のディレクトリにcuDNNを配置した後にchainerをインストールします。cudaのディレクトリは標準ではC:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v6.5
の様な場所にあると思いますので、ダウンロードしたものをcudaのディレクトリにコピーします。

Pythonが正しくインストール出来たら、コマンドプロンプトを開いてpipコマンドが使えるはずです。まずはpip install -U pipとpip install -U setuptoolsをしましょう。その後pip install chainer で運が良ければインストールできます。エラーが出る場合は、エラーをよく読み、根気強く必要な物をインストールしたりする作業が必要です。アンインストールするときは pip uninstall chainerでアンインストールできます。

大体多いエラーは必要なものが無いというのが多かったのでpip install **** でインストールして再度チャレンジすれば良いと思います。コンパイラも忘れずにインストールしておかないとエラーが出ます。

  • 手書き文字認識mnistのサンプル

chainerのサンプルコードはgithubから入手できます。
https://github.com/pfnet/chainer

examplesの中のmnistというフォルダが手書き文字認識のサンプルプログラムです。
幾つか中身が入っていると思いますが、net.pyはネットワークが記述されたプログラム、train_mnist.pyが学習の流れが書かれたプログラムです。
使い方は簡単で、コマンドプロンプトをからpython train_mnist.py -g 0 で実行できます。
すると下の画像のようにずらずらと学習結果が出てくると思います。
f:id:hiro2o2:20160415163139p:plain

もし実行できない場合はpython train_mnist.py を試してみてください。
これが動けばchainerのインストールは成功してますがGPUのライブラリのインストールに失敗しています。
動かなければchainerが上手くインストールできてないです。

  • 画像分類のimagenetのサンプル

こちらのプログラムはサンプルそのままではmultiprocessing関係のエラーがでるのでWindowsで動きません。少しだけ修正が必要です。

以下train_imagenet.pyの一番下の部分

if __name__ == '__main__':
    # Invoke threads
    feeder = threading.Thread(target=feed_data)
    feeder.daemon = True
    feeder.start()
    logger = threading.Thread(target=log_result)
    logger.daemon = True
    logger.start()

    train_loop()
    feeder.join()
    logger.join()

    # Save final model
    serializers.save_npz(args.out, model)
    serializers.save_npz(args.outstate, optimizer)

if __name__ == '__main__': を追加してその下を全てインデントして下さい。
これだけでWindowsでも学習できるようになります。

独自のデータセットを使う際には、train.txt, test.txtが必要になり、それぞれ学習データ・テストデータへのパスとラベルが書かれたファイルです。
C:\Users\user\Desktop\stop256\test\neg0.png 0
C:\Users\user\Desktop\stop256\test\neg1.png 0
C:\Users\user\Desktop\stop256\test\neg2.png 0
C:\Users\user\Desktop\stop256\test\neg3.png 0
C:\Users\user\Desktop\stop256\test\neg4.png 1
C:\Users\user\Desktop\stop256\test\neg5.png 1
C:\Users\user\Desktop\stop256\test\neg6.png 1
C:\Users\user\Desktop\stop256\test\neg7.png 1
C:\Users\user\Desktop\stop256\test\neg8.png 2
C:\Users\user\Desktop\stop256\test\neg9.png 2
C:\Users\user\Desktop\stop256\test\neg10.png 2
C:\Users\user\Desktop\stop256\test\neg11.png 2
この様な感じでパス[半角スペース]ラベルとなっています。
画像は256×256の画像を用意して下さい。無理やりリサイズさせてもokです。

学習にはpython train_imagenet.py train.txt test.txt -g 0 で学習が始まります。
デフォルトではNetwork In NetworkというCNNで学習できます。
f:id:hiro2o2:20160415164946p:plain

学習が終わるとmodelというファイルができます。これが学習済みのネットワークで、このモデルを使って識別等を行うことが可能です。
識別のために、nin.pyを書き換えます。

import math

import chainer
import chainer.functions as F
import chainer.links as L


class NIN(chainer.Chain):

    """Network-in-Network example model."""

    insize = 227

    def __init__(self):
        w = math.sqrt(2)  # MSRA scaling
        super(NIN, self).__init__(
            mlpconv1=L.MLPConvolution2D(
                3, (96, 96, 96), 11, stride=4, wscale=w),
            mlpconv2=L.MLPConvolution2D(
                96, (256, 256, 256), 5, pad=2, wscale=w),
            mlpconv3=L.MLPConvolution2D(
                256, (384, 384, 384), 3, pad=1, wscale=w),
            mlpconv4=L.MLPConvolution2D(
                384, (1024, 1024, 1000), 3, pad=1, wscale=w),
        )
        self.train = True

    def clear(self):
        self.loss = None
        self.accuracy = None

    def __call__(self, x, t):
        self.clear()
        h = F.max_pooling_2d(F.relu(self.mlpconv1(x)), 3, stride=2)
        h = F.max_pooling_2d(F.relu(self.mlpconv2(h)), 3, stride=2)
        h = F.max_pooling_2d(F.relu(self.mlpconv3(h)), 3, stride=2)
        h = self.mlpconv4(F.dropout(h, train=self.train))
        h = F.reshape(F.average_pooling_2d(h, 6), (x.data.shape[0], 1000))

        self.loss = F.softmax_cross_entropy(h, t)
        self.accuracy = F.accuracy(h, t)
        return self.loss

    def predict(self, x_data):
        x = chainer.Variable(x_data, volatile=True)
        h = F.max_pooling_2d(F.relu(self.mlpconv1(x)), 3, stride=2)
        h = F.max_pooling_2d(F.relu(self.mlpconv2(h)), 3, stride=2)
        h = F.max_pooling_2d(F.relu(self.mlpconv3(h)), 3, stride=2)
        h = self.mlpconv4(F.dropout(h, train=self.train))
        h = F.reshape(F.average_pooling_2d(h, 6), (x.data.shape[0], 1000))
        
        return F.softmax(h)
        

predictという関数を追加しています。
この関数を使って評価を行うプログラムは他の開発者さんのものをお借りします。
https://github.com/shi3z/chainer_imagenet_tools/blob/master/inspection.py

これで画像識別までをWindows環境下でchainerを用いて動かすことができます。
まだサンプルを動かせただけですので、何か不具合が見つかるかもしれませんが、とりあえずはWindowsで動かして遊べる事を確認しました。