ようやくpart3でございます。
-- 目次 --
まずは2値分類してみる。
ここから主要キャラの分類
- part5 主要キャラで分類問題(未知データに適用するよ編)
- part5.5 主要キャラで分類問題(改良編)
- part6 主要キャラで分類問題(GPU使ったよ編)
- part7 新規データをFasterRCNNを使って分類させる(むしろ全てはこれのため)
正直、2値分類をやる必要はないのかもしれませんが、
まずはどんなもんなのか結果が早く見たいのでとりあえずってことで。
コード
先にコードを載せます。
まずは作ったモデル部分。clf_bake_model.pyとして保存しています。
#coding:utf-8 import os import chainer from chainer import optimizers import chainer.functions as F import chainer.links as L import chainer.serializers as S import numpy as np #chainerからChainを継承してクラスを作成します。 class clf_bake(chainer.Chain): def __init__(self): super(clf_bake, self).__init__( # ※1. このネットワークの部分は後々改善させます。 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, 2) #最終的に2クラスに分類させるため出力は2 ) def clear(self): self.loss = None self.accuracy = None def forward(self, X_data, y_data, train=True): self.clear() #初期化 X_data = chainer.Variable(np.asarray(X_data), volatile=not train) y_data = chainer.Variable(np.asarray(y_data), volatile=not train) h = F.max_pooling_2d(F.relu(self.conv1(X_data)), ksize = 5, stride = 2, pad =2) h = F.max_pooling_2d(F.relu(self.conv2(h)), ksize = 5, stride = 2, pad =2) h = F.dropout(F.relu(self.l3(h)), train=train) y = self.l4(h) return F.softmax_cross_entropy(y, y_data), F.accuracy(y, y_data)
軽い解説
今回、モデル自体をクラスとしています。後々、モデルの保存やロードのためにchainerのserializersを使っているのですが、
いまいちこの使い方がわからず(特にload_hdf5)結局こんな感じになっています。
まずメンバーとして今回ネットワークで使う関数を定義しています。
クリアモジュールは初期化を行うだけの簡単な関数。
そしてメインのフォワードモジュール。
ここの部分の作り方で、学習の精度は変わると思うのですが、ここの作り方の明確な方法がよくわからず(経験則ちっくなのかな?)
とりあえず、下記の参考サイトさまと同じようにネットワークの定義をしました。おそらくまず一番最初に取りかかるべき改善点はここになるはず。
最後のF.softmax_cross_entropy(y, y_data)の部分についてはもう少し深い解説をします。
ソフトマックス関数とは?
ソフトマックス関数とは他クラス分類で用いられる関数で以下のように定義されています。
分類クラスの出力を(kはクラスの番号)とし、関数 l4(h) によって得られる出力をとした時、
をソフトマックス関数とする。
こいつの特徴は、
- 出力の総和が1になること
- この層の出力全てを使って定義されている
この2点ですね。
まず1個目の特徴から想像しやすいと思いますが、ソフトマックス関数の出力は確率を表現しています。
一番確率が高かったクラスに画像を分類していく感じです。
2個目の特徴は活性化関数としてソフトマックス関数を見た時の特徴なので、あまり気にしなくてもいいです。
※いやいやこれ2値分類じゃねーか!!と思う方もいるかもしれませんが、2値分類は多クラス分類の部分集合なので大丈夫です。
後々のことも考えてソフトマックス使っちゃってます。
さっそく学習!
以下がbake_train.pyの学習&テスト用のコードです。
#coding: utf-8 #必要なライブラリのインポート import cv2 import os import six import datetime import chainer from chainer import optimizers import chainer.functions as F import chainer.links as L import chainer.serializers as S from clf_bake_model import clf_bake import numpy as np #その1 ------データセット作成------ #フォルダは整数で名前が付いています。 #今回0が負例で、1が暦フォルダになっております。 def getDataSet(): #リストの作成 X_train = [] X_test = [] y_train = [] y_test = [] for i in range(0,2): #まずは2値分類を目指すので暦フォルダとothersフォルダの中身だけ引っ張ってきます。 path = "/Users/path/to/dir/" #ここにディレクトリのパスを設定 imgList = os.listdir(path+str(i)) #データを4:1の割合でtrainとtestに分けます。 imgNum = len(imgList) cutNum = imgNum - imgNum/5 for j in range(len(imgList)): imgSrc = cv2.imread(path+str(i)+"/"+imgList[j]) #またimreadはゴミを吸い込んでも、エラーで止まらずNoneを返してくれます。 #ですので読み込み結果がNoneでしたらスキップしてもらいます。 if imgSrc is None:continue if j < cutNum: X_train.append(imgSrc) y_train.append(i) else: X_test.append(imgSrc) y_test.append(i) return X_train,y_train,X_test,y_test #その3 ---------学習させる------- def train(): #上で作った関数でデータセットを用意します。 X_train,y_train,X_test,y_test = getDataSet() #このままだとchainerで読み込んでもらえないので、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) # 諸々の初期設定 model = clf_bake() optimizer = optimizers.Adam() optimizer.setup(model) epochNum = 5 batchNum = 50 epoch = 1 # 学習とテスト while epoch <= epochNum: print("epoch: {}".format(epoch)) print(datetime.datetime.now()) trainImgNum = len(y_train) testImgNum = len(y_test) #---学習--- sumAcr = 0 sumLoss = 0 perm = np.random.permutation(trainImgNum) for i in six.moves.range(0, trainImgNum, batchNum): #ランダムにbatchNumの数だけ抽出する X_batch = X_train[perm[i:i+batchNum]] y_batch = y_train[perm[i:i+batchNum]] optimizer.zero_grads() loss, acc = model.forward(X_batch, y_batch) loss.backward() optimizer.update() sumLoss += float(loss.data) * len(y_batch) sumAcr += float(acc.data) * len(y_batch) print('train mean loss={}, accuracy={}'.format(sumLoss / trainImgNum, sumAcr / trainImgNum)) #---テスト--- sumAcr = 0 sumLoss = 0 perm = np.random.permutation(testImgNum) for i in six.moves.range(0, testImgNum, batchNum): X_batch = X_test[perm[i:i+batchNum]] y_batch = y_test[perm[i:i+batchNum]] loss, acc = model.forward(X_batch, y_batch, train=False) sumLoss += float(loss.data) * len(y_batch) sumAcr += float(acc.data) * len(y_batch) print('test mean loss={}, accuracy={}'.format( sumLoss / testImgNum, sumAcr / testImgNum)) epoch += 1 #モデルの保存 S.save_hdf5('model'+str(epoch+1), model) train()
なるべくコメントを入れるようにしたので、わかりづらい場合はその辺を参照してください。
この学習の推移はこんな感じです。
epoch: 1 2016-03-21 12:49:10.113708 train mean loss=0.0788115997615, accuracy=0.971096151501 test mean loss=0.672512343231, accuracy=0.794669000601 epoch: 2 2016-03-21 12:58:13.964969 train mean loss=0.0256145460107, accuracy=0.991072739408 test mean loss=0.731735554987, accuracy=0.816637370031 epoch: 3 2016-03-21 13:07:38.601048 train mean loss=0.0198095579206, accuracy=0.993999710844 test mean loss=1.09153083491, accuracy=0.777387220851 epoch: 4 2016-03-21 13:17:16.501081 train mean loss=0.0147453846982, accuracy=0.995682718603 test mean loss=0.759238675944, accuracy=0.808142935594 epoch: 5 2016-03-21 13:26:54.957321 train mean loss=0.0132983507236, accuracy=0.995755893488 test mean loss=0.691285667368, accuracy=0.836555356405
やはりGPUを使わないとかなり時間がかかってしまいます。
学習は割と上手く行っています。
epochが少ないですが次のパートでは、この学習済みモデルを使って未知データに適用させてみます。
-- 目次 --
まずは2値分類してみる。
ここから主要キャラの分類
- part5 主要キャラで分類問題(未知データに適用するよ編)
- part5.5 主要キャラで分類問題(改良編)
- part6 主要キャラで分類問題(GPU使ったよ編)
- part7 新規データをFasterRCNNを使って分類させる(むしろ全てはこれのため)