日本語配列のHHKBを買ったけど英語配列にすれば良かったなぁと少し後悔した話
4月の頭に日本語配列のHHKBを購入しました。
家にいる時間が長くモチベーションが死んでいたので、奮発してしまった...
10万円貰ってないけど作業環境の作りの手始めにHHKB! pic.twitter.com/sTsmmwd8oM
— サニーナッツ☕️ (@ji_o_k) April 8, 2020
最初は尊師スタイルしたいなぁぐらいの気持ちで買ったのですが、今はこれが無いと物足りないと感じるぐらい素晴らしいものでした。
HHKB最高!HHKB最高!HHKB最高!(皆さんも復唱しましょう)
ただ、使ってて英語配列のを買えば良かったなぁとすこーし後悔することが2点あったので書いておきます。(基本的に満足しています)
1. BackSpaceとEnterキーが遠くて押しにくい
使用方法は画像のようにMBAで尊師スタイルなんですが、HHKBってMBAのキーボードより微妙に横に長いんですよ。
この微妙に長いというのが肝で、いままでそれほど違和感なく押していたBackSpaceとEnterキーがとても遠く感じて押すのが絶妙に面倒くさいんですよ。
その点、英字配列はその二つのキーが横長なので右小指でそのまま触れていいなぁと思ったりします。
後、日本語配列の特権である矢印キーもホームポジションから遠くてコーディングの時とかは使わないのでいらなかったなぁという感じです。
結局 BackSpace, Enter, 矢印キーはホームポジションで完結するように、Karabainer Elementsでキーバインド自作しました。
2. 英語配列の方がキーキャップのカスタマイズができる
これ購入するまでは完全に盲点だったんですよね...
元々HHKBはキーキャップのカスタマイズがあまりできないっぽいです。(REALFORCE用のを適応させたり色々できるのはできるらしい)
それでも全く無いわけではなく、PFUの公式ページやKBD FANS、AliExpressで売っています。
HHKB – KBDfans Mechanical Keyboards Store
ただし、売られているのは英語配列
HHKB用のキーキャップは英語配列のものばっかりで、日本語配列用のものが全然見当たらないんですよね。
これとか付けてみたかった...
色々書きましたが、英語配列は触って無いので実は日本語配列の方が性に合っている、、、なんて話になるかもしれません。参考にする方は話半分に見てもらえれば嬉しいです。
まぁ、隣の芝生は青く見えるって事なんですかね。
Pythonでやらかした話とNaNの話
近畿大学 Advent Calendar 2019の10日目 軽い気持ちで始めたアドベントカレンダー、全日埋まってとてもビックリしています。 参加していただいた方々には感謝の気持ちでいっぱいです、ありがとうございます。
この記事では、私がPythonを触っていてやらかしたところを公開します。あと、NaNについてハマった時に調べたものも一応残しておきます。 自分の振り返りと皆さんの反面教師になれればと思います。
==とis
PythonになれてきてからPythonicな書き方を心がけていく中、知り合いに「Pythonではis
で等価判定したほうがオシャレ!」と言われ、==
をis
に全て置換したことがあります。
そうすると一見正しそうに動くのですが...
==とisの挙動の違い ==は値が等しいかどうかを判定する isはオブジェクトidが等しいかどうかを判定する
Pythonの文字列やリストは同値でも、オブジェクトidが違う場合があります。(文字列は日本語が入るとis
ではおかしくなる気がする)なので、is
で判定をすると同値でもFalseが返ってきてハマります。
left = "pythonやらかし" # ==判定 left == "pythonやらかし" # > True # is判定 left is "pythonやらかし" # > False
2次元配列の参照コピー
Pythonは文字列や配列を*
で掛けることができます。
"オラ" * 10 # 'オラオラオラオラオラオラオラオラオラオラ' [0] * 10 # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
しかし、これで二次元配列で生成すると参照コピーの罠にハマります。 これ気づかずにめっちゃ時間溶かしました... 横着せずにリスト内包表記などで書くと良いと思います。
two_d_list = [[0] * 10] * 10 # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] # [0,0]の値だけ1足したい two_d_list[0][0] += 1 # [1, 0, 0, 0, 0, 0, 0, 0, 0, 0] # [1, 0, 0, 0, 0, 0, 0, 0, 0, 0] # [1, 0, 0, 0, 0, 0, 0, 0, 0, 0] # [1, 0, 0, 0, 0, 0, 0, 0, 0, 0] # [1, 0, 0, 0, 0, 0, 0, 0, 0, 0] # [1, 0, 0, 0, 0, 0, 0, 0, 0, 0] # [1, 0, 0, 0, 0, 0, 0, 0, 0, 0] # [1, 0, 0, 0, 0, 0, 0, 0, 0, 0] # [1, 0, 0, 0, 0, 0, 0, 0, 0, 0] # [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
for文
PythonのCなどと違って、for文はシーケンス型を反復する仕組みです。なので、以下のようなことをするとハマります。
1. i
のようなターゲットリストを途中で変更しても反映されない
2. ループ中のシーケンスオブジェクトに内部からappendすると無限ループになる
1つ目に関しては諦めるしかないと思います... 2つ目はスライス表記を用いることで解決できます
num_list = list(range(1, 4)) # ターゲットリストを途中でいじっても意味がない for i in num_list: print(i) i += 1 # 1 # 2 # 3 # スライスを用いてループ中にappendする for i in num_list[:]: num_list.append(i) # [1, 2, 3, 1, 2, 3]
Pythonインタプリタ
ちょっとした計算や何か試したいことがあれば、ターミナルからPythonのインタプリンタを呼び出すことがよくあります。
しかし、一度エンターを押してしまうと、後戻りが出来ず、「あっ、変数名間違えた」「あっ、インデント間違えた」といったやらかしがよくあります。なのでipython
を使うようになりました。ipythonはjupyter notebookでおなじみのセルでコードを実行する環境でコマンドで実行できる対話環境としてデフォルトのインタプリンタより優れているのでおすすめです。
バージョン3.4以下のgcd関数のモジュール
これは主にAtCoderに関連する話なのですが、Pythonのバージョンが3.4以下だとgcd
関数がmathモジュールにありません(現在のAtCoderのPython3のバージョンは3.4)
mathモジュールにgcd
関数が置かれるのは3.5以降で、それ以前のバージョンだとfractionsモジュールにあるのでそれを使用しなければなりません。些細なやらかしかもしれませんが、一分一秒の時間が惜しい競プロでは結構なやらかしでした。
# 3.4以下はこれが使えない from math import gcd # 3.4以前はこっちを使う(3.5以降は非推奨) from fractions import gcd
NaN
a bit pattern of a single-precision or double-precision real number data type that is a result of an invalid floating point operation. 訳: 無効な浮動小数点演算の結果である単精度または倍精度の実数データ型のビットパターン IEEE 754より
PandasでNaNを判定する関数をずっとdf.isnan()
だと思っていてハマりました(?)
実際にはdf.isnull()
です。個別で判別する場合は、math.isnan()
, numpy.isnan()
も有効です。
おまけ(むしろ本題)
np.nan == np.nan # is always False! Use special numpy functions instead.
NaNはあらゆるものとの数値比較、等価演算でFalseを返すのは特性がありますが(自分自身との比較でFalseを返すのもこの特性から)、参照先で比較するisは数値比較ではないので、この原則に当てはまりません。 しかし、Pythonにはmath.nanとnumpy.nanの二種類のNaNがあり、これらはオブジェクトidが違います。
# isを使ったNaN同士の比較 math.nan is math.nan # True # mathモジュールのNaN id(math.nan) # 4538852576 # numpyライブラリのNaN id(np.nan) # 4569389960
ですが、これらはそれぞれのNaNを別々のライブラリの関数で正しく判定できています。
# それぞれのisnan()関数でNaNが正しく判定できる math.isnan(np.nan) and np.isnan(math.nan) # True
どのような実装になっているのか気になったので調べてみました。
math.isnanの実装
math.nanの実装は、cpythonより、Cの実装に従っていると思われます。 そのCの実装はこちらのサイトを参照しました。 - https://ja.cppreference.com/w/c/numeric/math/isnan)
#define Py_IS_NAN(X) isnan(X)
numpy.nanの実装
NumPy core libraries よりC99の実装に従うことがわかる
.. c:function:: int npy_isnan(x) This is a macro, and is equivalent to C99 isnan: works for single, double and extended precision, and return a non 0 value is x is a NaN.
つまりオブジェクトidは違うが元の実装がCで同じなので、同じように動くということでした。なんとなく予想はついていましたが、実際に確認できるとやはり嬉しいですね!
おわりに
アドカレ期限ギリギリになってしまいましたが、これも全てバックアップを阻害して、25G程度のバックアップに3日もかけるようになったiCouldが悪い
Ubuntu18.04にNVIDIA Container Toolkitをインストールする
最初に
nvidia-docker2が非推奨になったそうなので新しく環境構築しました。
手元で上手くいった例を記録として残しているだけで、何が正しいか分かっていない(動けば正義)ので無駄な手順等を行なっている可能性があります、ご了承ください
またコマンドと出力が一緒になっている部分はコマンド前に$
を付けています
環境
試した環境
$ cat /etc/lsb-release DISTRIB_ID=Ubuntu DISTRIB_RELEASE=18.04 DISTRIB_CODENAME=bionic DISTRIB_DESCRIPTION="Ubuntu 18.04.3 LTS"
nvidia-driverとCUDAをインストールする
以下のサイトを参考にインストール
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/cuda-ubuntu1804.pin sudo mv cuda-ubuntu1804.pin /etc/apt/preferences.d/cuda-repository-pin-600 sudo apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/7fa2af80.pub sudo add-apt-repository "deb http://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/ /" sudo apt-get update # 自動で合うdriverを入れてくれる # sudo ubuntu-drivers autoinstallだと435, 以下コマンドだと418のインストールを確認 sudo apt-get -y install cuda-drivers sudo apt-get -y install cuda
.bashrc
にパスを追加
# 以下を追記 export PATH="/usr/local/cuda/bin:$PATH" export LD_LIBRARY_PATH="/usr/local/cuda/lib64:$LD_LIBRARY_PATH"
sudo reboot
で再起動、以下で確認
$ nvidia-smi Fri Nov 15 00:06:50 2019 +-----------------------------------------------------------------------------+ | NVIDIA-SMI 418.87.01 Driver Version: 418.87.01 CUDA Version: 10.1 | |-------------------------------+----------------------+----------------------+ | GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. | |===============================+======================+======================| | 0 GeForce GTX 166... On | 00000000:01:00.0 On | N/A | | 29% 33C P8 5W / 120W | 110MiB / 5911MiB | 0% Default | +-------------------------------+----------------------+----------------------+ | 1 GeForce GTX 1660 On | 00000000:03:00.0 Off | N/A | | 28% 31C P8 3W / 120W | 1MiB / 5914MiB | 0% Default | +-------------------------------+----------------------+----------------------+ +-----------------------------------------------------------------------------+ | Processes: GPU Memory | | GPU PID Type Process name Usage | |=============================================================================| | 0 953 G /usr/lib/xorg/Xorg 39MiB | | 0 1011 G /usr/bin/gnome-shell 69MiB | +-----------------------------------------------------------------------------+ $ nvcc -V nvcc: NVIDIA (R) Cuda compiler driver Copyright (c) 2005-2019 NVIDIA Corporation Built on Sun_Jul_28_19:07:16_PDT_2019 Cuda compilation tools, release 10.1, V10.1.243
Dockerをインストールする
以下のサイトを参考にインストール
sudo apt update sudo apt install apt-transport-https ca-certificates curl gnupg-agent software-properties-common -y curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - sudo apt-key fingerprint 0EBFCD8 sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" sudo apt update sudo apt install docker-ce docker-ce-cli containerd.io -y # 確認 sudo docker run hello-world
バージョンの確認(NVIDIA Container ToolkitはDockerが19.03以降でないとダメ)
$ docker -v Docker version 19.03.4, build 9013bf583a
NVIDIA Container Toolkitをインストール
以下のサイトを参考にインストール
distribution=$(. /etc/os-release;echo $ID$VERSION_ID) curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add - curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit sudo systemctl restart docker
動くか確認
$ nvidia-container-cli info NVRM version: 418.87.01 CUDA version: 10.1 Device Index: 0 Device Minor: 0 Model: GeForce GTX 1660 Ti Brand: GeForce GPU UUID: GPU-4dd3cbbf-cb72-be49-a3d0-4625043ce50e Bus Location: 00000000:01:00.0 Architecture: 7.5 Device Index: 1 Device Minor: 1 Model: GeForce GTX 1660 Brand: GeForce GPU UUID: GPU-914b0174-e95d-0b8f-cf13-d20ed58f707e Bus Location: 00000000:03:00.0 Architecture: 7.5
これでGPUコンテナを実行するとき(run)に--gpus
オプションを付ければ動く
メモ
dockerコマンドの際に一々sudoを打たなくていいようにする この記事より以下コマンドを入力後、再起動 dockerコマンドをsudoの付与無しに実行できるようにする
sudo gpasswd -a "権限を付与するuser" docker
実際にpytorchを動かしてみる
閑話休題 今回はお手軽にpytorch公式のdockerhubからイメージを持ってくる(動くか確認もしたかった)
# 色々オプションをつけているが、最低限なら-pとか-vはいらない # -vするなら適当に作業ディレクトリに移動してから行う docker run -itd --name pytorch -p 8888:8888 -v $PWD/:/workspace --gpus all pytorch/pytorch:1.3-cuda10.1-cudnn7-devel docker exec -it pytorch /bin/bash
nvidia-smi
もちゃんと動くことを確認
学習部分だけいい感じに切り取ってくれている記事があったので利用させてもらう
Docker(19.03)でgpu有効化してpytorchで訓練するまでやる(Ubuntu18.04)
無事動くことを確認
おまけ
この記事より丁寧でわかりやすい導入記事() NVIDIA Container Toolkit を使って Docker コンテナで GPU を使う
エイプリルフールに発表される新元号が、偽物かどうかAIだけで判別する物語 【完】
はじめに
エイプリルフールに発表される新元号が、偽物かどうかAIだけで判別する物語の続き
以下ポエム(改めて見直すと、テストになってないのでガバガバ記事ですが、暖かい目で見ていただけると幸いです)
新元号は令和
西暦2019年4月1日11時30分(ちょい過ぎ)から新元号の記者会見が始まった。
そして菅官房長官によって発表された新元号「令和」
典拠は日本最古の歌集「万葉集」の梅の花の歌より
于時初春令月 氣淑風和 (時に、初春の令月にして、気淑く風和ぎ)
初めて中国古典ではなく、日本古典から採用されたらしい。
予想ガチ勢がAIの学習用辞書データに四書五経とか使っていたら、外れている可能性が高そうである。Wikipediaを辞書データにしてよかった。(中国の詩文集「文選」に似たようなフレーズが存在し、著者はそれを参考にしたという説もあるらしい。つまり中国古典を辞書にしても当たっていた可能性もある。)
後、元号発表がお祭騒ぎだったからか、あまりデマ元号は流れているのを見なかった。
元号判別機は「令和」を元号として判別したのか
グダグダと話したが、「令和」は元号として判定されました、めでたしめでたし。
-----令和----- 適切な語と語の距離感 0.06 <= x <= 0.54 語と語の距離感: 0.1180524155497551 適切な一語目 0.1 <= x <= 0.56, 適切な二語目 0.03 <= y <= 0.49 一語目 平均: 0.1357724815607071, 二語目 平均: 0.1358799934387207 元号かもしれないです! (MTSHの確認は人がしてください)
せっかくなので精度を見てみる
これ前の記事でやらなきゃいけないこと、、、
令和を除く全ての元号を訓練データにしたので、正直なところテストデータがそれこそ新元号「令和」しかなかった。(勉強不足なので他に良い方法があるのかもしれない)
だからと言ってテストデータ1つに対して正解したので精度100%というのもありえない話なので、この際、判別機を少しいじってでも(前回でいうフィルター)過去の元号にも当てはめてエセ精度を見てみようの回。
以下で行う精度は学習データをテストデータにするエセもエセなので気楽に馬鹿らしく見ていきましょう。(書いてた時は血迷ってました。)
テストで使う元号の総数は243
# 過去の元号 +「令和」のリストをgengoとする all_gengo_len = len(gengo) # 243
過去元号フィルターを外す
過去の元号フィルターを外す。
ルール: これまでに元号として用いられたものでないこと
を適用しているが、過去の元号も元号っぽいか判別するので当然外す。
外す作業はjudge
関数から適宜行なっている。
では精度はどうだろう。
# judge関数はTrueかFalseを返す unfil_kakogengo_len = len([gen for gen in gengo if judge(gen, model, mecab)]) # 70 # 精度 unfil_kakogengo_len/all_gengo_len*100 # 28.80658436213992
低い、ハサミギロチンの命中と同じレベル。この数字だけ見れば「令和」もヤマカンで当たったように見える。
しかし、1つ1つを見るとMeCabに品詞として発見されているものが多く見える。過去の元号は既存の単語なのだから引っ掛かって当然か。
-----平成----- 既存の品詞のようです 元号ではなさそうです ・ ・ ・ -----大化----- 既存の品詞のようです 元号ではなさそうです
既存品詞フィルターを外す
既存品詞フィルターを外す。
ルール: 俗用されているものでないこと
を適用していたもの。先ほどの例でも既存の品詞として多くの元号が引っ掛かっていたので、外す。
# 同上 unfil_kakogengo_hinsi_len = len([gen for gen in gengo if judge(gen, model, mecab)]) # 140 # 精度 unfil_kakogengo_hinsi_len/all_gengo_len*100 # 57.61316872427984
精度は倍ほど上がったがそれでもまだ57%、さいみんじゅつと同レベル。
内訳は以下の通り
- 元号の可能性がある (57.61316872427984%)
- 単語間の距離が適切でない (27.160493827160494%)
- 常用漢字ではない (12.345679012345679%)
- 単語の意味が元号向きではない (2.880658436213992%)
3番に関してはどうしようもない。過去の元号は読み書きのしやすさなど重視していなかったのだろうが、ここは外せない。
先ほどから精度を出すために一部のフィルターを外しているが、あくまで元号判別の機能を残すことは忘れてはいけない。(全て外せば100%なのは当たり前)
では2番と4番も外せないのだろうか。
1つの仮説
前回の記事の中で、ルール: 国民の理想としてふさわしいようなよい意味を持つものであること
に関して、その漢字の出し方について考察し、そこで以下のような仮説を立てた。(参考: やっぱり最大の難関は「よい意味の判断」)
重要なのは、この仮説では元号にふさわしい漢字を絞り込んだのではなく、元号の一語目・二語目それぞれにふさわしい漢字を絞り込んだという点である。
ここで1つの仮説が出てきた。
それは 過去の元号は、いい感じの距離感を持つ、いい意味の漢字2文字から成り立っていて、それを基に一語目・二語目それぞれにふさわしい漢字が決まるならば、同様にふさわしい距離感も保証されるというものだ。
それはつまり、元記事の平成の次の元号を、AIだけで決めさせる物語で考察していた「元号として適切な組み合わせ・元号としての組み合わせのバランス」は元号の一語目・二語目それぞれにふさわしい漢字が決まったならば、既に求められているということだ。
はっきり言って、仮説の上に立つ仮説なので正しい保証はどこにもないが、なんだか正しそうな気がする。 (ヤケクソ)
二語の距離感フィルターを外す
上記の仮説に従えば、語の適正フィルターに通りさえすれば、二語の距離感フィルターを通さずとも元号として適切な距離感を持つので、信じて外す。
# 同上 unfil_kakogengo_hinsi_ad_len = len([gen for gen in gengo if judge(gen, model, mecab)]) # 198 # 精度 unfil_kakogengo_hinsi_ad_len/all_gengo_len*100 # 81.48148148148148
精度が80%を超えた。ようやくハイドロポンプの命中率にまで持ってくることができた。 もうこれ以上は外せるフィルターがないので、これで打ち止め。
終わりに
「令和」が無事元号で良かった!
インスタを巧みに使う官庁も流石にエイプリルフールには乗らなかったようで。
ついでにエセ精度も評価してみた。判別機として機能するギリギリのラインで過去の元号を判別すると8割以上の確率で正解を出すことが分かった。
これに関しては、同じデータを使っているので、精度も何もないのだがそこも含めてポエムなのでご愛嬌ということで。
他のテストデータも試してみたいところではあるが、ホイホイ元号が変わるのも面倒なので、しばらくは「令和」が続くことを祈ることにする。
【補足】 リークされた元号候補も判別してみる
コメントで面白そうな情報とリンクをいただいたので判別してみる。
リークされた他の案も調べてみてください! https://www3.nhk.or.jp/news/html/20190402/k10011870221000.html
リークされた「令和」を除いた5つの原案は以下の通り 「英弘」・「久化」・「広至」・「万和」・「万保」
はい、どん!
-----英弘----- 常用漢字ではないようです 元号ではなさそうです -----久化----- 適切な語と語の距離感 0.06 <= x <= 0.54 語と語の距離感: -0.21727973222732544 単語間の距離が適切ではないようです 元号ではなさそうです -----広至----- 適切な語と語の距離感 0.06 <= x <= 0.54 語と語の距離感: 0.21013568341732025 適切な一語目 0.1 <= x <= 0.56, 適切な二語目 0.03 <= y <= 0.49 一語目 平均: 0.004297872539609671, 二語目 平均: 0.16898992657661438 単語の意味が元号向きではないようです 元号ではなさそうです -----万和----- 適切な語と語の距離感 0.06 <= x <= 0.54 語と語の距離感: 0.05686895176768303 単語間の距離が適切ではないようです 元号ではなさそうです -----万保----- 適切な語と語の距離感 0.06 <= x <= 0.54 語と語の距離感: 0.11570872366428375 適切な一語目 0.1 <= x <= 0.56, 適切な二語目 0.03 <= y <= 0.49 一語目 平均: 0.05171163007616997, 二語目 平均: 0.1425367295742035 単語の意味が元号向きではないようです 元号ではなさそうです
まさかの全て元号ではないという判定。 単語間の距離が適切ではないものに関しては仮説に基づいてフィルターを外してみる。
-----久化----- 適切な一語目 0.1 <= x <= 0.56, 適切な二語目 0.03 <= y <= 0.49 一語目 平均: 0.40043678879737854, 二語目 平均: 0.03221416100859642 元号かもしれないです! (MTSHの確認は人がしてください) -----万和----- 適切な一語目 0.1 <= x <= 0.56, 適切な二語目 0.03 <= y <= 0.49 一語目 平均: 0.05171163007616997, 二語目 平均: 0.1358799934387207 単語の意味が元号向きではないようです 元号ではなさそうです
それぞれ考察してみる。
「英弘」
「弘」が常用漢字ではなく、JIS第1水準漢字なため。 JIS第1水準漢字は画数の多い漢字も入っているので、これは致し方ない。 ただし、過去に「弘化」・「弘治」など「弘」を使った元号がいくつかあるので対策を考えるべきかもしれない。
「久化」
元号かもしれない。(元号ではなかったが) 「久」・「化」ともに元号経験のある漢字なので、この中では一番安牌。 むしろ安牌過ぎて選ばれなかった可能性。
「広至」
「広」が元号向きの単語ではないため。 いい意味には見えるけど、個人的にコレジャナイ感はわかる。
「万和」
「万」が元号向きの単語ではないため。 鶴は千年亀は万年、「万」は「亀」と深い関係性があるような気もするがダメみたい。
「万保」
上と同じく、「万」が元号向きの単語ではないため。 一語目に「千」・「百」を比較として入れてみるとこっちはOKだった。
まとめ
令和以外の元号候補は「久化」を除いて全て撃沈する面白い結果がみれた。 AI的に見ても他の候補は元号足り得なかったのか。 はたまた、碌に機能しないオンボロ判別機だったのか。 結果論で言うなら、元号になった原案にのみ正常に機能した素晴らしい判別機だ。(笑) その辺りは見る人の評価に任せたいと思う。
エイプリルフールに発表される新元号が、偽物かどうかAIだけで判別する物語
注意書き
この記事は、@youwht さんがQiitaに投稿されている平成の次の元号を、AIだけで決めさせる物語をとてもとても(パクリといっていいレベルで)参考にしています。 すごく面白い記事なのでおすすめです。 なお、Wikipediaを元にしたchar2vecモデルの作成法などはそちらを参考にしてください。
背景
新元号は4月1日に公表、5月1日に改元することが正式に発表されている。 でも発表が4月1日ということで嘘の新元号が発表されるのでは??? とネット上ではもっぱらの噂である。 さすがにそれは冗談だとしてもエイプリルフールに乗じて、 嘘(ネタ)の新元号の情報が大量に流れるのはありそうな話ではある。
そんな時に先の記事を見て、 AIだけで元号予想ができるなら、 AIだけで元号かどうかの判別もできるんじゃね? と思ったのでAIだけで元号判別機を作ってみる。
元号のルール
ルールは以下のようにする。 (おくり名は面倒なので今回はパス)
- 国民の理想としてふさわしいようなよい意味を持つものであること
- 漢字2字であること
- これまでに元号として用いられたものでないこと
- 常用漢字であること
- 俗用されているものでないこと
- M(明治)、T(大正)、S(昭和)、H(平成)とアルファベットのイニシャルが異なること
方針
方針はシンプルに元号のルールで定めたルールに対応したフィルターを作り、 それらのフィルターを全て通過した文字列は元号の可能性があるとする。
逆にフィルターのどこかで引っかかれば、新元号ではないということにしよう。
やっぱり最大の難関は「よい意味の判断」
ルール1. 国民の理想としてふさわしいようなよい意味を持つものであること よい意味ってなんだよ(哲学) 元記事では
発想として、良い意味の漢字=既に元号で使われたことのある漢字、として、 それに似たベクトルを持つ漢字が、元号の候補となる漢字なのではないか?
としてChar2Vecで過去の元号で使われた漢字とコサイン類似度が一定値以上の漢字を列挙。 その中から組み合わせを探していたが、決め打ちの漢字以外は元号として認めない判別機は判別機にあらず! ということで別の手を探す。
ここでさっきの発想を1つ進めて、
と仮定してみる。
つまり「平成」という漢字は、 一語目の「平」は昭和以前の元号の一語目との 二語目の「成」は昭和以前の元号の二語目との関連性があるのではないか?
そしてこの仮定が正しければ、 全ての元号の一語目・二語目どうしの組み合わせから 元号の一語目・二語目にふさわしい漢字が統計的に絞り込めるはず!
仮説が正しいのか確認するため、 元号の一語目・二語目どうしの組み合わせのコサイン類似度を見る。 (同じ漢字の場合コサイン類似度は 1 なのでそこは省く)
import numpy as np import matplotlib.pyplot as plt import random dimension = 0 # 0 = 一語目のコサイン類似度, 1 = 二語目のコサイン類似度 を測る distancelist=[] for idxone in range(0, len(gengo)): for idxtwo in range(idxone, len(gengo)): distance = model.similarity(gengo[idxone][dimension], gengo[idxtwo][dimension]) if not (distance == 1.0): distancelist.append(distance) # データ数が多くてグラフが潰れたのでサンプルを抽出してグラフ化 sample_distance = random.sample(distancelist, 200) # 折れ線グラフを出力 left = np.array(range(len(sample_distance))) height = np.array(sample_distance) plt.plot(left, height)
一語目の組み合わせのコサイン類似度
二語目の組み合わせのコサイン類似度
なんとなく、距離感を保っている気がする
仮説としては使えそうなので、これでいく!(他の方法が思いつかない) 実際に統計的に使える値を算出する。
m = np.mean(distancelist) median = np.median(distancelist) variance = np.var(distancelist) stdev = np.std(distancelist) print('平均: {0:.2f}'.format(m)) print('中央値: {0:.2f}'.format(median)) print('分散: {0:.2f}'.format(variance)) print('標準偏差: {0:.2f}'.format(stdev))
-----1語目の値----- 平均: 0.33 中央値: 0.30 分散: 0.06 標準偏差: 0.23 -----2語目の値----- 平均: 0.26 中央値: 0.24 分散: 0.05 標準偏差: 0.23
平均と中央値が近いのでいい感じに見える。 上の結果の平均と標準偏差を使用して漢字の範囲を絞ることにする。
前準備
import
import numpy as np import MeCab from gensim.models.word2vec import Word2Vec mecab = MeCab.Tagger("-Ochasen") # 後述の作成したモデル model = Word2Vec.load('mychar2vec_fromWikiALL.model')
モデル作成
今回の判別に必要なchar2vecモデルを作成する。 学習データはWikipediaのダンプデータ。 先の記事の通りにやればできた。 ひとまず動くか確認。
print(*model.most_similar(positive = u'病', topn=10), sep="\n")
('患', 0.8373429775238037) ('罹', 0.7904560565948486) ('肺', 0.7740147113800049) ('癌', 0.7698979377746582) ('医', 0.7508054375648499) ('臓', 0.7485206723213196) ('胃', 0.7131659388542175) ('腫', 0.7036600112915039) ('瘍', 0.6957879662513733) ('症', 0.6940966844558716)
想像以上にヘビーな結果となったが、ちゃんと機能してそうに見える。
必要なデータを集める
必要なデータは2つ
使用する際にはPythonのリスト型にしておく。
必要なフィルター群
ルールに対応したフィルター群
前処理フィルター
タブを全角スペースに置換しておく。
# 前処理「\t」はトリムしておく。全角スペース化 def preprocessing(is_era_name): return is_era_name.replace(u'\t', u' ')
2文字フィルター
ルール2. 漢字2字であることを実装。 2文字以外は弾く。
# 2文字か def check_2word(is_era_name): if len(is_era_name) == 2: return True else: return False
常用漢字フィルター
ルール4. 常用漢字であることを実装。
常用漢字のリストをjoyo_kanji
とする。
常用漢字以外なら弾く。
# 常用漢字か def check_joyo_kanji(is_era_name): one = is_era_name[:1] two = is_era_name[1:] if one in joyo_kanji and two in joyo_kanji: return True else: return False
過去元号フィルター
ルール3. これまでに元号として用いられたものでないことを実装。
過去の元号のリストをgengo
とする。
過去の元号なら弾く。
# 過去の元号として使われていないか def check_kakogengo(is_era_name): if is_era_name in gengo: return False else: return True
既存品詞フィルター
ルール5. 俗用されているものでないことを実装。 俗用されているもの = 品詞として存在するもの として元記事にしたがってMeCabの形態素解析を行う。
# 品詞として存在しないか def check_hinsi(is_era_name, mecab): wordHinsi = [] parsed_line = mecab.parse(is_era_name) wordsinfo_list = parsed_line.split("\n") for wordsinfo in wordsinfo_list: info_list = wordsinfo.split("\t") if len(info_list) > 2: wordHinsi.append((info_list[0], info_list[1], info_list[3])) # 分かれたかを判別 if(len(wordHinsi) == 2): return True return False
二語の距離フィルター
ルール1. 国民の理想としてふさわしいようなよい意味を持つものであること で元号の語の組み合わせについて、 元記事の「AIに「元号として適切な組み合わせ」を作らせる」項の仮説
過去元号の距離感に近づく組み合わせにする
がもっともらしいので適用してみる。 つまり、過去の元号たちの二語のコサイン類似度を計算し、 平均と標準偏差から二語の距離の範囲を絞る。 平均と標準偏差は元記事の値の平均: 0.30、標準偏差: 0.24を使う。
# 語と語の距離感が適切か(平均:0.3, 標準偏差:0.24 以内か) def check_appropriate_distance(is_era_name, model): distance = model.similarity(is_era_name[:1], is_era_name[1:]) print("適切な語と語の距離感 0.06 <= x <= 0.54") print("語と語の距離感: {}".format(distance)) if(0.30-0.24 < distance and distance < 0.30+0.24): return True else: return False
語の適正フィルター
ルール1. 国民の理想としてふさわしいようなよい意味を持つものであることを実装。
やっぱり最大の難関は「よい意味の判断」で求めた 一語目の平均: 0.33、二語目の平均: 0.26を中心として、 標準偏差: 0.23の範囲内に一語目・二語目が入っていればOKとする。
# 一語目と二語目がそれぞれ元号として適切な語か(一語目平均:0.33, 二語目平均:0.26, 標準偏差:0.23 以内か) def check_meaning(is_era_name): distancelist1=[] distancelist2=[] mean1 = 0.33 mean2 = 0.26 std1 = std2 = 0.23 # 一語目と過去の元号一語目との距離感を測る for i in range(len(gengo)): distance = model.similarity(is_era_name[:1], gengo[i][0]) if not (distance == 1.0): distancelist1.append(distance) # 二語目と過去の元号二語目との距離感を測る for j in range(len(gengo)): distance = model.similarity(is_era_name[1:], gengo[j][1]) if not (distance == 1.0): distancelist2.append(distance) distance1 = np.mean(distancelist1) distance2 = np.mean(distancelist2) print("適切な一語目 {} <= x <= {}, 適切な二語目 {} <= y <= {}".format(mean1-std1, mean1+std1, mean2-std2, mean2+std2)) print("一語目 平均: {}, 二語目 平均: {}".format(distance1, distance2)) if(mean1-std1 < distance1 and distance1 < mean1+std1 and mean2-std2 < distance2 and distance2 < mean2+std2): return True else: return False
MTSHフィルター
実は最大の難関はここだった
MeCabを使って読み仮名を取ろうとしたが、訓読みで判別されたりして、上手くいかなかったので断念。 そこは人力でフィルタリングしてもらおう。
# 音読みなので「ガン・ゲン」で出て欲しい '元\tモト\t元\t名詞-一般\t\t\nEOS\n'
1つの関数にまとめる
ifの深いネストを使って1つの関数にまとめあげる。 引数には ( 元号っぽい文字列, char2vecのモデル, MeCabのインスタンス ) を指定する。
def judge(is_era_name, model, mecab): # 前処理 preprocessing(is_era_name) # 2文字か if check_2word(is_era_name): # 過去の元号として使われてないか if check_kakogengo(is_era_name): # 常用漢字か if check_joyo_kanji(is_era_name): # 既存の品詞でないか if check_hinsi(is_era_name, mecab): # 語と語の距離感が適切か if check_appropriate_distance(is_era_name, model): # 一語目と二語目がそれぞれ元号として適切な語か if check_meaning(is_era_name): print("元号かもしれないです!\n(MTSHの確認は人がしてください)") return True else: print("単語の意味が元号向きではないようです") else: print("単語間の距離が適切ではないようです") else: print("既存の品詞のようです") else: print("常用漢字ではないようです") else: print("過去に元号として使用されています") else: print("2語ではないようです") print("元号ではなさそうです") return False
デモ
ちゃんと動くか試してみる。 元記事でAIが導き出した元号「孝天」で実験。 元記事と評価基準がほぼ変わらないのでちゃんと動けば元号認定されるはず。
# 元記事でAIが導き出した元号 judge("孝天", model, mecab)
-----孝天----- 適切な語と語の距離感 0.06 <= x <= 0.54 語と語の距離感: 0.4474194347858429 適切な一語目 0.1 <= x <= 0.56, 適切な二語目 0.03 <= y <= 0.49 一語目 平均: 0.43369507789611816, 二語目 平均: 0.32817038893699646 元号かもしれないです! (MTSHの確認は人がしてください)
ちゃんと元号認定された。 ついでに元記事第二候補の「元清」も元号認定されました。
-----元清----- 適切な語と語の距離感 0.06 <= x <= 0.54 語と語の距離感: 0.46765050292015076 適切な一語目 0.1 <= x <= 0.56, 適切な二語目 0.03 <= y <= 0.49 一語目 平均: 0.3887541890144348, 二語目 平均: 0.40423762798309326 元号かもしれないです! (MTSHの確認は人がしてください)
せっかくなので他にも試してみよう。
-----平和----- 既存の品詞のようです 元号ではなさそうです -----安久----- 既存の品詞のようです 元号ではなさそうです -----感永----- 適切な語と語の距離感 0.06 <= x <= 0.54 語と語の距離感: -0.050712406635284424 単語間の距離が適切ではないようです 元号ではなさそうです -----玉英----- 既存の品詞のようです 元号ではなさそうです -----永元----- 適切な語と語の距離感 0.06 <= x <= 0.54 語と語の距離感: 0.6839832067489624 単語間の距離が適切ではないようです 元号ではなさそうです -----永元----- 適切な語と語の距離感 0.06 <= x <= 0.54 語と語の距離感: 0.6839832067489624 単語間の距離が適切ではないようです 元号ではなさそうです
全てあえなく撃沈。 ちゃんと機能してないのか??? 単語間の距離が適切ではないと弾かれてるものが多いが、 過去の元号では距離がマイナスなものもそこそこあるので「感永」は結構いい感じかもしれない。
発表された新元号が偽物判定されたらどうしよう
新元号が発表された続き
PythonによるSelenium Tips
マグロ大学(近畿大学)アドベントカレンダー 2018の19日目
はじめに
何番煎じだっていうSeleniumの記事です。 使っていてパーツパーツしてるなと思ったので、各パーツ単位で解説してます。 Seleniumってすごいですね。基本的なHTMLとCSSが分かっていれば(分からなくても)難しいことをせずにデータのスクレイピングができます。機械学習やりたいけど、素材の集め方分からないって人は学習コストほぼなしでデータをそろえることができるのでおすすめです。 後、勝手にブラウザが動き回るのは見てても楽しいし、テンション上がる。
Seleniumとは
所謂ヘッドレスブラウザーができるやつです。コードを書いて自動でブラウザを操作することができます。こいつのすごいところはブラウザを実際に動かしているので、Reactなど動的に要素を生成するホームページでも要素を取得することができます。 ただ、難点としてBeautifulSoupで解析してrequests等で要素を取得する場合に比べて実行速度が遅いです。並列化すれば多少はマシになるかもしれないです。(やってないので知らない)
使うもの
Import
import系一覧
# 行動起点 全ての始まり from selenium import webdriver # キーの入力を受け付ける from selenium.webdriver.common.keys import Keys # スクロール等の動作をする場合必要 from selenium.webdriver.common.action_chains import ActionChains # 最初にオプションとしてつけることでヘッドレスにできる from selenium.webdriver.chrome.options import Options # 必須 サーバー側に配慮のないリクエストはDoS攻撃と同義 import time
基本的な使い方
# ---上記import文達--- # ヘッドレスにする場合、下記の4行を使う # options = Options() # options.add_argument('--headless') # options.add_argument('--disable-gpu') # driver = webdriver.Chrome(options=options) driver = webdriver.Chrome() # ドライバが設定されるまでの待ち時間を設定する。 driver.implicitly_wait(10) # リンクに転移 driver.get("http://www.python.org") # 要素の取得 element = driver.find_element_by_name("q") # 要素に対しての操作 element.clear() element.send_keys("pycon") element.send_keys(Keys.RETURN) # ドライバ終了 driver.close()
pyconって文字がsearch欄に入力されて一瞬でブラウザ消えたのが見えれば動いてます。 使えるメソッド関連は下記の記事がわかりやすく、まとまっています。 Selenium webdriverよく使う操作メソッドまとめ
リンクを新規タブで開く
- Chromeデフォルトの機能 Ctl+Enter(MacはCommand+Enter)で新規タブを開く
- JavaScriptを使う
上記2つの方法が使えます。 ただし、1つ目の方法はあくまでもリンクを踏んだ時にしか使えません。
# リンク要素 link_element = ... #driver.find_element # ---1つ目の方法--- # デフォルト機能で新規タブを開く (MacならCONTROLをCOMMANDにする) link_element.send_keys(Keys.CONTROL, Keys.ENTER) # ---2つ目の方法--- # javascriptで新規タブを開く driver.execute_script("window.open(arguments[0], 'newtab')", link_element) # タブの変更 driver.switch_to.window(driver.window_handles[1]) # 何らかの処理 ... # タブの消去 driver.close() # 元のタブに戻る driver.switch_to.window(driver.window_handles[0])
特定の要素までスクロール
ActionChainsを使うと特定の要素までスクロールできます。
# スクロールする目的地の要素 scroll_element = ... #driver.find_element # 初期化 actions = ActionChains(driver) # 動作の決定 actions.move_to_element(scroll_element) # 実行 actions.perform()
リストなど同系の要素を全て取得する
基本的に複数の要素をまとめて取得するときはfind_elements
メソッドを使ってリストで取得するのが普通です。
しかし、取得したい要素が他のクラス名と被っていて余計なものまで取得してしまうような場合には、少し手間ですがX-Pathを使うのがおすすめです。
基本的にまとめて取得したいものは要素の形がほぼ一緒で番号だけが違うと言うことが多いのでそこを利用して、format()
メソッドで部分的に変更して要素を取得していきます。
# ---普通--- elements = driver.find_elements_by_class_name("・・・") # ---X-Pathを使う例--- elements = [] # format()メソッドで使う添字 i=1 While True: try: # X-Pathは各自で解析してformat()で変えるべきところを探してください elements.append(driver.find_element_by_xpath('//*[@id="content"]/div/section/div[1]/div[{}]'.format(i)) # X-Pathの要素をずらしていく i+=1 except: break
画像を保存する
画像を保存するのはrequestsを使う方が簡単
スクリーンショットでいいならsave_screenshot(export_file_name)
メソッドを要素の後につけるだけで撮れます。
# 入ってない場合はpipでinstall import requests # 高水準ファイルライブラリ import shutil # ファイル名 export_file_name = ... # imageタグのsrcやcssのbackground-image等、画像のパスを取得 img = icon.value_of_css_property("background-image") # 画像を取得 res = requests.get(img, stream=True) # 画像を保存 with open(export_file_name, "wb") as f: shutil.copyfileobj(res.raw, f)
最後に
情報が結構バラバラに点在してたので自分が使った分だけでも、まとめてみました。
基本的にSeleniumは情報量が多いので、ググれば結構親切に出てきてそういった面でも学習コストは低いと思います。
例外処理とtime.sleep()
だけ忘れずにスクレピングを楽しみましょう。
ChainerRLでブロック崩しを学習する
CSGAdventCalendar最終日です。 ChainerRLを使ってブロック崩しの学習をさせるチュートリアルをやりました。 実装はGoogleColaboratoryを使いました。
ChainerRLとは
Chainerを使って実装していた深層強化学習アルゴリズムを”ChainerRL”というライブラリとしてまとめて公開したもの。 以下のような最近の深層強化学習アルゴリズムを共通のインタフェースで使えるよう実装している。
- Deep Q-Network (Mnih et al., 2015)
- Double DQN (Hasselt et al., 2016)
- Normalized Advantage Function (Gu et al., 2016)
- (Persistent) Advantage Learning (Bellemare et al., 2016)
- Deep Deterministic Policy Gradient (Lillicrap et al., 2016)
- SVG(0) (Heese et al., 2015)
- Asynchronous Advantage Actor-Critic (Mnih et al., 2016)
- Asynchronous N-step Q-learning (Mnih et al., 2016)
- Actor-Critic with Experience Replay (Wang et al., 2017)
- etc.
準備
ChainerRLのインストール
!apt-get -qq -y update # Install Chainer and CuPy! !apt-get -qq -y install libcusparse8.0 libnvrtc8.0 libnvtoolsext1 > /dev/null !ln -snf /usr/lib/x86_64-linux-gnu/libnvrtc-builtins.so.8.0 /usr/lib/x86_64-linux-gnu/libnvrtc-builtins.so !pip install cupy-cuda80 chainer # Install ChainerRL and OpenAI Gym !apt-get -qq -y install xvfb freeglut3-dev ffmpeg cmake swig zlib1g-dev> /dev/null !pip -q install chainerrl atari-py gym 'gym[atari]' 'gym[box2d]' pyglet pyopengl pyvirtualdisplay
Google driveとの接続
atari のゲームを DQNで学習させると、とても時間がかかります。そのため、Google drive に経過を保存できるようする。 次のコードセルを実行し、以下の手順で Google アカウントの認証を行います。
- URLが表示されるのでそれをクリック
- Google アカウントにログイン
- 表示されるトークンをコピー
- このノートに戻って、テキストボックスにそのトークンを貼り付け
- 再度URLが表示されるのでそれをクリック
- このノートに戻って、テキストボックスにそのトークンを貼り付け
!apt-get install -y -qq software-properties-common python-software-properties module-init-tools > /dev/null !add-apt-repository -y ppa:alessandro-strada/ppa 2>&1 > /dev/null !apt-get update -qq 2>&1 > /dev/null !apt-get -y install -qq google-drive-ocamlfuse fuse > /dev/null from google.colab import auth auth.authenticate_user() # Generate creds for the Drive FUSE library. from oauth2client.client import GoogleCredentials creds = GoogleCredentials.get_application_default() import getpass !google-drive-ocamlfuse -headless -id={creds.client_id} -secret={creds.client_secret} < /dev/null 2>&1 | grep URL vcode = getpass.getpass() !echo {vcode} | google-drive-ocamlfuse -headless -id={creds.client_id} -secret={creds.client_secret} !mkdir -p drive !google-drive-ocamlfuse drive
環境の準備
import chainer from chainer import functions as F from chainer import links as L import chainerrl from chainerrl.envs import ale import numpy as np
chainerrl.experiments.prepare_output_dir ではログ出力のディレクトリを設定します。 ale.ALEで環境を作ります。学習用の環境と、バリデーション用の環境を作ります。 また、学習用の環境は、報酬を -1〜1の範囲にクリップするため、chainerrl.misc.env_modifiers.make_reward_clippedを呼び出します。
outdir = chainerrl.experiments.prepare_output_dir(None, "drive/dqn_out") ROM = "breakout" TRAIN_SEED = 0 TEST_SEED = 2 ** 16 - 1 - TRAIN_SEED env = ale.ALE(ROM, use_sdl=False, seed=TRAIN_SEED) chainerrl.misc.env_modifiers.make_reward_clipped(env, -1, 1) eval_env = ale.ALE(ROM, use_sdl=False, treat_life_lost_as_terminal=False, seed=TEST_SEED) n_actions = env.number_of_actions q_func = chainerrl.links.Sequence( chainerrl.links.NatureDQNHead(), L.Linear(512, n_actions), chainerrl.action_value.DiscreteActionValue) # Use the same hyper parameters as the Nature paper's optimizer = chainer.optimizers.RMSpropGraves(lr=2.5e-4, alpha=0.95, momentum=0.0, eps=1e-2) optimizer.setup(q_func) rbuf = chainerrl.replay_buffer.ReplayBuffer(10 ** 6) explorer = chainerrl.explorers.LinearDecayEpsilonGreedy( 1.0, 0.1, 10 ** 6, lambda: np.random.randint(n_actions)) # In testing DQN, randomly select 5% of actions eval_explorer = chainerrl.explorers.ConstantEpsilonGreedy(5e-2, lambda: np.random.randint(n_actions)) def dqn_phi(screens): assert len(screens) == 4 assert screens[0].dtype == np.uint8 raw_values = np.asarray(screens, dtype=np.float32) # [0,255] -> [0, 1] raw_values /= 255.0 return raw_values agent = chainerrl.agents.DQN(q_func, optimizer, rbuf, gpu=0, gamma=0.99, explorer=explorer, replay_start_size=5 * 10 ** 4, target_update_interval=10 ** 4, clip_delta=True, update_interval=4, batch_accumulator='sum', phi=dqn_phi)
学習
import sys STEPS = 10 ** 7 def step_hook(env, agent, step): sys.stdout.write("\r{} / {} steps.".format(step, STEPS)) sys.stdout.flush() chainerrl.experiments.train_agent_with_evaluation( agent=agent, env=env, steps=STEPS, eval_n_runs=10, eval_interval=10 ** 5, outdir=outdir, eval_explorer=eval_explorer, eval_env=eval_env, step_hooks=[step_hook])
モデルのリロード
import pandas as pd import glob import os model_files = glob.glob("drive/dqn_out/*/*/model.npz") model_files.sort(key=os.path.getmtime) last_model_dir = os.path.dirname(model_files[-1]) last_model_dir import chainer from chainer import functions as F from chainer import links as L import chainerrl from chainerrl.envs import ale import numpy as np ROM = "breakout" TRAIN_SEED = 0 TEST_SEED = 2 ** 16 - 1 - TRAIN_SEED env = ale.ALE(ROM, use_sdl=False, seed=TRAIN_SEED) chainerrl.misc.env_modifiers.make_reward_clipped(env, -1, 1) eval_env = ale.ALE(ROM, use_sdl=False, treat_life_lost_as_terminal=False, seed=TEST_SEED) n_actions = env.number_of_actions q_func = chainerrl.links.Sequence( chainerrl.links.NatureDQNHead(), L.Linear(512, n_actions), chainerrl.action_value.DiscreteActionValue) # Use the same hyper parameters as the Nature paper's optimizer = chainer.optimizers.RMSpropGraves(lr=2.5e-4, alpha=0.95, momentum=0.0, eps=1e-2) optimizer.setup(q_func) rbuf = chainerrl.replay_buffer.ReplayBuffer(10 ** 6) explorer = chainerrl.explorers.LinearDecayEpsilonGreedy( 1.0, 0.1, 10 ** 6, lambda: np.random.randint(n_actions)) def dqn_phi(screens): assert len(screens) == 4 assert screens[0].dtype == np.uint8 raw_values = np.asarray(screens, dtype=np.float32) # [0,255] -> [0, 1] raw_values /= 255.0 return raw_values agent = chainerrl.agents.DQN(q_func, optimizer, rbuf, gpu=0, gamma=0.99, explorer=explorer, replay_start_size=5 * 10 ** 4, target_update_interval=10 ** 4, clip_delta=True, update_interval=4, batch_accumulator='sum', phi=dqn_phi) agent.load(last_model_dir)
学習結果の確認
import pandas as pd import glob import os score_files = glob.glob("drive/dqn_out/*/scores.txt") score_files.sort(key=os.path.getmtime) score_file = score_files[-1] df = pd.read_csv(score_file, delimiter='\t' ) df
結果が下記です。 グラフ化します。
df[["max", "median", "mean", "stdev", "min"]].plot()
実行結果の確認
frames = [] for i in range(10): obs = env.reset() done = False R = 0 t = 0 while not done: action = agent.act(obs) obs, r, done, _ = env.step(action) frames.append(env.ale.getScreenRGB()) R += r t += 1 print('test episode:', i, 'R:', R) agent.stop_episode()
アニメーションの作成 最初のやつです。
import matplotlib.pyplot as plt import matplotlib.animation import numpy as np from IPython.display import HTML fig = plt.figure(figsize=(5, 5)) plt.axis('off') images = [] for f in frames: image = plt.imshow(f) images.append([image]) ani = matplotlib.animation.ArtistAnimation(fig, images, interval=30, repeat_delay=1) HTML(ani.to_jshtml())
終わりに
チュートリアルそのまま行うだけで簡単にDQNで学習とわかりやすい形で結果の可視化ができました。 色々いじって遊びたいと思います。