studylog/北の雲

chainer/python/nlp

pythonのインデント

最初に結論を書くと

  • pythonのインデントはスペース4つ
  • タブを使うと色んなデメリットがある
  • スペース4つ打ちが面倒ならエディタの設定でタブキーをスペース4つに置き換える


もうかれこれ4年はpython使っててこれまでインデントについて深く考えた事がありませんでした。
pythonのインデントはスペース4つ」と最初に覚えてからそれを疑いもせずにこれまでやってきたんですが、今日他の人のコードを見てていくつかビックリした点が。pythonのインデントって結構自由なんですね…(あんまりいい意味じゃなくて)。

事の発端はchainerのフォーラムで貼られてたコードを自分も試してみようと思ったらインデントがめちゃくちゃ。ブラウザで見たときは正しいインデントなんだけど自分のエディタでは崩れる。何でだろうと思ったらスペースじゃなくてほとんどタブでインデントされてて、さらにタブとスペースが混在してるところもある。

そこからインデントについて調べてみたのだけれど、

1.インデント幅は1でも2でも7でもいい 何でもいい
2.コード全体で一つのインデント幅に固定されてるわけじゃなく、ブロックごとに統一されてればいい

という事を知ってビックリ。
1についてはまあわかる。でも2はどうなんだろう。

for i in range(0,10):
 print(i)
 if i == 4:
                  print("あいうえお")

こんな変なインデントでも許容されるらしい。確かにエラーが出ない。てっきりコード内で最初に現れたインデント幅をコード全体に適用するみたいな設計になってると思ってた。

3.タブは内部で8文字スペースに置き換えられる
4.タブとスペースの混合インデントはpython3でエラー

という事も今日初めて知った。

3で問題になるのはエディタ側でタブを4文字インデントだと解釈して表示しちゃってる場合は見た目上のインデントが崩れるってこと。
4で問題になるのはpython2で書かれたタブとスペース混合のコードが3で動かないってこと。


pep8というコーディング規約ではスペース4つインデントを推奨しているし、ほとんどのライブラリもそれに従ってるのでタブは使わない方がいいんじゃないかと思います。自分だけで使う分には何だっていいのかもしれないけどタブを使ってる人は絶対どこかしらでスペースが混入して混合インデントになってるはずで、いつかpython3に移行するときに面倒なことになりそうです。

pythonのインデントはスペース4つが基本。
面倒だなあとう人はエディタの設定でタブキーをスペース4つに置き換える。

あと本題と関係は無いけれど、できればprintにも括弧付けて欲しいです。
付けてた方がいつか3系に移行する時に楽だし、何より3系の人がそのコードを気軽に実行できるのでフォーラム等で質問した時に回答がつく確率が高まりそう。

Chainerメモ12 恐怖のActualエラー

chainerを触りだした頃に一番遭遇していたエラーです。トラウマになりそうでした。

こんなやつ。

chainer.utils.type_check.InvalidType: Expect: in_types[0].ndim == 1
Actual: 2 != 1

#こういうパターンもある 不等号だったりもする
Actual: (1,2) != (1,1)

どういう意味か

データの次元やshapeなどが正しくないという意味です。
Actual: 2 != 1の場合は、左が入力された間違った次元、右が本来入力されるべき正しい次元です。
Actual: (1,2) != (1,1)だったら本来は右の(1,1)というshapeで入力されるべきなのに(1,2)で来ちゃってますよということ。

で、この記事を書く前までは「左は自分が入力した間違い、右はchainerが求めている正しい次元」で固定されてると思っていたのですが、どうやら状況によっては左右が逆になったりしている?ようで混乱しています。はっきりしたことがわかれば追記するかもしれません。

追記:中の人のブログ記事も参考に
Chainerのtype_check
unnonouno: Chainerのtype_check

またありがちな例としてこんなのも。

Actual: (1, 1) != (1,)

そもそも(1,1)と(1,)ってどう違うかというと、

a = numpy.array([0])
a.shape #=>(1,)

b = numpy.array([[0]])
b.shape #=>(1,1)

です。

たいていの場合はバッチ処理(複数のデータを入力して、同時に計算する)で書かれたサンプルコードをよくわからないなりに修正しながら自分の処理を書いていると、いつのまにか入力データはバッチだけど正解データはバッチじゃなくなってる、という具合でshapeが不一致になってしまうのかなと思います。自分も触りだした頃にmnistサンプルを何がなんだかわからないなりに弄っててよくActualエラーに遭遇していました。

このエラーが出たらどうすればいいのか

エラーが出たところのVariableのdataをprintなりでshapeを確認してください。

chainer.functions.mean_squared_error(x, y)

ここでActual: (1, 1) != (1,)と出たら、xとyのshapeを確認すればたいていの場合は解決します。

print(x.data)
#あるいは
print(x.data.shape)

今度はバッチ処理の書き方をチュートリアルっぽくまとめてみたいと思います。

Chainer1.4.0は延期

二週間後の10月28日に延期されました。
モデルの構造をがらっと変えるための議論と実装に時間がかかっているようです。

公式のモデル保存&読み込み方法も実装される予定でしたが、いずれも1.5.0(11月下旬?)になりそうです。
今のFunctionSetをpickleで保存するやり方ではバージョン間の互換性が保てない問題があるっぽいんですよね。
あと、もしモデル構造が変わると以前のバージョンとの互換性がますます無くなっちゃうんでしょうか。
ドキュメントもgithubも全部英語なので追うのが大変です。

他に1.4.0で気になってるのは

  • cuda.initの復活(例外を出すのではなくdeprecatedを出すだけにして1.3より前のコードとの互換性を維持)
  • cupyのallとany
  • タスクによってはLSTMよりも精度が高いGRU
  • LSTMのAPIの変更?(入力をLinearで4倍にしなくてよくなりそう)

このあたり。
楽しみに待ちたいと思います。

mecab-NEologdを使うと語彙はどのくらい増えるのか

mecab-ipadic-neologdを使って形態素解析すると語彙が増えすぎてしまうんじゃないかという心配があったので調べてみた。


(追記訂正:最初のデータは間違っていました。ごっそり消してwikipediaの詳細データだけ掲載します。すみません。)
コーパスのテキスト量と増加率の関係を見たかったのでwikipediaの日本語版を項目数(記事数)で483分割して処理。コーパスNo.がおおよそのコーパステキスト量に該当します。コーパスNo.10はNo.1のおおよそ10倍のテキスト量。ただあくまで「項目数」での分割なので正確なデータでは無いです。

コーパスNo デフォルト NEolodg 増加率(%)
1 46258 58556 26.6
2 68258 88678 29.9
3 84709 111791 32.0
4 99337 132455 33.3
5 113074 152992 35.3
6 122964 168559 37.1
7 132508 182266 37.6
8 144769 199847 38.0
9 158041 218060 38.0
10 168850 233722 38.4
11 179991 249132 38.4
12 189231 263175 39.1
13 199275 277872 39.4
14 208830 291307 39.5
15 216722 303374 40.0
16 223735 314840 40.7
17 231265 325952 40.9
18 239104 337614 41.2
19 246950 349032 41.3
20 254110 359583 41.5
21 261795 370543 41.5
22 269645 381550 41.5
23 277826 392692 41.3 この辺から増加率は減少
24 286525 404269 41.1 コーパスの5%
25 293764 414527 41.1
26 300150 423689 41.2
27 308806 435006 40.9
28 317068 445591 40.5
29 323247 454250 40.5
30 328518 461914 40.6
31 334377 470542 40.7
32 339846 478158 40.7
33 344783 485576 40.8
34 350621 493363 40.7
35 356892 501996 40.7
36 363141 510389 40.5
37 368756 517924 40.5
38 375833 527029 40.2
39 381728 534884 40.1
40 388231 543156 39.9
41 394783 551588 39.7
42 400534 559312 39.6
43 405993 566811 39.6
44 411687 574370 39.5
45 417879 582061 39.3
46 422953 589005 39.3
47 427705 595386 39.2

諸事情によりここでストップ。

増えすぎるのが嫌だったわけ

Chainerで言語モデルを作るときに使うEmbedIDが語彙の数だけ線形でメモリ消費量が増えるので貧弱GPUだと辛い。

まあこのくらいの増加率だとよほど大規模コーパスじゃ無い限りは~100MB程度の増加で済みそうなので一安心。もっといいGPU欲しいなあ。すみませんプログラムミスでいい加減なデータでした。このデータも正しいのか不安です。

実際はかなり差がありますね。このくらい差があるとかなりメモリ確保量に差が出てしまいます。
自分の環境だと語彙30万/512次元でEmbedID作った直後で1200MBのGPUメモリを確保していました。
NEolodgの方が1.4倍ほど語彙が多いのでメモリ確保量もそれだけ増えることになります。
2GBクラスはもちろんのこと4GBクラスでも心もとないですね。

もう一つの懸念

例えば校名が変わった大学があるとする。
旧)はてな大学 → 新)びっくり大学

デフォルト辞書だと「びっくり+大学」になるはずがNEologdは「びっくり大学」とやってくれるはず。

ところがこれは良い面ばかりでもなさそうで、校名が変わった直後はまだコーパスにそれがあまり出てこないので、RNN等で言語モデルを作った時に「びっくり大学」の意味を判断できない可能性が高そう。もし「びっくり+大学」と分けた場合なら「大学」が入っているので学校関係という情報が入ってくれるはず。
あるいは新興企業の「はてな不動産」という会社があった場合に「はてな+不動産」で区切ってくれると不動産屋という情報が入ってくれそう。この辺は善し悪しだなと。

コーパスにあまり出てこない新単語の場合はデフォルト辞書の形態素区切りでの情報も使ってみるといいのかな。
http://www.hshindo.com/data/shindo-NL150927-talk.pdfのように単語区切りだけの情報を使うのではなくて文字をCNNでまとめた情報も使うとか。全ての単語もやっちゃうと弊害が大きそうなのでコーパスにあまり出てこない単語だけやるようにすればいいいのだろうか。

Chainerメモ11 GPUで速度が出ない時

GPUなのに学習速度があまり速くならない、あるいはCPUより遅い時ってありませんか?

そういうとき自分はまず「nvidia-smi -l 1」でGPUの使用率を見て100%に近い値を維持できているかどうかチェックします。NVIDIA System Management Interfaceというものらしいです*1-lオプションに数値を指定するとn秒間隔でループしてその時のGPUの状態を出力してくれます。よく見る項目はGPU使用率、メモリ使用量、温度あたりでしょうか。

この使用率が低ければ低いほど効率的にGPU計算できていないことになります。計算以前のところがボトルネックになっている可能性が高い。

list → numpy or cupyへの変換速度で差が出る

pythonのリストをchainerで使えるようにnumpy or cupyに変換する時の速度が両者でだいぶ異なるようです。後者の方が遅い。

1.2以前のpycuda版でも同様なのでこれはchainerの問題では無く、もうそういうものだと思って諦めるしかないっぽいです。

このあたりを意識しないとせっかくのGPUなのに損します。

  • listからcupy.ndarrayへの変換をいかに少なくするか(同じ変換を二度としない epochごとに変換してたら無駄になる)
  • numpy/cupyの行列操作を適切に使う
  • ifやforをなるべく使わない、ブロードキャスティングをしっかり使う

酷い例

誰もこんな酷いコード書かないかもしれませんが、致命的に遅い例として自分が実際にやっちゃった例を。

a = [1,2,3]

[[1,2,3]]

にしたい時、python本体だったらa = [a]で問題ありません。numpyの場合も

a = np.asarray([1,2,3])
a = np.asarray([a]) #一度pythonのlistで囲ってから再度変換

と無理やりやってしまってもそこまで致命的な速度ロスは無いんですが、同じようなことをcupyでやると場合によっては100倍以上時間がかかります。普段からnumpyをバリバリに使っている人からすればお茶を吹くレベルのありえないコードでしょうがちょっと前までの自分はこんなコードがよく紛れ込んでました。

GPUなのに逆に遅くなった人はこういうnumpy/cupyの流儀から外れたコードを書いている恐れがあるので見なおしてみてください。pythonとは別の言語だと思って臨んだ方がいいようです。

numpyにあるけどcupyに無いので変換

anyやallなどがまだcupyには無いのでnumpyに変換する必要がある。こういうところでもじわじわと損をします。この2つは10月中旬のバージョン1.4で実装予定だそうです。ありがたい。

タイトルの内容は以上です。

タイトルと関係ないメモ GPUでモデル保存するときのやり方とメモリの挙動

(これは現時点1.3.2の情報です。モデル保存が公式サポートされる1.4以降では変わっている可能性が高いので注意してください。)

メモリをギリギリまで使って学習させている時にモデルを保存しようとして落ちたことないですか?やり方によって挙動が異なるようです。modelにはFunctionSetが入っていることを想定して、

#1  GPUのまま保存なのでCPUからは使えない メモリを消費しない 
pickle.dump(model, open(filename, 'wb'))

#2  コピーしたものをCPUに変換して保存 コピーする時にメモリを使うので落ちる可能性がある
pickle.dump(copy.deepcopy(model).to_cpu(), open(filename, 'wb'))

#3  破壊的にCPUになっちゃうのでepoch2以降はそのままじゃ続行できないがメモリ使わずCPUからも利用できる
pickle.dump(model.to_cpu(), open(filename, 'wb'))

と一長一短。
GPUはCUDAのコア数なども大事かもしれませんが、メモリが少ないと制約が多すぎて辛いです。お金に余裕がある人は最初からいいやつ買いましょう。

*1:CUDAドライバ本体入れたら一緒に入るのかな?もしかしたら追加でインストールしたのかもしれません

chainer-Variational-AutoEncoderを使ってみた

最近早起きした日は北海道神宮円山公園へリスに会いに行ってます。山のリスと違って人馴れしまくってて自分から足元にやってくるエゾリス。多分エサやってる人がいるんだろうな。こんな至近距離で撮れたのは初めてなので記念にアップ。
f:id:kitanokumo:20150921095051j:plain
近所を散歩してたらリスに囲まれるなんて、北海道ってやっぱりすごい。贅沢。

Variational-AutoEncoder

略してVAEとやらを最近勉強してますが論文・日本語の解説・コードを見ても何がなんだかわからない。わからないんだけど面白そう。

応用例。
http://deeplearning.jp/wp-content/uploads/2014/04/dl_hacks2015-04-21-iwasawa1.pdf
http://www.slideshare.net/beam2d/semisupervised-learning-with-deep-generative-models
これらは主に画像の例だけど自然言語にも適用できないのかな?と思案中。

chainer実装

chainerで書いてくれている方がいた。ありがたい( -人- )github.com
Variational Autoencoderでアルバムジャケットの生成 - Use At Your Own Risk


試してみたけれどまだ自分で学習させるところまではいかなかった。VAE自体を全く理解できていないので、どうやって入力したらいいのかわからない。うまくいったら追記します。

学習済みのモデルが配布されてたのでそれを利用して生成だけやってみたんだけど多分ハマる人いそうなのでメモ。

pickle化されてる学習済みモデルはmodelディレクトリの中にzip化されてますが、これがどうも現時点の最新バージョン1.3系では読み込めない。linearが無いと言われる。多分chainer本体の構造自体が変わったからなのかな。保存しているmodelを見るとFunctionSetを継承したclassにforword等の学習コードが入ってて、これだとchainerのバージョンによっては読み込めなくなることがある(参考:chainerメモ9 保存するmodelに学習するコード等を含ませない - studylog)

pip uninstall chainer #一回削除
pip install chainer=="1.2.0" #これでバージョン指定インストール

で1.2.0に落としたら成功。ちなみに1.1.0はダメでした。
modelディレクトリのzipを解凍してから、

python generate_yz_x.py --model model/mnist_VAE_YZ_X_1000.pkl --dataset mnist --n_samples 30

これでgenerated_yz_xディレクトリに30個の画像が生成される。
f:id:kitanokumo:20150921101758j:plain
f:id:kitanokumo:20150921101800j:plain
f:id:kitanokumo:20150921101802j:plain
f:id:kitanokumo:20150921102405j:plain
f:id:kitanokumo:20150921102406j:plain
f:id:kitanokumo:20150921102407j:plain
f:id:kitanokumo:20150921102626j:plain
f:id:kitanokumo:20150921102627j:plain
f:id:kitanokumo:20150921102628j:plain
f:id:kitanokumo:20150921102629j:plain
f:id:kitanokumo:20150921102630j:plain

一番左の数字だけは誰かが書いた実際のデータ。
これを与えると同じような雰囲気(筆跡・線の太さ)で右の0~9の数字を生成する。
最初は右の数字も誰かが書いたmnist内にある実データで似たようなのを拾ってきてるだけだと思ってたけど、自分で生成しちゃってるってのはすごいなあ。

もうひとつ、Street View House Numbers (SVHN)というデータセットを使った学習モデルも。model/svhn_VAE_YZ_X_1000.pklの方ですね。これはGoogleストリートビューで収集した家・マンションの部屋番号数字の認識タスク用データだそうです。カラー。

python generate_yz_x.py --model model/svhn_VAE_YZ_X_1000.pkl --dataset svhn --n_samples 30


f:id:kitanokumo:20150921132536j:plain
f:id:kitanokumo:20150921132542j:plain
f:id:kitanokumo:20150921132543j:plain
f:id:kitanokumo:20150921132544j:plain
f:id:kitanokumo:20150921132546j:plain
f:id:kitanokumo:20150921132547j:plain
f:id:kitanokumo:20150921132548j:plain
f:id:kitanokumo:20150921132538j:plain
f:id:kitanokumo:20150921132539j:plain
f:id:kitanokumo:20150921132540j:plain
f:id:kitanokumo:20150921132541j:plain

mnistと比べると少し精度が落ちているように見えるけど色はほぼ完璧に再現している。すごいなあ。

これ言葉に適用できたら面白いと思う。

chainerメモ10 AttributeError: 'module' object has no attribute 'init'

追記)2015.10.30
互換性維持のためバージョン1.4.0でcuda.init()が復活しました。
正確にはエラーで落ちなくなりdeprecatedを出すようになりました(つまり書いても書かなくても何もしない)。
以下の記事は1.3.0~1.3.2限定の話です。



chainerでcuda.initはもう使えない - Qiita

こちらの人が遭遇しているように、1.3.0でGPUのためのnumpyバックエンドがPyCuda→CuPyへと変わった関係でcuda.init()が必要なくなりました。

chainer1.3.0リリース - studylog
cupy使ってみる - studylog
でも書いたのですがcuda.init()があるとタイトルのエラーが出ます。初期化にcuda.init()はいりません
再掲しておこうと思います。

GPU対応コードが変わるところ

初期化

#旧
cuda.init()
#1.3.0以降
cuda.check_cuda_available()
cuda.get_device(0).use() #0はgpuデバイスナンバー

CPUとCPUでコードを分岐しなくても済むようになった

#旧
x = numpy.asarray([1,1,1] , dtype=np.int32)
if args.gpu >= 0: #GPUの時はto_gpuを通さないといけなかった
    x = cuda.to_gpu(x)

#1.3.0以降
#予め初期化段階で以下のコードを書いておいてxpにGPUだったらcupy、CPUだったらnp(numpy)を入れておく
xp = cuda.cupy if gpu >= 0 else np  #npはimport numpy as np のnpってことです

x = xp.asarray([1,1,1] , dtype=xp.int32)#これでGPU、CPUの両対応

xp.asarray()にデータを放り込めばどっちにも対応してくれるようになったので煩わしさが減りました。もちろんその前にxxp = cuda.cupy if gpu >= 0 else npを一度書いておく。

思うこと

かなり活発に開発されているchainerは2週間ごとに新しいバージョンにアップデートされて仕様がどんどん変わっているので一ヶ月前に動いていたコードが動かないのはざらです。自分は英語は全くと言っていいほどわからないけど1.0.0の時からずっと追い続けているので何とか変更についていけてる。でも、まだ中身がよくわからないけど(ブログなどに書かれていた非公式の)サンプルコードを試してみるか、という段階の人にとってはそのコードが動かなかったらもうお手上げです。ブログやgithubにあげたコードをchainerのバージョンが上がるたびに修正するこまめな人っていないだろうし、英語ドキュメントだと斜め読みして変更点を見つけて自分で修正って非常に辛い。Githubも慣れてない人はバージョンごとの変更点って見つけにくいと思う。見ても英語だし。よくわかってるものが対象なら英語でも情報が読み取れても、全く未知の対象物の細かい変更点を英語で読み取るのは敷居が高い。

もっと使う人が増えて日本語の情報が増えればなあ。そしたらエラーに遭遇してもエラー文で検索したらすぐに解決策がヒットして自力だと何十分悩んでたはずが1分で解決、とかなりそうだし。今のところchainerのエラー文をブログに書いてる人って全然いない。多分自分が一番書いてるんじゃないか。検索に引っかかることを意識してどんどん書いてるつもり。それでも今回のタイトルエラーのきっかけとなったcuda.init()については「1.3.0では必要なくなった」とは書いてたけどエラー文自体は書いてなかった。書いておけばハマった人が少なくとも一人(一番上のqiitaの人)は減ったはず。

お互いこういう時間を減らしたい。他の人のハマった時間をなるべく減らしたい。なのでしょーもないエラー文でもどんどん書いていこうと思う。そのうちどんどん使う人が増えてくれて、英語圏でも使われまくってstack overflowを見ればたいてい解決、となる日が来ますように。