クロの制作日記

クロの制作日記

田舎の大学生がUnityとか機械学習関連の制作物をひたすらアップします。ブログで紹介したコード一覧https://github.com/kuroshum/blog_code

初心者が頑張るTensorflow入門(実装解説編)

はじめに

前回の記事はこちら
kurora-shumpei.hatenablog.com


この記事では、前回の記事で解説した用語を元に、実際に、Tensorflowで簡単な線形回帰モデルを実装して、実装上に用いた関数などの説明を行います。

また、本記事はTensorflow1.xの解説で、Tensorflow2.0以降の解説ではないことをご了承ください。


時々前回の記事の図4がでてくるので、ここに貼っておきます。

f:id:kurora-shumpei:20191126151751p:plain
図4

私がよく参考にしている本はこちらです。


TensorFlow機械学習クックブック Pythonベースの活用レシピ60+ impress top gearシリーズ


詳解 ディープラーニング ~TensorFlow・Kerasによる時系列データ処理~

Tensorflowのインストール

Tensorflowのインストールは他のライブラリと同じようにpipでインストールすることができます。しかし、「pip install tensorflow」と入力すると、tensorflow2.0以降のバージョンがインストールされてしまいます。なので、バージョンを指定してインストールする必要があります。

こんな感じにすればおkです。

pip install tensorflow==1.15.0

私が後輩にこのコマンドを実行してもらったときに、何人かは色々エラーをはいて入らなかったので、その時の状況も一応書いておきます。

32bitのpythonが入っていた

64bitではなく32bitのpythonが入っているとtensorflowのパッケージをインストールすることができないようなので、64bit版を入れるようにしましょう。

CPUが古かった

割と古いCPU(今回の場合は古いcore i3だった)を使っていると、1.8.0くらいのバージョン以前しかインストールできないことがありました。そのときは、エラー文にこれ以前のバージョンをインストールしてくれと書いているので、それを確認しましょう。

rc版をインストールしてしまった

間違えて、1.15.0rc1を指定してしまって、rc版のtensorflowをインストールしてしまう場合もありました。これは、もう一回上のコマンドを実行すれば、1.15.0がインストールするはずです。




Tensorflowで線形回帰を実装

では、実際にTensorflowを使って線形回帰を行っていきます。データセットは「Boston house-prices」、通称ボストンデータセットを使用します。

ボストンデータセット

ボストンデータセットの詳細は以下のようになっています。

  • データ数 : 506個
  • カテゴリー数 : 14個(説明変数が13個、目的変数が1個)
  • 欠損値 :なし
変数 説明
CRIM 町ごとの一人当たりの犯罪率
ZN 宅地の比率が25,000平方フィートを超える敷地に区画されている。
INDUS 町当たりの非小売業エーカーの割合
CHAS チャーリーズ川ダミー変数(川の境界にある場合は1、それ以外の場合は0)
NOX 一酸化窒素濃度(1000万分の1)
RM 1住戸あたりの平均部屋数
AGE 1940年以前に建設された所有占有ユニットの年齢比率
DIS 5つのボストンの雇用センターまでの加重距離
RAD ラジアルハイウェイへのアクセス可能性の指標
TAX 10,000ドルあたりの税全額固定資産税率
PTRATIO 生徒教師の比率
B 町における黒人の割合
LSTAT 人口当たり地位が低い率
MEDV(目的変数) 1000ドルでの所有者居住住宅の中央値

実装

実際に線形回帰を実装したプログラムが以下のリポジトリになります。

リポジトリにスターを押してくれるとマジで喜びます。
github.com

Tensorflowでの実装箇所の説明

では、main関数を上から確認していき、Tensorflowで実装している箇所について説明していきます。

tf.placeholder

111行目くらいにtf.placeholderと記述されている箇所があるかと思います。これは図4で言う変数の型・形(shape)の定義に当たります。先ほど説明したように、データは実行するときに入力しますので、ここでは、どのようなデータを入力するかを定義するだけです。

x_t = tf.placeholder(tf.float32,[None,xDim])
y_t = tf.placeholder(tf.float32,[None,1])
どんな変数をplaceholderで定義するか

基本的にplaceholderで定義する変数は、データの中身が変化するものです。例えば、説明変数や目的変数です。これらのデータは学習とテストのときに別々のデータ(学習データとテストデータ)を入力することになりますので、中身まで定義しておくと後々面倒になりますのでplaceholderで定義しておきます。

Noneとは

変数のshapeを定義するときにNoneというワードを使っています。shapeを定義するときにNoneを使用すると、shapeを指定せずに変数を定義することができます。先ほど説明したように、placeholderには学習データとテストデータのように、次元数は同じだが、データ数が異なるデータが入力されます。なので、placeholderでshapeを定義するときに、データ数も定義してしまうと、学習データかテストデータのどちらかしか入力することができなくなります。なので、placeholderで変数を定義するときは、データ数の箇所をNoneと書いて、敢えて定義しないようにします。

説明変数・目的変数の基本的なshape

基本的に、説明変数・目的変数は、[データ数, 次元数]というshapeで定義されることが多いです。これが、画像データの時だと、[データ数, 幅, 高さ, チャネル数]になります。これは、説明変数に重みを掛けるときに、次元数を最後にしていた方が計算するのが楽になる(転置とかしなくてよい)からです。

tf.constant

これは恐らく皆さんが想像しているものです。そう、定数の定義です。定数は読んで字のごとく「一定の数」なので、この段階で中身も定義します。

learning_rate = tf.constant(0.01, dtype=tf.float32)

ちなみに、learning_rateというのは学習率のことです。学習率とは何かを説明するためには勾配降下法を思い出す必要があります。勾配降下法は簡潔に説明すると、損失関数の勾配が0になる所を探す手法です。勾配が0になる所を探すために、損失関数の上を移動しながら様々な箇所の勾配を確認していくのですが、その移動の幅に相当するのが、学習率となっています。

tf.variable_scope

でましたね〜、説明するのが一番めんどくさい箇所です。tensorflowにはC++と同じように「名前空間」が採用されています。名前空間が何かとは以下の記事を参考にしてください。

programming.pc-note.net

簡単に言うと、変数や関数の名前が被らないようにするための機能です。

名前空間は、関数などの「名前」が存在する「住所」を定義するようなものです。
同じ「山田さん」でも、「1丁目の山田さん」と「2丁目の山田さん」は別の人であると識別できます。
この「1丁目の」「2丁目の」が名前空間に当たります。
先ほどのremove関数の例で言うと、それぞれを「stdio.hのremove関数」「自作関数のremove関数」と呼ぶことで、別の関数と識別することができます。

上記のプログラムだと71行目にあるlinear_regression関数で使用しています。

以下のようにして、linear_regressionという名前空間を定義します。

with tf.variable_scope('linear_regression') as scope

このwith文の中に重みwやバイアスbを定義していくことで、「linear_regression/w」や「linear_regression/b」のように、linear_regressionという名前空間内にあるwやbとして定義することができます。

この名前空間ですが、ニューラルネットワーク等で共有(再利用)したいパラメータがあるときに使用します。ここでいうパラメータは、基本的には、上記のプログラムのように重みwやバイアスbのことですね。

複雑な深層学習のモデルだと、学習の時と生成の時で異なるネットワークを使うにもかかわらず、重みやバイアスを共有することもあります。上記のプログラムは、線形回帰モデルなので、そんな複雑なことをしませんが、117~122行目のように、学習とテストで別々の線形回帰の出力値やlossを定義したい時にも、重みやバイアスを共有する必要性があります。

# 線形回帰を実行
output_train = linear_regression(x_t, xDim)
output_test = linear_regression(x_t, xDim, reuse=True)

# 損失関数(最小二乗誤差)
loss_square_train = tf.reduce_mean(tf.square(y_t - output_train))
loss_square_test = tf.reduce_mean(tf.square(y_t - output_test))

重みやバイアスを共有(再利用)するときには、scope.reuse_variablesを使用します。

if reuse:
    scope.reuse_variables()

上記のプログラムでは、bool型変数のreuseを引数に設定することで、共有(再利用)するときだけ、scope.reuse_variablesを動かします。こうすることで、重みやバイアスが最定義されずに、共有(再利用)することができます。

tf.get_variable

これは、重みやバイアスを初期化しているときに使用する、変数の定義を行う関数です。この関数を使うことで、重みやバイアスの共有(再利用)を行うことができます。

同じような機能を持つ関数として、tf.Variableがありますが、基本的に使わないのと、説明するとややこしいことになるので今回は省略します。この辺りの違いを詳しく知りたい方は以下の記事を読んでください。

qiita.com

qiita.com

tf.get_variableの挙動

tf.get_variableは、同名の変数が存在しない場合は新規作成、同名の変数が存在する場合は、エラーがでます。

あれ?同名の変数が存在する場合は共有(再利用)するんじゃないの?と思うかもしれませんが、共有(再利用)するのは、scope.reuse_variablesが実行された時だけです。scope.reuse_variablesが実行されていない時に、同名の変数を定義すると、元の変数が上書きされちゃうかもしれないよとエラーを吐いてくれます。

このようにtf.get_variableを使えば、予期していない変数の共有(再利用)や上書きを防ぐことができますので、基本的に重みやバイアスを定義するときは、tf.get_variableを使うようにしましょう。

tf.train.GradientDescentOptimizer

これは最適化関数を呼び出すためのクラスです。最適化関数とは、勾配降下法のように、損失関数の出力値を最小にするために用いられる関数のことです。もう少し詳しく知りたい方は以下の記事を参照してください。

qiita.com

この最適化関数は様々な種類があり、Tensorflowにも8つほどの最適化関数が実装されています。今回は、その中でも一番スタンダードなtf.train.GradientDescentOptimizer(勾配降下法)を使用しています。他によく使われる関数として、tf.train.AdamOptimizer(Adamアルゴリズム)という関数があります。詳細な説明は行いませんが、この2つは覚えておくと良いでしょう。

詳しく知りたい方は以下の記事を参考にしてください。

postd.cc

そして、このクラスには以下のようにtf.constantの節で説明した学習率を引数に渡す必要があります。

opt = tf.train.GradientDescentOptimizer(learning_rate)

ここまでは、最適化関数の定義をしただけですので、実際に最適化関数を実行するためのプログラムを書く必要があります。

training_step = opt.minimize(loss_square_train)

このように、定義した最適化関数のクラスにはminimizeという関数が用意されています。この関数を上記のように実装することで、最適化関数が実行されます。また、minimize関数には、損失関数を引数にわたす必要があります。

この最適化関数を定義し、minize関数を実装する作業は、基本的にセットで行われることを覚えておくと良いでしょう。

tf.Session

これは、言わずもがなSessionの作成です。Sessionを作成することで、今までに定義した、グラフや変数が使える用になります。

sess = tf.Session()

tf.global_variables_initializer

これは、変数の初期化を行うクラスです。このクラスをインスタンス化し、Sessionに渡して実行することで、変数の初期化を行ってくれます。

init = tf.global_variables_initializer()
sess.run(init)

sess.run

sess,runは、placeholderで作成した変数に入力するデータを渡すことで、定義したグラフや変数を実行します。そして、入力したデータに準ずるデータ型の数値を出力値として取得することができる関数です。

_, loss_train = sess.run([training_step, loss_square_train], feed_dict=train_dict)

上記のように、第一引数には実行したい関数(または中身を確認したい変数)を、第二引数には、placeholderに入力するデータを辞書型で渡します。

上記の場合は、まず、training_step(最適化関数)を実行することで、定義した重みやバイアスを更新し、次にloss_square_train(損失関数)を実行することで、損失関数の出力値を確認することができます。

最後に

この記事では、実際に簡単な線形回帰モデルを実装し、そこで使われているTensorflowの関数などの使い方を説明しました。

大体の使い方は理解できたと思います(思いたい)。なので、試しに、mnistデータの分類モデルをTensorflowで実装してみましょう。以下に金型となるプログラムを用意してみたので、この記事を参考に、出来れば答えを見ずに実装してみてください。

CODEと書いてある部分と、バッチ正規化とlossの履歴をplotする箇所を頑張ってやってみてください。ちなみに、

となっています。
github.com




追記

後輩にやってもらったときに、scikit-learnのfetch_openmlがないぞ!とエラーがでました。この原因は、sckit-learnのバージョンが古くてopenmlが実装されていないことみたいです。

そのときは、以下のコマンドでscikit-learnのアップデートをしてください。

pip install -U scikit-learn



TensorFlow機械学習クックブック Pythonベースの活用レシピ60+ impress top gearシリーズ


詳解 ディープラーニング ~TensorFlow・Kerasによる時系列データ処理~