jupyter notebookでscroll boxの高さを変える

jupyter notebookでscroll boxの高さを変える方法が検索しても出てこなかったのでメモ。

結論から言うと、以下のようにすればとりあえずできる。

from IPython.core.display import display, HTML
display(HTML("<style>.scroll_box { height:30em  !important; }</style>"))

参考: stackoverflow.com

上記の方法では、下記のような方法でjupyter notebook全体のcellの横の長さを変更できることを紹介している。

from IPython.core.display import display, HTML
display(HTML("<style>.container { width:100% !important; }</style>"))

そういえばこいつもブラウザでみれるからHTMLなんだなぁということを思いだし、要素の検証からscroll boxに対応するクラスのスタイルを見つけて中身を変更すればよい。

chainerのVGG16 pretrainモデルを早速使ってみた

最近めっきり記事を書いてないので、今後はメモくらいのつもりでもいいから小出しに書いていこうと思う。

chainerがv1.22.0でVGG16LayersとResNet50Layersをchainer.linksでサポートした。
Standard Link implementations — Chainer 1.22.0 documentation

これによってcaffeの学習済みモデルがより気軽にchainerで試せるようになったようだ。
ちょうど学習済みモデルを手っ取り早く使いたいなと思ったところでタイミングが良かったので早速試してみた。

今回はVGG16Layersのみについて書いていこうと思う。

基本的な使い方

基本的な使い方はchainerのリファレンスをみるのが手っ取り早い。
Standard Link implementations — Chainer 1.22.0 documentation
これによると、以下のように使えると書かれている。

from chainer.links import VGG16Layers
from PIL import Image

model = VGG16Layers()
img = Image.open("path/to/image.jpg")
feature = model.extract([img], layers=["fc7"])["fc7"]

お手軽感がすごい。

特徴

特徴をまとめると以下のようになる。

extractpredictというメソッドがある
  • 入力はPIL.Imageとnumpy.ndarray (height,width)(height,width,channels)(channels,height,width)のどれでもサポートする
  • バッチで与えたいときは、入力を上記のオブジェクトのリストで与えればよい(1つのnumpy.arrayに統合してはいけないので注意)
  • extractはlayers=['fc6','fc7']のようにlayersで指定した層の出力をまとめて辞書形式で返す。
  • predictはfc8(最上層)の出力、つまり各クラスの予測値を返す

主にお世話になるのはextractだと思われる。

chainier.links.model.vision.vgg.prepareというメソッドがある

VGGモデルは、通常入力画像を(batchsize,channels,height,width)、channelsはRGBではなくBGRという形式のnumpyarrayに整形する必要があるが、これはそれらの操作を行ってくれる。また、256x256へリサイズ、224x224へクロップする作業、VGG16の平均画素[103.939, 116.779, 123.68]の引き算も同時に行う。
(extractとpredictにはこれが既に内部に組み込まれているのでわざわざ使う必要はない)

処理の高速化のため、画像のローディングを別スレッドorプロセスで並列に行いたい場合はこれを使えばよいということだと思われる。
その場合、既に1つのミニバッチ化されたデータを入力として、そのままcallすればメインのスレッドorプロセスでの処理はextractよりも少なくて済む。
ちなみに、このメソッド自体は複数入力に対応していないので、リストでなく1つずつ入力する必要がある。

パラメータ固定、fine-tuningでそれぞれ使ってみる

VGG16Layersの良いところはchainer.linksでサポートされてる点だと思っている。書き方を少し工夫すれば、自分で定義したエンコーダにVGG16を隠蔽することができるので仕様に振り回されなくて済む。例えば、以下のようにかける。

from chainer import links as L
class Encoder(Chain):
    def __init__(self,n_image):
        super(Encoder, self).__init__(
            model = L.VGG16Layers(),
            fc = L.Linear(4096,n_image))

    def __call__(self, x, train=True, finetune=False):
        x = Variable(self.xp.asarray(x,dtype=np.float32),volatile=not finetune)
        h = self.model(x,layers=['fc7'], test=not finetune)
        h = h['fc7']
        if finetune==False:
            h = Variable(h.data,volatile=not train)
        return F.tanh(self.fc(h))

    def make_optimizer(self):
        return optimizers.Adam(alpha=2e-4, beta1=0.5)

pretrainモデルをimage-captioningなどのEncoderとして用いる場合、最初からfine-tuningするとまだ学習が進んでいないDecoderのエラーが伝搬してpre-train部分が悪影響を受けることがある。従って、pre-train側とそうじゃない部分は分けて学習できるようになっているのが望ましい。今回の例ではEncoderの後段がpretrainモデルじゃない場合のEncoderという想定で書いてみた。訓練フェイズでpretrainモデル部分を固定にするかfine-tuningをするか切り替えるには、単純にVariableの中身を一度取り出して再度Variableでラッピングして計算グラフを切るか切らないかで切り替えることができる。

torchのnarrowメソッド

torchのnarrowメソッドがパッと見よくわからんので試してみた結果のメモ。
[Tensor] narrow(dim, index, size)

↑のサイトを参考にした。
narrowメソッドは何をするかというと、テンソル内部のある次元のある部分を切り取って返す関数である。ちなみに参照渡しなので注意。

[Tensor] narrow(dim, index, size)の3つの引数はそれぞれ、
・dim:切り取る次元(行列(2次テンソル)なら行方向で切るか(1)列方向で切るか(2)の2種類 (ただし、dim > 0)
・index:切り取る部分の始点 (ただし、1 < index <= Tensorsize(dim))
・size:切り取る部分のサイズ(始点からどこまで切るか) (ただし、index+size-1 <= Tensorsize(dim) )
※ここで、Tensorsize(dim)はテンソルのdim次元の最大のindexの長さとする

例(参考サイトから引用)

th> x = torch.Tensor(5,6):zero()
                                                                      [0.0001s]
th> print(x)
 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
[torch.DoubleTensor of size 5x6]

                                                                      [0.0003s]
th> y = x:narrow(1,2,3)
                                                                      [0.0001s]
th> print(y)
 0  0  0  0  0  0
 0  0  0  0  0  0
 0  0  0  0  0  0
[torch.DoubleTensor of size 3x6]

                                                                      [0.0003s]
th> y:fill(1)
 1  1  1  1  1  1
 1  1  1  1  1  1
 1  1  1  1  1  1
[torch.DoubleTensor of size 3x6]

                                                                      [0.0003s]
th> print(x)
 0  0  0  0  0  0
 1  1  1  1  1  1
 1  1  1  1  1  1
 1  1  1  1  1  1
 0  0  0  0  0  0
[torch.DoubleTensor of size 5x6]

                                                                      [0.0003s]

これは、2次元テンソルつまり行列の例で、y=x:narrow(1,2,3)は「行方向に2行目からスタートして2+3-1行目まで切ったものをyに代入する」という意味になる。
これは参照渡しになっているので、y:fill(1)するともとのxもそれに応じて変化する。

試しに列方向にも切ってみる。

th> r = x:narrow(2,3,4)
                                                                      [0.0001s]
th> r
 0  0  0  0
 1  1  1  1
 1  1  1  1
 1  1  1  1
 0  0  0  0
[torch.DoubleTensor of size 5x4]

                                                                      [0.0003s]
th> r:fill(2)
 2  2  2  2
 2  2  2  2
 2  2  2  2
 2  2  2  2
 2  2  2  2
[torch.DoubleTensor of size 5x4]

                                                                      [0.0003s]
th> x
 0  0  2  2  2  2
 1  1  2  2  2  2
 1  1  2  2  2  2
 1  1  2  2  2  2
 0  0  2  2  2  2
[torch.DoubleTensor of size 5x6]

                                                                      [0.0003s]

これは「列方向に3行目からスタートして3+4-1行目まで切ったものをrに代入する」という操作に対応する。エラーが起きないように注意が必要な操作だ・・・

GTX1080 Ubuntu16.04 CUDA8 でNaNトラブル

備忘録メモ。

GTX1080をUbuntu16.04,CUDA8.0でセットアップしたが,今までTITANやTeslaで動いていたコードがNaNを吐き出すようになった。使っていたライブラリはchainer。

調べたところconvolutional layerのforward計算の出力がNaNになっていることが分かった。chainerの本家のmnist exampleの層をconvolutionに変えても同様にNaNを吐いた。

gpuメモリの初期不良を疑ったが,LSTMなどの計算には問題は起きていなかったので,試しにcuDNNを外してみたところ、正常に動いた。

最新のバージョンのcudnnのみを入れなおしたところ,同様に正常に動いたことから,GTXがcuDNNフォルダに一緒に入っていた古いcuDNNを認識した結果NaNを吐くようになっていたらしい。

今までこんなことなかったので勝手に認識してくれるものだと思っていた...バージョン管理の大切さよ...

chainerで正方行列の対角成分を抜き出す

はじめに

chainerでforwardの計算をするときに行列の対角成分をとりたい時があったが、これを計算するfunctionsが現行のchainer(v1.19.0)に確認できなかった。

ちなみにTensorflowだとdiag_part()がこれに相当する。
http://devdocs.io/tensorflow~python/math_ops#diag_part

Theanoだとdiagonal()という関数がある。がこれは成分でなく対角成分のみでつくった対角行列を返す?(試してない)
github.com

やりたい処理をnumpyで表現すると、具体的には以下のようになる。

>>> import numpy as np
>>> x=np.arange(9).reshape(3,3)
>>> x
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])
>>> np.diag(x)
array([0, 4, 8])

対処

自作で対応する関数を作ってもいいのだが、暇もないので今あるfunctionsで作ることにした。
作ったのは以下の通り。
github.com

案は2つあって、
1. 単位行列を要素ごとにかけて列ごとの和をとる
2. batch_matmulを使って正方行列(nxn)と単位行列(nxn)をベクトル(nx1)ごとに分割して計算する

実験

どちらが速いのか実際やってみた。
cpu計算とgpu計算で正方行列のサイズは100x100,1000x1000,10000x10000。 GPUNVIDIA Corporation GM206 [GeForce GTX 960] を用いた。

100x100 matrix
#cpu
$ python chainer_diag.py 100 -1
time of diag_part_elementwise: 0.0002493858337402344
time of diag_part_batch_matmul: 0.00036978721618652344
#gpu
$ python chainer_diag.py 100 0
time of diag_part_elementwise: 0.10309004783630371
time of diag_part_batch_matmul: 0.06094169616699219

1000x1000 matrix
#cpu
$ python chainer_diag.py 1000 -1
time of diag_part_elementwise: 0.0017409324645996094
time of diag_part_batch_matmul: 0.0019404888153076172
#gpu
$ python chainer_diag.py 1000 0
time of diag_part_elementwise: 0.10210251808166504
time of diag_part_batch_matmul: 0.06030750274658203

10000x10000 matrix
#cpu
$ python chainer_diag.py 10000 -1
time of diag_part_elementwise: 0.11125564575195312
time of diag_part_batch_matmul: 0.0674281120300293
#gpu
$ python chainer_diag.py 10000 0
time of diag_part_elementwise: 0.12932491302490234
time of diag_part_batch_matmul: 0.1076195240020752

考察

gpu計算よりもcpu計算のが速い・・・
個別に比較すると、大体の場合要素ごとの計算よりもbatch_matmulを使った方が若干速いらしい。
ということで、batch_matmulを使う方法で実装しようと思う。

この博士論文にはフィロソフィーが足りない

先輩方の博士論文の公聴会を聴講して、個人的に色々と刺さるものがあったので一応メモとして残しておく。

審査員の先生方から入ったツッコミをまとめると、主に以下の3点が刺さった。

  • [定義の説明]
    研究の学術体系についての説明、概念・用語の定義の説明
  • [研究の位置づけ]
    学術体系における研究の位置づけ(新規の取り組み・貢献)、その妥当性(焦点の当て方、アプローチの方法、評価の方法)
  • [問題点・展望]
    データ依存性、手法の適用可能性・限界、残されている課題とその展望

「この博士論文にはまだフィロソフィーが足りない」という言葉は特に刺さった。傍から聞いてた自分も11回くらい射殺された。手法とか、ネットワークの設計とかでもなんとなくやってるんでは意味がなくて、細かいところにもちゃんと目的・理由付けを持ってないといかんなーと改めて思った次第。

ちなみに先輩方さすがうまいな、と思ったのは以下の点。ここらへん自分も学振とかの書類申請で書いてはきたが、ちゃんと説明できるようにしておきたい。

  • [研究の動機・意義]
    研究の成果を最初にデモで見せてから詳細な説明に入る。
    この技術の社会的有用性・意義の説明。この技術が社会にどのような影響を及ぼすかの展望の説明。

詳細についてはあとで追加するかもしれない。(気が向いたら)

python3でcv2をimportする方法

いままでopencvpythonから呼び出すのにcv2を苦労してパス通してたが、やらなくて良い苦行であったことを知ったのでメモ。

python2については下記の記事を参照のこと。
qiita.com

ただ、python3ではうまく入らなかったので、少し探してみた。
するとやっぱりあった。
https://anaconda.org/menpo/opencv3

リンク先を参考に

conda install -c menpo opencv3=3.1.0

するとcv2が使える。なんかバージョンがopencv2とopencv3で違うが、とりあえずcv2を使うだけならこれで良さそう。