Python
機械学習
MachineLearning
TensorFlow

初めてのTensorFlow(改訂版)- 線形回帰とロジスティック回帰

More than 1 year has passed since last update.

以前書いた記事 初めてのTensorFlow - イントロダクションとしての線形回帰 は,TensorFlow が公開された直後のタイミングに執筆したこともあり,私の記事の中でも"いいね"をつけてもらえる記事となっている.ただ約一年半前の記事なので,TensorFlowのバージョンupを考慮し,記事を改訂したいと考えた.内容は,前記事を踏襲し,線形回帰とロジスティック回帰とする.

(プログラミング環境は,以下になります.2017/5/29時点.)

  • Python 3.5.2
  • TensorFlow 1.1.0
  • Numpy 1.11.3
  • Scikit-learn 0.18.1

Linear Regression(線形回帰)

前の版の「初めての...」では,"Theano" のTutorial,Newmu/Theano-Tutorials - GitHub を紹介し,これを TensorFlow に移植する形で話を進めている.
https://github.com/Newmu/Theano-Tutorials

先日,ほぼ同じ内容で,TensorFlowのTutorialにしたものをGitHubに見つけたので紹介したい.
https://github.com/nlintz/TensorFlow-Tutorials

TensorFlow-Tutorials

Introduction to deep learning based on Google's TensorFlow framework. These tutorials are direct ports of Newmu's Theano Tutorials.

短いtutorialコードが掲載されており,順を追って"TensorFlow"への理解を深めることができるようになっている.Pythonコードの他,jupyter notebookも掲載されている.「もう初心者じゃないし...」という方も,PCにリポジトリをダウンロード(clone)して,コーディング時に参照するといった,クックブック的な使い方もできるので参照をお勧めする.00_multiply.py (単純なかけ算)の次に 01_linear_regression.py (線形回帰), 02_logistic_regression.py (ロジスティック回帰)と続くので,本記事もこれに沿って説明していきたい.但し,コード転載ではつまらないので,内容は若干,カスタマイズした.

まず,線形回帰から.関係モジュールをimportした後,使用するデータを準備する.(オリジナルは切片bを持たないテスト用データでしたが,ここでは b (bias=3.)を含むようにしています.)

import tensorflow as tf
import numpy as np

trX = np.linspace(-1, 1, 101)
# オリジナルのコードではバイアス項を持たないが,ここではバイアス(=3)を含んだモデルとする
target_w = 2.
target_b = 3.
noise_gain = 0.33
trY = target_w * trX + target_b + np.random.randn(*trX.shape) * noise_gain

model() は回帰モデルとして使う関数である.(パラメータに傾きwと切片b を持つ線形関数.)

def model(X, w, b):
    # 線形予測子のモデル
    # オリジナルコードでは tf.muliply() を使っているが演算子 "*" でOK.
    # return tf.multiply(X, w) + b
    return X * w + b

次に必要な変数を用意する.

# TensorFlow placeholders
X = tf.placeholder(tf.float32)
Y = tf.placeholder(tf.float32)
# TensorFlow Variables
w = tf.Variable(0.0, name="weights")    # zero initialize
b = tf.Variable(0.0, name="bias")       # zero initialize

x, y は,独特の用語(terminology)である"プレースホルダー(placeholder)"で用意する.プレースホルダー(置き場所)なので,これを宣言した時点で実態はなく,この後のプログラム処理で実際の値がアサインされる.一方,普通のTensor変数である w, b は,ゼロを初期値とした.

それから重要な,Graphと呼ばれる変数の関係性を記述する.

y_model = model(X, w, b)
cost = tf.reduce_mean(tf.square(Y - y_model))

cost は,自分のモデルと実データの差異に対応するコスト関数である."tf.reduce_mean()" は平均を計算する関数である.よって上のコードは,MSE(=mean square error)を算出する.

次にパラメータ探索の計算のためのOptimizer指定とそのメソッドを定義する.

train_op = tf.train.GradientDescentOptimizer(0.01).minimize(cost)

ここでは勾配降下法のOptimizerを指定し,引数として学習率(Learning Rate) 0.01を与えている.

Optimizerについて

さて気になるOptimizerであるが,TensorFlowでは現在,次のもの(下の表は一部)がサポートされている.
http://www.tensorflow.org/api_docs/python/train.html#optimizers

Optimizer name Description (参考リンク)
GradientDescentOptimizer 勾配降下法 https://ja.wikipedia.org/wiki/%E7%A2%BA%E7%8E%87%E7%9A%84%E5%8B%BE%E9%85%8D%E9%99%8D%E4%B8%8B%E6%B3%95
AdagradOptimizer AdaGrad法 https://ja.wikipedia.org/wiki/%E7%A2%BA%E7%8E%87%E7%9A%84%E5%8B%BE%E9%85%8D%E9%99%8D%E4%B8%8B%E6%B3%95#AdaGrad
MomentumOptimizer モメンタム法 https://ja.wikipedia.org/wiki/%E7%A2%BA%E7%8E%87%E7%9A%84%E5%8B%BE%E9%85%8D%E9%99%8D%E4%B8%8B%E6%B3%95#.E3.83.A2.E3.83.A1.E3.83.B3.E3.82.BF.E3.83.A0.E6.B3.95
AdamOptimizer Adam法 https://ja.wikipedia.org/wiki/%E7%A2%BA%E7%8E%87%E7%9A%84%E5%8B%BE%E9%85%8D%E9%99%8D%E4%B8%8B%E6%B3%95#Adam
RMSPropOptimizer RMSProp法 https://ja.wikipedia.org/wiki/%E7%A2%BA%E7%8E%87%E7%9A%84%E5%8B%BE%E9%85%8D%E9%99%8D%E4%B8%8B%E6%B3%95#RMSProp

Optimizerにより,設定しなければならないパラメータが異なるが,今回は,基本となる GradientDescentOptimizerを使うこととする.(必要なパラメータ学習率は,上記の通り設定した.)

Linear Regression(線形回帰)(続き)

さて,ほぼ準備は整ったので,メインの計算部分を示す Session を開始する.

# Launch the graph in a session
with tf.Session() as sess:
    # you need to initialize variables (in this case just variable W)
    tf.global_variables_initializer().run()

    for i in range(100):
        for (x, y) in zip(trX, trY):
            sess.run(train_op, feed_dict={X: x, Y: y})

    final_w, final_b = sess.run([w, b])

Session開始直後に,変数(Variables)の初期化を行う.
(注.前の版で「Sessionを開始前に変数(Variables)の初期化を行わなければならない」と書きましたが,誤りです.前回のコードでは,Session前に,初期化処理(init op)を定義し,それをSession開始後,sess.run(init) で実行するという流れでした.今回は,Session直後に,初期化処理を定義し run() させています.)

「Variablesの初期化」について,一応,確認しておくと,

  • tf.Variable() の変数は,初期化が必要
  • tf.placeholder() の変数は,初期化不要.(あとで実体とアサインされる)
  • tf.constant の変数(定数)は,初期化不要. とのことである.

Sessoinを動かすためのプログラムの書き方にいくつか種類があるようだが,上記のように"with" 文で囲むことでSessionの区切りが明確になり,かつ"with"を抜けるところで,自動的に開かれたSessionはclose()されるので便利である.

一つTensorFlowにおけるTrainデータの与え方が難しいところである.データの与え方 (Data Feeding)は学習の進め方で処理が変わってくる部分であるが,上のリストの通り,"feed_dict" を使うことになるので覚えておきたい.

以上で,回帰パラメータが算出される.
(注.現バージョンの tensorflow では,環境によって cpu_feature_guard.cc が warning を出力しますが,無視して結構です.シェルの環境変数を使って warning を止めることはできます.)

$ python 01_linear_regression.py 

predicted model: y = [   2.045] * x + [   3.001]
target model   : y = [   2.000] * x + [   3.000]

predicted model の回帰パラメータが,target model の近似値になっていることが分かる.

以上まとめて,再度プログラムを掲載する.

import tensorflow as tf
import numpy as np

trX = np.linspace(-1, 1, 101)
# オリジナルのコードではバイアス項を持たないが,ここではバイアス(=3)を含んだモデルとする
target_w = 2.
target_b = 3.
noise_gain = 0.33
trY = target_w * trX + target_b + np.random.randn(*trX.shape) * noise_gain

def model(X, w, b):
    # 線形予測子のモデル
    # オリジナルコードでは tf.muliply() を使っているが演算子 "*" でOK.
    # return tf.multiply(X, w) + b
    return X * w + b

# TensorFlow placeholders
X = tf.placeholder(tf.float32)
Y = tf.placeholder(tf.float32)

# TensorFlow Variables
w = tf.Variable(0.0, name="weights")    # zero initialize
b = tf.Variable(0.0, name="bias")       # zero initialize

y_model = model(X, w, b)

# コスト関数,回帰モデルなのでSquare Errorを定義する
cost = tf.reduce_mean(tf.square(Y - y_model))
# 最適化演算子(オプティマイザ) をセット. 学習率 = 0.01
train_op = tf.train.GradientDescentOptimizer(0.01).minimize(cost)

# Launch the graph in a session
with tf.Session() as sess:
    # you need to initialize variables (in this case just variable W)
    tf.global_variables_initializer().run()

    for i in range(100):
        for (x, y) in zip(trX, trY):
            sess.run(train_op, feed_dict={X: x, Y: y})

    final_w, final_b = sess.run([w, b])

# (w, b) = (2, 3)の近似値になる.
print('predicted model: y = [{:>8.3f}] * x + [{:>8.3f}]'.format(
                                        final_w, final_b))
print('target model   : y = [{:>8.3f}] * x + [{:>8.3f}]'.format(
                                        target_w, target_b))

Logistic Regression (ロジスティック回帰)

オリジナル(https://github.com/nlintz/TensorFlow-Tutorials) の Logistic Regressionコードでは,MNISTを扱っているが,ここでは,scikit-learnに付属する,軽量手書き文字データセット"digits"を扱う.

まず,weights初期化のサポート関数と,モデルを定義.

# weights初期化のサポート関数
def init_weights(shape):
    return tf.Variable(tf.random_normal(shape, stddev=0.01))

# オリジナルコードでは,バイアスをmodelに入れていないが,本コードは入れる.
def model(X, w, b):
    return tf.matmul(X, w) + b

"digits" データを準備する.

# digits は,scikit-learn が用意する手書き文字データセット.pixel: 8 x 8
def load_data():
    digits = load_digits()
    digits_images = digits.data / 16.   # scaling to (0 .. 1)
    digits_target_ = []
    for i in range(len(digits.target)):
        target_one = np.zeros([10], dtype=np.float32)
        target_one[digits.target[i]] = 1.
        digits_target_.append(target_one)
    digits_target_onehot = np.asarray(digits_target_)
    return digits_images, digits_target_onehot

X, Y = load_data()

# train / test セットに分割する
trX, teX, trY, teY =  train_test_split(X, Y, test_size=0.2)

あとは,前項のLinear Regressionに倣って,Graph定義を行う.

# TensorFlow placeholders
X_ph = tf.placeholder(tf.float32, [None, 64])
Y_ph = tf.placeholder(tf.float32, [None, 10])

# オリジナルコードでは,バイアスをmodelに入れていないが,本コードでは入れる.
w = init_weights([64, 10])
b = tf.zeros([10])

py_x = model(X_ph, w, b)

# コスト関数,オプティマイザ,予測
cost = tf.nn.softmax_cross_entropy_with_logits(logits=py_x, labels=Y_ph)
train_op = tf.train.GradientDescentOptimizer(0.01).minimize(cost)
predict_op = tf.argmax(py_x, 1)

Linear RegressionではMSE(Mean Square Error)で定義していたコスト関数を,Logistic Regressionでは cross entropy (softmax cross entropy) に置き換えている.上リストの後半部にある "tf.nn.softmax_cross_entropy_with_logits()" は,多クラス分類における cross entorpy を計算する関数である.(この関数は,logits=, labels= のkey wordが必要)

変数(Variables)の初期化,Sessionの進め方は前のコードと同様である.再度,コードをまとめて掲載する.

import tensorflow as tf
import numpy as np
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split

# weights初期化のサポート関数
def init_weights(shape):
    return tf.Variable(tf.random_normal(shape, stddev=0.01))

# オリジナルコードでは,バイアスをmodelに入れていないが,本コードは入れる.
def model(X, w, b):
    return tf.matmul(X, w) + b

# digits は,scikit-learn が用意する手書き文字データセット.pixel: 8 x 8
def load_data():
    digits = load_digits()
    digits_images = digits.data / 16.   # scaling to (0 .. 1)
    digits_target_ = []
    for i in range(len(digits.target)):
        target_one = np.zeros([10], dtype=np.float32)
        target_one[digits.target[i]] = 1.
        digits_target_.append(target_one)
    digits_target_onehot = np.asarray(digits_target_)
    return digits_images, digits_target_onehot

X, Y = load_data()

# train / test セットに分割する
trX, teX, trY, teY =  train_test_split(X, Y, test_size=0.2)

# TensorFlow placeholders
X_ph = tf.placeholder(tf.float32, [None, 64])
Y_ph = tf.placeholder(tf.float32, [None, 10])

# オリジナルコードでは,バイアスをmodelに入れていないが,本コードでは入れる.
w = init_weights([64, 10])
b = tf.zeros([10])

py_x = model(X_ph, w, b)

# コスト関数,オプティマイザ,予測
cost = tf.nn.softmax_cross_entropy_with_logits(logits=py_x, labels=Y_ph)
train_op = tf.train.GradientDescentOptimizer(0.01).minimize(cost)
predict_op = tf.argmax(py_x, 1)

# TensorFlow session
with tf.Session() as sess:
    # you need to initialize all variables
    tf.global_variables_initializer().run()

    for i in range(100):
        for start, end in zip(range(0, len(trX), 128), range(128, len(trX)+1, 128)):
            train_fd = {X_ph: trX[start:end], Y_ph: trY[start:end]}
            sess.run(train_op, feed_dict=train_fd)
        # numpy レベルで正解率(accuracy)を算定し出力
        if i % 10 == 0:
            print('step {:>3d}: accuracy = {:>8.3f}'.format(
                i, np.mean(np.argmax(teY, axis=1) ==
                         sess.run(predict_op, feed_dict={X_ph: teX}))))

TensorFlowでは,(他のtoolも同様と思われるが)比較的プリミティブなAPIから,Keras APIを含む高レベルAPIまでさまざまな書き方ができる.意見が分かれるかも知れないが,初学者は初めに上記のようなプリミティブな使い方を学び,ある程度中身がわかってきたところで,高レベルAPIに移るのがいいのではないだろうか?

また,最近,興味本位で"PyTorch"の勉強を始めているが,TensorFlowの知識があっても,いろいろトラブルが多い.簡単な「線形回帰」「ロジスティック回帰」のようなTutorialコードは,どのライブラリであっても入門の勉強に役立つはずと考えている.

(さらなるTensorFlowの勉強には,繰り返しになりますが, https://github.com/nlintz/TensorFlow-Tutorials のチェックをどうぞ!)

参考文献 (web site)