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を使うだけならこれで良さそう。

WaveNetメモ

WaveNetのArXiv原稿を読んだのでメモ

概要
生の音声波形の生成を行うDNN
自己回帰型モデル
前の全ての波形サンプルから次のサンプルを予測するための予測分布を持つ(自己回帰型なので)(途中の波形の履歴から次のデータを生成できる、くらいの意味)
10,000サンプル/secの音声波形を使った
TTSに適用すると、SOTAの性能。評価方法は人間による音声の自然性の主観評価、比べた相手はEnglish,Chinese両言語についての一番良いパラメトリックでいくつもの手法を組み合わせた手法
単体のWaveNetは多くの異なる話者の特徴を真に迫ってとらえている
Speakerのidentity(というパラメータ?)で条件づけることで対応する話者をスイッチ可能
音楽に適用すると、最新のすごくリアルな音楽の断片を生成できた
WaveNetは識別モデルにも適用可能であることを示し、音声認識でよい結果を出した

Introduction
PixelCNNdecoderやPixelRNNは良い生画像を生成できる。でも64x64=4096次元
特にPixelCNNに注目、これは音声(16,000 sample/sec)のような長い系列データでもうまく生成できるのか
この論文の貢献
・WaveNetを使って今までにない自然性を持つ音声の生成が可能なTTSを発表、人手評価による
・長期的な系列を扱うためにDilated causal convolutions(超大きな受容野を持つ)をもとにした新しいアーキテクチャを提案
・話者ごとに違った音声(声)を生成可能
・同じアーキテクチャで小さな音声認識データセットを使っても、強い結果を得た。また、音楽のような違う音声モダリティ(というかドメイン?)も生成できることを示した

アーキテクチャ
PixcelCNNから
条件確率分布はstack of convolutional layersでモデル化されている
プーリング層がない、出力は入力と同じ時間次元数(?)を持つ
出力はsoftmax層を使ったカテゴリ分布(カテゴリは次の値なのでx_tの数値のとりうる値だけある(量子化してるはずだがいくつか分からない))
最尤推定→扱いやすくてoverfitting, underfittingがみやすいから

Dilated causal convolutions
Stack of causal convolutional layers→図2
これを使うときちんと時系列を考慮して計算ができる(前の系列が後の系列に依存して生成されるというようなことがない)
Causal convolutionは[Oord 2016a]のmasked convolutionと等価→調べる
→あらかじめマスクテンソルとその乗算を要素ごとに、convoultion kernelについて行う
音声の場合次元が1次元になるのでいくらか実装が簡単

訓練中、条件付き分布はground truthを知っているので並列化可能(生成時はもちろん無理)

Causal convolutions は再帰結合をもたないのでRNNを訓練するより訓練時間が短くて済む
問題は多くの層を必要とすること、受容野を増やすため大きなフィルターが必要ということ(つまり、メモリをバカ食いする)
図2だと受容野は5つ(=5層+filter length 1 -1)filter lengthとは?
この論文では受容野のオーダーを大きくしつつ、計算コスト(時間もメモリも)を少なく抑えるためにdilated convolutionsを使った

Dilated convolution(図3)は要は結合の飛ばしを入れた形
以前にも信号処理や画像セグメンテーションで使われている手法
Dilationは1層上がるごとに2倍増えてくようにする(理由は不明)
1,2,4,8,…,512,1,2,4,8,…,512,1,2,…
↑2分木の形にすると層を深くできないから?

Softmax distributions
画像も256値のsoftmax層を用いた多クラス分類とするとうまくいくことが報告されたので
音声でもsoftmax予測をする。音声は16bitでサンプルされてるので、216=65,536値の多クラス分類をする
でももっと扱いやすいように、μ-law companding transformation(?)をデータに適用して256値にした
どういう経緯でこれが出てきたかは不明

Conditional wavenets
条件付きに拡張
話者ごとのスイッチ、TTSにおけるテキスト入力が該当
全ての時間について入力は固定
TTSのパラメータ(時系列)を使いたい場合→transposed convolutional networkでupsamplingしてから音声信号に時系列のスケールを合わせる
Transposed convolutionせずにむりやり利用してもいいが、若干劣る

Experiment
音声生成
English multi-speaker corpus (CSTR vice cloning toolkit (VCTK))を使用
WaveNetは話者の条件付けバージョン→speaker ID のone-hot vector
109人からの44時間分のデータセット
テキストで条件づけてないのでしゃべってる内容はわからないけど人間っぽい話声の単語の羅列がスムーズにでてくる
しかしCloser inspection(近い検査?元のデータ?)に対し明らかに不自然な部分がある
→これはモデルの受容野サイズが制限されているから(約300msec)つまり、前の2-3の音素しかちゃんと思い出せないということ
話者の違いはちゃんと学習できていた(評価は?)
声だけでなく、acoustics, recording quality, breathing, mouth movementsの特徴もとらえていた(だから評価は?)

TTS
同じ単一話者のデータベース(GogleのNorth American EnglishとMandarin Chinese) のTTSをつくった?
前者は24.6時間の音声データ、後者は34.8時間のデータ。どちらもプロの女性話者
WaveNetは局所的にテキストから抽出された言語特徴(詳細不明)とlogF_0で条件付けして訓練した
外部モデルが言語特徴から予測したlogF_0とphone durationsはそれぞれの言語で訓練に使った
受容野のサイズは240 msec

ベースライン
HMM-driven unist selection concatenative [Gonzalvo 2016]
LSTM-RNN-based statistical parametric [Zen 2016]
使うデータセット、言語特徴量は同じ。

評価
主観のペア比較(A/B test?)2つの音源から好きな方を選ぶ、好みがなければ自然な方を選ぶ
Mean opinion score(MOS) 自然性を5段階のリッカート尺度で評価

図5はペア比較の結果
一番上はベースライン2つ
真ん中はWaveNet2つ
一番下は上2つの決勝戦(ただし、bestベースラインはサンプルごとに上のもの逐一とってきてるものと思われる)
WaveNet:言語特徴のみの条件付けだと自然なクオリティはあるが、韻律がおかしいところがあった(強調しないはずの単語を強調していて若干不自然)
これはおそらくF_0の高さの長期的な依存をとらえられてないのが原因
受容野のサイズが240 msec分と短いのがこの原因
F_0も条件付けにいれた場合はこの問題が起きないので良くなっている。
外部のF_0予測モデルは200Hzという低周波数で動いているので、F_0のうち長期のスケールを持つ周波数帯も学習できたといえる

表1はMOSの結果(高いほど良い)
WaveNetが最良の結果

音楽
The MagnaTagAtune dataset
200時間の音楽、29秒ごとのクリップに188種類のうち該当するタグがついている(ジャンル、instrumentation, tempo, volume, mood of the music
YouTube pianoデータセット
60時間のピアノソロ動画、単一の楽器のみなのでモデル化は簡単だと思われる

定量的な評価は難しいが、主観評価は可能
受容野を大きくするのが超重要
数秒の受容野をもってしても、ジャンルの別や楽器の種類、音量、音声の質を学習したりできなかった
しかし、生成したサンプルはしばしば調和的で審美的に良かった。条件付けがなくても同様
MagnaTagATuneは比較的タグにノイズがあったり、省略が多かったり(タグデータの欠損?)したので、クリーニングして、似ているタグは一元化してやると比較的良い結果を得られたことを確認した

音声認識
TIMITデータセット
Dilated convolutionの後ろにmean-pooling層を入れた
荒いフレーム(10 msecのスパン)(160 x dounsampling)→スムージングみたいなイメージ
pooling層の後ろはnon-causal convolutions(普通のconvolution?後ろの時間にも依存して出力?)
2つのロス関数、一つは次のサンプルの予測(分類)、もう一つはフレームの音素のクラス分類
モデルは単一のロス関数にするよりも汎化性能がよかった
Testsetに対して18.8 PER(phone error rate)
これはTIMITコーパスにおいて波形から直接学習したモデルで著者らの知る限りベストスコア