デジタル・デザイン・ラボラトリーな日々

アラフィフプログラマーが数学と物理と英語を基礎からやり直す。https://qiita.com/yaju

順列と組合せを理解してみる-重複順列

はじめに

高校生の時に順列と組合せを学びました。公式と計算方法は覚えているのですが、きちんと理解していないので実践で組合せを考えても答えがすぐに導き出すことが出来ないんですね。

日曜日の「林先生が驚く初耳学」という番組の中で林修先生が「数学を理解することができれば見え方が変わるからもったいない。」と仰っていました。 例えば講演会の時などに参加者の方と一緒に写真を撮ることになり4人の方と写真を撮ることとなった場合、数学ができる人だと返す言葉が違う、全通り撮っても4人なのでたった15通りと計算することができる。数学を学んでいればそのことが一瞬で処理することができる、なので写真を撮ることは「そんなに大変なことじゃないですよ」と上手に解決することができる。他のケースでも応用することができる「どうせ多くても15枚だし」という考え方になるのでどれだけ写真を撮ってもOKですよと数学的思考がある人は日々の生活での解決法が違う」

私も林修先生のように組合せを瞬時にできるようになりたい。
先ずは重複順列をやります。

写真の組合せ

林先生と写真を撮りたい4人との組合せは全部で16通り。 写りたい人がそれぞれ「いる」か「いない」の2択が4人なので 2×2×2×2=16 全員いなかったら林先生の1ショットになるので(4人全員が「いない」という組合せになるため)その1は引くので(16-1)で15通りとなる。

組合せの基礎

学校で習ったのは、 _n P _r_n C _rn! です。写真の件は n^r - 1 ですよね。

丁度、記号の英語名を探していたら「LaTeXコマンド集」で見つけました。

  • 重複順列 (repeated permutation)
  • 順列 (permutation)
  • 組合せ (combination)
  • 重複組合せ (repeated combination)

重複順列

「同じものを繰り返し取ってよいという約束のもとで」できる順列を重複順列といいます。

参照 重複順列重複順列 nr

プログラマーなら二進数の問題が分かりやすいと思います。
問1:二進数は、2種類の記号0,1を並べて表現されます。2種類の記号0,1を合計3個使って作れる記号は何通りありますか?
答1:2 \times 2 \times 2 = 2^3 = 8 通りとなります。

問2:異なる3個の文字a,b,cから重複を許して4個取って並べる順列の総数は何通りありますか?
答2:3 \times 3 \times 3 \times 3 = 3^4 = 81 通りとなります。

デシジョンテーブル

参照:第5回 仕様整理に「デシジョンテーブル」を使ってみよう

重複順列が分かると下記3つの条件があった場合、条件はそれぞれ独立していて Y/N の2通りだと2 \times 2 \times 2 = 2^3 = 8通りとすぐに導き出してデシジョンテーブルExcelにさらっと書けるようになります。

  • クーポン持参
  • 平日割引
  • 平日シニア割引(65歳以上)

f:id:Yaju3D:20160630235211p:plain

TensorFlowコトハジメ Fizz-Buzz問題

はじめに

Fizz-Buzz問題
1から100までの数をプリントするプログラムを書け。ただし3の倍数のときは数の代わりに「Fizz」と、5の倍数のときは「Buzz」とプリントし、3と5両方の倍数の場合には「FizzBuzz」とプリントすること。

ここ最近、通勤中の車の中でFizz-Buzz問題をTensorFlowを使って予測出来るんじゃないかと考えていたのですが、 どうやってモデルを作ればいいのか機械学習の初心者なんで方法が思い付かない。
訓練では下記ソースのように数式を使えますが、予測させるわけですから倍数も剰余も使えない。

for i in range(1,101):
    if i % 15 == 0:
        print 'FizzBuzz'
    elif i % 5 == 0:
        print 'Buzz'
    elif i % 3 == 0:
        print 'Fizz'
    else:
        print i

今日(2016/06/04)、何気に誰かやってないかと「FizzBuzz 機械学習」で検索したら見つけました。
2016/05/23に少しバズってたようですが気が付かなかったです。その後リンク先追加

ソースコード

いつものようにDockerのJupyter Notebook上で動かしたのですが、4000台あたりで固まって最後まで動作しませんでした。 Ubuntu上で「python fizz_buzz.py」とすれば最後まで動作しました。
github.com

再度挑戦、Jupyter Notebook上で動かす際にprintを100単位(if epoch % 100 == 0)に出力するようにしたところ、最後まで動作することが出来ました。

# coding: utf-8
# Fizz Buzz in Tensorflow!
# see http://joelgrus.com/2016/05/23/fizz-buzz-in-tensorflow/

import numpy as np
import tensorflow as tf

NUM_DIGITS = 10

# Represent each input by an array of its binary digits.
def binary_encode(i, num_digits):
    return np.array([i >> d & 1 for d in range(num_digits)])

# One-hot encode the desired outputs: [number, "fizz", "buzz", "fizzbuzz"]
def fizz_buzz_encode(i):
    if   i % 15 == 0: return np.array([0, 0, 0, 1])
    elif i % 5  == 0: return np.array([0, 0, 1, 0])
    elif i % 3  == 0: return np.array([0, 1, 0, 0])
    else:             return np.array([1, 0, 0, 0])

# Our goal is to produce fizzbuzz for the numbers 1 to 100. So it would be
# unfair to include these in our training data. Accordingly, the training data
# corresponds to the numbers 101 to (2 ** NUM_DIGITS - 1).
trX = np.array([binary_encode(i, NUM_DIGITS) for i in range(101, 2 ** NUM_DIGITS)])
trY = np.array([fizz_buzz_encode(i)          for i in range(101, 2 ** NUM_DIGITS)])

# We'll want to randomly initialize weights.
def init_weights(shape):
    return tf.Variable(tf.random_normal(shape, stddev=0.01))

# Our model is a standard 1-hidden-layer multi-layer-perceptron with ReLU
# activation. The softmax (which turns arbitrary real-valued outputs into
# probabilities) gets applied in the cost function.
def model(X, w_h, w_o):
    h = tf.nn.relu(tf.matmul(X, w_h))
    return tf.matmul(h, w_o)

# Our variables. The input has width NUM_DIGITS, and the output has width 4.
X = tf.placeholder("float", [None, NUM_DIGITS])
Y = tf.placeholder("float", [None, 4])

# How many units in the hidden layer.
NUM_HIDDEN = 100

# Initialize the weights.
w_h = init_weights([NUM_DIGITS, NUM_HIDDEN])
w_o = init_weights([NUM_HIDDEN, 4])

# Predict y given x using the model.
py_x = model(X, w_h, w_o)

# We'll train our model by minimizing a cost function.
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(py_x, Y))
train_op = tf.train.GradientDescentOptimizer(0.05).minimize(cost)

# And we'll make predictions by choosing the largest output.
predict_op = tf.argmax(py_x, 1)

# Finally, we need a way to turn a prediction (and an original number)
# into a fizz buzz output
def fizz_buzz(i, prediction):
    return [str(i), "fizz", "buzz", "fizzbuzz"][prediction]

BATCH_SIZE = 128

# Launch the graph in a session
with tf.Session() as sess:
    tf.initialize_all_variables().run()

    for epoch in range(10000):
        # Shuffle the data before each training iteration.
        p = np.random.permutation(range(len(trX)))
        trX, trY = trX[p], trY[p]

        # Train in batches of 128 inputs.
        for start in range(0, len(trX), BATCH_SIZE):
            end = start + BATCH_SIZE
            sess.run(train_op, feed_dict={X: trX[start:end], Y: trY[start:end]})

        # And print the current accuracy on the training data.
        if epoch % 100 == 0:
            print(epoch, np.mean(np.argmax(trY, axis=1) ==
                             sess.run(predict_op, feed_dict={X: trX, Y: trY})))

    # And now for some fizz buzz
    numbers = np.arange(1, 101)
    teX = np.transpose(binary_encode(numbers, NUM_DIGITS))
    teY = sess.run(predict_op, feed_dict={X: teX})
    output = np.vectorize(fizz_buzz)(numbers, teY)

    print(output)

出力結果

(0, 0.53196099674972919)
(100, 0.53412784398699886)
(200, 0.53412784398699886)
(300, 0.53412784398699886)
(400, 0.53412784398699886)
(500, 0.53412784398699886)
  ︙
(9500, 1.0)
(9600, 1.0)
(9700, 1.0)
(9800, 1.0)
(9900, 1.0)
['1' '2' 'fizz' '4' 'buzz' 'fizz' '7' '8' 'fizz' 'buzz' '11' 'fizz' '13'
 '14' 'fizz' '16' '17' 'fizz' '19' 'buzz' 'fizz' '22' '23' 'fizz' 'buzz'
 '26' 'fizz' '28' '29' 'fizzbuzz' '31' '32' '33' '34' 'buzz' 'fizz' '37'
 'fizz' 'fizz' 'buzz' '41' 'fizz' '43' '44' 'fizzbuzz' '46' '47' 'fizz'
 '49' 'buzz' 'fizz' '52' '53' 'fizz' 'buzz' '56' 'fizz' '58' '59'
 'fizzbuzz' '61' '62' 'fizz' '64' 'buzz' 'fizz' '67' 'fizz' '69' 'buzz'
 '71' 'fizz' '73' '74' 'fizzbuzz' '76' '77' 'fizz' '79' '80' '81' '82' '83'
 'fizz' 'buzz' '86' '87' '88' '89' 'fizzbuzz' '91' '92' 'fizz' '94' 'buzz'
 '96' '97' '98' 'fizz' 'buzz']

幾つか不正解してますね、15が'fizz'だったり。

NUM_DIGITS = 10→14に変更するだけでも下記結果の通り精度があがります。

['1' '2' 'fizz' '4' 'buzz' 'fizz' '7' '8' 'fizz' 'buzz' '11' 'fizz' '13'
 '14' 'fizzbuzz' '16' '17' 'fizz' '19' 'buzz' 'fizz' '22' '23' 'fizz'
 'buzz' '26' 'fizz' '28' '29' 'fizzbuzz' '31' '32' 'fizz' '34' 'buzz'
 'fizz' '37' '38' 'fizz' 'buzz' '41' 'fizz' '43' '44' 'fizzbuzz' '46' '47'
 'fizz' '49' 'buzz' 'fizz' '52' '53' 'fizz' 'buzz' '56' 'fizz' '58' '59'
 'fizzbuzz' '61' '62' 'fizz' '64' 'buzz' 'fizz' '67' '68' 'fizz' 'buzz'
 '71' 'fizz' '73' '74' 'fizzbuzz' '76' '77' 'fizz' '79' 'buzz' '81' '82'
 '83' 'fizz' 'buzz' '86' 'fizz' '88' '89' 'fizzbuzz' '91' '92' 'fizz' '94'
 'buzz' '96' '97' '98' 'fizz' 'buzz']

仕組み

It would be cheating to use the numbers 1 to 100 in our training data, so let's train it on all the remaining numbers up to 1024:

学習用データに1から100までの数値を使うことは不正行為とのことで、101から1023までのデータで学習したニューラルネットワークに対して、1から100までの答えの予測を出力するプログラムになっている。

多クラス識別問題

手書き文字認識(MNIST)による多クラス識別問題の時に10クラス(0-9)に分けましたが、今回は4クラスに分けます。
one-hot ベクトル (one-of-K表現) f:id:Yaju3D:20160604231742p:plain

# One-hot encode the desired outputs: [number, "fizz", "buzz", "fizzbuzz"]
def fizz_buzz_encode(i):
    if   i % 15 == 0: return np.array([0, 0, 0, 1])
    elif i % 5  == 0: return np.array([0, 0, 1, 0])
    elif i % 3  == 0: return np.array([0, 1, 0, 0])
    else:             return np.array([1, 0, 0, 0])

入力値と正解値の準備

入力値となる特徴ベクトル「trX」と正解値の「trY」の配列を、101~210まで用意します。
手書き文字認識(MNIST)の入力値は白黒画像(28x28ピクセル=784)でした、Fizz-Buzz問題の入力値は数値をバイナリ変換することである種の白黒画像にするってことになりますね。

NUM_DIGITS  =  10 

# Our goal is to produce fizzbuzz for the numbers 1 to 100. So it would be
# unfair to include these in our training data. Accordingly, the training data
# corresponds to the numbers 101 to (2 ** NUM_DIGITS - 1).
trX = np.array([binary_encode(i, NUM_DIGITS) for i in range(101, 2 ** NUM_DIGITS)])
trY = np.array([fizz_buzz_encode(i)          for i in range(101, 2 ** NUM_DIGITS)])
入力値のtrXは101~1023までの数値のバイナリ表現

trX=
[[1 0 1 ..., 0 0 0] <<101の2進数(0000110101)
 [0 1 1 ..., 0 0 0] <<102の2進数(0000110110)
 [1 1 1 ..., 0 0 0]
 ..., 
 [1 0 1 ..., 1 1 1]
 [0 1 1 ..., 1 1 1]
 [1 1 1 ..., 1 1 1]] <<1023の2進数((1111111111))


正解値のtrYは101~1023までのfizzbuzz値のバイナリ表現
trY= 
[[1 0 0 0] <<101:5でも3でも割り切れない
 [0 1 0 0] <<102:3で割り切れる
 [1 0 0 0]
 ..., 
 [1 0 0 0]
 [1 0 0 0]
 [0 1 0 0]] <<1023:3で割り切れる

ニューラルネット

入力層:10(NUM_DIGITS)ノード, 隠れ層:100(NUM_HIDDEN)ノード, 出力層:4ノード f:id:Yaju3D:20160605163528p:plain

ノードの作成

入力ノードと出力ノードはplaceholderという名前の関数で構築されます.第一引数は型,第二引数は配列の形状です。

# Our variables. The input has width NUM_DIGITS, and the output has width 4.
X = tf.placeholder("float", [None, NUM_DIGITS])
Y = tf.placeholder("float", [None, 4])

ノード重みの作成

モデルのパラメーターとなる結合重み(ノードの間をつなぐ重み)はVariables関数を利用して領域を確保します。
ニューラルネットワークで学習を行う際、ネットワークの各重みの初期値は乱数により決定することが一般的となっている。
重み初期化:正規分布(ガウス分布)による乱数で初期化(平均 0.0, 分散 0.01)

def init_weights(shape):
    return tf.Variable(tf.random_normal(shape, stddev=0.01))

# Initialize the weights.
# モデルパラメータ(入力層:10ノード, 隠れ層:100ノード, 出力層:4ノード)
w_h = init_weights([NUM_DIGITS, NUM_HIDDEN])
w_o = init_weights([NUM_HIDDEN, 4])

予測式(モデル)を定義

def model(X, w_h, w_o):
    h = tf.nn.relu(tf.matmul(X, w_h))
    return tf.matmul(h, w_o)

# Predict y given x using the model.
# 予測式(モデル)
py_x = model(X, w_h, w_o)

入力した値を0から1の間に収めてくれる関数として八百屋で識別問題の際にシグモイド曲線を使いました。 今回は隠れ層の活性化関数としてReLU(ランプ関数)を使用します。 f:id:Yaju3D:20160605005559p:plain
プラスならどこでも一定の勾配になります。中間層で0未満の値を0にするいうことでモデルの精度を上げる目的。

qiita.com なんでシグモイド曲線ではなく、ReLU使っているのか分かるかも知れません。

誤差関数と最適化手法を記述

tf.reduce_mean()は、平均を計算する関数。

# We'll train our model by minimizing a cost function.
# 誤差関数(loss)
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(py_x, Y))
# 最適化手段(最急降下法)
train_op = tf.train.GradientDescentOptimizer(0.05).minimize(cost)
  • 誤差関数を変更
    多クラス識別(分類)問題
    →多クラス用クロスエントロピー(softmax_cross_entropy_with_logits)

モデルの評価

tf.argmaxは、いくつかの軸に沿ったテンソルで最大値となるインデックスを一つ返す。

# And we'll make predictions by choosing the largest output.
# 1に一番近いインデックス(予測)が正解とあっているか検証
predict_op = tf.argmax(py_x, 1)

fizz-buzz値の出力

predict_opのインデックスからfizz-buzz値を出力する。

# Finally, we need a way to turn a prediction (and an original number)
# into a fizz buzz output
def fizz_buzz(i, prediction):
    return [str(i), "fizz", "buzz", "fizzbuzz"][prediction]

セッションを準備し,変数を初期化

一般的な初期化処理

BATCH_SIZE = 128

# Launch the graph in a session
with tf.Session() as sess:
    tf.initialize_all_variables().run()

訓練開始

10000回繰り返す。 トレーニングデータは順番に並んでいるが、それでは面白くないのでランダムに並び替えている。
np.random.permutationは、配列をランダムに並び替える(多次元配列のときは、第1軸の方向だけでランダム入れ替え)

ミニバッチのバッチサイズ(BATCH_SIZE)を「128」としています。
利用した学習データに対して更新を終えるのが、1エポックというサイクルになります。通常は、このエポックを何回か繰り返して学習していきます。 ただ、単純に繰り返しているとあまりよろしくないので、エポックの度に学習データをシャッフルしたり、ミニバッチの場合はミニバッチの取得位置をずらしたりランダムにサンプリングしたりします。
len(trX)は、1024-101=923なので、startの値は(0,128,256,384,512,640,768,896)となります。

    for epoch in range(10000):
        # Shuffle the data before each training iteration.
        p = np.random.permutation(range(len(trX)))
        trX, trY = trX[p], trY[p]

        # Train in batches of 128 inputs.
        for start in range(0, len(trX), BATCH_SIZE):
            end = start + BATCH_SIZE
            sess.run(train_op, feed_dict={X: trX[start:end], Y: trY[start:end]})

学習データの出力

np.meanは、平均を計算する関数、sess.runには実行結果の値が入ります。
np.mean(1==1)は「1.0」、np.mean(1==0)は「0.0」となります。

        # And print the current accuracy on the training data.
        if epoch % 100 == 0:
            print(epoch, np.mean(np.argmax(trY, axis=1) ==
                             sess.run(predict_op, feed_dict={X: trX, Y: trY})))

※Jupyter Notebook上で動かす際にprintを100単位としました。

予測出力

101から1023までのデータで学習した結果から、1から100までの答えの予測を出力する。

np.transposeは、転置行列にします。
numpy.vectorizeは配列を引数に取って配列として返す関数を作るもの。

    # And now for some fizz buzz
    numbers = np.arange(1, 101)
    teX = np.transpose(binary_encode(numbers, NUM_DIGITS))
    teY = sess.run(predict_op, feed_dict={X: teX})
    output = np.vectorize(fizz_buzz)(numbers, teY)

    print(output)

np.transposeのサンプル

numbers = np.arange(1, 4)
print(np.transpose(binary_encode(numbers, NUM_DIGITS)))

[[1 0 0 0 0 0 0 0 0 0]
 [0 1 0 0 0 0 0 0 0 0]
 [1 1 0 0 0 0 0 0 0 0]]

最後に

処理を分解してみて何となくやっていることは分かりましたが、まだまだしっくりとはいっていません。
基礎的な理解がまだ足りていないので、分かったら記事を修正していきます。
ディープラーニング入門としては、Fizz-Buzz問題はいいと思います。

人工知能ハンズオンの資料公開

はじめに

静岡Developers勉強会では、今年の勉強会のテーマとして「人工知能ハンズオン」を2016/4/23に開催しました。
勉強会が行われた静岡市産学交流センター 小会議室2には、10人+2人(私と友人)=12人名が集まりました。あと、懇親会は5人で行きましたことをご報告しておきます。
人工知能というタイトルですがサブタイトルは「TensorFlowコトハジメ」です。まーバズワードにしないと東京と違って集客力がないのです。静岡って地域は新幹線の駅が6つあるくらい広すぎてしまって逆に人が集まらないんすよね。
connpass.com

当日は自分のPCとプロジェクターが相性があまりよくなくて、スライドがフルスクリーンにならないのでマウスの操作等に手間取ってしまいました。
また、準備していた際にはプロジェクターが映っていたので他の準備をしていたら映らなくなっていて、PCを再起動するはめになり予定開始時刻を10分程遅らせてしまったのは運営的に申し訳なかったです。
フルスクリーン関しては、今にして思えばPC側の解像度を下げれば良かったのかも知れません。

Windows 32bit PCの方用にとクラウド(AWS EC2 Docker版)を作成しておいたのですが、よく考えたらパブリックIPでWebなので他の人にも使えることに気がついて、TensorFlow環境を作成出来なかった人など数人の方にはクラウド版を使ってもらいました。

人工知能ハンズオンの資料自体は勉強会当日に既に公開していたのですが、今回修正した上で再度アップロードし直しましたのでブログで告知という段階となりました。

セッション内容

13:00 会場準備/受付
13:25 開催・挨拶
13:30 人工知能の概要
13:50 TensorFlowコトハジメ 概要
14:30 TensorFlowコトハジメ フィボナッチ数列
15:00 TensorFlowコトハジメ 八百屋で勾配降下法八百屋で識別問題
16:00 TensorFlowコトハジメ 手書き文字認識(MNIST)による多クラス識別問題
(未定) TensorFlowコトハジメ Automatic Colorization(白黒画像に彩色)
17:15 片付け
17:30 終了

プロジェクターの問題で開始自体は10分遅れの13:35あたりになってしまったんですが、人工知能の概要の無駄な説明を省いて時間的には取り戻しました。
TensorFlow関連の説明で淡々と進んでしまったのと、「Automatic Colorization(白黒画像の自動彩色)」については事前に試すことが出来なかったため、紹介程度で済ませてしまったので、予定より1時間以上早く終わってしまいました。
ここから先は、自由時間ということで途中退席も可能としました。LTとかする方もいなかったので仕方なく残りの1時間は人工知能関連の動画を流してました。
確かに説明を淡々としすぎてしまったんですが、私自身も口下手なのと細かく説明できるほどのまだ理解が出来てないのでどうしようもなかったですよね。あと、フィボナッチとフィナボッチって間違えて覚えてしまっていて、ツッコミ受けてその場で記事を修正したりしました。

「Automatic Colorization(白黒画像の自動彩色)」については、GW期間とそれ以降に実際に試すことが出来たので記事を書きました。 yaju3d.hatenablog.jp

スライド資料

www.slideshare.net qiita.com

動画について

下記3つの動画を流しました。

www.youtube.com

https://www.youtube.com/watch?v=1zO2Nu__JWgwww.youtube.com

www.youtube.com

最後の動画は、下記ブログを書いている方で理解するには数学的背景は大切ですよね。
enakai00.hatenablog.com

動画の追加(2016/06/27)
www.youtube.com

www.youtube.com

はじまりは半分

韓国のことわざに「はじまりは半分」というのがあります。
「一度何かを始めたらそれは半分成し遂げたと同じことだ」という意味です。

今回の勉強会でしかTensorFlowをやることは無いって方もいるでしょう。でも、きっかけは出来たと思います。
ひとりでも多くの方に目覚めるきっかけが出来たら、勉強会をやったかいがあるってものです。

数学モデルの作成

人工知能をちゃんと出来るようになるには、数学モデルを作れるようにならないと駄目ですね。
TensorFlowのプログラム自体は難しくないのですが、やりたいことをTensorFlowを使ってプログラムを組みたいと思っても数学モデルが作れないと如何ともし難いです。でも、ネタ的なアニメキャラ等の分類器はMINSTの応用だったりするので、数学モデルはあまり関係なかったりしますけどね。

プログラマーは文系でも出来る仕事だったりするわけですが、そろそろ文系と理系の差が開く時代が到来してきているなと感じています。 プログラマのための数学勉強会とか人数が集まっていますもんね。

最後に

準備不足

昨年の静岡Developers勉強会で来年は「人工知能ハンズオン」をやってみたいなーという漠然とした構想はあったので本とWebサイトなど記事自体は集めていたのですが、今年に入って3月中旬くらいまで何もやる気は起きなくて、静岡Developers勉強会は年3回くらいはやらないとなーと思って、そろそろ動かねばならぬと、情報処理技術者試験とGW期間に入る間の4/23にやる宣言をしてしまったわけです。会議室も運良く取れた。
自分はお尻に火が着かないと駄目なので日程を決めたからにはと、そこからTensorFlowのインストール記事やTensorFlowのことを理解するための記事とか書いたりしたのですが、やはり準備期間が1ヶ月では遅かったです。スライド資料も前日~当日午前4時と出かける直前までで作成してたしね。

次回開催

次に開催する時は説明がちゃんと出来るくらい理解した上でやりたいですね。そうすると11月とかあたりですかね。
その間の8月くらいは、IoTとVRに興味があるのでそちらをやろうと今は考えています。

TensorFlowコトハジメ Automatic Colorization(白黒画像の自動彩色)

はじめに

静岡Developers勉強会では、今年の勉強会のテーマとして「人工知能ハンズオン」を2016/4/23に開催しました。
その際に「Automatic Colorization(白黒画像の自動彩色)」については、間に合わずに事前に試すことが出来なかった為、勉強会当日は紹介のみに留まってしまいました。
GW期間があって試すことができましたので、その顛末を記事を書いてみました。なお、実行する場合にはメモリ不足から解消しないと2度手間になりますので注意してください。

Automatic Colorizationとは

白黒写真や動画をディープ・ラーニングを用いて自動でカラーに変換するRyan Dahlさんによるプロジェクトです。ちなみにライアン・ダール(Ryan Dahl)さんは、Node.jsの創始者でもあります。 自動彩色については他にもバークレーのコンピュータ科学者 Richard Zhangさん他、幾つかのプロジェクトがあります。

日本での取り組み

早稲田大学にて、飯塚 里志さん、シモセラ エドガーさん、石川博さんが白黒写真の自動色付けを行っています。
参照: ディープネットワークを用いた大域特徴と局所特徴の学習による白黒写真の自動色付け-早稲田大学

eiji_kさんのTwitterによれば

試した方がいます。プログラム言語はLuaを使用しているようです。
参照: Automatic Colorization of Grayscale Images を試すメモ

トレーニング有り版

下記サイトを公開しているPavel Gonchar氏のcolornetでは、白黒写真の自動色付けのトレーニング用も含まれています。
GitHub - pavelgonchar/colornet: Neural Network to colorize grayscale images

これを使ったeiji_kさんのTwitterによれば

導入方法

VirtualBox内Ubuntu 14.04.2 LTSのPython数値計算環境「Anaconda」にて試したところ、「Segmentation fault (コアダンプ)」エラーとなって動作しなかった為、Dockerを使用することにしました。Dockerを使うことで少ないメモリで実行できるようです。
今回のDockerイメージは、TensorFlow公式サイト版で説明していきます。 yaju3d.hatenablog.jp

2016/07/13追記 Segmentation faultの回避方法 qiita.com

環境

  • Windows 10 Home 64bit(Intel(R) Core(TM) i7-4700MQ CPU 2.40GHz メモリ 8.00GB)
  • DockerToolbox 1.10.3 → 1.11.1
  • VirtualBox 5.0.16 → 5.0.20

Dockerイメージ(TensorFlow公式サイト版)

TensorflowのサイトにあるDockerイメージにはscikit-imageがデフォルトでインストールされていないため、Jupyter NotebookのTerminalにて下記コマンドでインストールする必要があります。インストールには10分程かかります。

pip install scikit-image
 ︙
Successfully installed dask-0.9.0 networkx-1.11 scikit-image-0.12.3 toolz-0.7.4   

※Docker stop等でコンテナが破棄されると初期化されるため、毎回インストールする必要があります。

学習済みモデルファイルの取得

学習済みモデルのファイルが「colorize-20160110.tgz.torrent 492M」というトレントファイル(.torrent)として配布されています。
"BitTorrent(ビットトレント)"とは、大容量のファイルを高速に配信する為のP2Pソフトウェアです。
ダウンロードするには専用のソフトが必要になり、私はBitComet 日本語公認サイトから辿った先の「BitComet 1.40」を使いました。

世の中にはトレントファイルではなくtgzファイルのまま公開している方がいるので、そちらからダウンロード(colorize-20160108.tgz)するのが楽かと思います。
※トレント版が20160110なので20160108と2日早いのが気になります。

解凍すると下記3つのファイルが展開されます。

  • colorize.tfmodel (学習済みモデル)
  • forward.py (自動彩色実行ファイル Python 2.7)
  • shark.jpg (サンプルの白黒のサメ画像 224x224)
フォルダ共有による方法(追記:2016/05/19)

DockerとWindowsのフォルダ共有方法 with tensorflowのサイトを参考にしたらフォルダ共有が出来ました。この方法ならDockerコンテナにコピーする必要がないのでいいですね。

colorize-20160110フォルダをC:\Users(ユーザー名)の配下にします。
ダウンロードフォルダのままでも問題なかったです。

$ docker run -d -m 4g -p 8888:8888 -p 6006:6006 -v /c/Users/(ユーザー名)/Downloads\colorize-20160110:/notebooks b.gcr.io/tensorflow/tensorflow

この後は「実行」まで読み飛ばしても構いません。試行段階の経緯も書いているため、参考程度にお読みください。

Dockerイメージ(mokemokechickenさん版)

mokemokechickenさんのDockerファイルにはscikit-imageが含まれており、その他にもGraphvizなどもあるので今後いろいろやりたい場合には便利です。

githubサイトから「Download ZIP」ボタンで「jupyter-tensorflow-master.zip」をダウンロードし、解凍してダウンロードフォルダに展開しました。

DockerファイルからDockerイメージを作成します。

$ docker build -t tensorenv /c/Users/(ユーザー名)/Downloads/jupyter-tensorflow-master

※VirtualBoxの設定にある共有フォルダは「c/Users」になっています。"c/"は英小文字なので注意してください。

qiita.com

以前は出来たのですが、直近(2016/05/15)では下記エラーが出ました。

E: Unable to locate package graphviz-dev
E: Unable to locate package graphviz
E: Package 'pkg-config' has no installation candidate
E: Unable to locate package libgdal-dev

DockerFileのRUN apt-getの直後に「update && apt-get」を付けるといいでしょう。

RUN apt-get install -y graphviz-dev graphviz pkg-config
RUN apt-get install -y libgdal-dev
↓
RUN apt-get update && apt-get install -y graphviz-dev graphviz pkg-config
RUN apt-get update && apt-get install -y libgdal-dev

後述するメモリ制限値の"-m"オプションを付けて実行します。"-d"オプションはコンテナをバックグラウンドで動かします。

$ docker run -d -m 4g -p 8888:8888 -p 6006:6006 tensorenv

学習済みモデルファイルのコンテナへのコピー

$ docker ps
#コンテナ名(NAME)を取得 例 loving_minsky

$ docker cp /c/Users/(ユーザー名)/Downloads/colorize-20160110/colorize.tfmodel loving_minsky:/home/jovyan/work

Docker内へのファイルアップロード

Jupyter NotebookにはUpload機能があるのですが、アップロードできるサイズが25MByteまでとなっています。そのため、colorize.tfmodelファイルは529MByteと巨大なため、別方法でアップロードする必要があります。 f:id:Yaju3D:20160514134901p:plain

docker cpコマンドによる方法

docker run している場合、Control-Cで停止してください。

$ docker cp [コピー元ファイル] [CONTAINER ID or NAME]:[コピー先ファイル]

今回、colorize-20160110はダウンロードフォルダに展開しました。
※VirtualBoxの設定にある共有フォルダは「c/Users」になっています。"c/"は英小文字なので注意してください。
※ホストからコンテナへのコピーにdocker execコマンドを使った方法があるのですが、Docker 1.8からホストからコンテナへのコピーも docker cp コマンドでサポートされたため、修正しました。

$ docker ps
#コンテナ名(NAME)を取得 例 cranky_murdock

$ docker cp /c/Users/(ユーザー名)/Downloads/colorize-20160110/colorize.tfmodel cranky_murdock:/notebooks

$ docker start cranky_murdock

qiita.com

curlコマンドによる方法

docker execコマンドを使わない方法として、Jupyter NotebookのTerminalにて下記コマンドを入力します。これによりnotebooksフォルダ配下にcolorize-20160108フォルダが出来ます。

# curl -SL https://s3.amazonaws.com/tinyclouds-storage/colorize-20160108.tgz | tar -xzC /notebooks                                                                     
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current                                                                               
                                 Dload  Upload   Total   Spent    Left  Speed                                                                                 
100  491M  100  491M    0     0  8714k      0  0:00:57  0:00:57 --:--:-- 9284k                                                                             

※この方法を使用した場合、ソースリストのファイル指定時に"colorize-20160108\colorize.tfmodel"としてフォルダを指定する必要があります。

実行

Jupyter Notebookにて下記ソースコードを実行します。 f:id:Yaju3D:20160514140332p:plain

import tensorflow as tf
import skimage.transform
from skimage.io import imsave, imread

def load_image(path):
    img = imread(path)
    # crop image from center
    short_edge = min(img.shape[:2])
    yy = int((img.shape[0] - short_edge) / 2)
    xx = int((img.shape[1] - short_edge) / 2)
    crop_img = img[yy : yy + short_edge, xx : xx + short_edge]
    # resize to 224, 224
    img = skimage.transform.resize(crop_img, (224, 224))
    # desaturate image
    return (img[:,:,0] + img[:,:,1] + img[:,:,2]) / 3.0

shark_gray = load_image("shark.jpg").reshape(1, 224, 224, 1)

with open("colorize.tfmodel", mode='rb') as f:
    fileContent = f.read()
graph_def = tf.GraphDef()
graph_def.ParseFromString(fileContent)
grayscale = tf.placeholder("float", [1, 224, 224, 1])
tf.import_graph_def(graph_def, input_map={ "grayscale": grayscale }, name='')

with tf.Session() as sess:
    inferred_rgb = sess.graph.get_tensor_by_name("inferred_rgb:0")
    inferred_batch = sess.run(inferred_rgb, feed_dict={ grayscale: shark_gray })
    imsave("shark-color.jpg", inferred_batch[0])
    print("saved shark-color.jpg")

メモリエラー

残念ながら実行すると「MemoryError」が出力されてしまいます。

/usr/local/lib/python2.7/dist-packages/google/protobuf/internal/python_message.pyc in SerializePartialToString(self)
   1072     out = BytesIO()
   1073     self._InternalSerialize(out.write)
-> 1074     return out.getvalue()
   1075   cls.SerializePartialToString = SerializePartialToString
   1076 

MemoryError: 

メモリを増やす

DockerToolboxで最初に作成されるdefaultマシンのメモリは1GByteとなっていますので、下記サイトを参考にVirtualBoxの設定にてdefaultマシンのメモリを増やします。
※ちなみにクラウドのAWS EC2の無料で使えるt2.microはメモリが1GByteとなっているので動作出来そうもありません。

qiita.com
defaultの電源オフの状態で設定ボタンをクリックする
f:id:Yaju3D:20160514135543p:plain
1024MBから4096MBにメモリを変更 f:id:Yaju3D:20160514135556p:plain

docker runの"-m"オプションを付けてメモリ制限値を指定します(※割り当てではない)。単位には b,k,m,g があります。

$ docker run -m 4g -p 8888:8888 -p 6006:6006 b.gcr.io/tensorflow/tensorflow

dockerコンテナが破棄されると初期化した状態になるため、再度scikit-imageのインストールと学習済みモデルをセットしてください。

Jupyter NotebookのTerminalにて「free」コマンドを入力するとメモリサイズが表示されます。

# free                                                                                                                                                        
             total       used       free     shared    buffers     cached                                                                                     
Mem:       4045896    1188836    2857060     167164      40364     953316                                                                                     
-/+ buffers/cache:     195156    3850740                                                                                                                      
Swap:      1939036          0    1939036   

再実行

先ずはサンプルのshark.jpgをやってみました。変換は15秒程度です。
f:id:Yaju3D:20160105021604j:plain f:id:Yaju3D:20160514140945j:plain

鉛筆画を試す

Twitterユーザーの古谷振一さん(@shtt4881)の鉛筆画が白黒写真のようにしか見えないと話題になっておりました。
参照:きれいな写真だなー……って絵だとぉ!? 鉛筆で描かれた芸能人たちが目を疑う精巧さと美しさ
むむ、白黒画像なら自動彩色してみようと試してみました(サイズ 224x224 2値画像に変換してから試す)。ちょっと微妙ですかね。 連続してやるとメモリ不足になるので、毎回Shutodownしてメモリを解消しました。
f:id:Yaju3D:20160407003537p:plain

f:id:Yaju3D:20160514160825j:plain f:id:Yaju3D:20160514160838j:plain
f:id:Yaju3D:20160514160854j:plain f:id:Yaju3D:20160514160914j:plain
f:id:Yaju3D:20160514160931j:plain f:id:Yaju3D:20160514160947j:plain
f:id:Yaju3D:20160514161005j:plain f:id:Yaju3D:20160514161015j:plain

坂本龍馬を試す

顔写真だけだとつまらないので、坂本龍馬で検索した画像を試してみました。背景の草や板に色が付くようになっています。
f:id:Yaju3D:20160514162118j:plain f:id:Yaju3D:20160514162106j:plain

動画の変換

下記サイトにて動画のカラー化を行っています。
前後にffmpegでの処理を行って、Automatic Colorizationをフォルダ内の画像に対してループしているとのこと。 http://www.shun.bz/20160127/1050814061.htmlwww.shun.bz

カラー化した画像ファイルをホストへコピー

docker cpコマンドでホストへファイルをコピーすることが出来ます。

$ docker ps
#コンテナ名(NAME)を取得 例 cranky_murdock

$ docker cp cranky_murdock:/notebooks/shark-color.jpg /c/Users/(ユーザー名)

TensorFlowコトハジメ 手書き文字認識(MNIST)による多クラス識別問題

はじめに

手書き文字認識(MNIST)による多クラス識別問題をやってみる。

前回に引き続きこの資料を基に理解していく。

MNISTとは

手書きの文字列を認識するもので、画像認識では定番と言えるテーマだ。 手書き文字の認識データは、機械学習の著名な研究者であるYann LeCun氏のwebsiteで公開しており、0-9のいずれかの数字が学習用、テスト用それぞれで60000枚と10000枚含まれている。
各数字画像の大きさは28×28ピクセルの単色画像で、RGBではなくGray-scaleの色空間となっている。
f:id:Yaju3D:20160422062644p:plain

多クラス識別問題

前回の八百屋の識別問題が、買えるか買えないかの2クラスの分類であったが、今回は手書き文字を0-9と多クラスの分類となる。

手書き文字認識

f:id:Yaju3D:20160422063603p:plain

機械学習の流れ

f:id:Yaju3D:20160422063746p:plain

one-hot ベクトル (one-of-K表現)

f:id:Yaju3D:20160422073005p:plain

案1: ロジスティック回帰を拡張

f:id:Yaju3D:20160422064743p:plain
f:id:Yaju3D:20160422064833p:plain
ソフトマックス関数と呼ばれており、シグモイド関数を多クラス問題に対応させた活性化関数である。
ソフトマックス関数を使うと出力層の各ユニットの和が1になります。つまり、出力値が各クラスである確率と見なせるようになります。

案2: さらに中間層を追加

f:id:Yaju3D:20160422064903p:plain

1. 予測式(モデル)を記述

TensorFlowによる実装

# 入力変数と出力変数のプレースホルダを生成
x = tf.placeholder(tf.float32, [None, 784])
y_ = tf.placeholder(tf.float32, [None, 10])
# モデルパラメータ(入力層:784ノード, 隠れ層:100ノード, 出力層:10ノード)
W1 = tf.Variable(tf.truncated_normal([784, 100]))
b1 = tf.Variable(tf.zeros([100]))
W2 = tf.Variable(tf.truncated_normal([100, 10]))
b2 = tf.Variable(tf.zeros([10]))
# モデル式
h = tf.sigmoid(tf.matmul(x, W1) + b1) # 入力層->隠れ層
u = tf.matmul(h, W2) + b2             # 隠れ層->出力層 (ロジット)
y = tf.nn.softmax(u)                  # 隠れ層->出力層 (ソフトマックス後)

f:id:Yaju3D:20160422071235p:plain

2. 誤差関数と最適化手法を記述

# 誤差関数(loss)
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(u, y_))
# 最適化手段(最急降下法)
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(loss)
# 正答率(学習には用いない)
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
acc = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
  • 誤差関数を変更
    多クラス識別(分類)問題
    →多クラス用クロスエントロピー(softmax_cross_entropy_with_logits)

3. 学習実行

# (2) バッチ型確率的勾配降下法でパラメータ更新
for i in range(10000):
    # 訓練データから100サンプルをランダムに取得
    batch_xs, batch_ys = mnist.train.next_batch(100)
    # 学習
    _, l = sess.run([train_step, loss], feed_dict={x: batch_xs, y_: batch_ys})
    if (i + 1) % 1000 == 0:
        print "step=%3d, loss=%.2f" % (i + 1, l)
  • 大規模なデータで学習する時の工夫
    各ステップで、100個の訓練データをランダムに取り出して学習 (確率的勾配降下法)
    →速く学習が進む

参考: 学習結果

f:id:Yaju3D:20160422071950p:plain

4. 予測

# (1) テスト用データを1000サンプル取得
new_x = mnist.test.images[0:1000]
new_y_ = mnist.test.labels[0:1000]

# (2) 予測と性能評価
accuracy, new_y = sess.run([acc, y], feed_dict={x:new_x , y_:new_y_ })
print "Accuracy (for test data): %6.2f%%" % accuracy
print "True Label:", np.argmax(new_y_[0:15,], 1)
print "Est Label:", np.argmax(new_y[0:15, ], 1)

ここはこれまでと同様

【実行結果例】

Accuracy(test data): 80.0% 
True Label: [7 2 1 0 4 1 4 9 5 9 0 6 9 0 1]
Est Label: [7 2 1 0 9 1 4 9 2 9 0 6 9 0 1]

最終ソースコード

# coding: utf-8
import numpy as np
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

# MNISTデータの取得
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

# 1. 学習したいモデルを記述する
# 入力変数と出力変数のプレースホルダを生成
x = tf.placeholder(tf.float32, [None, 784])
y_ = tf.placeholder(tf.float32, [None, 10])
# モデルパラメータ(入力層:784ノード, 隠れ層:100ノード, 出力層:10ノード)
W1 = tf.Variable(tf.truncated_normal([784, 100]))
b1 = tf.Variable(tf.zeros([100]))
W2 = tf.Variable(tf.truncated_normal([100, 10]))
b2 = tf.Variable(tf.zeros([10]))
# モデル式
h = tf.sigmoid(tf.matmul(x, W1) + b1) # 入力層->隠れ層
u = tf.matmul(h, W2) + b2             # 隠れ層->出力層 (ロジット)
y = tf.nn.softmax(u)                  # 隠れ層->出力層 (ソフトマックス後)

# 2. 学習やテストに必要な関数を定義する
# 誤差関数(loss)
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(u, y_))
# 最適化手段(最急降下法)
train_step = tf.train.GradientDescentOptimizer(0.01).minimize(loss)
# 正答率(学習には用いない)
correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
acc = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

# 3. 実際に学習処理を実行する
# (1) セッションを準備し,変数を初期化
sess = tf.Session()
init = tf.initialize_all_variables()
sess.run(init)

# (2) バッチ型確率的勾配降下法でパラメータ更新
for i in range(10000):
    # 訓練データから100サンプルをランダムに取得
    batch_xs, batch_ys = mnist.train.next_batch(100)
    # 学習
    _, l = sess.run([train_step, loss], feed_dict={x: batch_xs, y_: batch_ys})
    if (i + 1) % 1000 == 0:
        print("step=%3d, loss=%.2f" % (i + 1, l))

# 4. テスト用データに対して予測し,性能を確認
# (1) テスト用データを1000サンプル取得
new_x = mnist.test.images[0:1000]
new_y_ = mnist.test.labels[0:1000]

# (2) 予測と性能評価
accuracy, new_y = sess.run([acc, y], feed_dict={x:new_x , y_:new_y_ })
print("Accuracy (for test data): %6.2f%%" % accuracy)
print("True Label:", np.argmax(new_y_[0:15,], 1))
print("Est Label:", np.argmax(new_y[0:15, ], 1))

# 5. 後片付け
# セッションを閉じる
sess.close()

最終結果

Extracting MNIST_data/train-images-idx3-ubyte.gz
Extracting MNIST_data/train-labels-idx1-ubyte.gz
Extracting MNIST_data/t10k-images-idx3-ubyte.gz
Extracting MNIST_data/t10k-labels-idx1-ubyte.gz
step=1000, loss=2.03
step=2000, loss=1.43
step=3000, loss=0.90
step=4000, loss=0.84
step=5000, loss=1.04
step=6000, loss=0.76
step=7000, loss=0.63
step=8000, loss=0.66
step=9000, loss=0.48
step=10000, loss=0.77
Accuracy (for test data):   0.79%
True Label: [7 2 1 0 4 1 4 9 5 9 0 6 9 0 1]
Est Label: [7 2 1 0 4 1 4 4 6 7 0 6 9 0 1]

チュートリアル(MNISTビギナー編)

チュートリアルのMNIST For ML Beginners用の本来のソースリストは使わず、今回は、株式会社ブレインパッドの技術エントリーTensorFlowを遊び倒す! 1-1. MNIST For ML Beginnersのソースリストを動くように変更したものを使います。

チュートリアルは2015年11月時点の記事を参考にしても、ディレクトリ配置が変わってしまったようなので注意が必要です。
tensorflow/g3doc/tutorials/mnist/ → tensorflow/examples/tutorials/mnist/

#変更箇所
import input_data  → from tensorflow.examples.tutorials.mnist import input_data
tf.device("/gpu:1") → tf.device("/cpu:0")
printprint()

ソースリスト

# coding: utf-8
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data


def main():
    # mnistのダウンロード
    mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
    
    # 複数GPUがあるので指定
    with tf.device("/cpu:0"):
        # n * 784 の可変2階テンソル
        x = tf.placeholder("float", [None, 784])
        
        # 重み行列とバイアスの宣言
        W = tf.Variable(tf.zeros([784, 10]))
        b = tf.Variable(tf.zeros([10]))
        
        # ソフトマックス層を定義
        y = tf.nn.softmax(tf.matmul(x, W) + b)

        # 正解用2階テンソルを用意
        y_ = tf.placeholder("float", [None, 10])

        # 誤差関数の交差エントロピー誤差関数を用意
        cross_entropy = -tf.reduce_sum(y_*tf.log(y))

        # 学習方法を定義 0.01は学習率
        train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)

    # 変数の初期化 ここでGPUにメモリ確保か
    init = tf.initialize_all_variables()
    sess = tf.Session()
    sess.run(init)

    # 1に一番近いインデックス(予測)が正解とあっているか検証し、その平均
    correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))

    # 学習開始
    for i in range(1000):
        batch_xs, batch_ys = mnist.train.next_batch(100)
        sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})
        #print(sess.run(accuracy, feed_dict={x: batch_xs, y_: batch_ys}))


    print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels}))

if __name__ == "__main__":
    main()

実行結果

Successfully downloaded train-images-idx3-ubyte.gz 9912422 bytes.
Extracting MNIST_data/train-images-idx3-ubyte.gz
Successfully downloaded train-labels-idx1-ubyte.gz 28881 bytes.
Extracting MNIST_data/train-labels-idx1-ubyte.gz
Successfully downloaded t10k-images-idx3-ubyte.gz 1648877 bytes.
Extracting MNIST_data/t10k-images-idx3-ubyte.gz
Successfully downloaded t10k-labels-idx1-ubyte.gz 4542 bytes.
Extracting MNIST_data/t10k-labels-idx1-ubyte.gz
0.9139

参照

チュートリアル(MNISTエキスパート編)

チュートリアルのMNIST For ML Beginners用の本来のソースリストは使わず、株式会社ブレインパッドの技術エントリーTensorFlowを遊び倒す! 2-1. MNIST For Expertsのソースリストを動くように変更したものを使います。

#変更箇所
import input_data  → from tensorflow.examples.tutorials.mnist import input_data
tf.device("/gpu:1") → tf.device("/cpu:0")
strides=[1, 1, 1, 1], # 真ん中2つが縦横のストライド → コメント移動
strides=[1, 2, 2, 1], # 真ん中2つが縦横のストライド → コメント移動
printprint()

ソースリスト

# coding: utf-8
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

def weight_variable(shape):
    """適度にノイズを含んだ(対称性の除去と勾配ゼロ防止のため)重み行列作成関数
    """

    initial = tf.truncated_normal(shape, stddev=0.1)
    return tf.Variable(initial)

def bias_variable(shape):
    """バイアス行列作成関数
    """
    initial = tf.constant(0.1, shape=shape)
    return tf.Variable(initial)

def conv2d(x, W):
    """2次元畳み込み関数
       strides 真ん中2つが縦横のストライド
    """
    return tf.nn.conv2d(x,
                        W,
                        strides=[1, 1, 1, 1],
                        padding='SAME')

def max_pool_2x2(x):
    """2x2マックスプーリング関数
       strides 真ん中2つが縦横のストライド
    """
    return tf.nn.max_pool(x,
                          ksize=[1, 2, 2, 1],
                          strides=[1, 2, 2, 1],
                          padding='SAME')

def main():
    # mnistのダウンロード
    mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

    sess = tf.InteractiveSession()
    with tf.device("/cpu:0"):
        # データ用可変2階テンソルを用意
        x = tf.placeholder("float", shape=[None, 784])
        # 正解用可変2階テンソルを用意
        y_ = tf.placeholder("float", shape=[None, 10])

        # 画像をリシェイプ 第2引数は画像数(-1は元サイズを保存するように自動計算)、縦x横、チャネル
        x_image = tf.reshape(x, [-1, 28, 28, 1])

        ### 1層目 畳み込み層

        # 畳み込み層のフィルタ重み、引数はパッチサイズ縦、パッチサイズ横、入力チャネル数、出力チャネル数
        # 5x5フィルタで32チャネルを出力(入力は白黒画像なので1チャンネル)
        W_conv1 = weight_variable([5, 5, 1, 32])
        # 畳み込み層のバイアス
        b_conv1 = bias_variable([32])
        # 活性化関数ReLUでの畳み込み層を構築
        h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)

        ### 2層目 プーリング層

        # 2x2のマックスプーリング層を構築
        h_pool1 = max_pool_2x2(h_conv1)

        ### 3層目 畳み込み層

        # パッチサイズ縦、パッチサイズ横、入力チャネル、出力チャネル
        # 5x5フィルタで64チャネルを出力
        W_conv2 = weight_variable([5, 5, 32, 64])
        b_conv2 = bias_variable([64])
        h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)

        ### 4層目 プーリング層
        h_pool2 = max_pool_2x2(h_conv2)

        ### 5層目 全結合層

        # オリジナル画像が28x28で、今回畳み込みでpadding='SAME'を指定しているため
        # プーリングでのみ画像サイズが変わる。2x2プーリングで2x2でストライドも2x2なので
        # 縦横ともに各層で半減する。そのため、28 / 2 / 2 = 7が現在の画像サイズ

        # 全結合層にするために、1階テンソルに変形。画像サイズ縦と画像サイズ横とチャネル数の積の次元
        # 出力は1024(この辺は決めです) あとはSoftmax Regressionと同じ
        W_fc1 = weight_variable([7 * 7 * 64, 1024])
        b_fc1 = bias_variable([1024])

        h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * 64])
        h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)

        # ドロップアウトを指定
        keep_prob = tf.placeholder("float")
        h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

        ### 6層目 Softmax Regression層

        W_fc2 = weight_variable([1024, 10])
        b_fc2 = bias_variable([10])

        y_conv = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)

    # 評価系の関数を用意
    cross_entropy = -tf.reduce_sum(y_*tf.log(y_conv))
    train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
    correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
    sess.run(tf.initialize_all_variables())

    for i in range(20000):
        batch = mnist.train.next_batch(50)
        if i%100 == 0:
            train_accuracy = accuracy.eval(feed_dict={x:batch[0],
                                                      y_: batch[1],
                                                      keep_prob: 1.0})
            print("step %d, training accuracy %g"%(i, train_accuracy))
        train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})

    print("test accuracy %g"%accuracy.eval(feed_dict={
        x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))

if __name__ == "__main__":
    main()

実行結果 CPUのみとしたため、3時間程度かかりました。

自PCは「Intel Core i7-4700MQ 2.4GHz」です。
akiraakさんのブログには「Intel Core i7-6700K 4GHz」で30分、「NVIDIA TITAN X」のGPU実行では1分30秒に短縮されますとのこと。速いは正義!

Extracting MNIST_data/train-images-idx3-ubyte.gz
Extracting MNIST_data/train-labels-idx1-ubyte.gz
Extracting MNIST_data/t10k-images-idx3-ubyte.gz
Extracting MNIST_data/t10k-labels-idx1-ubyte.gz
step 0, training accuracy 0.1
step 100, training accuracy 0.78
step 200, training accuracy 0.94
step 300, training accuracy 0.88

(中略)

step 19800, training accuracy 1
step 19900, training accuracy 1
test accuracy 0.9926

実行前の注意点(2016/05/28追記)

Docker上で実行した場合、"The Kernel appears to have died."という表示とともに計算が停止したとのコメントを頂きました。
TensorFlowコトハジメ Automatic Colorization(白黒画像の自動彩色)の記事内のメモリ不足を参考に、VirtualBoxのdefaultを4GByteに、docker runに「-m 4g」のオプションを付けて実行してください。
実行後にfreeコマンドでメモリ状況を参照したところ使用メモリが「1138420」となっていました。defaultマシンの初期メモリは1GByteなのでメモリが足りなかったのでしょう。

             total       used       free     shared    buffers     cached                                                                                     
Mem:       4045692    1138420    2907272      91824      10164     108692                                                                                     
-/+ buffers/cache:    1019564    3026128                                                                                                                      
Swap:      1946364     244772    1701592 

参照

TensorFlowコトハジメ 八百屋で識別問題

はじめに

前回、八百屋で勾配降下法を実行したので、次の識別問題をやってみる。 yaju3d.hatenablog.jp

前回に引き続きこの資料を基に理解していく。

識別問題とは

入力データから判断して分類させる問題。今回の例では買えるのか買えないのかを判断させる。
識別分類器としてロジスティック回帰を使う。

ロジスティック回帰とは

ロジスティック回帰は、発生確率を予測する手法です。
基本的な考え方は線形回帰分析と同じなのですが、予測結果が 0 から 1 の間を取るように数式やその前提に改良が加えられています。 今回のように購入有無(買える or 買えない)の2値しかとりえない値を従属変数の実績値として用い、説明変数を用いてその発生確率を説明するという構造になっています。

例題2

たかしくんは八百屋へ財布を預かってお使いに行きました。
しかし、たかしくんはお金を 数えられません。

気まぐれおやじ曰く、
リンゴ2個+ミカン3個、リンゴ0個+ミカン16個 なら買えるが、リンゴ3個+ミカン1個、 リンゴ2個+ミカン8個は買えないとのこと。

リンゴ1個+ミカン11個は買えますか? → 識別問題 f:id:Yaju3D:20160421011949p:plain

式で表そうとしてみる…
f:id:Yaju3D:20160421012015p:plain f:id:Yaju3D:20160421012037p:plain

シグモイド曲線

f:id:Yaju3D:20160421012100p:plain
※シグモイド曲線とは、入力した値を0から1の間に収めてくれる関数の1つ
 多くの自然界に存在する事柄は、このようなS字曲線を取る。
 生物の神経細胞、細胞の生存率曲線などなど

予測式(モデル)が作れた!
f:id:Yaju3D:20160421012204p:plain

ロジスティック回帰
f:id:Yaju3D:20160421012219p:plain

予測式(モデル)を記述

# 1. 予測式(モデル)を記述する
# 入力変数と出力変数のプレースホルダを生成
x = tf.placeholder(tf.float32, shape=(None, 2), name="x")
y_ = tf.placeholder(tf.float32, shape=(None, 1), name="y")
# モデルパラメータ
a = tf.Variable(-10 * tf.ones((2, 1)), name="a")
b = tf.Variable(200., name="b")
# モデル式
u = tf.matmul(x, a) + b
y = tf.sigmoid(u)

補足説明
tf.onesは、要素が全て「1」である1行列をセット 参照:TensorflowのAPIについて

今回の予測式に合わせてモデルとパラメーターを修正
f:id:Yaju3D:20160421012251p:plain

誤差関数と最適化手法を記述

# 2. 学習に必要な関数を定義する
# 誤差関数(loss)
loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=u, labels=y_))
# 最適化手段を選ぶ(最急降下法)
train_step = tf.train.GradientDescentOptimizer(0.1).minimize(loss)

・誤差関数を変更
 識別(分類)問題→クロスエントロピー
f:id:Yaju3D:20160421012315p:plain

訓練データを作成(or読込)

# 3. 実際に学習処理を実行する
# (1) 訓練データを生成する
train_x = np.array([[2., 3.], [0., 16.], [3., 1.], [2., 8.]])
train_y = np.array([1., 1., 0., 0.]).reshape(4, 1)
print("x=", train_x)
print("y=", train_y)

学習実行

# (2) セッションを準備し,変数を初期化
sess = tf.Session()
init = tf.global_variables_initializer()
sess.run(init)

# (3) 最急勾配法でパラメータ更新 (100回更新する)
for i in range(1000):
    _, l, a_ = sess.run([train_step, loss, a], feed_dict={x: train_x, y_: train_y})
    if (i + 1) % 100 == 0:
        print("step=%3d, a1=%6.2f, a2=%6.2f, loss=%.2f" % (i + 1, a_[0], a_[1], l))
        
# (4) 学習結果を出力
est_a, est_b = sess.run([a, b], feed_dict={x: train_x, y_: train_y})
print("Estimated: a1=%6.2f, a2=%6.2f, b=%6.2f" % (est_a[0], est_a[1], est_b))   

線形回帰とほぼ同じ
【変更箇所】

  • パラメータbの出力を追加
  • 更新回数を100回→1000回に
    ※更新回数は、対象問題やデータ、初期値、モデルなどでまちまちです。

学習結果

f:id:Yaju3D:20160421012400p:plain

予測結果

# 4. 新しいデータに対して予測する
# (1) 新しいデータを用意
new_x = np.array([1., 11.]).reshape(1, 2)

# (2) 学習結果をつかって,予測実施
new_y = sess.run(y, feed_dict={x: new_x})
print(new_y)

0.95848435 ≒ 0.96 f:id:Yaju3D:20160424232047p:plain

最終ソースコード

# 必要なモジュールを読み込む
# coding: utf-8
import numpy as np
import tensorflow as tf

# TensorFlow でロジスティック回帰する

# 1. 学習したいモデルを記述する
# 入力変数と出力変数のプレースホルダを生成
x = tf.placeholder(tf.float32, shape=(None, 2), name="x")
y_ = tf.placeholder(tf.float32, shape=(None, 1), name="y")
# モデルパラメータ
a = tf.Variable(-10 * tf.ones((2, 1)), name="a")
b = tf.Variable(200., name="b")
# モデル式
u = tf.matmul(x, a) + b
y = tf.sigmoid(u)

# 2. 学習やテストに必要な関数を定義する
# 誤差関数(loss)
loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=u, labels=y_))
# 最適化手段(最急降下法)
train_step = tf.train.GradientDescentOptimizer(0.1).minimize(loss)


# 3. 実際に学習処理を実行する
# (1) 訓練データを生成する
train_x = np.array([[2., 3.], [0., 16.], [3., 1.], [2., 8.]])
train_y = np.array([1., 1., 0., 0.]).reshape(4, 1)
print("x=", train_x)
print("y=", train_y)

# (2) セッションを準備し,変数を初期化
sess = tf.Session()
init = tf.global_variables_initializer()
sess.run(init)

# (3) 最急勾配法でパラメータ更新 (1000回更新する) 
for i in range(1000): 
    _, l, a_, b_ = sess.run([train_step, loss, a, b], feed_dict={x: train_x, y_: train_y})
    if (i + 1) % 100 == 0:
        print("step=%3d, a1=%6.2f, a2=%6.2f, b=%6.2f, loss=%.2f" % (i + 1, a_[0], a_[1], b_, l))

# (4) 学習結果を出力
est_a, est_b = sess.run([a, b], feed_dict={x: train_x, y_: train_y})
print("Estimated: a1=%6.2f, a2=%6.2f, b=%6.2f" % (est_a[0], est_a[1], est_b))

# 4. 新しいデータに対して予測する
# (1) 新しいデータを用意
new_x = np.array([1., 11.]).reshape(1, 2)

# (2) 学習結果をつかって,予測実施
new_y = sess.run(y, feed_dict={x: new_x})
print(new_y)

# 5. 後片付け
# セッションを閉じる
sess.close()

実行結果

0.95848435 ≒ 0.96 リンゴ 59.68 ≒ 60、ミカン 11.40 ≒ 11 → σ(185 - (60 x 1 + 11 x 11)) = 0.96(買えそう)

x= [[  2.   3.]
 [  0.  16.]
 [  3.   1.]
 [  2.   8.]]
y= [[ 1.]
 [ 1.]
 [ 0.]
 [ 0.]]
step=100, a1=-22.50, a2=-12.28, b=196.26, loss=42.75
step=200, a1=-35.00, a2=-12.06, b=192.68, loss=25.84
step=300, a1=-47.36, a2=-11.78, b=189.14, loss=9.24
step=400, a1=-55.13, a2=-11.51, b=186.75, loss=2.54
step=500, a1=-58.92, a2=-11.29, b=185.58, loss=0.02
step=600, a1=-59.26, a2=-11.23, b=185.47, loss=0.01
step=700, a1=-59.43, a2=-11.19, b=185.43, loss=0.00
step=800, a1=-59.53, a2=-11.17, b=185.39, loss=0.00
step=900, a1=-59.62, a2=-11.15, b=185.37, loss=0.00
step=1000, a1=-59.68, a2=-11.14, b=185.35, loss=0.00
Estimated: a1=-59.68, a2=-11.14, b=185.35
[[ 0.95848435]]

TensorFlowコトハジメ 八百屋で勾配降下法

はじめに

予測モデルを推測する分かりやすいサンプルを見つけたので、この資料を基に理解していく。

機会学習勉強会 (2016.2.27)のつぶやき

  • 実績データからパラメータを推定。パラメータを使って、結果を予測
  • パラメータ特定できない場合、合計金額の誤差が小さくなるような単価を採用する
  • 全ての組み合わせを検証するのではなく、少ない計算で最短距離で行う
  • 学習:予測式、誤差の計算、誤差の最小とする組み合わせ
  • 先程の例を線形回帰(線形重回帰)の式にする
  • 誤差を決める。誤差関数と最適化手法を記述
  • 学習率が大きすぎても失敗する。

勾配降下法とは

誤差の最小値を求める手法。
パラメータの特定できない場合、誤差が小さくなるような値を採用する。
イメージ例
f:id:Yaju3D:20160419021022p:plain

例題1

たかしくんは八百屋へお使いに行きました。
リンゴ1個とミカン3個を買うと190円,リンゴ3個とミカン1個を買うと330円するようです。
リンゴ2個とミカン4個を買うといくらになるでしょうか? f:id:Yaju3D:20160417225841p:plain f:id:Yaju3D:20160417225852p:plain
このくらいの問題なら、暗算で求めることが出来ます。
f:id:Yaju3D:20160417230109p:plain f:id:Yaju3D:20160417230118p:plain
では、次はどうでしょうか?
f:id:Yaju3D:20160417230234p:plain f:id:Yaju3D:20160417230455p:plain
f:id:Yaju3D:20160417230639p:plain
条件が当てはまる最小の値をひたすら繰り返し計算する。
f:id:Yaju3D:20160417230656p:plain f:id:Yaju3D:20160417230833p:plain
少ない計算で最小値を見つけたい。
f:id:Yaju3D:20160417230848p:plain f:id:Yaju3D:20171029162934p:plain

TensorFlowで同じように書いていく

f:id:Yaju3D:20160417231609p:plain

TensorFlow のほか高速な配列演算パッケージであるNumPyやプロットしたい場合はmatplotlib.pyplotなど使用するライブラリをロードする。

# 必要なモジュールを読み込む
import numpy as np
import tensorflow as tf

予測式(モデル)を記述

# 1. 予測式(モデル)を記述する
# 入力変数と出力変数のプレースホルダを生成
x = tf.placeholder(tf.float32, shape=(None, 2), name="x")
y_ = tf.placeholder(tf.float32, shape=(None, 1), name="y")
# モデルパラメータ
a = tf.Variable(tf.zeros((2, 1)), name="a")
# モデル式
y = tf.matmul(x, a)
  • 値を入れる"箱"を作成
    入出力 → placeholder,パラメータ → Variable
    (注意)shape = (訓練データの数, 次元数)
    ※"訓練データの数"は None にすると可変長扱い

補足説明
プレースホルダ(placeholder)とは、実際の内容を後から挿入するために仮に確保した場所のこと
tf.placeholderでセットした変数 x や y_ は特定の値ではなくplaceholderと呼ばれ
TensorFlowに計算を実行しろと頼むときに入力する値です。

tf.zerosは、要素が全て「0」である0行列をセット 参照:TensorflowのAPIについて

shape=(行, 列)は、各次元の要素数 (行数, 列数)。
shape=(None, 2)は、2列で後でサイズが決まるような場合に使用する。
f:id:Yaju3D:20160619200824p:plain
shape=(None, 1)は、1列で後でサイズが決まるような場合に使用する。
f:id:Yaju3D:20160619200836p:plain

tf.matmulは行列の掛け算(ここでは定義で実際に使うのは、loss = tf.reduce_mean(tf.square(y_ - y)) )
 { \displaystyle \binom{190}{330} = \binom{1\, \, 3}{3\, \, 1}\binom{a1}{a2}}
行列式を使った連立方程式の解き方
ke!san 連立方程式

  • 予測式(モデル)を記述
    f:id:Yaju3D:20160419002003p:plain

.誤差関数と最適化手法を記述

# 2. 学習に必要な関数を定義する
# 誤差関数(loss)
loss = tf.reduce_mean(tf.square(y_ - y))
# 最適化手段を選ぶ(最急降下法)
train_step = tf.train.GradientDescentOptimizer(0.02).minimize(loss)

mean も average も「平均値」ですが、mean のほうが学術用語で、average のほうが普通語です。
参照:meanとaverageの違いは?

  • 誤差関数を記述
    ふつうの回帰問題 → 平均二乗誤差  {\displaystyle \frac{1}{N}\sum_{i}^{n}{{(y_{i} - \tilde{y}_{i})^{2}}}}

yaju3d.hatenablog.jp

  • 最適化手法を選ぶ
    ・入門 → 最急降下法(勾配降下法) GradientDescent~
     ※どれを選ぶかで学習(最適化)の速さが変わる
    ・引数に適度な"学習率"を指定する
     ※大きすぎると学習失敗(発散)、小さすぎると学習が遅い

訓練データを作成(or読込)

# 3. 実際に学習処理を実行する
# (1) 訓練データを生成する
train_x = np.array([[1., 3.], [3., 1.], [5., 7.]])
train_y = np.array([190., 330., 660.]).reshape(3, 1)
print("x=", train_x)
print("y=", train_y)

f:id:Yaju3D:20160419005112p:plain
※予測式で定義した形状(shape)に合わせること
※実用場面では,外部データを(ファイルやSQLなどから) 読みとって2次元配列に整形する。

# (2) セッションを準備し,変数を初期化
sess = tf.Session()
init = tf.initialize_all_variables()
sess.run(init)

# (3) 最急勾配法でパラメータ更新 (100回更新する)
for i in range(100):
    _, l, a_ = sess.run([train_step, loss, a], feed_dict={x: train_x, y_: train_y})
    if (i + 1) % 10 == 0:
        print("step=%3d, a1=%6.2f, a2=%6.2f, loss=%.2f" % (i + 1, a_[0], a_[1], l))


# (4) 学習結果を出力
est_a = sess.run(a, feed_dict={x: train_x, y_: train_y})
print("Estimated: a1=%6.2f, a2=%6.2f" % (est_a[0], est_a[1]))

sess.run ( [出力, ...], feed_dict={入力リスト} )
sess.run を呼び出すことで、"出力"に指定した データフローグラフが計算される
※学習を回すには、先ほど作成した最適化手段(train_step)を出力値に指定して sess.run を呼び出す。

参考: 実行結果

f:id:Yaju3D:20160419010123p:plain

f:id:Yaju3D:20160419010158p:plain

# 4. 新しいデータに対して予測する
# (1) 新しいデータを用意
new_x = np.array([2., 4.]).reshape(1, 2)

# (2) 学習結果をつかって,予測実施
new_y = sess.run(y, feed_dict={x: new_x})
print(new_y)

予測でも sess.run を用いる
feed_dictには新しい入力値を指定することに留意。(当然ですが...)
f:id:Yaju3D:20160419010502p:plain

最終ソースコード

# coding: utf-8
# 必要なモジュールを読み込む
import numpy as np
import tensorflow as tf

# 1. 予測式(モデル)を記述する
# 入力変数と出力変数のプレースホルダを生成
x = tf.placeholder(tf.float32, shape=(None, 2), name="x")
y_ = tf.placeholder(tf.float32, shape=(None, 1), name="y")
# モデルパラメータ
a = tf.Variable(tf.zeros((2, 1)), name="a")
# モデル式
y = tf.matmul(x, a)

# 2. 学習に必要な関数を定義する
# 誤差関数(loss)
loss = tf.reduce_mean(tf.square(y_ - y))
# 最適化手段を選ぶ(最急降下法)
train_step = tf.train.GradientDescentOptimizer(0.02).minimize(loss)


# 3. 実際に学習処理を実行する
# (1) 訓練データを生成する
train_x = np.array([[1., 3.], [3., 1.], [5., 7.]])
train_y = np.array([190., 330., 660.]).reshape(3, 1)
print("x=", train_x)
print("y=", train_y)

# (2) セッションを準備し,変数を初期化
sess = tf.Session()
init = tf.initialize_all_variables()
sess.run(init)

# (3) 最急勾配法でパラメータ更新 (100回更新する)
for i in range(100):
    _, l, a_ = sess.run([train_step, loss, a], feed_dict={x: train_x, y_: train_y})
    if (i + 1) % 10 == 0:
        print("step=%3d, a1=%6.2f, a2=%6.2f, loss=%.2f" % (i + 1, a_[0], a_[1], l))


# (4) 学習結果を出力
est_a = sess.run(a, feed_dict={x: train_x, y_: train_y})
print("Estimated: a1=%6.2f, a2=%6.2f" % (est_a[0], est_a[1]))

# 4. 新しいデータに対して予測する
# (1) 新しいデータを用意
new_x = np.array([2., 4.]).reshape(1, 2)

# (2) 学習結果をつかって,予測実施
new_y = sess.run(y, feed_dict={x: new_x})
print(new_y)

# 5. 後片付け
# セッションを閉じる
sess.close()

実行結果

リンゴ 98.81 ≒ 99、ミカン 24.90 ≒ 25 → 99 x 2 + 25 x 4 ≒ 297  

x= [[ 1.  3.]
 [ 3.  1.]
 [ 5.  7.]]
y= [[ 190.]
 [ 330.]
 [ 660.]]
step= 10, a1= 70.35, a2= 46.23, loss=2189.06
step= 20, a1= 83.06, a2= 36.70, loss=771.90
step= 30, a1= 90.13, a2= 31.41, loss=334.34
step= 40, a1= 94.05, a2= 28.47, loss=199.24
step= 50, a1= 96.23, a2= 26.84, loss=157.52
step= 60, a1= 97.44, a2= 25.93, loss=144.64
step= 70, a1= 98.12, a2= 25.42, loss=140.67
step= 80, a1= 98.49, a2= 25.14, loss=139.44
step= 90, a1= 98.70, a2= 24.99, loss=139.06
step=100, a1= 98.81, a2= 24.90, loss=138.94
Estimated: a1= 98.81, a2= 24.90
[[ 297.22738647]]

スポンサーリンク