Numpy逆引きメモ
- Numpyでよく使う操作のメモ。自分用、随時追記
生成
リストで指定した内容の行列を生成
>>> x = np.array([[0,1],[3,4]]) >>> x array([[0, 1], [3, 4]])
numpy行列からlistへの変換
>>> x = np.array([3,4,5]) >>> x array([3, 4, 5]) >>> list(x) [3, 4, 5]
2次元配列で同じことをすると
>>> x array([[0, 1], [3, 4]]) >>> >>> list(x) [array([0, 1]), array([3, 4])]
となる。もし多重リスト形式に変換したければ、
>>> x = np.array([[0,1],[3,4]]) >>> x.tolist() [[0, 1], [3, 4]] >>> x_list = x.tolist() >>> x_list [[0, 1], [3, 4]]
1で埋めた行列を生成
>>> np.ones([2,3]) array([[ 1., 1., 1.], [ 1., 1., 1.]])
0で埋めた行列を生成
>>> np.zeros([2,3]) array([[ 0., 0., 0.], [ 0., 0., 0.]])
対角行列の生成
>>> np.eye(4) array([[ 1., 0., 0., 0.], [ 0., 1., 0., 0.], [ 0., 0., 1., 0.], [ 0., 0., 0., 1.]])
必ず正方行列になる。
ランダム要素で埋めた行列を生成
一様分布
np.random.uniform(2, -3, (5, 4))
上記の意味: 2から-3の間の値で埋めた5, 4の行列を生成する (4つの値が入ったリストが5つ入っているリスト)
指定区間の数列を生成する
>>> X = np.arange(10) >>> X array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
Stepや始点・終点も指定可
>>> X = np.arange(8,20,2) >>> X array([ 8, 10, 12, 14, 16, 18])
指定数がStepの倍数でない場合
>>> X = np.arange(70,40,-11) >>> X array([70, 59, 48])
型指定も可
>>> X = np.arange(10.,dtype=np.float32) >>> X array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.], dtype=float32)
情報取得
行列の要素数を取得する
>>> h = np.array([2,3,4]) >>> h.size 3 >>> h = np.array([[1,2,3],[4,5,6]]) >>> h.size 6
行列の形状を取得する
>>> h = np.array([[1,2,3],[4,5,6]]) >>> h.shape (2, 3)
行列の次元数を取得する
>>> h = np.array([[1,2,3],[4,5,6]]) >>> h.ndim 2
行列のrankを取得する
>>> A array([[1, 2], [3, 4]]) >>> np.linalg.matrix_rank(A) 2
形の一致する二つの行列で値が一致する数をカウントする
>>> y=np.array([1,1,2,3,7]) >>> t=np.array([1,2,3,4,7]) >>> >>> np.sum(y==t) 2
行列から最大値を抽出する
>>> hoge = np.array([[3,4,5],[2,3,4]]) >>> hoge.__class__ <class 'numpy.ndarray'> >>> np.max(hoge) 5
行列から最大値のindexを取り出す
>>> hoge = np.ndarray([[3,4,5],[2,3,4]]) >>> np.argmax(hoge) 2
軸を指定してsumの最大値を取り出す
>>> A = np.array([[3,4,5],[2,3,4]]) >>> np.max(A, axis=0) array([3, 4, 5]) >>> np.max(A, axis=1) array([5, 4])
特定の条件を満たす要素のindexを取得する
>>> a array([ 1, 3, 4, 2, 5, 8, 10]) >>> np.where(a>3) (array([2, 4, 5, 6]),) >>> >>> np.where(a==3) (array([1]),)
形状操作
行列の次元数を追加する
>>> h = np.array([1,2,3]) >>> h array([1, 2, 3]) >>> >>> h = h[np.newaxis,:] >>> h array([[1, 2, 3]]) >>> h = h[:, np.newaxis] >>> h array([[1], [2], [3]])
行列を結合する(連結する)
>>> A array([[1, 2], [3, 4]]) >>> B array([5, 6])
のとき、
縦に連結
>>> np.r_[A,B] array([[1, 2], [3, 4], [5, 6], [7, 8]])
横に連結
>>> np.c_[A,B] array([[1, 2, 5, 6], [3, 4, 7, 8]])
Flattenする
>>> a = np.array([[1,2], [3,4]]) >>> a.flatten() array([1, 2, 3, 4])
行列を切り落とす(y軸)
>>> h array([[1, 2, 3], [3, 4, 5], [4, 5, 6]]) >>> h[1:] array([[3, 4, 5], [4, 5, 6]]) >>> h[:1] array([[1, 2, 3]])
行列を切り落とす(x軸)
>>> h array([[1, 2, 3], [3, 4, 5], [4, 5, 6]]) >>> h[:,1:] array([[2, 3], [4, 5], [5, 6]]) >>> h[:,:2] array([[1, 2], [3, 4], [4, 5]])
軸操作
多重リスト要素の最大値のインデックスをリストで返す
>>> y = [[1,2,3],[5,6,7]] >>> t = [[0,0,1],[0,1,0]] >>> np.argmax(y, axis=1) >>> y = np.argmax(y, axis=1) >>>y array([2, 2]) >>> t = np.argmax(t, axis=1) >>> t array([2, 1])
多次元行列から特定次元だけ抜き出す
data = [ [1 ,0.4834], [2 ,0.5526], [3 ,0.6076], [4 ,0.5436], ... [299 ,0.9718], [300 ,0.97] ]
という2次元行列があるときに、
[1, 2, 3, ... 299, 300]
を抜き出すには
data[: , 0]
[0.4834, 0.5526, 0.6076, ... 0.97]
を抜き出すには
data[:, 1]
とすればよい
他の例:
>>> y array([[0, 1], [2, 3], [4, 5]])
に対して、
>>> y[:,0] array([0, 2, 4]) >>> y[:,1] array([1, 3, 5]) >>> y[:,-1] array([1, 3, 5])
(要素数2なので、前から数えて1番目と後ろから数えて-1番目は同じ)
要素の順番を入れ替える
>>> y array([[0, 1], [2, 3], [4, 5]]) >>> y[:,::-1] array([[1, 0], [3, 2], [5, 4]])
演算
行列の積を求める
>>> X = np.array([[2,3],[3,4]]) >>> Y = np.array([2,2]) >>> X.dot(Y) array([10, 14])
"*"による演算は、要素同士の掛け算になるため注意。
>>> X = np.array([[2,3],[3,4]]) >>> Y = np.array([2,2]) >>> X*Y array([[4, 6], [6, 8]])
行列要素の和を取る
>>> A array([[1, 2], [3, 4]]) >>> np.sum(A) 10
線形代数的操作
行列を転置する
>>> h array([[1, 2, 3], [3, 4, 5], [6, 7, 8]]) >>> np.transpose(h) array([[1, 3, 6], [2, 4, 7], [3, 5, 8]])
もしくは
>>> h array([[1, 2, 3], [3, 4, 5], [6, 7, 8]]) >>> h.transpose() array([[1, 3, 6], [2, 4, 7], [3, 5, 8]])
transposeは、数式に似せて
>>> h.T array([[1, 3, 6], [2, 4, 7], [3, 5, 8]])
と書くこともできる
多次元行列については、引数を与えなければx,y,z => z,y,xのように逆順になるが、 順番を与えることもできる
>>> x.transpose(0,1,2) array([[[ 0, 1, 2], [ 3, 4, 5]], [[ 6, 7, 8], [ 9, 10, 11]]]) >>> x.transpose(0,2,1) array([[[ 0, 3], [ 1, 4], [ 2, 5]], [[ 6, 9], [ 7, 10], [ 8, 11]]]) >>> x.transpose(2,0,1) array([[[ 0, 3], [ 6, 9]], [[ 1, 4], [ 7, 10]], [[ 2, 5], [ 8, 11]]])
行列式を出力する
>>> A array([[1, 2], [3, 4]]) >>> np.linalg.det(A) -2.0000000000000004
>>> P array([[2, 3], [4, 5]]) >>> np.linalg.det(P) -2.0
行列のtraceを出力する
>>> A array([[1, 2], [3, 4]]) >>> np.trace(A) 5
行列の対角要素を抜き出す
>>> A array([[1, 2], [3, 4]]) >>> np.diag(A) array([1, 4])
np.diag(A)は、Aの対角要素を返す。
対角要素から対角行列を生成する
>>> a array([ 1., 2., 3.]) >>> np.diag(a) array([[ 1., 0., 0.], [ 0., 2., 0.], [ 0., 0., 3.]])
ある行列の固有値の対角行列を得る
>>> np.diag(np.linalg.eigvals(A)) array([[-0.37228132, 0. ], [ 0. , 5.37228132]])
行列の固有値を求める
>>> A array([[1, 2], [3, 4]]) >>> np.linalg.eigvals(A) array([-0.37228132, 5.37228132])
行列の固有値、固有ベクトルを求める
>>> A array([[1, 2], [3, 4]]) >>> la, P = np.linalg.eig(A) >>> la array([-0.37228132, 5.37228132]) >>> P array([[-0.82456484, -0.41597356], [ 0.56576746, -0.90937671]])
laが固有値、Pが固有ベクトル。 la[n]がP[n,:]に対応する。
行列の逆行列を作る
>>> np.linalg.inv(A) array([[-2. , 1. ], [ 1.5, -0.5]])
行列の対角化を行う
>>> A array([[ 0, 14, 2], [-1, 9, -1], [-2, 4, 8]])
のとき、
>>> l, P = np.linalg.eig(A) >>> l array([ 4., 6., 7.]) >>> P array([[ 9.42809042e-01, -9.12870929e-01, -8.94427191e-01], [ 2.35702260e-01, -3.65148372e-01, -4.47213595e-01], [ 2.35702260e-01, -1.82574186e-01, 2.25257672e-15]])
lが固有値、Pが固有ベクトルを組み合わせて作った行列。 l[n]がP[n,:]に対応する(nは0,1,2)
固有値を対角行列化する
>>> D = np.diag(l) >>> D array([[ 4., 0., 0.], [ 0., 6., 0.], [ 0., 0., 7.]])
PDP-1がAと一致することを確認
>>> P.dot(np.diag(l)).dot(np.linalg.inv(P)) array([[ 9.79179023e-16, 1.40000000e+01, 2.00000000e+00], [ -1.00000000e+00, 9.00000000e+00, -1.00000000e+00], [ -2.00000000e+00, 4.00000000e+00, 8.00000000e+00]])
ベクトルの内積を求める
>>> x array([1, 2, 3]) >>> y array([3, 1, 0]) >>> x.dot(y) 5
行列のノルムを求める
>>> x array([1, 2, 3]) >>> np.linalg.norm(x) 3.7416573867739413
ベクトル同士のcos類似度を求める
内積を双方のノルムで割れば良いので
>>> x array([1, 2, 3]) >>> y array([3, 1, 0]) >>> x.dot(y) / (np.linalg.norm(x) * np.linalg.norm(y)) 0.42257712736425829
その他よく使うイディオム
多次元配列の要素をイテレーションする
配列の要素のインデックスを順番に取得できる
>>> x array([[0, 1], [3, 4]]) >>> it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite']) >>> it <numpy.nditer object at 0x10a444c10> >>> >>> it.multi_index (0, 0) >>> it.iternext() True >>> it.multi_index (0, 1) >>> it.iternext() True >>> it.multi_index (1, 0) >>> it.iternext() True >>> it.multi_index (1, 1) >>> it.iternext() False
行列要素の型を変換する
>>> A array([[1, 2], [3, 4]]) >>> A.astype("float32") array([[ 1., 2.], [ 3., 4.]], dtype=float32)
(わかりづらいが、小数点が付いてfloat型になっている)
map適用相当の関数適用
x = np.array([1, 2, 3, 4, 5]) squarer = lambda t: t ** 2 squares = np.array([squarer(xi) for xi in x])
printしたときに省略せずに表示する
np.set_printoptions(threshold=np.inf)
をコードのどこかで宣言したあとでprint
Chainer環境をAWSのUbuntu16.04+CUDA8.0上に構築する
目的
AWSのUbuntu 16.04上にChainer環境を構築する
構成
- OS: Ubuntu 16.04 (AMI ID: ami-0567c164)
- CUDA 8.0
- Cudnn8.0
- Chainer 1.18
手順
cudaのインストール
$ wget https://developer.nvidia.com/compute/machine-learning/cudnn/secure/v5.1/prod/8.0/cudnn-8.0-linux-x64-v5.1-tgz $ sudo dpkg -i cuda-repo-ubuntu1604_8.0.44-1_amd64.deb $ sudo apt-get update $ sudo apt-get install cuda
cudnnのインストール
agreementのせいでwgetで落とせないので、 https://developer.nvidia.com/rdp/cudnn-download から一旦PCに落としてscpでupした。
$ tar zxvf cudnn-8.0-linux-x64-v5.1.tgz
cudaディレクトリが解凍されるので、
$ sudo cp -r cuda/include/* /usr/local/cuda/include $ sudo cp -r cuda/lib64/* /usr/local/cuda/lib64
として、既存の/usr/local/cuda配下に配置する
cudaへのパスを通す
/usr/local/cuda/bin と /usr/local/cuda/lib64 にパスを通すために、
export PATH="/usr/local/cuda/bin:$PATH" export LD_LIBRARY_PATH="/usr/local/cuda/lib64:$LD_LIBRARY_PATH"
を.bashrc (ログインシェル環境なら.bash_profileに)追記する
linux-genericのインストール
AWSのイメージには、GPUを動かすのに必要なlinux-genericがインストールされていない という情報があったため、実施。(やらなくても動くかも?未検証)
$ sudo apt install linux-generic $ sudo reboot (再起動後) $ sudo apt remove linux-virtual $ sudo apt remove linux-virtual $ sudo apt autoremove
pipのインストール / アップグレード
$ sudo apt install python-pip $ pip install --upgrade pip
pyenvで必要になるので、opensslをインストール
$ sudo apt-get install libssl-dev
pyenv環境の構築
- Chainer等のバージョンアップが今後多くなることを想定し、pyenv環境を作っておく。
$ cd ~ $ git clone git://github.com/yyuu/pyenv.git ~/.pyenv
echo 'export PYENV_ROOT="${HOME}/.pyenv"' >> ~/.bashrc echo 'if [ -d "${PYENV_ROOT}" ]; then' >> ~/.bashrc echo ' export PATH=${PYENV_ROOT}/bin:$PATH' >> ~/.bashrc echo ' export PATH=${PYENV_ROOT}/shims:$PATH' >> ~/.bashrc echo ' eval "$(pyenv init -)"' >> ~/.bashrc echo 'fi' >> ~/.bashrc
- その場でも読み込んでおく(再ログインでも可)
$ source ~/.bashrc
上記は、インタラクティブシェルの場合。ログインシェル環境なら.bash_profileに追記し、source ~/.bash_profileする
- pyenv-virtualenvもインストールする
$ cd ~/.pyenv/plugins $ git clone git://github.com/yyuu/pyenv-virtualenv.git
- pyenvで利用するpythonをインストール(インストールできるものはpyenv install -l で確認)
$ pyenv install 3.5.2
- インストール完了の確認
$ pyenv versions * system (set by /home/ubuntu/.pyenv/version) 3.5.2
- pipもpyenv化されていることを確認する
$ which pip /home/ubuntu/.pyenv/shims/pip
/home/ubuntu/.local/bin/pip となっていたらおそらくパス設定をミスしている。
- 利用するpyenvを設定する
$ pyenv global 3.5.2
- 念のためログインし直しても同じ状態をキープしているか確認しておく
$ pyenv versions system * 3.5.2 (set by /home/ubuntu/.pyenv/version)
pip パッケージのインストール
下記の内容をrequirements.txtとして保存して、依存パッケージのインストール
appdirs==1.4.0 chainer==1.18.0 cupy==1.0.0 cycler==0.10.0 decorator==4.0.10 filelock==2.0.7 matplotlib==1.5.3 nose==1.3.7 numpy==1.11.2 Pillow==3.4.2 protobuf==3.1.0.post1 py==1.4.31 pyparsing==2.1.10 pytest==3.0.4 python-dateutil==2.6.0 pytools==2016.2.4 pytz==2016.7 six==1.10.0
Pillowとかは最初なくてもよいかも。
$ pip install -r requirements.txt
これで、3.5.2 env配下にpipがインストールされた。chainerのバージョンをあげたりした場合は、 envを消して別のenvを定義して再インストールすればよい。
動作確認
- CUDAの動作確認を行う。
$ python >>> import chainer >>> chainer.cuda.available True >>> chainer.cuda.cudnn_enabled True
- 公式サンプルを落として、実際に動かしてみる。
$ git clone https://github.com/pfnet/chainer.git $ cd chainer/examples/mnist/ $ python train_mnist.py -g=0
GPUなし: real 12m44.315s user 30m1.920s sys 60m23.720s
GPUあり: real 1m20.846s user 1m22.468s sys 0m1.708s
10倍速になっており、確かにCUDAによる高速化が実現できている。
お掃除
不要なディレクトリやファイルを削除する
$ cd ~ $ rm cuda-repo-ubuntu1604_8.0.44-1_amd64.deb $ rm cudnn-8.0-linux-x64-v5.1.tgz $ rm -r cuda $ rm requirements.txt
経緯:
AWSでGPU演算環境を確保しようと思い、適切なAMIを探していた。CUDAが最初からインストールされたAMIを使おうとしたらスポットリクエストには対応していなかったため、デフォルトのUbuntuでAMIを作成するのが結局割安に付きそうだったためクリーンインストールしてマイAMIを作成した
できるだけ丁寧にGANとDCGANを理解する
目的
- Chainerの扱いに慣れてきたので、ニューラルネットワークを使った画像生成に手を出してみたい
- いろいろな手法が提案されているが、まずは今年始めに話題になったDCGANを実際に試してみるたい
- そのために、 DCGANをできるだけ丁寧に理解することがこのエントリの目的
- 将来GAN / DCGANを触る人の助けになったり、理解間違ってるところにツッコミがあると嬉しい
本エントリの構成
- DCGANの前提となっているGANの論文の要点をまとめる
- DCGANでGANとの差分として提案されている要点をまとめる
- DCGANのmattyaさんの実装を読み通して詳細を理解する
1. GANについて
- GANは、サンプル群と似たような性質を持つ出力を生成するためのフレームワーク
- 2014年にIan J. Goodfellowらによって提案された
論文: Generative Adversarial Nets [リンク]
以下の2つのモデルの訓練を同時に進め、互いに競わせる
- D: Discriminator(鑑別器): (生成したいサンプルとGの出力物を正しく鑑別できることを目指す)
- G: Generator(生成器):(ランダムノイズを入力として、Dが誤ってサンプルであると認識する率を高めることを目指す)
- GANは下記の式の価値関数V(G, D)で表現されるminimaxゲームとして定義できる
- この式を、前提含め日本語で書き下してみると
[前提] ・はある種の任意の分布(例えば一様分布)を表す。zは個々のノイズサンプルを表す。 ・Gはzを入力とし、に分布させる。はジェネレータGから生成された出力の分布を示す ・Dは入力がサンプルから来た確率を表す(1であれば入力はサンプル分布から、0であればGの出力分布からと判断) [左辺] ・価値関数Vは関数DとGを引数に取る、右辺で表される関数である ・Dについての最大、Gについての最小となるようD, Gを定める [右辺] ・確率変数xは確率分布に従う ・確率変数zは確率分布に従う ・このとき、の期待値と、の期待値の和を評価関数とする (Dがサンプルを正しくサンプルと判定できればが大きくなり、DがGの出力をサンプルだと判定するとが小さくなる)
となる。(と思うのですが、間違っていたらご指摘いただければ嬉しいです..)
この式自体は、G、Dがニューラルネットワークであることを前提とはしていない(言い換えれば、別な関数最適化手法であっても適用できる、かもしれない)
論文ではD, Gにニューラルネットワークを使うことで、既存の最尤推定による生成モデルで手に負えないほど計算量が増える問題をbackpropagationで回避できるとしている
論文掲載のアルゴリズムは下記となる
- ミニバッチサイズm個のノイズをから取り出す(生成する)
(論文はからになってるけど、の誤植のような..?) - ミニバッチサイズm個のサンプルをデータ生成分布から取り出す
- 下記式の、における確率的勾配を上るように鑑別器Dを更新する
- 上記までをk回繰り返す
- ミニバッチサイズm個のノイズをから取り出す (ここもが正しいような..?)
- 下記式の、における確率的勾配を下るように生成器Gを更新する
- ここまで全てを、訓練回数分だけ繰り返す
- ミニバッチサイズm個のノイズをから取り出す(生成する)
鑑別器Dを十分な回数(k回)更新した上で生成器Gを1回更新することで、常に鑑別器が新しいGの状態に適用できるように学習を進める
4.1 ~ 4.2 [tex: p_g = p{data} ] の時にD, Gそれぞれについての最適化が達成される
==> このため、 を [tex: p{data} ] に近似させることが上記評価関数の解への近似として正当化される利点と欠点
- 欠点: 明示的なが最初は存在せず、DはGとシンクロさせて訓練しなければならない (特に、DをupdateせずにGだけを訓練すると、Gが入力ノイズzの多くをxと同じ値に収束させてしまう点に注意)
- 利点:
- マルコフ鎖で複数のモデルを混ぜるためにぼやけたものになるが、GANではマルコフ鎖が不要でシャープな画像が生成できる。
- 勾配を得るためにBPが使えるため、学習に近似が不要。
- 様々なモデルを用いることができる
- そして何より、「計算可能(computational)である」。
- サンプルと直接比較するのではなく、Discriminatorの評価を介して生成するため、inputの部品をGがそのまま丸覚えすることを避けられる。
2. DCGANについて
- GANは具体的なネットワークの構成に言及していない。(少なくとも論文中では)
- DCGAN(Deep Convolutional Generative Adversarial Networks) は、GANに対して畳み込みニューラルネットワークを適用して、うまく学習が成立するベストプラクティスについて提案したもの。
- 元になった論文 Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks リンク
- 要点をまとめると下記のようになる
- プーリング層を全て下記で置き換える
- バッチノルムを使用する(重要らしい)
- 深い構成では全結合層を除去する
- 生成器ではReLUを出力層以外の全ての層で活性化関数として使用し、出力層ではtanhを使用する
- 識別器ではLeakyReLUを全ての層で使用する
3. DCGANのコードを読む
- ChainerでのmattyaさんによるDCGAN実装を見て、実際にどのように構成されているかを確認する
前処理
- import / 定数宣言は解説省略
- image_dirから全てのイメージを読み込んで、dataset配列に追加している。
- ELU: exponential Linear Unitの定義。 数式でいうとの元で
グラフでいうと
(出典: Djork-Arne Clevert, Thomas Unterthiner & Sepp Hochreiter 2016 https://arxiv.org/pdf/1511.07289v5.pdf )
となるもので、LeakyReLUをなめらかにした感じのものらしい。
Generatorの定義:
- 一様分布ノイズz(1次元 / 100要素)を入力として、
=> 100入力、66512 出力のLinear Unit で(100, 512, 6, 6 )に=> BatchNormalization => relu => => 512チャネル入力、256チャネル出力、pad1、stride2、フィルタサイズ4のDeconvolution2D で(100, 256, 12, 12)に=> BN => relu
=> 256チャネル入力、128チャネル出力、pad1、stride2、フィルタサイズ4のDeconvolution2D で(100, 128, 24, 24)に=> BN => relu
=> 128チャネル入力、64チャネル出力、pad1、stride2、フィルタサイズ4のDeconvolution2D で(100, 64, 48, 48)に=> BN => relu
=> 64チャネル入力、3チャネル出力、pad1、stride2、フィルタサイズ4のDeconvolution2D
として、最終的に(100, 3, 96, 96) (つまり、ミニバッチ100枚 x RGB3チャネル x w96 x h96)のデータが得られる。
Discriminatorの定義:
- サンプルイメージ or Gの出力(3, 96, 96)を入力として、
=> 3チャネル入力、64チャネル出力、pad1、stride2、フィルタサイズ4のConvolution2Dで (64, 48, 48)に => 上述のELU
=> 64チャネル入力、128チャネル出力、pad1、stride2、フィルタサイズ4のConvolution2Dで(128, 24, 24)に => BN => ELU
=> 128チャネル入力、256チャネル出力、pad1、stride2、フィルタサイズ4のConvolution2Dで(256, 12, 12)に => BN => ELU
=> 256チャネル入力、512チャネル出力、pad1、stride2、フィルタサイズ4のConvolution2Dで(516, 6, 6)に => BN => ELU
=> 512 x 6 x 6 入力、2出力の全結合層
訓練
- Gの訓練
- Dの訓練
- x2をDの入力にして、yl2を出力。
- Dの損失に「yl2とbatch数だけ並んだ0とのソフトマックスクロスエントロピー関数出力」を足し合わせる
- 勾配初期化とbackpropagateによる重み更新
- image_save_interval回の訓練毎にGENを使って画像を100枚。
この際の生成のタネにする乱数は訓練開始前に生成したzvisを常に利用する
- 毎epoch完了ごとにdis, gen, o_dis, o_genを保存
- 諸々のインスタンスを生成して訓練を起動する
わからなかった点
- 何故Discriminatorが「2つ」の出力を持つようにしているのかわからない。入力のかららしさだけを出力するのなら、1出力でよさそうに思えてしまう。損失関数の計算方法から見るに、どちらか1つの要素がG、もう一つがdataからの入力というわけでもなさそうに思える。
その他
- なんちゃって!DCGANでコンピュータがリアルな絵を描く - PlayGround が参考になりました。ありがとうございます。
- Deconvolutionの処理、transposed(転置) convolutionと呼ばれる理由などはtheanoのドキュメントがわかりやすかった。気が向いたらまとめてみる
Chainerでフォントを識別してみる (2)
前回、二つのフォントをいい感じに識別できていたので、識別対象フォントを12に増やしてみた。
今回識別対象にしたフォントたち
Helvetica
Bodoni
Futura
Optima
Clarendon
GillSans
Caslon
Garamond
Avenir
Times New Roman
American Typewriter
Trajan
訓練データとテストデータの構成
- 訓練データは[a-mA-M]の64x64 グレースケール画像。画像ごとに位置はランダム
- テストデータは[n-zN-Z]の64x64 グレースケール画像。同じく画像ごとに位置はランダム化してある
実験
試行1: 3層多層パーセプトロン(隠れ層ニューロン数100)
前回使ったのと々、隠れ層2層、出力層1層の多層パーセプトロン。
=> 87%程度で識別率が頭打ちになった。2クラスへの識別だけであれば、同条件で97%まで行っていたので、
12クラスへの識別になって認識率が落ちたことが確認できる。
試行2: 3層多層パーセプトロン(隠れ層ニューロン数200)
2つの隠れ層の表現能力が足りないのかもしれないと思い、ニューロン数を200に増やしてみたパターン。
=> 90%程度で頭打ち。試行1よりかすかに識別率が上がった
試行3: CNN
CNN(畳み込みニューラルネットワーク)を導入。畳み込み+relu+Max Poolingを3セットの後にLinear層を2層。
epoch数100を超えたあたりで識別率が99.9%以上となった。
今後
・十分な汎化性能がありそうなので、多種多様な日本語フォントの識別を行ってみる
・各フォントにはItalicやBoldなどいろいろなスタイルもあるため、スタイル変更した際にも識別を行えるか試す
・スタイル転写とか使って、英字フォントから日本語フォント作れたら面白い気がする
Chainerでフォントを識別してみる (1)
Chainerで何か分類してみるにあたって、
1. 分類できて嬉しい
2. サンプル収集の目処が立ちそう
3. 莫大なデータ量にならない
な分類対象として、文字のフォントの分類をしてみようと考えた。
2. データセットの作成
- 大文字もしくは小文字を一文字ずつ64*64の画像に印字したデータを作成する。これより画像が小さいと、ジャギが立ってしまうので、フォント判別に良く無いと判断した
- 用意するデータ毎にx位置y位置をずらしておく。(フォントによって、ベースライン位置が異なっていたりするため、これを手掛かりにさせず、あくまで字形で分類させたいため)
- Train用のデータセットでは[a-mA-M]、Test用のデータセットでは[n-zN-Z]の文字を用いる
train用
test用
(※データをちゃんと作れたか確認のため画像出力したが、実際にはメモリ上でリスト保持するのみ)
- ChainerのOptimizer、Trainerを用いる事ができるように、DatasetMixinを継承したクラスとしてデータセットクラスを作成した。
(DataMixinを継承したクラスで__len__とget_exampleだけ定義してあげればChainerのIterationに渡せるようになる)
- Datasetのコンストラクト引数で、サンプル数とtrain / testの指定を行えるようにした
import sys import random import numpy as np from PIL import ImageFont from PIL import Image from PIL import ImageDraw import chainer from chainer import cuda, Function, gradient_check, report, training, utils, Variable from chainer import datasets, iterators, optimizers, serializers from chainer import Link, Chain, ChainList import chainer.functions as F import chainer.links as L from chainer.training import extensions class FontImageDataset(chainer.dataset.DatasetMixin): def __init__(self, datanum=10, normalize=True, flatten=True, train=True): self._normalize = normalize self._flatten = flatten self._train = train pairs = [] for _ in range(datanum): image_array, label = self.generate_image() pairs.append([image_array, label]) self._pairs = pairs def __len__(self): return len(self._pairs) def generate_image(self): fonts = [ 'font_files/Helvetica.ttf', 'font_files/BodoniSvtyTwoITCTT-Book.ttf', 'font_files/Futura-Medium.ttf', 'font_files/Optima-Regular.ttf' ] label = random.randint(0,len(fonts)-1) fontFile = fonts[label] font = ImageFont.truetype(fontFile, 60) train_characters = [ 'A','B','C','D','E','F','G','H','I','J','K','L','M', 'a','b','c','d','e','f','g','h','i','j','k','l','m' ] test_characters = [ 'N','O','P','Q','R','S','T','U','V','W','X','Y','Z', 'n','o','p','q','r','s','t','u','v','w','x','y','z' ] text = '' if self._train: text = random.choice(train_characters) else: text = random.choice(test_characters) w, h = 64, 64 text_w, text_h = font.getsize(text) text_x, text_y = (w - text_w) * random.random(), (h - text_h) * random.random() im = Image.new('L', (w, h), 255) draw = ImageDraw.Draw(im) draw.text((text_x, text_y), text, fill=(0), font=font) #im.save('image' + str(random.randint(0, 100)) + '.png') #if self._train: # im.save('temp/image_train' + str(random.randint(0, 100)) + '.png') #else: # im.save('temp/image_test' + str(random.randint(0, 100)) + '.png') image_array = np.asarray(im) if self._normalize: image_array = image_array / np.max(image_array) if self._flatten: image_array = image_array.flatten() # type cast image_array = image_array.astype('float32') label = np.int32(label) return image_array, label def get_example(self, i): image_array, label = self._pairs[i][0], self._pairs[i][1] return image_array, label
3. 学習モデルと訓練
ほとんどまだチュートリアルのまま回した感じ。これから分類クラス数を増やしたり
モデルを差し替えたりハイパーパラメータをあれこれして楽しみたい
- モデル: Chainerチュートリアルそのまんまのアフィン3層MLP
- ミニバッチサイズ200、epoch数300のSGDで訓練。
import numpy as np import chainer from chainer import cuda, Function, gradient_check, report, training, utils, Variable from chainer import datasets, iterators, optimizers, serializers from chainer import Link, Chain, ChainList import chainer.functions as F import chainer.links as L from chainer.training import extensions from font_image_dataset import * train_data = FontImageDataset(5000, train=True) test_data = FontImageDataset(5000, train=False) train_iter = iterators.SerialIterator(train_data, batch_size=200, shuffle=True) test_iter = iterators.SerialIterator(test_data, batch_size=200, repeat=False, shuffle=False) class MLP(Chain): def __init__(self, n_units, n_out): super(MLP, self).__init__( l1 = L.Linear(None, n_units), l2 = L.Linear(None, n_units), l3 = L.Linear(None, n_out) ) def __call__(self, x): h1 = F.relu(self.l1(x)) h2 = F.relu(self.l2(x)) y = self.l3(h2) return y class Classifier(Chain): def __init__(self, predictor): super(Classifier, self).__init__(predictor=predictor) def __call__(self, x, t): y = self.predictor(x) loss = F.softmax_cross_entropy(y, t) accuracy = F.accuracy(y, t) report({'loss': loss, 'accuracy': accuracy}, self) return loss model = L.Classifier(MLP(100, 2)) optimizer = optimizers.SGD() optimizer.setup(model) updater = training.StandardUpdater(train_iter, optimizer, device=-1) trainer = training.Trainer(updater, (300, 'epoch'), out='result') print("start running") trainer.extend(extensions.Evaluator(test_iter, model)) trainer.extend(extensions.LogReport()) trainer.extend(extensions.PrintReport(['epoch', 'main/accuracy', 'validation/main/accuracy'])) trainer.extend(extensions.ProgressBar()) trainer.run() print("end running")
5. この後
- 分類クラス数を増やしてみると識別率上限が低くなると思うのでいろんなモデルを試して見る
「ゼロから作るDeep Learning」を読み終わった
「ゼロから作るDeep Learning」を読み終わった。
ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装
- 作者: 斎藤康毅
- 出版社/メーカー: オライリージャパン
- 発売日: 2016/09/24
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (5件) を見る
Pros:
1ニューロンのパーセプトロンを作るところから始めて、3層NN、誤差逆伝搬法、CNNまで手作りしていく。
理論と実践のバランスが良く、該当分野の入門書としてとてもお勧めできると感じた。
中でも誤差逆伝搬法の説明が懇切丁寧で、他のNeural Network本を読んで
「で、全結合層以外の誤差逆伝搬はどうやって考えればいいんだ..」と悩んでいた自分にはぴったりだった。
あと、NNへ学習を導入する流れについても
=> 学習するっていうのはつまりどういうことか
=> 微分とは何か、勾配とは何か
=> 数値微分があればどんな関数でも勾配計算できるんだぜ
=> これでSGDすれば学習できるよ
=> でも遅いよね
=> じゃあ誤差逆伝搬を使ってみよう
という感じで、目的と手段をちゃんと切り分けて解説をしているところがとてもわかりやすい。
Cons:
贅沢を言えば、CNN層、Pooling層の逆伝搬についても解説があると嬉しかった。
(※ちなみに、入門用のもう一つのお勧めはオンラインテキスト "Neural networks and deep learning" 途中まで日本語版はこちら。こちらは、「なぜニューラルネットワークが画像を認識できるのか」の解説がとてもわかりやすかった。)
ブロックチェーンとは (bitcoin wiki 和訳)
ブロックチェーンとはbitcoin wikiの文章を和訳してみる。
誤訳等指摘いただければ喜びます
Block chain - Bitcoin Wiki
A block chain is a transaction database shared by all nodes participating in a system based on the Bitcoin protocol. A full copy of a currency's block chain contains every transaction ever executed in the currency. With this information, one can find out how much value belonged to each address at any point in history.
ブロックチェーンとはBitcoinプロトコルに則ってシステムに参加しているすべてのノードの間でシェアされている取引(transaction)のデータベースである。ある通貨のブロックチェーンのフルコピーは、その通貨において今までに行われたすべての取引を含んでいる。この情報から、それぞれのアドレスが、歴史上の任意の時点においてどれだけの価値を持っているかを各自が見出すことができる。
Every block contains a hash of the previous block. This has the effect of creating a chain of blocks from the genesis block to the current block. Each block is guaranteed to come after the previous block chronologically because the previous block's hash would otherwise not be known. Each block is also computationally impractical to modify once it has been in the chain for a while because every block after it would also have to be regenerated. These properties are what make double-spending of bitcoins very difficult. The block chain is the main innovation of Bitcoin.
それぞれのブロックは前のブロックのハッシュを含んでいる。これは原初のブロック(the genesis block)から現在のブロックまでを一つのブロックの連なり(a chain of blocks)にする効果を持っている。それぞれのブロックは時系列的に前のブロックの後ろにくることが保証されている、というのはもしそうでなければ前のブロックのハッシュが知られることができないからである。また、それぞれのブロックは一度チェーンに含まれてしばらくすると改変することは、その後のそれぞれのブロックを再生成しなければならないため計算量的に実行不可能である。これらの性質によりBitcoinの多重利用(double spending)はとても難しいものとなる。ブロックチェーンはBitcoinの主たるイノベーションである。
Honest generators only build onto a block (by referencing it in blocks they create) if it is the latest block in the longest valid chain. "Length" is calculated as total combined difficulty of that chain, not number of blocks, though this distinction is only important in the context of a few potential attacks. A chain is valid if all of the blocks and transactions within it are valid, and only if it starts with the genesis block.
もしそれが最長のValidなチェーンの最新のブロックである場合、正直な生成者達はブロックの上に(それを彼らが生成するブロックの中で参照しながら)構築するのみである。「長さ」はブロックの数ではなく、チェーンの難しさの総和として計算されるが、この区別はいくつかの潜在的な攻撃の文脈においてのみ重要になるものである。あるチェーンは、それに含まれるすべてのブロックと取引が有効であり、原初のブロックから始まっている場合においてのみ有効である。
For any block on the chain, there is only one path to the genesis block. Coming from the genesis block, however, there can be forks. One-block forks are created from time to time when two blocks are created just a few seconds apart. When that happens, generating nodes build onto whichever one of the blocks they received first. Whichever block ends up being included in the next block becomes part of the main chain because that chain is longer. More serious forks have occurred after fixing bugs that required backward-incompatible changes.
チェーン上のどのブロックにも、原初のブロックに至るたった一つのパスがある。しかしながら、原初のブロックから来るときには分岐が存在しうる。2つのブロックがほんの数秒の違いで生成されたときに、一つのブロック分岐が生まれることが時々ある。これが起きたときには、ノードはどちらでも先に受信した方のブロックの上に構築を行う。次のブロックに含まれるブロックはそれがより長くなるため結局はメインチェーンの一部となる。後方互換性の無い変更が必要なバグの修正後により深刻なフォークが起こったこともある。
Blocks in shorter chains (or invalid chains) are not used for anything. When the bitcoin client switches to another, longer chain, all valid transactions of the blocks inside the shorter chain are re-added to the pool of queued transactions and will be included in another block. The reward for the blocks on the shorter chain will not be present in the longest chain, so they will be practically lost, which is why a network-enforced 100-block maturation time for generations exists.
短い方のチェーン(もしくは無効なチェーン)に含まれるブロックは何にも用いられない。Bitcoinクライアントが他のより長いチェーンに切り替わった場合、短い方のチェーン内のすべての有効な取引のブロックはキューに積まれた取引のプールに再度追加され、他のブロックに組み込まれることとなる。短い方のチェーンへの報酬は最長のチェーンにおいては贈呈されないため、それらは事実上失われる。これが、生成に対してネットワークにより強制された100ブロックの熟成期間が存在する理由である。
These blocks on the shorter chains are often called "orphan" blocks. This is because the generation transactions do not have a parent block in the longest chain, so these generation transactions show up as orphan in the listtransactions RPC call. Several pools have misinterpreted these messages and started calling their blocks "orphans". In reality, these blocks have a parent block, and might even have children.
短いチェーン上のこれらのブロックはしばしば「孤児」(orphan) ブロックと呼ばれる。これはその生成取引(?: 原文 the generation transactions )が最長ブロックにおいて親ブロックを持っておらず、そのためこれらの生成取引はlisttransaction RPCコールにおいて孤児となって現れる。幾つかのプールはこれらのメッセージを誤解して、これらのブロックを「孤児」と呼び始める。実際にはこれらのブロックは親ブロックを持っており、子供さえ持っているかもしれないのだ。
Because a block can only reference one previous block, it is impossible for two forked chains to merge.
ブロックは前のブロック一つを参照することしかできないため、二つの分岐したチェーンをマージすることは不可能である。
It's possible to use the block chain algorithm for non-financial purposes: see Alternative chain.
ブロックチェーンアルゴリズムを金融ではない目的に使うことは可能である:Alternative chain を見よ
The block chain is broadcast to all nodes on the networking using a flood protocol: see Block chain download.
ブロックチェーンはfloodプロトコルを使ってネットワーク上のすべてのノードにブロードキャストされる: Block chain download を見よ
ーーー
この文書のライセンスは
http://creativecommons.org/licenses/by/3.0/