2020年12月4日金曜日

Node for MaxでMaxからTwitterを利用

Maxのバージョン8から追加された機能に,Node.jsの機能をMaxで利用できるNode for Maxというものがあります.Max8がリリースした当初は,少し盛り上がりましたが,今は使っている人もいる一方で,あまり多用されていない気がします.私個人の話に限って言えば,下記の点において,あまり使う必要性を感じていませんでした.


・PythonプログラムをMaxとは別で動かして,udpか何かで結果を受け取ればweb APIの利用は不可能ではない(というより,Node for Max登場以前からそうしていた)

・Node.jsが何か,あまり理解していない

・C++でエクスターナルを作れば,あらかた出来ないことはない(幸い,現時点でそのスキルを有している)

・Node.jsが何か,あまり理解していない


結局,Node.jsって何なんでしょう?今の時代,webやクラウド上に存在する膨大なリソースをMaxで直接利用できるようになったのは,必然とは言えるかもしれません.

私個人が利用する分においては,全く必要性を感じないNode for Maxですが,とある事情で,大急ぎで習得しないといけなくなったので,光速に限りなく近づいていく物体よりも重い腰を上げて勉強を始めました.まずは,Node.jsの利用から試します.よく使っているwebサービスと連動すると,何だか嬉しいので,Twitterの利用を試してみます.

まず,Twitter Developperでdevelopper accountを作成し,appを作成します.この手順が非常に多くの手順を必要とします.その後,MaxでTwitterを利用するNode.jsスクリプトおよびMaxパッチを作成します.

まずは,Twitter Developperに自身のTwitter IDでログインします(今は国民一人につき,一つはTwitter IDを持っている時代ですよねぇ・・・).

その状態で,"Apps"へ行きます.


"Create an app"をクリックします.


適当な目的を選んで,Nextをクリックします.


国と名前を入力してNextをクリックします.



このappの目的を聞かれるので,Google翻訳で嘘800の目的を書いておきます.それ以外の項目は,"No"にするとNextが押せるようになります.


確認画面が出るので,"Looks good"をクリックします.


規約を読んで,チェックボックスにチェックを入れ,"Submit application"をクリックします.


メールが届くので,"Confirm your email"をクリックすると,審査が始まります.しばらく時間がかかります.


何度か追加で説明を求められる場合もありますが,その度に追加で説明をします.(日本語でも良さそうです)

審査が完了すると,メールが届きます.

developper accountにアクセスすると,このような画面になります."Create Project"をクリックします.



プロジェクト名を入力し,Nextをクリックします.


目的を選んでNextをクリックします.


目的を入力します.先のステップで入力したものと同文で大丈夫です.


app名を入力して,"Complete"をクリックすると,app作成が完了します.


今作ったappを選択し,鍵マークをクリックします.


"Keys and tokens"タブに行き,"API Key &secret"及び"Access token & secret"の欄を"Generate"もしくは"Regenerate"します.


・API key
・API key secret
・Access token
・Access token secret

の4つが必要なので,メモしておきます.

いよいよ,Node.jsスクリプトを作って行きます.

ポイントは,Maxのnode.scriptが特定のメッセージを受け取ったときのイベントハンドラを作るところです.そのときに,Twitter APIを呼び出して,タイムラインの取得やツイートの投稿を行なっています.

const Max=require('max-api');//Node for MaxのAPI
const Twitter=require('twitter');//Twitter API
console.log('app start');
var client=new Twitter({
	consumer_key:'自分のAPI key',
	consumer_secret:'自分のAPI key secret',
	access_token_key:'自分のAccess token',
	access_token_secret:'自分のAccess token secret'
	});

Max.addHandler("timeline", () => { //node.scriptが"timeline"を受け取った際のイベントハンドラ
	client.get('statuses/user_timeline',{screen_name:"dakiyamasan"},function(error,tweets,response){ //dakiyamasanのタイムラインを取得
       	 	if(!error){
			//タイムラインを表示
                	console.log(tweets);
			Max.outlet(tweets);
        	}
	});			
});

Max.addHandler("Tweet",(msg)=>{ //node.scriptが"Tweet xxx"を受け取った際のイベントハンドラ.2番目の文字列がmsgに格納されている.
	client.post('statuses/update', {status:msg}, function(error, tweet, response){ //msgをツイートする
    		if (!error) {
        		console.log(tweet);
    		} else {
        		console.log('error');
    		}
	});
});

Maxパッチはこのようになります.node.scriptに先ほど作ったスクリプトを指定します.




ここで,node.scriptに"script npm --save twitter"メッセージを入力すると,Twitter APIの利用に必要なパッケージがインストールされます.インストールできたかどうか,右下のnode.script debug toolに表示されます.


node.scriptに"script start"を入力すると,実行が始まります.実行が始まっても,イベントドリブンなので,ほぼ何も起こりません.node.script debug toolがProcess runningになる程度です.


node.script debug toolでShow consoleをクリックすると,コンソールモードになります.ここで,"app start"が表示されていることで,console.log('app start'); が実行されたことがわかります.このようにしてデバッグすることができます.


node.scriptに"timeline"を入力して,タイムラインを取得します.




node.scriptに"tweet xxx"を入力して,ツイートを投稿します.テキストボックスにツイートを入力して,上のButtonをクリックします.



ツイートできました.


Node.jsでタイムラインを取得しても,今のツイートが反映されています.

とりあえず,MaxNode.jsをMaxでどう使うかは分かりました.

2020年7月28日火曜日

サイゼリヤの間違い探しを独立成分分析

家の近所にある唯一のファミレスがサイゼリヤなので,特に好きでもないのにたまに行くんですが,子供向けメニューに「間違い探し」が載っています.普段特に見ていないのですが,結構難しいと度々話題になるようです.

キッズメニュー間違い探し - サイゼリヤ

凄く「違い」が小さいエリアに存在していたり,間違い探しの問題文となる文字の部分に違いがあったりして,確かに非常に難しいです.

これを画像処理的に違いを見つけようという試みがいくつかweb上で行われています.私が最初に見つけたのはこちらです.

サイゼリヤの間違い探しがむず過ぎるので、独立成分分析をお見舞いしてやった

他にもっと「賢い」方法がいくつも公開されていますが,この方法は具体的にどうやるのか方法が公開されていないので,私の方でもやってみて纏めました.

まず,キッズメニュー間違い探し - サイゼリヤに掲載されている間違い探しから適当に選んで画像をダウンロードします.ダウンロード画像には,左右に白い余白があるので,それをまずカットしておきます.


これをMATLABで独立成分分析します.
独立成分分析とは,非常にざっくり言うとこんな感じの分析です.

こんな感じに,「なんか2つ(以上)の特徴に分割できそうだな〜」という2次元データの集合を,

こういう風に変形(無相関化)して,更に

こういう風に変形します.
こうすると,直交座標の横軸(第1成分)と縦軸(第2成分)それぞれに,見たかった特徴量が現れるという手法です.独立成分分析の応用でよく知られた分野に音源分離があります.2つのマイクロホンで録音した同一空間上に2人の話者が同時に発話している音声データ(2つのマイクロホンからの音声は,どちらも2人の声が混ざって拾っている),独立成分分析することで,2人それぞれの声に分離することができます.

独立成分分析で間違い探しを行う大まかなアイデアは以下です.

まず,2枚の画像データを比較したい訳ですが,「間違い探し」である以上,両画像データのほとんどは全く同じデータであり,「間違い」となる両画像で差異のあるデータは,全体の中の一部しかないということが予想されます.

2枚の画像データ(画像1,画像2)の,それぞれ対応する場所の画素データを並べた画像の総画素数x2の行列

{(画像1(0,0)のR, 画像2(0,0)のR), (画像1(0,0)のG, 画像2(0,0)のG),(画像1(0,0)のB, 画像2(0,0)のB), (画像1(1,0)のR, 画像2(1,0)のR), (画像1(1,0)のG, 画像2(1,0)のG),(画像1(1,0)のB, 画像2(1,0)のB), ・・・(画像の最後まで)}

の各行をプロットするとこのようになります.(画素データを-0.5~0.5にスケールしています)


この時,上記予想を踏まえると,以下のことが言えそうです.

・両画像で違いがない画素は,y=x の直線上(実用的にはその付近)に存在する.
・y=x の直線上(実用的にはその付近)にない画素は,「間違い」の画素である.


このデータを独立成分分析して,「間違い」の成分だけを可視化します.最終的にはこうなれば,両画像で同じである成分と「間違い」の成分を直交座標から別々に読み取ることができます.



まずデータの準備.

clear;
close all;
%元画像読み込み
src=imread('body.png');
srcsize=size(src);
width=srcsize(2)/2;%画像の横幅は元画像の半分(両画像が横に並べられているから)
height=srcsize(1);
img1=zeros(width,height,3);%画像1
img2=zeros(width,height,3);%画像2
dst1=zeros(width,height,3);%分析後の画像1
dst2=zeros(width,height,3);%分析後の画像2
ivec=zeros(2,width*height*3);%画像データのベクトル(実際の分析はこのベク鳥に対して行う)
%画像を分割,ベクトルに代入
for y=1:height
    for x=1:width
        for c=1:3
            img1(y,x,c)=src(y,x,c);
            img2(y,x,c)=src(y,x+width,c);
            ivec(1,3*x+(y-1)*width*3+c)=(img1(y,x,c)/255)-0.5;
            ivec(2,3*x+(y-1)*width*3+c)=(img2(y,x,c)/255)-0.5;
        end
    end
end
%画像を表示
figure(1);
imshow(uint8(img1));
figure(2);
imshow(uint8(img2));
%ベクトルの散布図を表示
figure(3);
plot(ivec(1,:),ivec(2,:),'.','MarkerSize',1);




先ほどの図が得られました.このデータの自己相関行列を求めて,データを無相関化します.

%独立成分分析
R=[ivec(1,:)*ivec(1,:)' ivec(1,:)*ivec(2,:)';ivec(2,:)*ivec(1,:)' ivec(2,:)*ivec(2,:)']/length(ivec(1,:));%自己相関行列
[Q,L]=eig(R);%自己相関行列の固有値・固有ベクトル
V=sqrt(inv(L))*Q';%信号無相関化行列
xd=V*ivec;%無双感化されたデータxdを得る
figure(4);
plot(xd(1,:),xd(2,:),'.','MarkerSize',1);
xdをプロットするとこうなります.




なんか,もうこれでいいような気もしますが,一応,無相関化されたデータが与えられた時に,それの各成分が直行座標の各軸に重なるような回転行列を求めます.
これはFastICAという手法によって行います.
%FastICA
B=eye(2,2);
% 1列目の基底探索 %
sh=xd;
for i=1:50
    b1_past=B(:,1);                           %b1の記録
    sh(1,:)=B(:,1)'*xd;                           
    B(1,1) = mean( sh(1,:).^3 .* xd(1,:)) - 3*B(1,1);%Bの第1基底の1番目の要素更新
    B(2,1) = mean( sh(1,:).^3 .* xd(2,:)) - 3*B(2,1);%Bの第1基底の2番目の要素更新
    B(:,1)=B(:,1)/norm(B(:,1));                       %Bの第1基底を正規化
    if abs( B(:,1)'*b1_past -1 ) < 0.001      %収束判定
        break;
    end
end

% 2列目の基底探索 %
B(:,2)=B(:,2)-B(:,1)*(B(:,1)'*B(:,2));                    %b2の初期値をb1と直交化する

for i=1:50
    b2_past=B(:,2);                           %b2の記録
    sh(2,:)=B(:,2)'*xd;                           
    B(1,2) = mean( sh(2,:).^3 .* xd(1,:)) - 3*B(1,2);%Bの第2基底の1番目の要素更新
    B(2,2) = mean( sh(2,:).^3 .* xd(2,:)) - 3*B(2,2);%Bの第2基底の2番目の要素更新
    B(:,2)=B(:,2)-B(:,1)*(B(:,1)'*B(:,2));                    %b2をb1と直交化する
    B(:,2)=B(:,2)/norm(B(:,2));                       %Bの第2基底を正規化
    if abs( B(:,2)'*b2_past -1 ) < 0.001      %収束判定
        break;
    end
end
figure(5);
plot(sh(1,:),sh(2,:),'.','MarkerSize',1);

このデータを画像として可視化すれば,「間違い」の部分が浮かび上がります.
%データを0-255にスケール
sh(1,:)=255*sh(1,:)/max(abs(sh(1,:)));
sh(2,:)=255*sh(2,:)/max(abs(sh(2,:)));
%分離した各成分を画像化
for y=1:height
    for x=1:width
        for c=1:3
            dst1(y,x,c)=sh(1,3*x+(y-1)*width*3+c);
            dst2(y,x,c)=sh(2,3*x+(y-1)*width*3+c);
        end
    end
end
%表示
figure(6);
imshow(uint8(dst1));
figure(7);
imshow(uint8(dst2));

ある程度「間違い」の部分を見つけられるようになりましたが,凄く小さい間違いはほとんど分かりません.
また,右下のゴーグルはこの分析では違いがほとんど目立ちませんでした(一応色は付いています).

参考
キッズメニュー間違い探し - サイゼリヤ
https://www.saizeriya.co.jp/entertainment/

ディジタル音声&画像の圧縮/伸張/加工技術 - CQ出版
https://www.cqpub.co.jp/hanbai/books/31/31451.htm

2019年11月23日土曜日

ここ最近の使い物にならないMacBook Proは機械学習マシンとして使えるか ~PlaidML~

機械学習の話題ばかりですみません.

ここ最近のMacBook Pro,本当に使えないですねぇ.

ここ最近のMacBook Pro

ここ最近のMacBook Proの嫌いな点を箇条書きで書きます.

・キーボードが押しづらい.たまに'a'キーが,押した感触が一切なくなる(でも押せている)
・Touch Bar(キーボード上部の細長いタッチパネル)などといううどの大木のせいで物理escキーがなくなった
・トラックパッドがでかすぎる.タイピングしている途中に,知らないうちに触っていて,意図しないところをクリックしてしまう
・外部機器とのインタフェースがUSB Type-Cだけになったことによって,互換性が非常に悪くなった(特に,BootCamp時に外部ディスプレイに接続する時の成功率が著しく低い.映像ソフトウエアの開発をする身としては致命的な欠陥)
GPUがNVidia GeforceではなくAMD Radeon Proになってしまった

何が「GAFAでは生産性が重視される」ですか.自分は生産性を低下させる製品を世に出しておいて良い御身分ですね.若い人材を使い潰して生産人口を低下させる日本のブラック企業と同様に社会に害しかもたらしていない企業です.

私のしょぼいキャリアの中で言えば,GPUがNVidiaでなくなってしまったのは,かなりキツイです.CUDAが使えなくなったからです.私は仕事でも趣味でも結構CUDAを使ってきました.これまでのMacBook ProはNVidiaのGPUを搭載していたので,焼肉食べ放題とかに行ってCUDAの開発をしたりということができていました.しかし,ある時期からMacBook Proに搭載するGPUがAMDになってしまったせいで,それが出来なくなってしまいました.そのために,最近はOpenCLでグラフィック表示との連携を試したりもしています.(でもCUDAの方が断然分かりやすいです.)

さて,ここ一年くらいで,私はようやく重い腰をあげて機械学習を勉強し始めて,自分が出演できそうなAVのジャンルとプレイを明らかにしたりしてきました.一個人が機械学習をやってみる上で,最も課題となるのは,その計算量の多さです.ちょっとした機械学習プログラムを実行するのでも,CPUで計算すると,何日単位で計算時間がかかります.
そのため,凄いGPU(大抵NVidia)を積んだマシンを用意するか,凄いGPUや計算デバイスを積んだクラウドサーバに計算させるかという方法をとります.後者の代表的なサービスがGoogle Colaboratoryです.このサービスは,無料でGPUを使った機械学習を実行させることができるかなり優良サービス(無料なのに優良.ガハハ)なのですが,私個人としてはちょっと嫌だなと思っている点がいくつかあります.

・データファイルを保持しておくストレージが一時的にしかファイルを保持しない.Google Driveをストレージとして連携する方法があるが,通常の動作方法より一手間多くなる


・一旦計算を開始して,長時間放っておくと,ランタイムが終了している.下記のような対策もなきにしもあらずだが,一体何のためのクラウドサービスなんだという感想しか抱かない.(下記でも全てのセッション切れを対策できない)


・私がやりたいのは,焼肉屋とかでラップトップを開いて機械学習をすることであって,焼肉屋はインターネット接続できるかどうか定かではない.

・本記事の冒頭で「GAFA」と一纏めにして悪く言っている以上,Googleのサービスを利用するのは男のすることではない

どれもこれも,MacBook ProがNVidiaを搭載しなくなったのが悪いのです.(MacBook ProにNVidiaのGPUが搭載されていたからといって,それが本当に機械学習のデバイスとして動作するのか,定かではありません.でもCUDA自体は動くわけだから,何かやり方があるだろうと思っています.)
私はただ,焼肉を食べながら機械学習の勉強をしたいだけなのに,なんでいちいちインターネット接続してクラウドサービスなんて使わないといけないのでしょう?

要は,MacBook Proに積んでいるAMDのGPUで機械学習できれば良いわけです.というわけで調べた結果,PlaidMLというフレームワークが存在することを知りました.PlaidMLは,様々なCPUやGPUを対象にして開発されている機械学習フレームワークで,Kerasのバックエンドとしても使えます.これを導入すれば,ここ最近のMacBook ProのAMD Radeon Proで機械学習できるようなので,試してみました.

実験に使ったのは,本ブログの以前の記事


の,画像を100種類に分類するプログラムです.



この記事では,学習したモデルとパラメータを一旦ファイルとして保存し,別プログラムでそのファイルを読み込んで検証画像を分類しました.

今回は,実験用に1つのプログラムで学習し,学習後,どうプログラム内で検証画像を分類します.まず,単純に上記記事のプログラムを1つのプログラムにまとめたのが以下.実行時間を計測する処理も加えています.

import tensorflow as tf
from tensorflow import keras
import numpy as np
import matplotlib.pyplot as plt
import time

#時間計測開始
start = time.time()

#CIFAR100のラベル名
CIFAR100_LABELS_LIST = [
                        'apple', 'aquarium_fish', 'baby', 'bear', 'beaver', 'bed', 'bee', 'beetle',
                        'bicycle', 'bottle', 'bowl', 'boy', 'bridge', 'bus', 'butterfly', 'camel',
                        'can', 'castle', 'caterpillar', 'cattle', 'chair', 'chimpanzee', 'clock',
                        'cloud', 'cockroach', 'couch', 'crab', 'crocodile', 'cup', 'dinosaur',
                        'dolphin', 'elephant', 'flatfish', 'forest', 'fox', 'girl', 'hamster',
                        'house', 'kangaroo', 'keyboard', 'lamp', 'lawn_mower', 'leopard', 'lion',
                        'lizard', 'lobster', 'man', 'maple_tree', 'motorcycle', 'mountain', 'mouse',
                        'mushroom', 'oak_tree', 'orange', 'orchid', 'otter', 'palm_tree', 'pear',
                        'pickup_truck', 'pine_tree', 'plain', 'plate', 'poppy', 'porcupine',
                        'possum', 'rabbit', 'raccoon', 'ray', 'road', 'rocket', 'rose',
                        'sea', 'seal', 'shark', 'shrew', 'skunk', 'skyscraper', 'snail', 'snake',
                        'spider', 'squirrel', 'streetcar', 'sunflower', 'sweet_pepper', 'table',
                        'tank', 'telephone', 'television', 'tiger', 'tractor', 'train', 'trout',
                        'tulip', 'turtle', 'wardrobe', 'whale', 'willow_tree', 'wolf', 'woman',
                        'worm'
                        ]
#CIFAR-100 datasetの読み込み
(x_train, y_train), (x_test, y_test) = keras.datasets.cifar100.load_data(label_mode='fine')

train_labels_onehot=keras.utils.to_categorical(y_train,100)
test_labels_onehot=keras.utils.to_categorical(y_test,100)

#画像をfloat32(0.~1.)に変換
x_train=x_train.astype("float32")/255.0
x_test=x_test.astype("float32")/255.0

print(x_train.shape)
print(y_train.shape)
print(x_test.shape)
print(y_test.shape)

#学習画像を少し見てみる
plt.figure(figsize=(5,5))
for i in range(25):
    plt.subplot(5,5,i+1)
    plt.xticks([])
    plt.yticks([])
    plt.imshow(x_train[i+25])
    plt.xlabel(CIFAR100_LABELS_LIST[int(y_train[i+25])])

#ネットワーク作成
model=keras.Sequential()

model.add(keras.layers.Conv2D(filters=32,kernel_size=(3,3),padding='same',activation='relu',input_shape=(32,32,3)))
model.add(keras.layers.Conv2D(filters=32,kernel_size=(3,3),padding='same',activation='relu'))
model.add(keras.layers.MaxPool2D(pool_size=(2,2)))
model.add(keras.layers.Dropout(0.25))

model.add(keras.layers.Conv2D(filters=64,kernel_size=(3,3),padding='same',activation='relu'))
model.add(keras.layers.Conv2D(filters=64,kernel_size=(3,3),padding='same',activation='relu'))
model.add(keras.layers.MaxPool2D(pool_size=(2,2)))
model.add(keras.layers.Dropout(0.25))

model.add(keras.layers.Flatten())
model.add(keras.layers.Dense(512,activation='relu'))
model.add(keras.layers.Dropout(0.5))
model.add(keras.layers.Dense(100,activation='softmax'))

model.compile(optimizer=tf.train.AdamOptimizer(),loss='categorical_crossentropy',metrics=["accuracy"])

#学習用データで学習してみる
model.fit(x_train[:,:,:,:],train_labels_onehot,epochs=200,batch_size=64)

#テスト画像を入力して識別
labels=model.predict(x_test[:,:,:,:])

#結果を少し見る
plt.figure(figsize=(5,5))
for i in range(25):
    plt.subplot(5,5,i+1)
    plt.xticks([])
    plt.yticks([])
    #ランダムな番号の画像を見る
    index=np.random.randint(0,9999)
    true_index=np.argmax(test_labels_onehot[index])#正解
    predict_index=np.argmax(labels[index])#予測したインデックス
    plt.imshow(x_test[index])#画像表示
    plt.xlabel("{}({})".format(CIFAR100_LABELS_LIST[predict_index],CIFAR100_LABELS_LIST[true_index]),color=("green" if predict_index==true_index else "red"))#"予測したラベル(正解のラベル)"で表示.正解なら緑,間違っていれば赤で表示

#時間計測終了
process_time = time.time() - start
#実行時間を表示
print(process_time)

plt.show()
結果は後で書くとして,結構時間がかかります.

それでは,いよいよPlaidMLを導入します.初めに言っておくと,PlaidMLを導入して,本当にプログラムのバックエンドをPlaidMLにするためには,後述のように,ほんの少しプログラムの修正が必要です.導入後に上記ソースを実行してもそれまでのようにCPUで実行されます.

①PlaidMLをインストールします.私の場合,OSにPython2.xがインストールされていて,Python3.xを起動するコマンドが"python3"なので,"pip"も"pip3"になっています."python"でPython3.xが起動する環境では"pip"になります.


pip3 install plaidml-keras plaidbench

②下記コマンドを実行します.何故かこの手順を省略している記事をよく見かけますが,このコマンドを実行しないと次の手順に進めません.


export PLAIDML_NATIVE_PATH=/usr/local/lib/libplaidml.dylib

export RUNFILES_DIR=/usr/local/share/plaidml

③下記コマンドを実行して,PlaidMLの環境を設定します.


plaidml-setup

CUIで色々質問して来ます.まず,外部の計算デバイスを使うかどうか聞かれます.勿論yesです.

PlaidML Setup (0.6.4)

Thanks for using PlaidML!

Some Notes:
  * Bugs and other issues: https://github.com/plaidml/plaidml
  * Questions: https://stackoverflow.com/questions/tagged/plaidml
  * Say hello: https://groups.google.com/forum/#!forum/plaidml-dev
  * PlaidML is licensed under the Apache License 2.0


Default Config Devices:
   metal_amd_radeon_pro_560.0 : AMD Radeon Pro 560 (Metal)
   metal_intel(r)_hd_graphics_630.0 : Intel(R) HD Graphics 630 (Metal)

Experimental Config Devices:
   metal_intel(r)_hd_graphics_630.0 : Intel(R) HD Graphics 630 (Metal)
   opencl_cpu.0 : Intel CPU (OpenCL)
   opencl_amd_radeon_pro_560_compute_engine.0 : AMD AMD Radeon Pro 560 Compute Engine (OpenCL)
   opencl_intel_hd_graphics_630.0 : Intel Inc. Intel(R) HD Graphics 630 (OpenCL)
   llvm_cpu.0 : CPU (LLVM)
   metal_amd_radeon_pro_560.0 : AMD Radeon Pro 560 (Metal)

Using experimental devices can cause poor performance, crashes, and other nastiness.


Enable experimental device support? (y,n)[n]:

次に,実際に計算に使うデバイスを聞かれます.ここではAMD Radeon Pro 560.0 (Metal)を選択します.

Multiple devices detected (You can override by setting PLAIDML_DEVICE_IDS).
Please choose a default device:

   1 : metal_intel(r)_hd_graphics_630.0
   2 : opencl_cpu.0
   3 : opencl_amd_radeon_pro_560_compute_engine.0
   4 : opencl_intel_hd_graphics_630.0
   5 : llvm_cpu.0
   6 : metal_amd_radeon_pro_560.0


Default device? (1,2,3,4,5,6)[1]:

試しに行列の計算をしてみて,成功すると,/Users/user/.plaidmlに設定を保存するか聞かれます.yesを選択します.


Selected device:
    metal_amd_radeon_pro_560.0

Almost done. Multiplying some matrices...
Tile code:
  function (B[X,Z], C[Z,Y]) -> (A) { A[x,y : X,Y] = +(B[x,z] * C[z,y]); }
Whew. That worked.

Save settings to /Users/user/.plaidml? (y,n)[y]:y

Success!

"Success!"と出たら成功です. 

これで,PlaidMLの導入は終了です.

いよいよ先述のプログラムをPlaidML用に修正して,実行してみます.
ソースの最初に

import plaidml.keras
plaidml.keras.install_backend()

を追加します.その辺の部分以外は全く同じです.

import plaidml.keras
plaidml.keras.install_backend()
import numpy as np
import matplotlib.pyplot as plt
import time
import keras
from keras.optimizers import adam
#時間計測開始
start = time.time()

#CIFAR100のラベル名
CIFAR100_LABELS_LIST = [
                        'apple', 'aquarium_fish', 'baby', 'bear', 'beaver', 'bed', 'bee', 'beetle',
                        'bicycle', 'bottle', 'bowl', 'boy', 'bridge', 'bus', 'butterfly', 'camel',
                        'can', 'castle', 'caterpillar', 'cattle', 'chair', 'chimpanzee', 'clock',
                        'cloud', 'cockroach', 'couch', 'crab', 'crocodile', 'cup', 'dinosaur',
                        'dolphin', 'elephant', 'flatfish', 'forest', 'fox', 'girl', 'hamster',
                        'house', 'kangaroo', 'keyboard', 'lamp', 'lawn_mower', 'leopard', 'lion',
                        'lizard', 'lobster', 'man', 'maple_tree', 'motorcycle', 'mountain', 'mouse',
                        'mushroom', 'oak_tree', 'orange', 'orchid', 'otter', 'palm_tree', 'pear',
                        'pickup_truck', 'pine_tree', 'plain', 'plate', 'poppy', 'porcupine',
                        'possum', 'rabbit', 'raccoon', 'ray', 'road', 'rocket', 'rose',
                        'sea', 'seal', 'shark', 'shrew', 'skunk', 'skyscraper', 'snail', 'snake',
                        'spider', 'squirrel', 'streetcar', 'sunflower', 'sweet_pepper', 'table',
                        'tank', 'telephone', 'television', 'tiger', 'tractor', 'train', 'trout',
                        'tulip', 'turtle', 'wardrobe', 'whale', 'willow_tree', 'wolf', 'woman',
                        'worm'
                        ]
#CIFAR-100 datasetの読み込み
(x_train, y_train), (x_test, y_test) = keras.datasets.cifar100.load_data(label_mode='fine')

train_labels_onehot=keras.utils.to_categorical(y_train,100)
test_labels_onehot=keras.utils.to_categorical(y_test,100)

#画像をfloat32(0.~1.)に変換
x_train=x_train.astype("float32")/255.0
x_test=x_test.astype("float32")/255.0

print(x_train.shape)
print(y_train.shape)
print(x_test.shape)
print(y_test.shape)

#学習画像を少し見てみる
plt.figure(figsize=(5,5))
for i in range(25):
    plt.subplot(5,5,i+1)
    plt.xticks([])
    plt.yticks([])
    plt.imshow(x_train[i+25])
    plt.xlabel(CIFAR100_LABELS_LIST[int(y_train[i+25])])

#ネットワーク作成
model=keras.Sequential()

model.add(keras.layers.Conv2D(filters=32,kernel_size=(3,3),padding='same',activation='relu',input_shape=(32,32,3)))
model.add(keras.layers.Conv2D(filters=32,kernel_size=(3,3),padding='same',activation='relu'))
model.add(keras.layers.MaxPool2D(pool_size=(2,2)))
model.add(keras.layers.Dropout(0.25))

model.add(keras.layers.Conv2D(filters=64,kernel_size=(3,3),padding='same',activation='relu'))
model.add(keras.layers.Conv2D(filters=64,kernel_size=(3,3),padding='same',activation='relu'))
model.add(keras.layers.MaxPool2D(pool_size=(2,2)))
model.add(keras.layers.Dropout(0.25))

model.add(keras.layers.Flatten())
model.add(keras.layers.Dense(512,activation='relu'))
model.add(keras.layers.Dropout(0.5))
model.add(keras.layers.Dense(100,activation='softmax'))

model.compile(optimizer=adam(lr=0.001,decay=1e-6),loss='categorical_crossentropy',metrics=["accuracy"])

#学習用データで学習してみる
model.fit(x_train[:,:,:,:],train_labels_onehot,epochs=200,batch_size=64)

#テスト画像を入力して識別
labels=model.predict(x_test[:,:,:,:])

#結果を少し見る
plt.figure(figsize=(5,5))
for i in range(25):
    plt.subplot(5,5,i+1)
    plt.xticks([])
    plt.yticks([])
    #ランダムな番号の画像を見る
    index=np.random.randint(0,9999)
    true_index=np.argmax(test_labels_onehot[index])#正解
    predict_index=np.argmax(labels[index])#予測したインデックス
    plt.imshow(x_test[index])#画像表示
    plt.xlabel("{}({})".format(CIFAR100_LABELS_LIST[predict_index],CIFAR100_LABELS_LIST[true_index]),color=("green" if predict_index==true_index else "red"))#"予測したラベル(正解のラベル)"で表示.正解なら緑,間違っていれば赤で表示

#時間計測終了
process_time = time.time() - start
#実行時間を表示
print(process_time)

plt.show()

MacBook ProのCPUとRadeon Pro 560,そしてGoogle ColabratoryのGPUで実行時間を比較しました.

CPU: 30950.88秒(8.59時間)
Radeon Pro 560 (Metal): 23190.82秒(6.44時間)
Google Colaboratory: 2580.95秒(43分)

Radeon Proも少しは速くなっていますが,そんなに速くなっていないですね.(MetalではなくOpenCLの方も試してみましたが,寧ろCPUの方が速いくらいでした)Google Colaboratoryだけ桁が違いますねえ.やはりGoogle Colaboratoryは素晴らしいサービスです.焼肉屋に行ったらテザリングでインターネットに繋いだら良いじゃないですか.Google Colaboratoryを積極的に使っていきましょう.