うずまき2017 powered by Jun-Systems

耳管開放症, SAS, 統計解析, 人工知能, プログラミングそれに思考

*

ChainerのFFNNで連続データの重回帰

      2016/08/08

「最近SASやってないんですか??」って聞かれますが、やってます。素晴らしい先人の皆々様に比べて大した知見を持つpostが書けないから投下してないだけです。

さて、前回までTensorFlowで重回帰とかやってましたが、諸事情ありChainerに移りました。今日は諸事情ばっかです。
Chainerに移ったのはいいんですけど、TensorFlowと違って連続データの回帰とかのサンプルコードが数えるほどしか落ちていなかったので、とりあえずChainerのFeedforward Neural Network(FFNN)で重回帰をやります。

import numpy as np
import chainer
from chainer import cuda, Function, gradient_check, Variable, optimizers, serializers, utils, Link, Chain, ChainList
import chainer.functions as F
import chainer.links as L
from chainer.functions.loss.mean_squared_error import mean_squared_error as mse

手始めに好き勝手にライブラリを呼びます。

データの生成

画像認識にも分類にも興味がないので、自分で好き勝手に連続データを作ってそれをRegressionさせます。

rng = np.random
obs = 50
x1 = rng.normal(1,1,obs)
x2 = rng.normal(1,1,obs)
x3 = rng.normal(1,1,obs)
b0 = np.sqrt(rng.normal(1,10)**2)
b1 = rng.normal(1,10)
b2 = rng.normal(1,10)
b3 = rng.normal(1,10)
noise = rng.normal(0,1,obs)
y_real  = np.array(b0 + b1*x1 + b2*x2 + b3*x3 - noise,dtype=np.float32)

dat = np.c_[x1,x2,x3]
dat = np.array(dat,dtype=np.float32)
nvar = dat.shape[1]

今回はx1-x3の3変数とそれに対応する係数、そして切片、あとは誤差を全て正規分布で適当に生成させ、あとは全て足しあわせて真値y_realを作りました。Intercept (b0)を正にしているのはなんとなくです。NumPyの扱いが微妙にわからず行列の連結に戸惑うなど。そしてfloat32を指定するのを忘れずに。
自分のデータを使いたい場合はここを丸ごと省略してファイルのインポートなりしてから、最後に投入する変数の数としてのnvarぐらいは取得しとくといいんじゃないですかね。

Chainの定義

Chainを継承してクラスMyChainを定義、中にネットワークの構造を書きます。n_inは当然使用する変数の数に一致させるわけですが、隠れ層内のニューロンの数としてのn_unitや、そもそもの隠れ層の数に関しては各自やっていってください。

class MyChain(chainer.Chain):
    def __init__(self,n_in,n_unit):
        super(MyChain,self).__init__(
                l1 = L.Linear(n_in,n_unit),
                l2 = L.Linear(n_unit,n_unit),
                l3 = L.Linear(n_unit,1)
                )

    def pred(self, x):
        h1  = F.sigmoid(self.l1(x))
        h2  = F.sigmoid(self.l2(h1))
        out = self.l3(h2) 
        return out

まずひとつ目の塊でChain全体の構造を定義しています。いくつの入力からいくつのユニット(=素子)に出力して、それをまたいくつのユニットに…という形を作るわけであります。つまりこういうことです。
neural network linear connection
当該隠れ層内のn_in(画像ではn_1)個の入力からn_unit(画像ではn_2)個のユニットに送り出すわけです。

ふたつめの塊では、各層内のユニットでの処理を書いています。最初の隠れ層h1では入力された変数の和をシグモイド関数に通してから出力します。それを繰り返して最終的にoutから出力させます。

セットアップ(?)

最適化の方法の指定とか。チュートリアルにはSetupって書いてある。

model = MyChain(nvar,2)
opt = optimizers.SGD()
opt.setup(model)

まずさっき作ったChainをモデルとして指定します。さっきMyChainの引数には(n_in,n_unit)を指定するよう定義したんですが、柔軟に動いて欲しいので結局n_inにはそのままnvarを入れました。n_unitに関しては今回は50Obsに3変数しか作ってないし、あんま大量にユニット作っても仕方ないので2つにしてます。
そして誤差の最小化にはStochastic Gradient Descent(SGD, 確率的勾配法)を使ってます。
ユニットの数とか隠れ層とか最適化の方法とか色々と調整すべきパラメータは多いのですが、今回はデモなので細かいことは何一つ考えていません。普段は適当にいい感じになるように調整してます。

学習ループ

ループでガツガツ学んでもらいます。
鹿が立てるようになるのも人間が歩けるようになるのもトライアンドエラーの賜物であります。

loss_val = 100
epoch = 0
while loss_val > 1e-5:
    x = chainer.Variable(dat.reshape(obs,nvar))
    t = chainer.Variable(y_real.reshape(obs,1))
    model.zerograds()
    y = model.pred(x)
    loss = mse(y,t)
    loss.backward()
    opt.update()
    if epoch % 1000 == 0:
        loss_val = loss.data
        print 'epoch:', epoch
        print('train mean loss={}'.format(loss_val))
        print ' - - - - - - - - - '
    if epoch >= 100000:
        break
    epoch += 1

tの定義にあたっては、元のy_realがただのnobsの長さのベクトルとして定義されているので、reshapeで(nobs,1)の行列に変換してあげましょう。
わざわざy = model.pred(x)とかやってますが、チュートリアルを始めとしてChainer 1.5系では標準的なやり方とされている、Chainの定義のときに__call__作ってy = model()で呼ばなかったのは !!諸事情!! です。こっちにもいろいろあるのです。
参考にさせていただいてたコードでは投入したxとかyとかがいちいち全部吐かれるようになってたんですが、普段10万Obsとかで触ってる身には辛いので、EpochとMSEだけ吐くようにしました。

次はクロスバリデーションというかモデルにテストデータを当てる話を書きたい。
最終的には変数の数とオブザベーションの数とテストデータに使う割合と隠れ層の数と中のユニット数だけ指定してMSEだけぽんっと出力される関数にまとめて性能の比較でもやりまくりたいところですね。解析回しすぎて前日研究室のPCがまたぶっ壊れたばかりですけど。


そもそもなんでわざわざ回帰のコードなんか書いて公開してるのかというと、公式のサンプルコードも含めてMNISTの分類タスクだと、モデルの定義をする際に…
model = L.Classifier(net.MnistMLP( 784, n_units, 10))
ってなってるんですけど、このL.Classifierの部分が連続データの回帰だとどうなるのかわかんなかったからです。考えてみたら別にそのままモデル書いて指定するだけでよかったっぽい。つら。

linkedin: Junichiro NIIMI
そろそろ大学院の退院も近いし、ブロガーでやっていくわけでもないし、俺は働き手として食っていくんだ…!!という想いが強まってきたのでこれからはこういうの貼っていくことにしました。


 - Chainer , , , , ,

Message

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

  関連記事

関連記事はありませんでした