Chainerを用いて単純なニューラルネットワークを構築し、sin波を学習してみましょう!
とりあえず試してみたい方はこちらからすぐに実行可能なコードに触れることができます!
関連:ニューラルネットで時系列データを学習しよう【Chainer入門】
関連: あなたに最適なpython入門書を見つけよう!
はじめに
この記事では、単純なニューラルネットを用いてsin波の学習を行います。
今回使用したコードはGoogle Colaboratoryにて公開しており、こちらから実行できます。
playgroundモードに写すことで実際に実行し、試してみることができます。
以下では上のコードについての解説を行います。
前提
この記事ではニューラルネットの構築にChainerを使用します。
また数値計算ライブラリのnumpyも使用します。
上で紹介したNotebookを使用せず、ご自身のパソコンで実験する場合には、以下の手順で必要なライブラリをインストールしてください。
$ pip install numpy $ pip install chainer
データ
今回はsin波を学習するニューラルネットを作り、実験を行います。
-4.0から4.0の区間で0.01刻みの数値xを用意し、それに対応するsin関数の値t=sin(x)を学習するデータとします。
import numpy as np # -4.0 ~ 4.0 xs = np.arange(-400,400)*0.01 # shuffle np.random.shuffle(xs) splitPoint = int(len(xs)*0.9) # training and test inputs xsTrain = xs[:splitPoint] xsTest = xs[splitPoint:] # trainin and test golds tsTrain = np.sin(xsTrain) tsTest = np.sin(xsTest)
xsはシャッフルされた-4.0から4.0の値です。
splitPointはデータを9:1に分割するための変数で、データの90%を学習、10%をテストに用います。
xsTrain,とtsTrainは学習に用いる入力と教師データで、xsTestとtsTestはテストに用いる入力と正解データです。
ここで入力とは、ニューラルネットへの入力を指します。
用意した学習用データとテスト用データは以下のグラフのようになっています。
横軸が入力で、対応する教師データが縦軸です。
青が学習に用いるデータ、赤がテストに用いるデータです。

さらにニューラルネットに食わせやすくするため、データの向きを変えます。
また、Chainerではfloat32を使用する必要があるため型を変換しておきます。
これらの処理はニューラルネットについて本質的ではないため、理解は後回しで構いません。
# 使いやすいようにデータの向きを変えておく
xsTrain = np.expand_dims(xsTrain,axis=1)
xsTest = np.expand_dims(xsTest,axis=1)
tsTrain = np.expand_dims(tsTrain,axis=1)
tsTest = np.expand_dims(tsTest,axis=1)
# chainerではfloat32を使う
xsTrain = xsTrain.astype('float32')
xsTest = xsTest.astype('float32')
tsTrain = tsTrain.astype('float32')
tsTest = tsTest.astype('float32')
モデル

今回用いるモデルのイメージを上に示しました。
ここでxはニューラルネットへの入力、yはニューラルネットによる出力です。
青色の丸角四角はベクトル、ベージュの三角は線形層、紫の四角は非線形関数です。
詳細な説明は省きますが、1次元の入力をより表現力の高い10次元に写したのち、再度1次元に戻すというイメージをつかんでください。
これをChainerで書くと以下のようになります。
from chainer import Chain
from chainer import links as L
from chainer import functions as F
class Model(Chain):
  def __init__(self, hidSize=10):
    super().__init__()
    linear1 = L.Linear(1,hidSize)
    linear2 = L.Linear(hidSize,1)
    self.add_link('linear1',linear1)
    self.add_link('linear2',linear2)
  
  def forward(self, xs):
    hs = F.tanh(self.linear1(xs))
    ys = self.linear2(hs)
    return ys
入力はxの値、出力は対応するsin(x)ですので、入力出力ともに次元数は1です。
これを線形層(linear1)によって一度10次元にしたのちに、異なる線形層(linear2)によって1次元に戻します。
学習
クラスの宣言
# モデル宣言 model = Model() # optimizer宣言 from chainer import optimizers opt = optimizers.Adam() opt.setup(model)
上で定義したモデルを用いるために、まずはモデルを宣言します。
また、学習を行うためにOptimizerを設定します。
詳しくは省略しますが、ニューラルネットワークによる予測値と、正解の数値(教師データ)との誤差が小さくなるように学習を行うものだと考えてください。
train関数
上で宣言したモデルを学習するための関数を書きます。
def train(startEpoch=0, endEpoch=100):  
  for i in range(startEpoch, endEpoch):
    model.cleargrads()
    ys = model(xsTrain)
    loss = F.average(F.squared_error(ys,tsTrain))
    loss.backward()
    opt.update()
各エポックにおいて、xsTrainをニューラルネットに入力し、その出力ysと教師データtsTrainとの二乗誤差を最小化します。
今回は二乗誤差を用いていますが、この値は「ロス」と呼ばれ、ロスを最小化するようにニューラルネットのパラメーターを更新することで、「学習」を行います。
evaluate関数
また、学習済みのモデルをテストデータで評価するための関数が必要です。
ほとんどtrain関数と同じですが、学習を行わずに二乗誤差の結果だけを表示しています。
(ノートブックでは結果をグラフにプロットするようになっています。)
def evaluate():
  ys = model(xsTest)
  # loss
  print('average loss', F.average(F.squared_error(ys,tsTest)))
  
  # show
  plt.scatter(xsTest, ys.data, color='blue')
  plt.scatter(xsTest, tsTest, color='red')
実験
最後に、作成したニューラルネットを用いて実験を行います。
各エポックの学習時点でのロスの下がり方と、テストデータに対する予測値をグラフでお見せします。
1~100 epoch
 
100 epochまでのロスの遷移と、予測に対する正解データのプロットです。
プロットは青が予測、赤が正解となっています。
100エポック程度ではあまり学習ができていません。
同様にして残りの学習結果も見てみましょう。
100~1000 epoch
 
順調にロスが下がり、テストへの結果もフィットしてきています。
1000~5000 epoch
 
5000エポックほどでほとんどロスは収束し、学習が完了した状態になります。
予測もほとんど正解と差がありません。
5000~10000 epoch
 
さらに学習を続けると、ロスにトゲのようなものが見られる変化が現れます。
これはニューラルネットの学習においてみられる学習過程で、ロスが下がる前に一度ロスが大きく上がる現象です。
今回は詳しく踏み込まないことにしますが、このように「壁を乗り越えてよりロスを下げる学習ができる」ことが、ニューラルネットの強みと言えます。
まとめ
この記事ではChainerを用いてsin波の学習実験を行いました。
単純な構造のため、ニューラルネットワーク自体の勉強にもなる題材です。
ぜひ皆さんも試してみて、ニューラルネットへの理解を深めましょう!
関連:ニューラルネットで時系列データを学習しよう【Chainer入門】

