grbl1.1+Arduino CNCシールドV3.5+bCNCを使用中。
BluetoothモジュールおよびbCNCのPendant機能でスマホからもワイヤレス操作可能。
その他、電子工作・プログラミング、機械学習などもやっています。
MacとUbuntuを使用。

CNCマシン全般について:
国内レーザー加工機と中国製レーザー加工機の比較
中国製レーザーダイオードについて
CNCミリングマシンとCNCルーターマシンいろいろ
その他:
利用例や付加機能など:
CNCルーター関係:



*CNCマシンの制作記録は2016/04/10〜の投稿に書いてあります。


2018年9月17日月曜日

Android上のPython:Pydroid 3, Jupyter Notebook, Colab

スマホでちょっとしたPythonのコードを確かめられないかと探してみると、Google PlayストアにPydroid 3というPython環境があったのでインストールしてみました。

 

pipを使うことが可能で、numpyやmatplotlibもインストール可能。ためしにサンプル(上画像)を実行させてみました。特に問題なく動きます。


pipでインストールする方法:

 
メイン画面の「≡(メニューマーク)」をタップすると、上画像左のような項目が出てくるので、「Pip」をタップすればライブラリを検索する画面になります。そして必要なライブラリ名を入力して「INSTALL」をタップ。
「QUICK INSTALL」タブには、主なライブラリがリストアップされているので、numpyやmatplotlibなどはこちらからインストールいたほうがいいかもしれません。インストールしたいライブラリが見当たらなければ「SEARCH LIBRARIES」タブで検索。


pipでJupter NotebookやKeras(Theano)をインストール:
pipの画面からKerasはインストール可能でしたが、Tensorflowは対応していないためかダメでした。そのかわりTheanoはインストールできたので、KerasのバックエンドとしてTheanoが使えます。

追記:
その後アップデート(2019年4月)があったようで、有償版にすればTensorflowもインストールできるようになっていました。

最近パソコンではJupyter Notebookばかり使っているので、スマホの方にもインストールしてみることにしました。


ターミナル画面からJupyter Notebookを起動:
基本は.pyファイルで保存ですが、Jupyter Notebookで.ipynbファイルも扱うことができます。
メニュー>Pipの画面から「jupyter」をインストールし、ターミナル画面に切り替えてから「jupyter notebook」を入力して起動すると、

 

Chromeが自動的に起動してJupyter Notebookの画面が出てきました。パソコンと同じような感覚で使うことができます。Chromeが自動的に開かない場合は、ターミナル画面に出てくるURLをChromeのアドレスにコピペすればJupyterの画面になるはずです。
あまり重い演算はさすがに無理ですが、ふと思いついたコードを試すにはよさそうです。

Android 7と8での違い:
Android 7では上記の方法でJupyter Notebookは動作しましたが、Android 8の場合だとセキュリティの違いのためかChromeが自動的に起動しません。ターミナル上に出力されたアドレスをChromeへコピペするしかありません。
追記:
その後のアップデートでAndroid 8でも自動的にChromeが立ち上がるようになっていました。

問題なのが、Jupyterが起動したあとPydroidのカーネルが途中で落ちてしまいます。マルチウィンドウ(二窓)でChromeとPydroidを起動しておけば落ちないのですが、Chromeを前面表示するとバックグラウンドで動いているPydroidが数秒で落ちてしまいます(対応策は下へ追記しました)。
このようにChrome(Jupyter)とPydroidを上下に同時に表示させて使う分にはPydroidのカーネルが落ちずに済みます。キーボード(画像ではフローティングにしていますが)は下のほうにでるので、Pydroidのターミナル画面に重なる感じならJupyter画面にもあまり邪魔にならないかと。
画面移行してしまうとPydroidが落ちてしまいますが、再度ターミナルでJupyterを起動し、Chromeのほうは画面をこのまま再読み込みさせれば大丈夫そうです(再度アドレスをコピペする必要がない)。

追記(上記の対応策:Huawei Nova lite 2の場合):
Android 8のバッテリー最適化機能でカーネルが落ちないようにするには、「設定」→「アプリと通知」→「アプリ」から画面下の「歯車」の設定マークをクリック→「アプリの設定」→「特別なアクセス」→「バッテリー最適化を無視」の画面で「すべてのアプリ」を表示させ「Pydroid 3」を一覧から選択し「許可」するに変更。
こうすることでバッテリー最適化によるアプリの自動切断を防ぐことができ、バックグラウンドでも動き続けるようです。


Jupyter nbextensionsのインストール:
Jupyter Notebookを使う場合、nbextensionsをインストールすれば様々な機能拡張が使えるようになります。
Pydroidのpip画面で

jupyter_contrib_nbextensions

を入力(あるいは検索)してインストール。
さらにターミナル画面に切り替えて、

jupyter contrib nbextension install --user

を入力(インストールはこれで終了)。
ターミナル画面から「jupyter notebook」入力で、ChromeにJupyterを立ち上げます。
 
そうすると「Nbextensions」というタブが増えているので、それを選択すればさまざまな機能拡張の一覧が出てきます。
「Nbextensions」タブがない場合は、「localhost:8888/nbextensions/」にアクセスすれば出てくるはずです。


Gist itを使う:
個人的に便利だと思うのはGithubのGistへボタン一発でファイル保存する機能です。
「Nbextensions」の一覧を見ていくとでてくるので、「Gist-it」にチェックをいれておきます。

 

そして、コーディングするページを開けば、

 
右上にGithubマークのボタンが増えているので(現れなければ画面をリロード)、これをクリック。
そうすると確認画面がでてくるので、青い「Gist it!」ボタンでアップロード(Tokenを登録する必要があります)。プライベートでアップロードしたいなら「Make the gist public」のチェックを外しておきます。
ファイルの保存先を忘れることもなく、後でパソコンからアクセスするのも容易なので便利です。


オンラインのJupyter Notebookを使う:
https://jupyter.org/にアクセスすればインストールせずにオンラインでもJupyterを試すことができるようです。
 
Jupyterのトップページ上の「Try it in your browser」をタップすれば、JupyterかJupyterLabなどを選択するページへ移動し、とりあえず「Try Jupyter with Python」をタップすると「Welcome to Jupyter」というサンプルページが表示されます(以下)。
 
左上の「≡Menu」から「File>Open...」を選べばディレクトリ一覧のページが表示されます。
ここで右上の「New▼」から「Python3/Text File/Folder/Terminal」を選択して新たなファイルを開くことができます。
Terminalを選択すればターミナル画面に移行し、「pip list」入力でインストールされているライブラリを確認できます。Numpy、Scikit-learn、Scipy、Pandasなど基本のライブラリはインストールされているようです。TensorflowやKerasはインストールされていませんが、「pip install tensorflow」で追加インストールできるようです。
ファイルも一時的に保存できるようですが、仮想サーバのためか、一旦ログアウトしてしまうとすべては消えてしまうようです。
ちょっとしたコードを試すだけなら、このオンラインのJupyterでも十分そうです。



Google Colabをスマホ上で使う:
Jupyter Notebookが使えるのは便利ですが、それならGoogle Colabを使えばいいのでは?ということでColabも試してみました。Colabの場合は全ての環境はクラウド上にあるので、ChromeさえあればスマホからでもGPU利用が可能です。Tensorflow、Keras、Numpy、Pandas、Matplotlibなど基本的なライブラリはすでにインストールしてあるUbuntu環境なので便利。
この場合、先ほどのPydroid 3は無関係で、単にChromeでcolab.research.google.comへアクセスすればいいだけ。


特に問題なく動きます。基本Google Driveにデータファイルなどを保存しておけば便利です。Colabの場合ならTensorflowも普通に使えるし、GPU演算なのでスマホでも問題ないという感じ。


仮想キーボードCodeBoard Keyboard for Coding:
コーディングするには、Google PlayにあるCodeBoard Keyboard for Codingが便利そうだったのでインストールしました。
既存のキーボードだと、数字や記号を入力する際に入力切替が必要だったりアローキーがなかったりするため少々不便なのですが、このキーボードであればコーディングに必要そうなキーが揃っているので便利です。コメントアウトの「#」記号だけ表面にないのですが、右上「SYM」を押せば記号一覧の中に出てきます。


まとめ:
Pydroid 3はスマホアプリなので一旦ダウンロードすればオフライン(通信料なしで)でも動く点では便利です。Tensorflowが使えなかったり、重い計算は無理なので多少の制約はあります。通信料が気になる場合はPydroid 3がいいかもしれません。ただしライブラリをインストールしすぎると1GBを超えたりするのでメモリを圧迫したくない場合は要注意。

一方、Colabの場合はコマンドのやりとりで通信料は発生しますが、演算自体はクラウド上(GPUでも可)で行うのでスマホであっても問題なく重い計算が可能という点が便利。また、ログインごとに(90分放置すると初期化)ライブラリやデータをインストールし直すのが面倒ですが、Google Driveに保存してあるデータをアップロードするのであれば、データのやりとりもクラウド上で行うのでデータが大きくてもその分の通信料はかからないはず。Colabを利用することで、スマホからでも普通にディープラーニングのコードを実行できるのはかなり画期的。Wifi環境下で通信料がかからないのであればColabがおすすめ。


データ分析ツールJupyter入門
Posted with Amakuri at 2018.12.21
掌田津耶乃
秀和システム
販売価格 ¥3,024

2018年9月15日土曜日

SCHOOL OF AI: MOVE 37(無料強化学習コース)


Siraj Raval氏のAI関係のYoutube動画はよく見るのですが、9/10から無料のコース「MOVE 37」が始まったので試しに登録してみました。内容は主に強化学習についてであり、10週で完了するコースです。

去年まではGANなどの生成的ネットワークにトピックが集中していたような気がしますが、最近は強化学習が流行ってきたのでしょうか?Pose Estimatorのようなアルゴリズムも公開され、ディープラーニングの応用法においても幅が広がってきたのかもしれません。


この「MOVE 37」というコースにおいては、以下を使って学ぶようです。

・Pytorch
・OpenAI Gym
・Google Collaborator
・TensorFlow

Google Colab上でコーディングし無料GPUで訓練できるので、どんなパソコンでも大丈夫という感じです。


コース内容は、1週間ごとに以下の項目を実施(合計10週)。

・Markov Decision Processes 0/7
・Dynamic Programming 0/7
・Monte Carlo Methods 0/6
・Model Free Learning 0/7
・RL in Continuous Spaces 0/7
・Deep Reinforcement Learning 0/7
・Policy Based Methods 0/7
・Policy Gradient Methods 0/7
・Actor Critic Methods 0/7
・Multi Agent RL 0/7

基本から始まり、一通り強化学習を順を追って勉強していくので、全体を網羅するにはいいのかもしれません。

1週目は「マルコフ決定過程」についてですが、
・強化学習の基礎知識(ビデオや文章)
・ミニテスト
・OpenAIのインストール
・Google Dopamine(Google最新の強化学習フレームワーク)の紹介
などです。

2週目(来週)から本格的にコーディングしていくという感じでしょうか。
無料コースなので、それほど親切丁寧に指導してくれるというわけではなさそうですが、Forumなどもあるので、聞きたいことがあれば誰かが答えてくれるといった環境はあります。そもそも強化学習に興味あるけど、どのような順番で何を勉強していけばいいかという手がかりは最低限得られるので、その後独学する手立てにもなると思います。

2018年9月10日月曜日

Google Colabの無料GPUで強化学習(Keras-RL)を試す

最近は、Google Colabの無料GPU(連続12時間まで)を使用して演算が可能なので、機械学習用GPUが搭載されていないパソコンでも充分機械学習の訓練などできそうです。使い方はオンラインのJupyter Notebookという感じです。


Colabスペック:
Ubuntu17.10(現在は18.04)
Intel(R) Xenon CPU @2.3GHz
13GB RAM
GPU NVIDIA Tesla K80

スペックはこんな感じ。アルゴリズムにもよりますがTesla K80がどのくらい高速なのかが気になります。自前のゲーミングノートはPascal世代のGTX1060ですが、K80はそれよりも2世代前のKepler世代のGPUのようです。
!nvidia-smiを打ち込むと以下。


このほか、機械学習に必要な基本的なライブラリはnumpy、pandas、matplotlib、Tensorflow、Kerasも含め既にインストールしてあるようです。Pytorchは入っていないのでインストールする必要があります(追記:その後Pytorchなどもインストール済みになったようです)。

ということで、前回のKeras-RL + Open AI Gym Atariの訓練(1750000ステップ)を比較してみようかと。自前のゲーミングノートGTX1060では約3時間かかりました。


インストール:
とりあえず、Keras-RLやGymなど必要なものをapt-get installやpipで前回同様にインストールする必要があります。
!pip install gym keras-rl pyglet==1.2.4
!apt-get install -y cmake zlib1g-dev libjpeg-dev xvfb ffmpeg xorg-dev python-opengl libboost-all-dev libsdl2-dev swig
!pip install 'gym[atari]'
最初に「!」マークをつけたあとpipやapt-getなどでインストールできます。今回は上記のものをインストールしました。前回Jupyterの際、pyglet1.3.2だとダメだったので、最新ではないpyglet1.2.4をインストールしています。
必要なライブラリなどはインストールできますが、再度ログインするとまたインストールし直さないといけないようです(Colab内は毎回クリアされる?)。


実行時間の比較:
以下が自前のGTX1060での訓練時のログ。これは前回Jupyter用に書き直したAtariブロック崩しの訓練です。
10000ステップで約60秒かかっています。訓練するには合計1750000ステップあるので、約3時間ということになります。

これに比べて、Google Colab(Tesla K80)の方は以下。
10000ステップで約108秒かかっています(意外と遅い)。
自前のゲーミングノート(GTX1060)のほうが1.8倍速いという計算になります。
とはいっても普通のMacBook(CPU演算)に比べればはるかに速いとは思います。

しかしながら、370000ステップの途中で突然ストップしてしまいました。
最後に、「Buffered data was truncated after reaching the output limit」という表示が出ています。何か出力限界に達したようで、途中停止しています。
検索してみると、Stack flowに似たような件について書いてありました。RAMかGPUのメモリをオーバーしてしまったようで、バッチサイズを少なくするか、小分けに途中経過(ウェイト)を保存しておいて再開するかなどの工夫が必要そうです。一応プログラム上ではチェックポイントを設けて250000ステップごとにウェイトを途中保存してはあります。
やはりある程度の規模になると、使用する上での工夫が必要そうです。今回の場合だと1750000ステップの訓練をさせるには概算すると5時間はかかりそうです。連続使用12時間以内で済みそうですが、それ以前にメモリなども気を遣わなければいけないということです。
ということで、処理速度を知りたかったので訓練は途中で終了。

動画表示:
そのかわりに、以前保存しておいたウェイトを読み込んでColab上に表示できるか試してみました。
尚、外部ファイルなどを読み込ませるには以下を書き込んでランさせるとファイル選択ダイアログがでてきて、パソコンの任意の場所からファイルを読み込むことができます。
from google.colab import files
uploaded = files.upload()
前回Jupyter上で表示可能であった方法を試してみましたが、画面のちらつき、動画としての更新速度が非常に遅く、いまいちという結果。

ということから一旦出力画像を非表示にして、matplotlibのArtistAnimationに変換後表示する方法を試してみました(以下)。
from rl.callbacks import Callback
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from IPython.display import HTML
%matplotlib inline

ims = []

class Render(Callback):
    def on_step_end(self, step, logs={}):
        im = plt.imshow(env.render(mode='rgb_array'))
        ims.append([im])

weights_filename = 'dqn_{}_weights.h5f'.format(ENV_NAME)
dqn.load_weights(weights_filename)

callbacks = Render()
fig = plt.figure(figsize=(4,5))
plt.axis('off')
dqn.test(env, nb_episodes=1, visualize=False, callbacks=[callbacks])

ani = animation.ArtistAnimation(fig=fig, artists=ims, interval=10)
# ani.save("anim.gif", writer = "imagemagick")  # imagemagick for Ubuntu
plt.close()

HTML(ani.to_jshtml())         # JavascriptHTML output
#HTML(ani.to_html5_video())   # HTML5 Video output
これは前回もJupyter上で試した方法です(前半の訓練時のコードは省いてあります)。一旦画像は非表示にして、Keras-RLのCallback関数をオーバーライドして表示用サブクラスをつくっています(詳しくは前回参照)。Keras-RLはdqn.test()で別窓に動画表示してしまうので、それを非表示にしておき、Jupyter上で表示可能なデータに変換してから表示しています。
結果的には、アニメーションとして最後表示することができました。Jupyterも表示には手こずりましたが、Colabはオンラインでもあるためどうしてもリアルタイムだと遅くなってしまうようで、このようにアニメーションとして変換してしまえば問題なさそうです。アニメーションに変換する分、少しだけ(数十秒程度)余計に時間がかかりますが、特に目立った遅さではないのでこれでいけそうです。

アニメーションとして保存してあるので、オンラインによるタイムラグはなく普通に速く動きます。
Atariの場合はこのようにColab上に表示されますが、CartPoleのようなClassic controlの場合はレンダリングの仕組みが違うためかColab上のアニメーションだけでなく別窓にも表示されてしまいます。

追記:尚、このボタン付きアニメーションをWebページに埋め込むには以下。
Matplotlib Animation embed on web page:アニメーションのWebページ上への埋め込み


まとめ:
結果的には自前のゲーミングノートGTX1060のほうが速かったのですが、数時間かかりそうな訓練の場合にはGoogle Colabのほうで処理させておき、メインのパソコンではまた別の作業をという感じで使うこともできそうなので、それなりに便利かと。
今後はこのようなGPUのクラウドサービスが主流となってくれば、機械学習をするにはわざわざゲーミングパソコンを買うまでもないかと思います。ネットだけできればいいような5万円前後のGoogle Notebookでもいいのかもしれません。


2018年9月7日金曜日

Keras-RLで強化学習/DQN(Deep Q-Network)を試してみる

前回GANについて理解を深めてみましたが、その後もGANの発展型となるACGANやInfoGANについても引き続き勉強中です。しかしながら、今回はやや方向転換して強化学習(Reinforcement Learning)について試してみました。

環境:
Ubuntu 18.04
GTX 1060
CUDA 9.0
python 3.6
tensorflow 1.9
Keras 2.1.6
Keras-rl 0.4.2
Jupyter Notebook 5.6


アルゴリズム:
強化学習には独特のアルゴリズムが使われており、ディープラーニング以前にも

・Q-Learning
・SARSA
・モンテカルロ法

などが基本としてあるようです。
その後、AlphaGOで有名となった

・DQN(Deep Q-Network)

そして、さらに改良された

・Double DQN
・Dueling DQN
・AC3
・UNREAL
・PPO

などがあるようです。日々改良されているようですが、どれがいいのかは目的によっても異なるようです。とりあえず今となってはDQNあたりが基本かと。


OpenAI:
手っ取り早く強化学習を勉強するならOpenAIのGYMを利用するとよさそうです。GYMには倒立振子やATARIのビデオゲームなどの教材があり、強化学習アルゴリズムを書き足せばすぐに試すことができます。
このページのインストール方法に従って必要なライブラリなどをインストールしますが、ATARIのビデオゲームを使いたい場合はcmakeも必要となるので、Ubuntuであれば一通り以下のコマンドで全てインストールしておいたほうが良さそうです。
apt-get install -y python-numpy python-dev cmake zlib1g-dev libjpeg-dev xvfb ffmpeg xorg-dev python-opengl libboost-all-dev libsdl2-dev swig

動作チェック:
Getting Started with Gymにも書いてありますが、以下のコードで100ループ動きます(ランダムな動き)。ちなみにこのままだとJupyter Notebookでは表示(レンダリング)されないので、.pyファイルにして実行させないといけません。

import gym

env = gym.make('CartPole-v0')
env.reset()

for _ in range(100):
    env.render()
    env.step(env.action_space.sample())
env.close()

あっというまに表示が終わってしまうので以下のようにtime.sleep()でディレイを加えてみました。

import gym
import time

env = gym.make('CartPole-v0')
env.reset()

for _ in range(100):
    env.render()
    env.step(env.action_space.sample())
    time.sleep(0.02)
env.close()


Jupyter Notebookの場合:
アニメーションをJupyter Notebook上で表示するには少し工夫が必要です。stack overflowにもいくつか方法が書いてあります。
ATARIの場合であれば以下の方法で表示可能でした。


import gym
from IPython import display
import matplotlib.pyplot as plt
%matplotlib inline

env = gym.make('Breakout-v0')
env.reset()

img = plt.imshow(env.render(mode='rgb_array'))

for _ in range(100):
    img.set_data(env.render(mode='rgb_array'))
    display.display(plt.gcf())
    display.clear_output(wait=True)
    action = env.action_space.sample()
    env.step(action)
env.close()
しかし、CartPole-v0のようなClassic controlの場合だとエラーがでてしまうので、インストールしてあるpyglet1.3.2を一旦アンインストール(pip uninstall pyglet)して、pyglet1.2.4をインストール(pip install pyglet==1.2.4)し直すといいようです(こちらの方法)。ただこの方法だとJupyter上だけでなく別窓も開いてしまいます。そして、env.close()を最後に書き加えないと、別窓を閉じることができなくなるので要注意。

別窓だけの表示でいいのであれば(Jupyter上には表示させない)、pyglet1.2.4にダウングレードさえしておけば、以下の方法でも可能でした。
import gym
import time

env = gym.make('CartPole-v0')
env.reset()

for _ in range(100):
    env.render()
    env.step(env.action_space.sample())
    time.sleep(0.02)
env.close()
Jupyter Notebook上に表示させる方法をいろいろ探してみましたが、別窓での表示であれば簡単そうなので、以下のKeras-RLでも別窓表示にすることにしました。


Keras-RL:
とりあえずDQNで強化学習をしたいので、どの機械学習フレームワーク(Tensorflow、Keras、Pytorchなど)を使えばいいかということですが、Keras-RLというKeras向けの強化学習用のライブラリがあり、以下のようなアルゴリズム(ここに書いてある)が既に搭載されており、すぐに使うことができます。

NameImplementationObservation SpaceAction Space
DQNrl.agents.DQNAgentdiscrete or continuousdiscrete
DDPGrl.agents.DDPGAgentdiscrete or continuouscontinuous
NAFrl.agents.NAFAgentdiscrete or continuouscontinuous
CEMrl.agents.CEMAgentdiscrete or continuousdiscrete
SARSArl.agents.SARSAAgentdiscrete or continuousdiscrete
複雑なアルゴリズムをコーディングしなくても、既存の関数にパラメータを渡せば計算してくれますが、基本となるQ-Learningの仕組みはある程度理解しておいたほうがよさそうです。
Action Space欄に離散値か連続値かの違いがあるので、目的に応じて使い分けるといいと思います。


DQNを試してみる:

CartPoleのサンプル:
Keras-RLにはいくつかのサンプルコードがあるので、dqn_cartpole.pyを試してみることに。
Jupyter NotebookでRunさせる場合は、別窓としてアニメーションが表示されます。終了後別窓を閉じるために、最後の行にenv.close()を追加しておきます。


import numpy as np
import gym

from keras.models import Sequential
from keras.layers import Dense, Activation, Flatten
from keras.optimizers import Adam
from rl.agents.dqn import DQNAgent
from rl.policy import BoltzmannQPolicy
from rl.memory import SequentialMemory

ENV_NAME = 'CartPole-v0'

env = gym.make(ENV_NAME)
np.random.seed(123)
env.seed(123)
nb_actions = env.action_space.n

model = Sequential()
model.add(Flatten(input_shape=(1,) + env.observation_space.shape))
model.add(Dense(16))
model.add(Activation('relu'))
model.add(Dense(16))
model.add(Activation('relu'))
model.add(Dense(16))
model.add(Activation('relu'))
model.add(Dense(nb_actions))
model.add(Activation('linear'))
print(model.summary())

memory = SequentialMemory(limit=50000, window_length=1)
policy = BoltzmannQPolicy()
dqn = DQNAgent(model=model, nb_actions=nb_actions, memory=memory, nb_steps_warmup=10,
               target_model_update=1e-2, policy=policy)
dqn.compile(Adam(lr=1e-3), metrics=['mae'])

dqn.fit(env, nb_steps=50000, visualize=True, verbose=2)
dqn.save_weights('dqn_{}_weights.h5f'.format(ENV_NAME), overwrite=True)

dqn.test(env, nb_episodes=5, visualize=True)
env.close()
GTX1060で、学習50000ステップ、約5分かかりました。
DQNAgentクラスに必要な項目を渡すだけなので、アルゴリズム的には超シンプルです。
CartPoleに関しては左か右に動かすだけなので、env.action_space.nは2になります。
最後のほうにあるdqn.save_weight()で学習したウェイトが外部保存されるので、次回このウェイトをつかってテストするには、以下のように書き換えることになります。
# 以下をコメントアウトして
# dqn.fit(env, nb_steps=50000, visualize=True, verbose=2)
# dqn.save_weights('dqn_{}_weights.h5f'.format(ENV_NAME), overwrite=True)

# かわりに保存したウェイトを読み込む
dqn.load_weights('dqn_{}_weights.h5f'.format(ENV_NAME))

dqn.test(env, nb_episodes=5, visualize=True)
env.close()
デフォルトでは、以下のように一回のepisodeでsteps: 200になっています。
例えばsteps: 500に変えるには、
env = gym.make("CartPole-v0")
env._max_episode_steps = 500
とすればいいようです(ここに書いてありました)。


ATARIブロック崩しをJupyter Notebook上に表示:
もうひとつは、ATARIのブロック崩しのサンプルです。これは (210, 160, 3) のRGB画像を入力としてCNNを通して学習していきます。画像から判断するので、どんなゲームでもいいということになります。サンプルにある通り1750000ステップ学習するために約3時間かかりました(GTX1060)。
もともとこのサンプルは.pyファイルですが、Jupyter Notebook上に表示できるように少し手を加えてみました。サンプルは最後の方にあるdqn.test()で結果表示されますが、既存コードを見るとenv.render(mode='human')が使用されており、Jupyter Notebook上に表示するにはenv.render(mode='rgb_array')に変換する必要がありそうです。
そのため、既存の結果表示はvisualize=Falseで非表示にし、かわりに自前のCallback関数を追加することで毎ステップ画像表示させることにしました。またargparseはJupyterでは使えないので消去し、そのかわりに各変数を用意しました。


from PIL import Image
import numpy as np
import gym

from keras.models import Sequential
from keras.layers import Dense, Activation, Flatten, Convolution2D, Permute
from keras.optimizers import Adam
import keras.backend as K

from rl.agents.dqn import DQNAgent
from rl.policy import LinearAnnealedPolicy, BoltzmannQPolicy, EpsGreedyQPolicy
from rl.memory import SequentialMemory
from rl.core import Processor
from rl.callbacks import FileLogger, ModelIntervalCheckpoint


INPUT_SHAPE = (84, 84)
WINDOW_LENGTH = 4

class AtariProcessor(Processor):
    def process_observation(self, observation):
        assert observation.ndim == 3
        img = Image.fromarray(observation)
        img = img.resize(INPUT_SHAPE).convert('L')
        processed_observation = np.array(img)
        assert processed_observation.shape == INPUT_SHAPE
        return processed_observation.astype('uint8')

    def process_state_batch(self, batch):
        processed_batch = batch.astype('float32') / 255.
        return processed_batch

    def process_reward(self, reward):
        return np.clip(reward, -1., 1.)

ENV_NAME = 'BreakoutDeterministic-v4'
env = gym.make(ENV_NAME)
np.random.seed(123)
env.seed(123)
nb_actions = env.action_space.n
input_shape = (WINDOW_LENGTH,) + INPUT_SHAPE

model = Sequential()

if K.image_dim_ordering() == 'tf':
    model.add(Permute((2, 3, 1), input_shape=input_shape))
elif K.image_dim_ordering() == 'th':
    model.add(Permute((1, 2, 3), input_shape=input_shape))
else:
    raise RuntimeError('Unknown image_dim_ordering.')

model.add(Convolution2D(32, (8, 8), strides=(4, 4)))
model.add(Activation('relu'))
model.add(Convolution2D(64, (4, 4), strides=(2, 2)))
model.add(Activation('relu'))
model.add(Convolution2D(64, (3, 3), strides=(1, 1)))
model.add(Activation('relu'))
model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dense(nb_actions))
model.add(Activation('linear'))
#print(model.summary())

memory = SequentialMemory(limit=1000000, window_length=WINDOW_LENGTH)
processor = AtariProcessor()

policy = LinearAnnealedPolicy(EpsGreedyQPolicy(), attr='eps', value_max=1., value_min=.1, value_test=.05,
                              nb_steps=1000000)

dqn = DQNAgent(model=model, nb_actions=nb_actions, policy=policy, memory=memory,
               processor=processor, nb_steps_warmup=50000, gamma=.99, target_model_update=10000,
               train_interval=4, delta_clip=1.)

dqn.compile(Adam(lr=.00025), metrics=['mae'])

# コールバックとJupyter表示用モジュールのインポート
from rl.callbacks import Callback
from IPython import display
import matplotlib.pyplot as plt
%matplotlib inline

# 表示用Renderサブクラス作成(keras-rlのCallbackクラス継承)
class Render(Callback):
    def on_step_end(self, step, logs={}):
        plt.clf()
        plt.imshow(env.render(mode='rgb_array'))
        display.display(plt.gcf())
        display.clear_output(wait=True)

MODE = 'train' # 'train' or 'test' 学習とテストのモード切替え

if MODE == 'train':
    weights_filename = 'dqn_{}_weights.h5f'.format(ENV_NAME)
    checkpoint_weights_filename = 'dqn_' + ENV_NAME + '_weights_{step}.h5f'
    log_filename = 'dqn_{}_log.json'.format(ENV_NAME)
    callbacks = [ModelIntervalCheckpoint(checkpoint_weights_filename, interval=250000)]
    callbacks += [FileLogger(log_filename, interval=100)]
    dqn.fit(env, callbacks=callbacks, nb_steps=1750000, log_interval=10000)

    dqn.save_weights(weights_filename, overwrite=True)
    # dqn.test(env, nb_episodes=10, visualize=False)
    
elif MODE == 'test':
    weights_filename = 'dqn_{}_weights.h5f'.format(ENV_NAME)
    dqn.load_weights(weights_filename)

    # 表示用コールバック関数を適用
    callbacks = Render()
    plt.figure(figsize=(6,8))
    dqn.test(env, nb_episodes=2, visualize=False, callbacks=[callbacks])

env.close()
元々のサンプルはargparseで'train'か'test'でモードの切り替えをしていましたが、かわりに変数MODEを用意して切り替えています(argparseの代わりにeasydictを使うといいようです)。
keras-rlのCallback関数をオーバーライドしJupyter用に表示用サブクラスをつくって、毎ステップごとにenv.render(mode='rgb_array')を呼び出して表示させています。keras-rlのCallbackクラスを見てみると、episodeやstepの前半後半のタイミングでコールバックできるようで、今回はstep後半のon_step_end()に表示機能を挿入しておきました。
この結果、一応Jupyter上には表示できるようになりましたが、plt.imshow()を使っているせいか動きが遅くなってしまいます。やはり別窓に表示させたほうがいいかもしれません。リアルタイムで表示させなくてもいいのであれば、以下の方法がいいかと。


Jupyter Notebook上にアニメーション表示とGIF動画保存:
matplotlibのArtistAnimationクラスで先程のブロック崩しを表示しつつ、GIF動画として保存する方法についてです。訓練後のテスト部分を少し変えて以下のようにしてみました。
from rl.callbacks import Callback
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from IPython.display import HTML
%matplotlib inline

ims = []  # アニメーション用リスト

class Render(Callback):
    def on_step_end(self, step, logs={}):
        im = plt.imshow(env.render(mode='rgb_array'))
        ims.append([im])

weights_filename = 'dqn_{}_weights.h5f'.format(ENV_NAME)
dqn.load_weights(weights_filename)

callbacks = Render()
fig = plt.figure(figsize=(4,5)) # 出力画面サイズ調整
plt.axis('off')                 # 目盛り、枠線なし
dqn.test(env, nb_episodes=1, visualize=False, callbacks=[callbacks])

ani = animation.ArtistAnimation(fig=fig, artists=ims, interval=10)
# ani.save("anim.gif", writer = "imagemagick") # GIFアニメ保存する場合はコメントアウト
plt.close()

# Jupyter Notebook上にアニメーション表示
HTML(ani.to_jshtml())        # JavascriptHTML出力
#HTML(ani.to_html5_video())  # HTML5 Video出力(.mp4ファイルとしてダウンロード可)
サブクラスRender()内で予め用意しておいたアニメーション用リストに毎ステップ画像を追加していき、それをあとからArtistAnimationで動画にするという手順です。ArtistAnimationを使えばすぐにGIF動画として保存もできます。


Jupyter上のアニメーション表示としては2種類あり、JavascriptHTMLは動画速度を変えて再生も可能なので便利です(上画像)。またHTML5 Video出力のほうは表示画面から.mp4として動画をダウンロードできる機能がついています。
尚、matplotlibのArtistAnimationについては以前投稿したここを参照して下さい。

追記:尚、このボタン付きアニメーションをWebページに埋め込むには以下。
Matplotlib Animation embed on web page:アニメーションのWebページ上への埋め込み


まとめ:
OpenAIのGYMとKeras-RLを使うことで簡単にDQNを試すことができます。DQNに渡すパラメータについて理解しておけばいいという感じです。
このほか、二足歩行モデルがあるMuJoCo、ロボットアームやハンドマニピュレータがあるRoboticsのサンプルもあります。学習させるには結構時間かかりそうなので、まだ試してはいませんが、強化学習は生成モデルとはまた違ったアプローチをしている部分が興味深いという感じ。また、強化学習と生成モデルの組み合わせもできそうなので、アルゴリズム的に面白くなりそうです。
GPUがなくても、Google Colabを使えばこの程度の訓練であれば短時間でおわるかもしれません。

関連:
Google Colabの無料GPUで強化学習訓練を試す(Keras-RL)



2018年8月5日日曜日

tf.kerasでDCGAN(Deep Convolutional Generative Adversarial Networks)

前回VAE(Variational Autoencoder)を試して見たので、今回はDCGAN(Deep Convolutional Generative Adversarial Networks)をKerasで実装しつつ理解を深めたいと思います。使用データはMNISTです。 元々GANによる画像生成に興味があったのですが、約10ヶ月前にサンプルを試したときには、二つの敵対するネットワークによって画像生成するという大まかな流れしか理解できませんでした。
チュートリアルなどでは、
・Autoencoder(AE)
・Variational Autoencoder(VAE)
・Generative Adversarial Networks(GAN)
という順番で説明されていることが多く、VAE(潜在空間、ベイズ推定、KLダイバージェンスなど)を理解しないことにはGANを理解することも難しいかなと勝手に思っていましたが、そもそもAEとVAEも大きく異なるしGANもまた別のアルゴリズムという感じで、基本のAEが分かればGANを理解することはできそうです。
GANの派生型はいろいろありますが、とりあえず今回はDCGANを理解しようと思います。


上の画像はDCGANの構造で、左半分がGeneratorで右半分がDiscriminatorです。最終的にはGenerator層の右端(上画像中央:64x64x3)に画像が生成されます。
まずGeneratorで画像生成する前に、Discriminatorの左端から訓練画像を入力してDiscriminatorだけを教師あり学習します。その後、GeneratorとDiscriminatorを連結させたネットワークで教師あり学習させます。このときDiscriminatorの学習を停止させておいてGeneratorだけが学習するようにします。そうすると既に学習されているDiscriminatorを利用しながらGeneratorだけが学習し、その結果として画像が生成されます。この交互に学習させる手順がわかりにくいので難しく見えるのかもしれません。
GeneratorはAEやVAEのdecoder層だけで構成されている感じで、最初のノイズ画像はVAEで言う潜在空間と呼びますが、途中でReparameterization TrickやKLダイバージェンスなどの複雑な計算を使うこともないので、潜在空間というよりは単なるノイズ画像(np.random.normalで生成)と捉えたほうがよさそうです。


GANの訓練の特長:
先ほどの訓練手順についてですが、GANの訓練では、以下のようにそれぞれ別々に訓練させるようです。
・Discriminatorの本物画像識別の訓練(訓練画像を利用)
・Discriminatorの偽物画像識別の訓練(Generator生成画像を利用)
・Generatorの本物画像生成の訓練(Discriminator層も利用するが訓練を一時停止)

GANの説明では、Discriminatorは本物か偽物を見分けると書いてあり、Discriminatorに入力した画像が最終的に1か0に判定されるような層になっています。訓練用画像(本物)を入力した際にはラベルを1とし、ノイズ画像(偽物)を入力した際にはラベルを0として固定して(教師データとして)、それぞれを分けて学習させていきます。そうすることで、Discriminator層には本物/偽物を見分ける重み付けが徐々に形成されていきます。

一方Generatorでは、ノイズ画像を本物画像に近づくように訓練しなければいけないのですが、AEやVAEのように具体的な訓練画像を目指してdecodeしていくわけではないので、一体どうやって本物に近づけていくのだろうと疑問に思っていました。
最終的にはGeneratorのノイズ画像が、Discriminator層の最後の1次元の出力層で1(本物)になるようにGenerator層が学習していけばいいということになります。そのためには、Generator単独で訓練するのではなく、Discriminator層も連結してラベル(教師データ)を1に固定して訓練させます。画像を教師データにして訓練するのではなく、本物かどうかというラベルを教師データにして訓練する点がGANの特長だと思います(それでも画像生成は可能)。ただし、二つを連結させると両方とも訓練してしまうので、二つのうちGenarator層だけを訓練させるために、

discriminator.trainable=False

を挿入してDiscriminatorの訓練を一時停止しておく必要があります。
この部分に注意すれば、あとはそれほど難しいアルゴリズムが登場してくることはないかと。解説を読むと数式や抽象的な概念が出てきますが、アルゴリズム的に訓練の手順を理解すればそれほど難しいものではないような気がします。AEではモデル全体は真ん中がくびれていますが、GANの場合は始まりと終わりが細くて真ん中が太くなっているので(decoderとencoderを逆につなげたように)一見わかりにくいという印象です。しかしよくみれば、100次元のノイズを入力元として、decoder(Genarator)で28*28次元のMNIST画像に拡大し(生成画像)、またそれをencoder(Discriminator)で1次元まで落として、最後はsigmoidで0/1判定するという流れになっています。


DCGAN実装:

環境:
Ubuntu 18.04.1
GTX 1060
CUDA 9.0
Python 3.6
Tensorflow 1.9 (Keras 2.1.6)
Jupyter Notebook 5.6


まずはモジュールのインポート。今回もJupyter Notebookで。
import tensorflow as tf
from tensorflow.keras.models import Sequential, Model
from tensorflow.keras.layers import Input, Dense, Reshape, Flatten, BatchNormalization
from tensorflow.keras.layers import Conv2D, Conv2DTranspose, Activation, LeakyReLU
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.datasets import mnist
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

tf.logging.set_verbosity(tf.logging.ERROR)
警告がでるので、tf.logging.set_verbosity()で非表示にしています。
次に、各種変数とGenerator層。
img_rows = 28
img_cols = 28
channels = 1
img_shape = (img_rows, img_cols, channels)
latent_dim = 100

def generator_model():
    model = Sequential()
    model.add(Dense(1024, input_shape=(latent_dim,)))
    model.add(BatchNormalization())
    model.add(LeakyReLU(alpha=0.01))
    model.add(Dense(7*7*128))
    model.add(BatchNormalization())
    model.add(LeakyReLU(alpha=0.01))
    model.add(Reshape((7,7,128)))
    model.add(Conv2DTranspose(64, kernel_size=5, strides=2, padding='same'))
    model.add(BatchNormalization())
    model.add(LeakyReLU(alpha=0.01))
    model.add(Conv2DTranspose(1,kernel_size=5, strides=2, padding='same'))
    model.add(Activation('tanh'))
    
    return model
Generator層では、BatchNomarization、LeakyReLU、Conv2DTransposeを入れてみました。DCGANを安定させる方法としていろいろ工夫があるようですが、いくつか試したなかで今回はこの方法で。LeakyReLUのalpha値をデフォルトにするだけでも結果が変わってしまうので、このへんのパラメーターチューニングは難しそう。

次に、Discriminator層。
def discriminator_model():
    model = Sequential()
    model.add(Conv2D(32, kernel_size=5, strides=2,padding='same', input_shape=img_shape))
    model.add(LeakyReLU(alpha=0.01))
    model.add(Conv2D(16,kernel_size=5,strides=2, padding='same'))
    model.add(BatchNormalization())              
    model.add(LeakyReLU(alpha=0.01))
    model.add(Flatten())
    model.add(Dense(784))
    model.add(BatchNormalization())
    model.add(LeakyReLU(alpha=0.01))
    model.add(Dense(1))
    model.add(Activation('sigmoid'))
    
    return model
こちらもLeakyReLU、BatchNomalizationを入れています。
Dropoutを入れて試してみましたが逆効果となってしまったので、今回はなし。

次は、GenaratorとDiscriminatorの連結層。
def combined_model():
    discriminator.trainable = False
    model = Sequential([generator, discriminator])
    
    return model
Generatorを訓練する際にこの連結層を使用します。そのため事前に、discriminator.trainable=Falseにしておきます。こうすることでGeneratorだけの訓練になります。

まずは、MNISTデータの読み込みと正規化(-1〜1)。そして、Discriminator、Generator、Combined(G + D)モデルの定義。Adamで最適化。
(x_train, _), (_, _) = mnist.load_data()
x_train = (x_train.astype('float32') - 127.5) / 127.5
x_train = x_train.reshape(-1, 28, 28, 1)

# Discriminator Model
discriminator = discriminator_model()
discriminator.compile(loss='binary_crossentropy', optimizer=Adam(lr=0.0002, beta_1=0.5), metrics=['accuracy'])

# Generator Model
generator = generator_model()

# Combined(G + D) Model
combined = combined_model()
combined.compile(loss='binary_crossentropy', optimizer=Adam(lr=0.00015, beta_1=0.5))

そして訓練ループ。
まずDiscriminatorの訓練をリアル画像とフェイク画像に分けて行います。
訓練はfitではなくtrain_on_batchでバッチごとに行うといいようです。その際にDiscriminatorの場合は、フェイク:0とリアル:1の二つのラベルを教師データとして与えておき、それぞれを別々に訓練し、最後にそのロスを合算しておきます。
次のGeneratorの訓練では、教師データをリアル:1として与えておき、Discriminatorの訓練を一時停止した状態で連結したcombinedモデルを訓練させます。そうすると出力がリアル:1になるようにGeneratorの重み付けが形成されます。この部分がGAN特有の訓練のさせ方だと思います。
batch_size = 32
real = np.ones((batch_size, 1))
fake = np.zeros((batch_size, 1))

epochs = 10000
Loss_D = []
Loss_G = []

import time
start = time.time()

for epoch in range(epochs):
    
    # shuffle batch data
    idx = np.random.randint(0, x_train.shape[0], batch_size)
    imgs = x_train[idx]
    
    # Train Discriminator
    # sample noise images to generator
    noise = np.random.normal(0, 1, (batch_size, latent_dim))
    gen_imgs = generator.predict(noise)

    # train discriminator real and fake
    d_loss_real = discriminator.train_on_batch(imgs, real)
    d_loss_fake = discriminator.train_on_batch(gen_imgs, fake)
    d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)

    # Train Generator
    g_loss = combined.train_on_batch(noise, real)
 
    Loss_D.append(d_loss[0])
    Loss_G.append(g_loss)

    if epoch % 100 == 0:
        print("%04d [D loss: %f, acc.: %.2f%%] [G loss: %f] %.2f sec" % (epoch, d_loss[0], 100*d_loss[1], g_loss, time.time()-start))

    if epoch == epochs - 1:
        r, c = 5, 5
        noise = np.random.normal(0, 1, (r * c, latent_dim))
        gen_imgs = generator.predict(noise)
        gen_imgs = 0.5 * gen_imgs + 0.5

        fig, axs = plt.subplots(r, c)
        cnt = 0
        for i in range(r):
            for j in range(c):
                axs[i,j].imshow(gen_imgs[cnt, :,:,0], cmap='gray')
                axs[i,j].axis('off')
                cnt += 1
        plt.show() 
Discriminatorの訓練(2種類別々で訓練し最後に合算):
d_loss_real=ノイズ-->Generator-->Discriminator-->realラベル(教師データ)
d_loss_fake=ノイズ-->Generator-->Discriminator-->fakeラベル(教師データ)
Discriminator_loss=0.5*(d_loss_real+d_loss_fake)

Generatorの訓練:
g_loss=ノイズ-->Generator-->Discriminator(訓練停止)-->realラベル(教師データ)
という手順でそれぞれを訓練しています。
教師データとなるreal/fakeラベルはbatch_size分用意しておき、train_on_batch()に代入します。

合計10000エポック回して、100エポックごとに各Lossを表示。最後に最終画像を表示。

生成画像結果:
生成画像(10000エポック)。

途中の画像も見てみましたが、5000エポックくらいでもそこそこ識別できるレベルにはなりましたが、10000エポックくらい回したほうがよさそうです(GTX1060で約6分、Macだと1時間はかかりそう)。モード崩壊(似たような画像ばかりになる現象)は発生していないようです。

生成画像(5000エポック)。やや不明瞭??

生成画像(2500エポック)。

生成画像(2000エポック)。このあたりだとやはり不鮮明。


以下のコードでLossを表示。
plt.plot(np.arange(epochs), Loss_D, 'r-')
plt.plot(np.arange(epochs), Loss_G, 'b-')

赤:Discriminator Loss、青:Generator Loss
これをみてもよくわからない。3000エポック以降はあまりかわっていないようなので5000エポックくらいの訓練でもいいのかもしれない。


まとめ:
DCGANは思っていたよりもシンプルな構造で、GeneratorとDiscriminatorをつくれば、あとはそれぞれの訓練の手順を間違わないようにコーディングしていけばいいという感じです。どちらかというとVAEのほうが難しかったという印象です。
ただしDCGANで難しいのは、GeneratorとDiscriminatorの中身の層をどうするか?ということかもしれません。ここを参考にすると、LeakyReLUやBatchNormを使った方がいいらしいのですが、層の順番やパラメータが少し違うだけでも生成画像がノイズのままで終わってしまうので、安定的に画像生成させるにはいろいろ試してみる必要がありそうです。GANの派生型はたくさんあるので、DCGAN以外のGANも試して比較してみたほうがよさそうです。


参考にしたサイト:
https://towardsdatascience.com/having-fun-with-deep-convolutional-gans-f4f8393686ed
https://elix-tech.github.io/ja/2017/02/06/gan.html
https://qiita.com/triwave33/items/1890ccc71fab6cbca87e
https://qiita.com/t-ae/items/236457c29ba85a7579d5


直感 Deep Learning ―Python×Kerasでアイデアを形にするレシピ
Posted with Amakuri at 2018.12.21
Antonio Gulli, Sujit Pal
オライリージャパン
販売価格 ¥3,672

人気の投稿