"機械学習","信号解析","ディープラーニング"の勉強

HELLO CYBERNETICS

深層学習、機械学習、強化学習、信号処理、制御工学などをテーマに扱っていきます

Pytorch基本メモ (主にtorchとtorch.aurograd.Variable)

 

 

f:id:s0sem0y:20170925014700j:plain

 

はじめに

 

残念ながら(?)ChainerとPytorchの優劣を決めるわけではありません。

 

Chainerから移行し、Pytorchを利用していく中で自分が気に掛かったところを比較、メモした記事です。このメモ内容がPytorchビギナーにもしかしたら有用かもしれないと思い記事にしました。

 

 

 

 

基本的にAtomのHydrogenプラグインを用いて、Jupyter notebookを動作させた際の画像を貼っていきます(特に長いコード書くわけじゃないので)。

 

 

Torch.Tensor

多次元配列

torchとnumpyの多次元配列をそれぞれ比較。

個人的にはtorchの配列の表示は見やすいと思います(括弧がやたらと出てこないので)。また、サイズの明記してくれるのが素晴らしい。

f:id:s0sem0y:20171006233442p:plain 

3次元配列の場合は画面には2次元配列までしか表示できないので、以下のようにゼロ番目の要素に入っている2次元配列はどんなふうになっているのかを表示してくれます。

 

f:id:s0sem0y:20171006234309p:plain

 

まあ、それに関してはnumpuも一緒ですが、何を表示しているのかがいつでも明示的になっているのがtorchの特徴と言えるでしょう。

f:id:s0sem0y:20171006234546p:plain

 

 

もっと高次元配列になると、この見やすさは顕著になります(まあ、高次元配列を数値で見ようなんてそうそうないと思いますが)。

numpyの場合は以下のように表示されます。これは、4×3の2次元配列が2個表示され、それが5セット分あるという形で表示されます(括弧の数に注目)。

 

f:id:s0sem0y:20171006234743p:plain

 

これがtorchの場合は、いつでもどのインデックスにどんな2次元配列が入っているのかというのを一貫して表示してきます。

以下で見ると、自分が何を見ているのかハッキリ分かりますね。

f:id:s0sem0y:20171006234819p:plain

 

torch.Tensor

numpy.arrayに対応するものがtorch.Tensorになります。

numpyが様々なライブラリで利用されていることをtorch側も認知しているため、なんとtorch.Tenssorからnumpy.arrayに変換するメソッドまで持ちあわせております。

 

f:id:s0sem0y:20171006235426p:plain

 

torch.Tensorの型変換

またtorch.Tensorはこの手のデータ型の変換をメソッドで行えるように一貫して実装されているため、例えばfloatからintにしたい場合でも以下のように実行可能です。

 

f:id:s0sem0y:20171006235751p:plain

 

特に覚えておいて欲しいのがlong型です。

なぜかtorchではラベルにint型(32bit整数)ではなくlong型(64bit整数)を用います。

 

そんな大きな整数いつ使うんだ…?って感じですけど、決まりなので仕方がない(Pytorch使ってクラス分類するときに最初にここでハマりました)。

 

f:id:s0sem0y:20171007000141p:plain

 

.cuda()

忘れちゃいけないのがgpu用の多次元配列です。gpuで演算を行う場合は全てこのcuda用の多次元配列でなければなりません。こちらももちろんメソッドを使った変換が可能であり、以下のように簡単に変換できます(こいつは便利だ!便利すぎてたまに忘れる)。

 

データ型、配列のサイズはもちろんのこと、どのGPUに渡されているのかも教えてくれます(私はシングルGPUなので自動的に0になります)。

f:id:s0sem0y:20171007000817p:plain

 

仮にマルチGPUの場合はメソッドの引数に番号を入れることになります。

.cuda(i)

のような形です。

 

 

torchは基本的にnumpyとさして変わりません。numpy.arrayにあってtorch.Tensorにはないものなども実際はあるのですが、やりたいことは大抵できるでしょう。

 

torch.autograd.Variable

さて、お待ちかねのPytorchに入っていきます。Pytorchでは基本的にtorch.Tensorをtorch.autograd.Variableで包んでやることで、計算グラフ構築の機能を持たせてやることになります。基本的な計算機能はtorch.Tensorにまかせておいて、torch.autograd.Variableがバックプロパゲーションに必要な情報の取り扱いをしてくれるということです。

 

chainer.Variableとtorch.autograd.Variable

一応chainer.Variableとtorch.autograd.Variableを比較しておきましょう。

いずれのVariableもあくまで計算グラフの情報を扱っているということなので、配列に関する表示はそれぞれnumpyとtorchに準じており、それぞれVariableで包んでいますよということを明記しています。

 

f:id:s0sem0y:20171007001653p:plain

 

torch.autograd.Variableの配列データを見る

Variableは何度も言いますがtorch.Tensorに計算グラフの情報を保持させる機能を追加しているというものです。VariableはTensorを中にself.dataの形で持っているため、以下のようにすぐにデータを見ることができます。

f:id:s0sem0y:20171007002106p:plain

 

TensorFlowだとeval()メソッドにより計算グラフを実行することで(実質Session.runで)値を出力しますが、Pytorchはあくまで配列データをPythonのクラスでラッピングしているだけなので、ここらへんの取り扱いが非常に楽です(Chainerのモロパクリなんだけども)。

 

 

GPUへの転送

もちろんのこと、VariableもGPUへ転送することが可能で、torch.Tensor同様に.cuda()メソッドを有しています。またVariableをGPUに転送して、その配列データを見るとGPUに転送されているのが確認できます。

f:id:s0sem0y:20171007002727p:plain

 

この操作はtorch.TensorをGPUに転送してしてからVariableでラッピングしても、VariableでラッピングしてからGPUに転送してもいいということです(上記のコードと下記のコードのcuda()メソッドの位置に注目)。

 

f:id:s0sem0y:20171007003108p:plain

 

 

実はこれは地味にコードを書く際にきいてきます。torchはもともとGPU利用が想定されて作られたので、この辺が非常にシームレスに扱うことができます。

 

Chainerの場合は、cupyがnumpyに寄せた実装をしているものの、あくまで別物であるため、gpuあたりの取り扱いが割と面倒になっています(とは言っても、その面倒な部分は結構しっかり隠蔽されていると思われます)。

 

 

chainerのgpu利用補足

torch.Tensorはprintで表示することでデータのサイズや型、どのGPUにいるのかを明示してくれますが、numpyやcupyではそうではありません。printの表示ではもはやnumpyなのかcupyなのかcpuにいるのかgpuにいるのかがわからないのです(以下のコードを見てください)。

 

f:id:s0sem0y:20171007004429p:plain

 

 

またnumpyをcupyに変換するには以下のような手続きを行います。

f:id:s0sem0y:20171007005725p:plain

 

あるいは上記の手続きと、下記の手続きは等価です。

 

f:id:s0sem0y:20171007005754p:plain

 

いずれにしても、torchほど楽とは行きません(それでも辛いわけではないですが)。

本質的な問題は、gpuへの転送に関してnumpyとcupyという多次元配列の間でしか行えないという点です(Variableで包んだ後は変更ができないということ)。

 

仮に下記のように、numpy.arrayをVariableで包んでからgpuへ割当を行おうとしてみましょう。

 

f:id:s0sem0y:20171007010242p:plain

 

すると、これはデータ型が対応していないということで怒られてしまいます。

 

f:id:s0sem0y:20171007010357p:plain

 

実際、これによって何か絶対にできないことが出てきてしまうということは無いと思いますが、コードをゴチャゴチャ書いている内に、これで怒られることはきっと出てくるでしょう。ただ、逆にcpuとgpu間を移動したデータ型が一体何であるかをハッキリと認識できるということになります。numpy⇔cupyのやりとりしかあり得ないからです。

 

 

cupyへの期待(余談)

しかし、ここまで完全にnumpyと表面上区別がつかないように実装されているとなると、numpyで実装されているいろいろなライブラリが、もしかしたらcupyに置き換えるだけでGPU利用になるんじゃないかという期待が持てます(PyMCとかscipyとか)。

 

ライブラリ実装のnp.の部分をxpに置き換えておいて

 

if gpu:

  xp = cp

else:

  xp = np

 

でなんとかなったりしないかな。無理か。

 

Variableを使った微分 

ここからは基本的にPyorchの話です。Chainerを使っている人にはお馴染みの微分操作について見ていきましょう。Pytorchでは全てVariableで値を扱っていきます。torch.TensorをVariableでラッピングしてあげるだけなので特に難しいことはありません。

後に、微分操作を必要とするものに関しては以下のように、Variableに渡す際に、reauires_grad=Trueとしておきます(デフォルトでTrueなので実際は書かなくても良いです)。

 

f:id:s0sem0y:20171007210346p:plain

 

 

さて、

 

y = wx + b

 

という一次関数の式を見てみましょう。具体的にx=3,w=5,b=2で見ていきます。そうするとy=17になってくれるはずです。

 

f:id:s0sem0y:20171007210910p:plain

 

期待通りの表示です。これは別にVariableでなくてもできることです。しかし、Variableで包んであげると、 どのような計算を辿ってその値になったのかを、変数自体が覚えておいてくれています。

 

xwと掛け算されているというのを変数が覚えているため、yxによる(偏)微分を求めることが簡単にできるのです。今回の場合

 

\displaystyle \frac{\partial y}{\partial x} = 5

 

となっているはずです。これを確認するには、微分される変数yについてy.backward()で微分を計算させておきましょう。するとxに対して、x.grad()微分の値が格納されます。

 

f:id:s0sem0y:20171007211754p:plain

 

実はy.backward()を行った時点で、他の全ての変数に関する微分が求まっています。すなわち

 

\displaystyle \frac{\partial y}{\partial w}

 

\displaystyle \frac{\partial y}{\partial b}

 

も計算されているということです。

 

f:id:s0sem0y:20171007212128p:plain

 

実装上の注意

先ほどと完全に同じ式で、微分計算(y.backward())を5回連続で行ったとしましょう。

 

f:id:s0sem0y:20171007212501p:plain

 

x.grad = 25

w.grad = 15

b.grad = 5

 

と出ています。実は5回連続でbackward()を呼ぶと、本来の微分値が5倍されてしまいます。1回微分するごとに足されていってしまってるのです(決して複数回微分操作を行ったところで、高階微分が求まるわけではないので注意)。

 

y.backward(retain_graph=True)

 

retain_graph=Trueの部分は、計算グラフを維持するかどうかを指定しています。仮に、この部分がFalseの場合は、1回微分操作を行ったら計算グラフを消去してしまうため、繰り返しの微分操作はできなくなります(代わりにメモリの消費は抑えられるというわけ。必要なときに必要に応じてしか計算グラフを維持しない)。

 

 

 

最後に

これは逐次更新するかもしれません。

 

 

 

s0sem0y.hatenablog.com