TadaoYamaokaの日記

山岡忠夫Homeで公開しているプログラムの開発ネタを中心に書いていきます。

将棋AIの実験ノート(引き分け有無の比較)

教師データ引き分けを加えた場合と、加えない場合の精度の比較を行った。
引き分けの学習には、ChainerのカスタムFunctionを使用した。

測定方法は前回正則化の効果の測定と同じ。

測定方法

条件

  • 教師データに引き分けを含むか含まないか
  • 引き分けを含めない場合は、引き分けの局面の除いてからシャッフルして2500万局面分を学習

引き分けの局面は、140,005,188局面中、1,339,842局面で、割合は0.957%。
千日手もしくは、400手で引き分けとしている。

測定結果

損失
条件 訓練損失 (合計) テスト損失 (Policy) テスト損失 (Value) テスト損失 (評価値)
引き分けあり 1.5940012 1.0582352 0.59627414 0.6488074
引き分けなし 1.5895698 1.0631737 0.59852475 0.6485649
テストaccuracy
条件 テスト accuracy (Policy) テスト accuracy (Value)
引き分けあり 0.36311078 0.66633254
引き分けなし 0.361024 0.66215944
テストデータの局面を推論した際のエントロピー
条件 エントロピー (Policy) エントロピー (Value)
引き分けあり 2.1945772 0.63117784
引き分けなし 2.1867323 0.6304863

考察

引き分けありの方がPolicyの損失が低い。
Valueの損失は引き分けなしの方が低いが誤差程度。

テストデータに対する一致率は引き分けありがPolicy、Valueともに高いが誤差程度。

テストデータを推論した際のエントロピーは、引き分けありの方が高い。


引き分けのデータは1%未満なのでもともと影響が少ないが、それでも引き分けを入れた方が微妙に良い結果になりそう。
この結果を受けて、今まで強化学習で引き分けのデータは生成していなかったが、今後は生成することにする。

将棋AIの実験ノート(正則化の影響の再測定)

先日、将棋AIのモデルにおけるエントロピー正則化とL2正則化の効果を測定したが、正しく測定できていなかったので再測定した。

前回の測定結果のtest accuacyが低いので、原因を調べていたら、ChainerのV5から追加されたstatic_graphを使っていたことが原因だった。
static_graphを使うと初期モデルからうまく学習ができなくなっていた。
学習済みモデルを読み込んで学習する場合は動作していて、詳しい理由はわかっていない。
理由は気になるが、ひとまずstatic_graphは使わないようにして再測定した。

測定方法、測定条件は前回と同じ。

測定方法

  • 10ブロック、192フィルタのモデル
  • PolicyとValueマルチタスク学習
  • Aperyで生成した1.4億局面をシャッフルして2500万局面分を学習
  • Momentum SGD(lr=0.01)
  • 初期モデルから学習
  • バッチサイズ1024
  • Policyの教師データは指し手(分布なし)
  • Valueの教師データは勝敗(引き分けあり)
  • Valueの損失は評価値によるブートストラップあり
  • テストデータにはfloodgateのR3500以上の棋譜を使用

条件

測定結果

損失
エントロピー正則化 L2正則化 訓練損失 (合計) テスト損失 (Policy) テスト損失 (Value) テスト損失 (評価値)
あり あり 1.5940012 1.0582352 0.59627414 0.6488074
なし あり 1.5837559 1.0621308 0.604218 0.6526976
あり なし 1.5987074 1.0657055 0.6020864 0.65500134
なし なし 1.5907097 1.0641797 0.60402834 0.65064603
テストaccuracy
エントロピー正則化 L2正則化 テスト accuracy (Policy) テスト accuracy (Value)
あり あり 0.36311078 0.66633254
なし あり 0.3576642 0.64967537
あり なし 0.36037222 0.6569107
なし なし 0.3598247 0.65009874
テストデータの局面を推論した際のエントロピー
エントロピー正則化 L2正則化 エントロピー (Policy) エントロピー (Value)
あり あり 2.1945772 0.63117784
なし あり 2.2205758 0.62397426
あり なし 2.2025535 0.6314291
なし なし 2.18667 0.63004863

考察

エントロピー正則化とL2正則化の両方を行った場合が、テストデータに対するPolicy、Value、評価値の損失が最も低くなっている。
テストデータに対するaccuracyも最も高い。
テストデータを推論した際のエントロピーは、Policyが3番目、Valueが2番目に高い。

L2正則化のみの場合、テストデータに対するaccuracyは、Policy、Valueともに最も低かった。
テストデータを推論した際のエントロピーは、Policyが最も高く、Valueが最も低い。
エントロピーからはL2正則化はPolicyに対しても機能しているようだが、accuracyは低くなっているので何とも言えない結果になった。

エントロピー正則化のみの場合、評価値の損失が最も高く、Valueエントロピーが最も高くなってこちらも何とも言えない。

正則化なしの場合、いずれのメトリックスも2番目か3番目になっている。

この結果からは、エントロピー正則化とL2正則化の両方を組み合わせて行うと、PolicyとValueの両方の精度に効果がありそうである。

ChainerでSENetを実装する

ILSVRC 2017で優勝したSqueeze-and-Excitation Networks (SENet)を、こちらのPyTorchの実装を参考にChainerで実装した。

GitHub - TadaoYamaoka/senet.chainer
実装したのは、SE-ResNet20/Cifar10のみ。

結果

通常のResNet
>python cifar.py --batch_size 64 --epochs 10 --baseline
epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
1           1.54999     1.5513                0.42727        0.460589                  28.2861
2           0.995298    0.95569               0.645407       0.660131                  53.5864
3           0.776467    0.86988               0.728153       0.698746                  79.6711
4           0.651746    0.68348               0.773467       0.768611                  104.735
5           0.5714      0.754005              0.80229        0.745422                  130.665
6           0.517285    0.652727              0.820042       0.777966                  157.214
7           0.472674    0.59009               0.835147       0.801453                  183.322
8           0.437737    0.579935              0.846951       0.801154                  209.395
9           0.411429    0.618022              0.854979       0.785032                  235.985
10          0.384668    0.614629              0.865217       0.797074                  261.89

SENet(reduction 16)

>python cifar.py --batch_size 64 --epochs 10 --reduction 16
epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
1           1.37516     2.41607               0.490369       0.348129                  49.4437
2           0.907583    0.860231              0.675136       0.697253                  95.2803
3           0.719768    0.791775              0.748319       0.723229                  141.569
4           0.61616     0.781935              0.785531       0.736664                  187.876
5           0.542171    0.70497               0.811881       0.759654                  234.988
6           0.489325    0.624126              0.828185       0.789112                  282.168
7           0.450093    0.674201              0.84221        0.771497                  329.322
8           0.411275    0.760015              0.855954       0.748408                  376.937
9           0.387795    0.6829                0.863091       0.785928                  424.193
10          0.363779    0.609526              0.872639       0.806131                  471.804

SENet(reduction 8)

>python cifar.py --batch_size 64 --epochs 10 --reduction 8
epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
1           1.3705      1.46585               0.494226       0.510251                  49.7231
2           0.91669     0.876904              0.671655       0.691182                  96.7188
3           0.747184    0.859343              0.737476       0.693969                  143.924
4           0.634395    0.798829              0.777629       0.733678                  191.17
5           0.566925    0.775446              0.803788       0.741043                  239.154
6           0.512038    0.688727              0.823223       0.768113                  287.381
7           0.466351    0.666799              0.838828       0.77707                   334.824
8           0.430302    0.651521              0.849532       0.77926                   381.764
9           0.407922    0.589221              0.858955       0.798268                  429.276
10          0.381586    0.627554              0.866957       0.792596                  475.764

SENet(reduction 4)

>python cifar.py --batch_size 64 --epochs 10 --reduction 4
epoch       main/loss   validation/main/loss  main/accuracy  validation/main/accuracy  elapsed_time
1           1.40231     1.74731               0.480858       0.462679                  49.334
2           0.919275    1.12474               0.669354       0.613455                  96.3172
3           0.724938    0.746381              0.745378       0.73756                   142.618
4           0.61876     0.680163              0.784871       0.761445                  189.007
5           0.547908    0.653891              0.808883       0.77916                   236.142
6           0.496254    0.847583              0.828265       0.720143                  282.802
7           0.4581      0.620524              0.839309       0.79578                   331.504
8           0.419443    0.582007              0.855014       0.804936                  377.842
9           0.399142    0.609093              0.861992       0.79369                   425.787
10          0.372682    0.606829              0.868938       0.796477                  473.873

Cifar10の10エポックの学習では、誤差程度の差しか現れなかった。

将棋AIでも効果があるか試す予定。

将棋AIの実験ノート(正則化の影響測定)

将棋AI用モデルの学習で正則化の有無による精度への影響を測定した。

測定方法

  • 10ブロック、192フィルタのモデル
  • PolicyとValueマルチタスク学習
  • Aperyで生成した1.4億局面をシャッフルして2500万局面分を学習
  • Momentum SGD(lr=0.01)
  • 初期モデルから学習
  • バッチサイズ1024
  • Policyの教師データは指し手(分布なし)
  • Valueの教師データは勝敗(引き分けあり)
  • Valueの損失は評価値によるブートストラップあり
  • テストデータにはfloodgateのR3500以上の棋譜を使用

条件

測定結果

2019/5/22 追記
下記の結果は、正しく測定できていなかったので、後日再測定した。

損失
エントロピー正則化 L2正則化 訓練損失 (合計) テスト損失 (Policy) テスト損失 (Value) テスト損失 (評価値)
あり あり 3.2301362 2.4167266 0.8728576 0.88259214
なし あり 3.2703197 2.516463 0.7008645 0.7254533
あり なし 3.4063873 2.6232579 0.7150356 0.7397537
なし なし 3.4155998 2.6385918 0.75832886 0.7684118
テストaccuracy
エントロピー正則化 L2正則化 テスト accuracy (Policy) テスト accuracy (Value)
あり あり 0.11211929 0.52602535
なし あり 0.10108494 0.5614139
あり なし 0.09025828 0.55845815
なし なし 0.08823568 0.5436531
テストデータの局面を推論した際のエントロピー
エントロピー正則化 L2正則化 エントロピー (Policy) エントロピー (Value)
あり あり 5.7225204 0.51445645
なし あり 5.651507 0.62702596
あり なし 6.2247043 0.6148288
なし なし 6.0029426 0.5822341

考察

エントロピー正則化とL2正則化両方を行った場合が、最もPolicyの精度が高かった。
しかし、Valueの精度が最も低いという結果になった。

L2正則化のみを行うと、Valueの精度が最も高くなった。
しかし、Policyのエントロピーが最も低くなっており方策が決定論的になる傾向がある。

エントロピー正則化のみを行うと、Policyの精度は3番目、Valueの精度は2番目という結果になった。
Policyのエントロピーは最も高く、決定論的になりにくい傾向がある。Valueエントロピーも比較的に高い。

どちらの正則化も行わない場合、Policyの精度が最も低く、Valueの精度は3番目という結果になった。


この結果からは、エントロピー正則化のみを行った場合が総合的に良さそうである。

AlphaZeroではL2正則化のみを行っているが、教師データに指し手を使う場合はL2正則化はPolicyの精度に悪影響がありそう。

2値分類で中間の値も学習する(続き)

昨日書いた2値分類で中間の値も学習するコードは、損失の計算で計算グラフを構築して、backward()時の微分はChainerに任せていた。

しかし、交差エントロピー微分は、以下のように引き算で表すことができるため、計算グラフを構築しなくてもよい。
\displaystyle
H'(p, t) = t - p

交差エントロピーの独自実装

Chainerで微分を独自実装する場合、カスタムFunctionクラスを実装する。
交差エントロピーは、以下のように実装できる(SigmoidCrossEntropyのコードを改造している)。

import numpy

from chainer import cuda
from chainer import function
from chainer.functions.activation import sigmoid
from chainer import utils
from chainer.utils import type_check

class SigmoidCrossEntropy2(function.Function):

    def __init__(self):
        pass

    def check_type_forward(self, in_types):
        type_check.expect(in_types.size() == 2)

        x_type, t_type = in_types
        type_check.expect(
            x_type.dtype == numpy.float32,
            t_type.dtype == numpy.float32,
            x_type.shape == t_type.shape
        )

    def forward(self, inputs):
        xp = cuda.get_array_module(*inputs)
        x, t = inputs

        # stable computation of the cross entropy.
        log1p_ex = xp.log1p(xp.exp(x))
        loss = t * (log1p_ex - x) + (1 - t) * log1p_ex

        self.count = len(x)

        return utils.force_array(
            xp.divide(xp.sum(loss), self.count, dtype=x.dtype)),

    def backward(self, inputs, grad_outputs):
        xp = cuda.get_array_module(*inputs)
        x, t = inputs
        gloss = grad_outputs[0]
        y, = sigmoid.Sigmoid().forward((x,))
        gx = xp.divide(
            gloss * (y - t), self.count,
            dtype=y.dtype)
        return gx, None


def sigmoid_cross_entropy2(x, t):
    return SigmoidCrossEntropy2()(x, t)

検証

昨日のコードの損失をsigmoid_cross_entropy2に変更して、MNISTデータセットを学習できるか検証した。

差分箇所
        ...
        # 損失計算
        loss = sigmoid_cross_entropy2(y, t)
        ...
        # 損失計算
        loss_test = sigmoid_cross_entropy2(y_test, t_test)
        ...
結果
GPU: 0
# unit: 1000
# Minibatch-size: 100
# epoch: 20
epoch=1, train loss=0.46885148, test loss=0.33832973
epoch=2, train loss=0.30149257, test loss=0.2579055
epoch=3, train loss=0.23578753, test loss=0.2050258
epoch=4, train loss=0.19029677, test loss=0.1703787
epoch=5, train loss=0.15914916, test loss=0.14760743
epoch=6, train loss=0.13843748, test loss=0.13353598
epoch=7, train loss=0.12384777, test loss=0.123705104
epoch=8, train loss=0.11282795, test loss=0.11471477
epoch=9, train loss=0.104268566, test loss=0.10847781
epoch=10, train loss=0.09730684, test loss=0.103607066
epoch=11, train loss=0.091508955, test loss=0.09841835
epoch=12, train loss=0.08627852, test loss=0.0952349
epoch=13, train loss=0.08196059, test loss=0.095587894
epoch=14, train loss=0.078234516, test loss=0.089423485
epoch=15, train loss=0.0742373, test loss=0.087859996
epoch=16, train loss=0.07113662, test loss=0.08602422
epoch=17, train loss=0.06793152, test loss=0.08308805
epoch=18, train loss=0.06521834, test loss=0.08113079
epoch=19, train loss=0.062456965, test loss=0.0854297
epoch=20, train loss=0.060211364, test loss=0.07905889

小さな誤差があるが、ほぼ同じ結果になった。

2値分類で中間の値も学習する

実験している将棋AIの学習では、今まで価値ネットワークの出力の活性化関数をシグモイドとして、勝ちと負けの2値で分類していた。
しかし、先日の世界コンピュータ将棋選手権に参加して、引き分けや千日手が結果を左右するゲームが多かったため、引き分けも教師にすべきと思い直した。
そこで、引き分けも学習できるように修正することにした。
(AlphaZeroのように出力をtanhにして損失をMSEにして学習することもできるが、確率的な事象のため交差エントロピーで学習した方がよいと思っている。)

Chainerで実装している学習処理では、損失をsigmoid_cross_entropyとしているため、そのままでは引き分けを学習できない。
交差エントロピーの定義通りに自分で計算する必要がある。

交差エントロピーの計算

ニューラルネットワークの出力の確率をp、教師データをtとすると交差エントロピーは以下の式で与えられる。
\displaystyle
\begin{align}
Loss(p, t) = -(t \log p + (1-t) \log(1-p))
\end{align}
これを、ロジットyを使って表すと、
\displaystyle
\begin{align}
Loss(y, t) = -(t (y - \log(e^y + 1)) - (1-t) \log(e^y + 1))
\end{align}
となる。

Chainerでは以下のように実装できる。

        log1p_ey = F.log1p(F.exp(y))
        loss = F.mean(-(t * (y - log1p_ey) - (1 - t) * log1p_ey))

※log1p_eyの部分はsoftplusでもよさそう。

MNISTで検証

sigmoid_cross_entropyと結果が同じになることをMNISTデータセットを使って検証する。
2値分類とするため、5より大きい場合を1、5以下を0として学習する。

sigmoid_cross_entropyによる実装
import numpy as np
import chainer
from chainer import Chain
import chainer.functions as F
import chainer.links as L
from chainer import cuda, Variable
from chainer import datasets, iterators, optimizers, serializers

import argparse

import random
random.seed(0)
np.random.seed(0)
cuda.cupy.random.seed(0)

class MLP(Chain):
    def __init__(self, n_units):
        super(MLP, self).__init__()
        with self.init_scope():
            self.l1 = L.Linear(None, n_units)
            self.l2 = L.Linear(None, n_units)
            self.l3 = L.Linear(None, 1)

    def __call__(self, x):
        h1 = F.relu(self.l1(x))
        h2 = F.relu(self.l2(h1))
        return self.l3(h2)

parser = argparse.ArgumentParser(description='example: MNIST')
parser.add_argument('--batchsize', '-b', type=int, default=100,
                    help='Number of images in each mini-batch')
parser.add_argument('--epoch', '-e', type=int, default=20,
                    help='Number of sweeps over the dataset to train')
parser.add_argument('--unit', '-u', default=1000, type=int,
                    help='number of units')
parser.add_argument('--gpu', '-g', type=int, default=-1,
                    help='GPU ID (negative value indicates CPU)')
args = parser.parse_args()

model = MLP(args.unit)
if args.gpu >= 0:
    cuda.get_device_from_id(args.gpu).use()
    model.to_gpu()

optimizer = optimizers.SGD()
optimizer.setup(model)

train, test = datasets.get_mnist()

train_iter = iterators.SerialIterator(train, args.batchsize)
test_iter = iterators.SerialIterator(test, args.batchsize, shuffle=False)

def mini_batch(batch):
    x_data = []
    t_data = []
    for data in batch:
        x_data.append(data[0].reshape((1, 28, 28)))
        if data[1] > 5:
            t_data.append([1])
        else:
            t_data.append([0])

    x = Variable(cuda.to_gpu(np.array(x_data, dtype=np.float32)))
    t = Variable(cuda.to_gpu(np.array(t_data, dtype=np.int32)))

    return x, t

for epoch in range(1, args.epoch + 1):
    sum_loss = 0
    itr = 0
    for i in range(0, len(train), args.batchsize):
        train_batch = train_iter.next()
        x, t = mini_batch(train_batch)

        y = model(x)

        model.cleargrads()
        # 損失計算
        loss = F.sigmoid_cross_entropy(y, t)
        loss.backward()
        optimizer.update()

        sum_loss += loss.data
        itr += 1

    sum_test_loss = 0
    test_itr = 0
    for i in range(0, len(test), args.batchsize):
        test_batch = test_iter.next()
        x_test, t_test = mini_batch(test_batch)

        y_test = model(x_test)
        #損失計算
        loss_test = F.sigmoid_cross_entropy(y_test, t_test)
        sum_test_loss += loss_test.data

        test_itr += 1

    print('epoch={}, train loss={}, test loss={}'.format(
        optimizer.epoch + 1, sum_loss / itr,
        sum_test_loss / test_itr))

    optimizer.new_epoch()
交差エントロピーを計算した実装

差分箇所のみ

def mini_batch(batch):
        ...
        if data[1] > 5:
            t_data.append([1.0])
        else:
            t_data.append([0.0])
        ...
    t = Variable(cuda.to_gpu(np.array(t_data, dtype=np.float32)))

        ...

        # 損失計算
        log1p_ey = F.log1p(F.exp(y))
        loss = F.mean(-(t * (y - log1p_ey) - (1 - t) * log1p_ey))

        ...

        #損失計算
        log1p_ey_test = F.log1p(F.exp(y_test))
        loss_test = F.mean(-(t_test * (y_test - log1p_ey_test) - (1 - t_test) * log1p_ey_test))
        ...

比較

それぞれの実行結果を比較した。
ランダムシードを固定しているため、結果の損失は同じになるはずである。

sigmoid_cross_entropyによる実装
GPU: 0
# unit: 1000
# Minibatch-size: 100
# epoch: 20
epoch=1, train loss=0.46885258, test loss=0.33833
epoch=2, train loss=0.30148864, test loss=0.25789934
epoch=3, train loss=0.23578335, test loss=0.20502168
epoch=4, train loss=0.19030152, test loss=0.17037769
epoch=5, train loss=0.15915988, test loss=0.14761437
epoch=6, train loss=0.13844858, test loss=0.1335538
epoch=7, train loss=0.1238606, test loss=0.12371666
epoch=8, train loss=0.11283412, test loss=0.1147375
epoch=9, train loss=0.10427594, test loss=0.10852282
epoch=10, train loss=0.0973152, test loss=0.1036295
epoch=11, train loss=0.091520935, test loss=0.09843105
epoch=12, train loss=0.08628643, test loss=0.09522996
epoch=13, train loss=0.08197385, test loss=0.095566705
epoch=14, train loss=0.078243226, test loss=0.0893925
epoch=15, train loss=0.07424576, test loss=0.087877
epoch=16, train loss=0.07114731, test loss=0.086056426
epoch=17, train loss=0.06794335, test loss=0.083081216
epoch=18, train loss=0.06522623, test loss=0.081139676
epoch=19, train loss=0.062456455, test loss=0.08545701
epoch=20, train loss=0.0602174, test loss=0.07909738
交差エントロピーを計算した実装
GPU: 0
# unit: 1000
# Minibatch-size: 100
# epoch: 20
epoch=1, train loss=0.46885142, test loss=0.33832955
epoch=2, train loss=0.30149198, test loss=0.25790796
epoch=3, train loss=0.23578809, test loss=0.20502794
epoch=4, train loss=0.1903022, test loss=0.17038527
epoch=5, train loss=0.15915494, test loss=0.14760974
epoch=6, train loss=0.13844547, test loss=0.13355586
epoch=7, train loss=0.12385938, test loss=0.12372275
epoch=8, train loss=0.11283637, test loss=0.11474205
epoch=9, train loss=0.10427626, test loss=0.10850772
epoch=10, train loss=0.09731431, test loss=0.10361102
epoch=11, train loss=0.09150928, test loss=0.09841354
epoch=12, train loss=0.08627809, test loss=0.09522151
epoch=13, train loss=0.08196366, test loss=0.095563896
epoch=14, train loss=0.07823428, test loss=0.089379594
epoch=15, train loss=0.07423272, test loss=0.08784414
epoch=16, train loss=0.07113342, test loss=0.085993215
epoch=17, train loss=0.06792906, test loss=0.08306415
epoch=18, train loss=0.06520847, test loss=0.081117764
epoch=19, train loss=0.062452678, test loss=0.085387066
epoch=20, train loss=0.060215034, test loss=0.079077914

小さな誤差があるが、ほぼ同じ結果になっている。

中間の値を学習する

中間の値を学習するには、交差エントロピーの教師データに0.5を与えればよい。
教師データを0.5にしても学習できるか確認するため、MNISTの学習を変更して、5より大きい場合1.0、5の場合0.5、5より小さい場合0.0として学習して検証する。

差分箇所のみ

def mini_batch(batch):
        ...
        if data[1] > 5:
            t_data.append([1.0])
        elif data[1] < 5:
            t_data.append([0.0])
        else:
            t_data.append([0.5])
        ...

学習結果は、以下のようになった。

GPU: 0
# unit: 1000
# Minibatch-size: 100
# epoch: 20
epoch=1, train loss=0.50217974, test loss=0.3803524
epoch=2, train loss=0.34845084, test loss=0.30468062
epoch=3, train loss=0.28511474, test loss=0.25564748
epoch=4, train loss=0.24253067, test loss=0.22479586
epoch=5, train loss=0.21511348, test loss=0.20560989
epoch=6, train loss=0.19752434, test loss=0.19374746
epoch=7, train loss=0.18512638, test loss=0.18558586
epoch=8, train loss=0.17562735, test loss=0.17736126
epoch=9, train loss=0.16807537, test loss=0.17147815
epoch=10, train loss=0.16175319, test loss=0.16750234
epoch=11, train loss=0.15649119, test loss=0.16236024
epoch=12, train loss=0.15169033, test loss=0.15876935
epoch=13, train loss=0.1475925, test loss=0.16045952
epoch=14, train loss=0.14412253, test loss=0.15328114
epoch=15, train loss=0.14068681, test loss=0.152177
epoch=16, train loss=0.13768907, test loss=0.15108886
epoch=17, train loss=0.1348263, test loss=0.1473368
epoch=18, train loss=0.13233264, test loss=0.14487615
epoch=19, train loss=0.13000856, test loss=0.14718775
epoch=20, train loss=0.12774076, test loss=0.14208344

テストデータの5の画像データに対して、0.5を出力できるか確認した。

x_data = []
for data in test:
    if data[1] == 5:
        x_data.append(data[0].reshape((1, 28, 28)))

x = Variable(cuda.to_gpu(np.array(x_data, dtype=np.float32)))
y = model(x)
p = F.sigmoid(y)
print(F.mean(p))
0.5094662

平均すると正しく0.5を予測できている。

5より大きい値(たとえば6)だと、

0.9532776

5より小さい値(たとえば3)だと、

0.055663988

となり、正しく予測できている。

ロジットを使ってエントロピーを計算する

方策勾配で強化学習を行う際に、方策が決定論的になっていないか監視するために、ログにエントロピーを出力するようにしたい。

エントロピーは、
\displaystyle
H({\bf p})=\sum_i p_i \log p_i
で計算できるが、確率がほぼ0の場合、\log(p_i)が-infになるため、この式のままでは計算できない。

p_iの最小値をクリップして、

F.log(F.clip(p, 1e-32, 1.0))

のようにすると、-infになることを防ぐことができるが、ニューラルネットワークの出力をロジットにしている場合、よりスマートに実装できる。

ロジットを使ったエントロピーの計算

ロジットをy_iとすると、ソフトマックスの定義から、
\displaystyle
\begin{align}
\log p_i &= \log \frac{e^{y_i}}{\sum_j e^{y_j}} \\
&= y_i - \log\sum_j e^{y_j}
\end{align}
となる。
ここで、\sum_j e^{y_j}がオーバーフローする可能性があるため、指数部から{\bf y}の最大値を引いて
\displaystyle
\begin{align}
\log p_i &= y_i - \log(\sum_j e^{y_j - y_{max}} \cdot e^{y_{max}}) \\
&= y_i - (\log\sum_j e^{y_j - y_{max}}  + y_{max})
\end{align}
のように変形する。

これを使うと、Chainerでは、エントロピーの計算を以下のように実装できる。

    p = F.softmax(y)
    #entropy = F.sum(- p * F.log(p), axis=1)
    y_max = F.max(y, axis=1, keepdims=True)
    log_p = y - (F.log(F.sum(F.exp(y - y_max), axis=1, keepdims=True)) + y_max)
    entropy = F.sum(- p * log_p, axis=1)

※コメントしている行のまま計算すると結果が、nanになる場合がある。

シグモイドの場合

結果がシグモイドの場合は、
\displaystyle
p = \frac{1}{1+e^{-y}}
となるので、\log p\log(1-p)は、それぞれ、
\displaystyle
\begin{align}
\log p &= \log \frac{1}{1+e^{-y}} \\
&= y - \log(e^y+1)
\end{align}
\displaystyle
\begin{align}
\log (1 - p) &= \log(1- \frac{1}{1+e^{-y}}) \\
&= \log\frac{1}{e^y + 1} \\
&= -\log(e^y + 1)
\end{align}
となる。
これを使って、Chainerでは、エントロピーの計算を以下のように実装できる。

    p = F.sigmoid(y)
    #entropy = -(p * F.log(p) + (1 - p) * F.log(1 - p))
    log1p_ey = F.log1p(F.exp(y))
    entropy = -(p * (y - log1p_ey) + (1 - p) * -log1p_ey)

※コメントしている行のまま計算すると結果が、nanになる場合がある。