AIPセンターシンポジウム 兼 成果報告会(2017年度)を聴講してきた

AIPのシンポジウムに参加してきた。日本のヒーロー大集合という感じでとても心が躍った。一般に開かれたシンポジウムだったので公開しても大丈夫だろうと判断し、忘れないうちに思ったことなどメモしておく。
社会実装,倫理から基礎数理まで:革新知能研究の最先端 | Center for Advanced Intelligence Project

追記:この記事の編集途中で引っ越しイベントのごたごたがあり、とってあったメモを紛失してしまった。とりあえず途中まで書けていた金出先生の講演についてだけ載せておく。

金出武雄先生の講演

金出先生からは「今後こういう研究が重要だろう」という展望についてのお話があった。
特に強調されていたのが、「良い研究は現実に起きている問題に取り組む中で生まれる」ということだった。
人工知能研究の冬の時代は、現実の問題を人工的につくられた解ける問題に落とし込み過ぎていることが一因だとも仰ってた。問題を解くための研究ではなく、研究業績を出すために問題を用意してるのがいけない、という意味だと受け取った。

「現実に起きている問題に取り組む中で生まれる良い研究」とはどんなものだろうか?金出先生からいくつか紹介があった。曰く、
製品検査、システムの異常検知
・負例が少数で学習が難しく、学習データにない新しい負例が出てくる可能性もある
・センサデータから始まる一貫した研究が必要
・ある製品について検査できるシステムが生まれれば、他の検査システムにも広く応用できる可能性がある
自分の決定を説明できるAI
・内省し、ユーザにわかる形で説明する
会議に参加貢献できるAIシステム
・問題と知識をどう表現するかはAIの根本的な問題
・本当に人のように有能なシステムの実現にはフレーム問題と記号接地問題が現れる
・会議では色々な話題が飛び交うので、データセットの世界に閉じていないAIが必要

どれもなるほど、と思わせられた。現実の問題の中でも、根本的なボトルネックとなっている部分は他の問題でも共通していることが多いから、これを解決できると色々応用が利くので十分なインパクトがある。例えば、機械学習では「学習するためのデータがない、DNNはブラックボックスなどで説明性に欠ける」などが根本的な問題として挙げられる。これらを解決するための学習理論を構築したりするのが肝要だとのことだ。各チームによるポスターセッションでは、確かにどのチームもこういった根本的な問題を研究のモチベーションにしているようだった。

感想

自分が取り組んでいる問題は現実に起きている問題設定からは少し離れているので私は大変恐縮してしまった。が、改めて考えるとあまりに広く知られている根本的な問題を攻めて世界中の才能とのパイの奪い合いに遅れて参加するのはどうなんだろう?、と思ってしまいイマイチ賛同はできなかった。
取り組むべき問題をきちんと吟味するのが大事というのは完全に賛同できる。個人的には汎用的に応用できる研究も大事だと思うが、多少タスクに依存した問題でも、それが解決することでブレークスルーに貢献するような仕事をしたいなぁと思った。
自分の問題で本当に本質的な問題はどこにあるのかを見直す良いきっかけになったと思う。ちょうど目をつけているところがまさにそうだと思うので、このまま走っていきたい。

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回くらい射殺された。手法とか、ネットワークの設計とかでもなんとなくやってるんでは意味がなくて、細かいところにもちゃんと目的・理由付けを持ってないといかんなーと改めて思った次第。

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

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

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