2018/11/22

【11.実践4】GenderNet/AgeNetで性別/年齢推定

どうも、ディープなクラゲです。
ゼロから学ぶディープラーニング推論」シリーズの11回目記事です。
このシリーズでは、Neural Compute StickとRaspberryPiの使い方をゼロから徹底的に学び、成果としてディープラーニングの推論アプリケーションが作れるようになることを目指しています。

第11回目は、GenderNetおよびAgeNetで性別や年齢を推論します。
具体的な実行方法とコードについて解説してゆきます

【 目次 】


GenderNet推論実行

GenderNetは顔画像から男女の性別を推定することが出来るモデルです。
SDKのサンプルソースには入ってはおらず、Neural Compute Application Zoo (NC App Zoo)というGitHubにて公開されています。
https://github.com/movidius/ncappzoo/

NC App ZooからMakefile等をダウンロードして、makeコマンドで必要なファイルを生成させる必要があります。
git cloneで全てのサンプルを一括ダウンロードします。クラゲは5分程度でダウンロードできました。

cd ~/workspace
git clone -b ncsdk2 https://github.com/movidius/ncappzoo.git

※2018年11月時点において、NCSDK 2.x 対応のリポジトリはmasterではなく ncsdk2 というbranchにあるため、上記のコマンドになっています。

全てのサンプルをmakeすると時間がかかるので、GenderNetだけmakeしましょう
GenderNetフォルダに移動して make します。数分で終わると思います。

cd ncappzoo/caffe/GenderNet
make

もし、no module named 'caffe' というエラーが出た場合は、ラズパイを再起動してもう一度実行してください。
run.pyを実行します

python3 run.py

最後に以下のような実行結果が出れば成功です

------- predictions --------
the predicted gender is Female with confidence of 94.3%

推論した画像は同じフォルダにある image.jpg です。ファイルマネージャーから辿って画像を見てみましょう

女性です。 94.3%でFemaleということなので推論は合ってそうです。

GenderNetコード解説

では、GenderNetのソースコードを見てゆきましょう
ソースコード全体はこちらからも見られます
https://github.com/movidius/ncappzoo/blob/ncsdk2/caffe/GenderNet/run.py

/usr/bin/env python3
 
# Copyright(c) 2017 Intel Corporation. 
# License: MIT See LICENSE file in root directory. 

ここは問題ないですね。1行目のお決まりコードとコメント行です

import sys
sys.path.insert(0, "../../ncapi2_shim")
import mvnc_simple_api as mvnc
import numpy
import cv2
import time
import csv
import os
import sys

2行目と3行目以外は「GoogLeNet」と同じです。2行目と3行目は一体何でしょうか?
実はGenderNetのソースコードは古い NCAPI v1 で書かれています。
この2行はNCAPI v2環境でNCAPI v1を動かすためのimportです。NCAPIのマイナーな話なので、覚えなくて良いです。詳細は以下のリンクに書いてあります。
https://github.com/movidius/ncappzoo/tree/ncsdk2/ncapi2_shim
ということで、GenderNetのNCAPIはは古いので、NCAPIについてはサラっと見てゆくようにしましょう

def execute_graph(blob,img):

「Python基礎」で習った関数です。関数名はexecute_graphで、第一引数としてblob、第二引数としてimgを受け取ります
以下、インデントが続く所までがこの関数のブロックになります。

  mvnc.SetGlobalOption(mvnc.GlobalOption.LOG_LEVEL, 2)

「NCAPI」の関数です。エラーログをどこまで出すかの設定です。

  devices = mvnc.EnumerateDevices()
  if len(devices) == 0:
    print('No devices found')
    quit()
  device = mvnc.Device(devices[0])
  device.OpenDevice()

「NCAPI」の「Device準備」です。ラズパイに接続されているNeural Compute Stickを認識して通信開始します

    with open(blob, mode='rb') as f:
        blob = f.read()
    graph = device.AllocateGraph(blob)

「NCAPI」の「Graph準備」「Graph割り当て」です。変数graphにはモデルと重みの情報が入ります

    graph.LoadTensor(img.astype(numpy.float16), 'user object')
    output, userobj = graph.GetResult()

「NCAPI」の「推論実行」「推論結果取得」です。
なお、変数imgはこの関数に渡される前に、整形処理済であることが前提です。

    graph.DeallocateGraph()
    device.CloseDevice()

「NCAPI」の後片付けです

    return output,userobj

推論結果output と 名称userobj を返します

# open the network blob files 
blob='graph'

ここからは関数の外です。関数より先に実行されます。
変数 blob に 'graph'、つまりGenderNetのモデルと重みを代入しています。

# categories for age and gender 
age_list=['0-2','4-6','8-12','15-20','25-32','38-43','48-53','60-100']
gender_list=['Male','Female']

age_listはGenderNetでは使いません。AgeNetのときに使うリストです。
gerder_listは見て分かるように、0のときはMale、1のときはFemaleであるという意味です。

# read in and pre-process the image: 
ilsvrc_mean = numpy.load('../../data/age_gender/age_gender_mean.npy').mean(1).mean(1) #loading the mean file 

「GoogLeNet」で出てきた構文と同じです。唯一の違いはファイルです。
このファイルはディープラーニング学習時に使ったAge/Gender用の学習画像データの平均画像です。
ilsvrc_mean は1次元配列で要素数が3つだけの配列で、それぞれの値は、全Blue画素の平均値、全Green画素の平均値、全Red画素の平均値です。

dim=(227,227)

変数dimにタプルを代入しています。読み込んだ画像ファイルを227x227へリサイズするときに使います。

os.system('wget -O image.jpg -N http://vis-www.cs.umass.edu/lfw/images/Talisa_Bratt/Talisa_Bratt_0001.jpg')

「Python基礎」で習った os.systemです。インターネットから画像ファイルをダウンロードして image.jpgという名前で保存しています。

img = cv2.imread('./image.jpg')
img=cv2.resize(img,dim)
img = img.astype(numpy.float32)
img[:,:,0] = (img[:,:,0] - ilsvrc_mean[0])
img[:,:,1] = (img[:,:,1] - ilsvrc_mean[1])
img[:,:,2] = (img[:,:,2] - ilsvrc_mean[2])

「GoogLeNet」で出てきた構文と同じで、正規化による前処理を行っています。
1行目は、image.jpgを読み込んで変数imgへ代入
2行目は、227x227へ画像サイズをリサイズ
3行目は、img配列の全要素を float32 の型に変換
最後の3行は、Blue,Green,Redそれぞれに対し、imgの全ての画素から平均画像の平均値を一律でマイナスする処理です。

#execute the network with the input image on the NCS 
output,userobj=execute_graph(blob,img)

ここで最初の方に定義した関数 excute_graph の実行です。第一引数としてblob、第二引数としてimgを渡しています。
推論結果がoutputに、文字列がuserobjに入ります。
なお、outputの中身を覗いてみると以下のような配列になっています

[0.05707 0.943 ]

今回は男女の性別のみを推論するため、要素としては男性である確率と女性である確率の2つのみです。

print('\n------- predictions --------')
order = output.argsort()

1行目は文字列の出力
2行目は変数orderに確率を昇順に並べたインデックスのリストが入ります。
具体的期には以下のような形です

[0 1]

outupt配列の要素を昇順に(小さい方から大きい方へと)並べたインデックスの結果であることが分かると思います。

last = len(order)-1

変数lastに order配列の要素数 -1 の値が入力されました。
つまり一番大きな確率が入っている要素番号を取得するという意味です。
今回の例だと具体的には 1 が入ります

predicted=int(order[last])

変数predictedに、一番大きな確率が入っている要素番号のインデックスを代入しています。intで整数化しているようですが、特に無くても問題ありません。
今回の例だと具体的には 1 が入ります

print('the predicted gender is ' + gender_list[predicted] + ' with confidence of %3.1f%%' % (100.0*output[predicted]))

推論結果の表示です。
今回の例だと predictedは1なので、前半の gender_list[predicted] は "Female" となります。

後半の "%3.1f%%" は「Python基礎」で習った「%記号を使ったprint」です。
100.0*output[predicted]はFemaleである確率を100倍した数値です。
この数値を小数点以下1桁かつ全体で3桁表現して出力しているという意味になります。

以上が GenderNetのソースコードの解説でした。

AgeNet推論実行

AgeNetは顔画像からおおよその年齢層を推定することが出来るモデルです。
こちらもSDKのサンプルソースには入ってはおらず、NC App Zooに公開されています。
既に先ほどのGenderNetの際に、git cloneで一括ダウンロード済ですので、AgeNetフォルダに移動してmakeしましょう。

cd ~/workspace/ncappzoo/caffe/AgeNet
make

run.pyを実行します

python3 run.py

最後に以下のような実行結果が出れば成功です

------- predictions --------
the age range is 25-32 with confidence of 99.9%

今回は 99.9%の確率で25~32歳という推論結果が出ました。
推論した画像は同じフォルダにある image.jpg です。ファイルマネージャーから辿って画像を見るとGenderNetのときと同じ画像であることが分かります。
推論結果は合っていそうです。

AgeNetコード解説

では、AgeNetのソースコードを見てゆきましょう
ソースコード全体はこちらからも見られます
https://github.com/movidius/ncappzoo/blob/ncsdk2/caffe/AgeNet/run.py

実はAgeNetのコードは、先ほどのGenderNetのコードとほぼ同じです。
こちらのWeb上でテキスト比較できるツール difff(デュフフ) を使って、差分を確かめてみましょう。
https://difff.jp/

差分がある箇所を色で教えてくれます。調べてみると3箇所差分がありました。
左側がGenderNetで右側がAgeNetです。表示の横幅を狭めているので、コードの途中で改行されています。

差分1


コードの先頭ブロックです。
差分として出力されていますが、よく見ると記述してある位置が異なるだけで、内容としては全く同じです。
つまり、プログラム的な差分は無いです。

差分2


関数 execute_graphのブロックです。
GenderNetにはなく、AgeNetのみにある記述が1行あります

opt = device.GetDeviceOption(mvnc.DeviceOption.DEVICE_NAME)

変数optにデバイス名を代入しているようですが、ここ以外にoptは出てきません。
つまり、この行は意味がないため、無視できます。ここもプログラム的な差分は無しです。

差分3


一番下の結果表示ブロックです。
最後のprint分に差分があります。

print('the age range is ' + age_list[predicted] + ' with confidence of %3.1f%%' % (100.0*output[predicted]))

前半の差分は"predicted gender"と"age range"という結果表示のための文字列の違いだけです。
後半の差分は配列です。GenderNetでは genger_listを使っていましたが、AgeNetではage_listを使っています。

age_list=['0-2','4-6','8-12','15-20','25-32','38-43','48-53','60-100']
gender_list=['Male','Female']

ここで改めて、age_listとgender_listの中身を見てみれば、意味が分かると思います。
このようにGenderNetとAgeNetのコードの差分は最後に出力するリストだけです。ただし、graphについてはファイル名は同じですが、中身は異なりますので注意してください。


次回はGenderNetを利用して、性別によって画像表示を切り替えるアプリの実行と解説を行います!
以上、「GenderNet/AgeNetで性別/年齢推定」でした。