DeepLearning
深層学習
TensorFlow

Deep Learning(深層学習)で検索順位を予測してみる

 ※こちらは2年ほど前にトライしたテーマなのですが、なにかしらの参考になるかもしれないと思い、投稿しました。実装に利用した深層学習の理論的背景の説明を導入に入れました。深層学習の理論をどのように応用するかというデザイン的な観点で役に立つかもしれません。

なぜ、Deep Learning(以下 DL)に着目したか?

 ここ3, 4年の技術進歩 (2015年末現在) が背景にあります。DLの理論は想像するよりも結構昔に完成していたのですが、実用化に際して、計算テクニックとビッグデータを扱う環境がボトルネックになっていました。言わずもがな、技術進歩のお陰で、このボトルネックが解消してきたんですね。
 DL自体は、機械学習という予測アルゴリズムの一手法なのです。学習器としてのポテンシャルは非常に高いです(高度な学習ができるということ)。

DLで何ができるか?

 DLの凄みは、ものを見分けるための特徴を、機械が独りでに抽出できる点です。機械学習の世界で使われる「学習」とは、一方と他方のモノの違いを見分けることを意味します(パターン認識といいます)。これまでの機械学習は、パターン認識のためのルール(見分ける特徴になるもの)を「人間の手」で設定していました。ところが、DLになると、特徴の獲得を人間なしにすることができるのです。(本格的な人工知能(AI)を作れるかもしれない、ということです。)
 違いが分かるようになると、同じパターンが未知の状態で来たとしても、機械は学習したとおりに判断処理してくれます。すわなち、予測することができます。未知の情報を予測するモデルは、未確定な事象を扱うビジネスにおいて、応用価値の高いものになるでしょう。

やったこと(実装した理論・テクニック)

 DNN(Deep Neural Network; 深層ニューラルネットワーク)という手法を試してみました。ロジックの型としては最も基本的なものです。実際に実装した学習器は、DNNを骨格として、関連する技術を複数取り入れたものになりました。

Neural Networkとは?

 入力データとパターン認識されたデータとが、あやとりの紐のように順方向網目状に繋がった学習器のことをいいます。人の脳の神経回路の形状をモチーフにしたことから、この名称がつきました。
NN.JPG
 (出典: 別Qiita記事より)

 1つ1つの丸をノードといい、入力データ(ベクトル形式が基本)の成分が1つずつ格納されます。黒い線があやとりの紐に当たり、重み$w$をかけて次の層引き渡されます。入力データの層を入力層、識別データの層を出力層、中間の複雑なところを中間層(隠れ層)といいます。層から層へは、「ノードの線形結合→活性化関数による変形」という数学的手続きにより接続されます。例えば、入力層からノード$z_i$に繋げるとき、

$$z_i = f(\sum_{j=1}^{N}x_jw_{ij}^{1})$$

となります。$f(x)$が活性化関数を表します。活性化関数は、シナプスの電気伝導を模したものです。数学的には、非線形の表現が取られます。
 Neural Networkの学習は、出力層のデータと、正解のデータ(教師データという)との突き合わせ(誤差評価)によって、進行します。誤差の表現は、誤差関数を使います。学習の目的を数学的に言うと、誤差関数を最小(極小)にするということです。入力データ$x$は既知の値なので、重み$w$の更新によって、極小解を探索します。すなわち、誤差関数は$E({\bf w})$で表され、

$$
最適な学習状態 \quad \Rightarrow \quad \nabla E \equiv \frac{\partial E(\textbf w)}{\partial\textbf w} = 0
$$

$$
\textbf w^{(t+1)} = \textbf w^{(t)} - \epsilon \nabla E
$$
という形になります。$\epsilon$は学習係数といい、重みの更新の度合いを制御します(学習率制御)。このような学習方法を誤差逆伝播法(Back Propagation)と言います。勾配の計算は、確率的勾配降下法(Stochastic Gradient Decent; SGD)という方法が最もポピュラーです。SGDは勾配降下計算に使う入力データサンプルを、更新ステップ(エポックという)ごとにランダムに選ぶことができます。メリットは、局所解トラップ(漫然と同じサンプルセット=バッチによる学習を繰り返すことで、最小でない極小解のところにとどまってしまうこと) を回避できることです。

深層化(DNNへ)の拡張

 浅層NNと深層NNの違いは、中間層(隠れ層)の層数です。中間層が1層までのNNを浅層、2層以上を深層と呼びます。2層以上のNNでは、中間層の帯域で特徴抽出の余力ができるため、冒頭で述べたDLの特性(機械が特徴量を抽出できる)を発揮することができます。
 実装の観点で言うと、層を増やすだけであれば、実装コストはそう高くありません。しかし、単純に層を積み上げただけでは、本来的なDLの学習性能を発揮することができません。なぜなら、誤差逆伝播法による重みの更新の影響が低層(出力層から遠い層)まで伝わりにくいからです。これを勾配消失問題といいます。勾配消失問題の解決がDL実用化の長年の課題であり、実用化を阻むボトルネックの1つでした。
 この課題の解決に一役買ったのが、自己符号化器(AutoEncoder)による事前学習です。自己符号化器の特徴は、入力層と出力層に同じデータを置くことです(下図)。意図は、中間層への変換(符号化)と出力層への逆変換(復号化)の処理に最も適合した重みを見つけることです。すなわち、入力データと出力データの誤差が最小(極小)のとき、重み、および中間層は、入力データの特徴を掴んでいると解釈します。

AE.JPG

 事前学習によって定められた重みを、本番のNNに適用します。すると、初期値の段階で入力データの特徴をよく掴んだ形で学習が始まるので、勾配消失で更新がされにくくなる前に、NNの求める最適解へ到達することができます。
 ただし、自己符号化器を深層ネットワークに適用するには、もう一工夫必要になります。深層ネットワークの中間層の分だけ、自己符号化器を積み上げて特徴抽出(教師なし学習)をしなければなりません。これを積層自己符号化器といいます(下図)。

stack.JPG

 今回実装したモデルは、3層積み上げています。DNNへの適用は、学習した重みを3層分割り振ることで可能になります。

DNN.JPG

実装した活性化関数

(1) 双曲線関数

$$
f(x) = \frac{e^{x} - e^{-x}}{e^{x} + e^{-x}}
$$

(2) シグモイド関数

$$
f(x) = \frac{1}{1 + e^{-x}}
$$

(3) ReLU (Rectified Linear Unit)

$$
f(x) = \left\{
\begin{array}{ll}
x & (x \geqq 0) \\
0 & (x \lt 0)
\end{array}
\right.
$$

(4) maxout(線形多区画関数)

$$
f(\textbf x) = \max_{i}(\textbf w_i\textbf x)
$$

(5)softmax関数

$$
f(\textbf x_k) = \frac{\exp{(\textbf w_k\textbf x)}}{\sum_{i=1}^{K}\exp{(\textbf w_i\textbf x)}}
$$

シナプスによる閾値の表現は、(1)と(2)の形状が最も近しいです。一方で、計算コストの増大や、$x$が大きい値のときの勾配消失が指摘されました。(3)と(4)は指摘に応えるために考案された関数で、(4)のほうが、複雑な表現をすることができます。softmax関数は、特別な位置づけで、多クラス分類で出力値を出すときのみに用います。

誤差関数の表現

パターン分類したい問題の系によって使い分けます。
実数値を予測したいときは、二乗誤差関数を使います。

$$
E(\textbf x) = \frac{1}{2}|\textbf t - o(\textbf x)|^2
$$

${\bf t}$は、教師データベクトル、$o({\bf x})$ は、出力値ベクトルを表します。
クラス分類をしたいときは、交差エントロピー誤差関数を用います。

$$
E(\textbf x) = -\sum_{k=1}^{K}t_k \log o(\textbf x)
$$

学習の制御

学習(重みパラメータの更新)フェーズでは、次の2点のリスクに直面します。

①解の収束性に関する問題(最適でない解への収束や遅い収束スピード)

 学習係数の制御や勾配降下が適切でない場合に発生します。解消するためのテクニックは、諸々開発されています。TensorFlowで実装する場合、SGDを起点として、AdaGrad, Momentum, FTRL, Adamといった手法が、デフォルトで用意されています。TensorFlowについては、後に記述します。学習制御テクニックについては、リンクを参照ください。

②過学習

 過学習(overfitting)とは、訓練データにパターン認識が偏りすぎるために、却って学習器の汎化能力が落ちることをいいます。特定の学習データに過適合しすぎると、却って汎化能力を失ってしまうのです。
 学習器の性能は、汎化能力の高さで判定されます。なぜなら、使いみちとして未知のデータの予測が挙げられるからです。
 機械学習プログラムを動かすとき、学習(訓練)データとテストデータの2種類を用意する必要があります。訓練データで学習させ、テストデータで学習器の性能評価をします。
 計算上、過学習を回避する工夫が数点あります。1点目は、重みの更新式そのものを改良することです。代表的な方法にL2正則化があります。方法は、重み${\bf w}$のノルムの1/2を更新前の重みに加えます。

$$
E_t(\textbf w) = E_n(\textbf w) + \frac{\lambda}{2}||\textbf w||^2
$$

これにより、$w$の大きい成分に引っ張られにくくなり、訓練データへの過適合を防ぐことができます。
 2点目は、Dropoutという手法です。学習時に、インプット側のユニット数を指定された割合だけ、ランダムに消失させます。あえて、ノードの一部を消失させることで、特定のノードに偏った学習の進行を防ぐことができ、過適合の抑制に繋がります。ノードが欠損した状態でも学習できることで、モデルが強靭なものになると期待されます (頑健性)。

自己符号化器の工夫

効果的な事前学習のために、2点の工夫を施しました。

①雑音除去(denoising)自己符号化器

 入力層の$x$に、"ノイズ"を入れて事前学習させます。その結果、自然に発生するゆらぎ(誤差項)への耐性がついた学習器を作ることができます。
 ノイズの発生は、標準正規分布の値を乱数で出す方法(ガウシアンノイズ)やノイズ項に0か1を等確率のランダムに付加する方法(Salt & Pepperノイズ)があります。

②スパース正則化自己符号化器

 スパース (Sparsity)とは、ひとことで言うと"疎"な感じのことをいいます。数学的には、ベクトルの中に、0がたくさん並び、たまに出る数字に意味が凝縮されている状態です。
 スパース性のある情報体は、一見0が多く無駄なイメージですが、言い換えれば余計な情報を削ぎ落とし、必要な情報だけが詰まった状態とも解釈できます。特徴抽出という点でも親和性があり、望ましいです。
 一般に、中間層のユニット数は、入力層のそれより小さいことが多いのですが(砂時計型)、スパース性を導入すると、大きい樽型のようなデザインにすることもできます。
 数学的には、重みの式に、カルバック・ライブラーダイバージェンスという項を加えます。

$$
\tilde{E}(\textbf w) \equiv E(\textbf w) + \beta\sum_{j}KL(\rho||\hat{\rho}_j)
$$

$\rho$は平均活性度といい、次元(ユニット数)のうち、ゼロ以外の活性している割合を指します。KLダイバージェンスは、目標となる$\rho$に近づくように処理するので、スパース性を増すことができるのです。

検索順位予測への応用

 本稿の導入に立ち返るのですが、深層学習プログラムの特長は、複雑な系の自律的パターン認識と未知事象の予測です。特徴を活かすために、我々は、適用の第一弾として「記事最適化ツール」という構想を考えました。記事サイトを構成している要素(文字数、画像数など)を入力データの次元に格納し、対象の記事ページの検索順位を予測するという構想です。期待通りに行けば、学習器は、インプットの情報から(出力の検索順位に関係する)特徴を抽出し、精度よく、ページの順位を予測してくれるはずです。

tool.JPG

 上記は、ツールのデザインイメージです。モデル実装と実験に際し、LAUGHYのデータを収集し、実際の記事の検索順位を予測させてみました(順位・および記事データは、2015年10月現在のものを使用しました)。順位の予測区分は、Organic Search集客として使えるクラス(1~10位)、使える候補(11~100位)、使えないレベル(101位~)という3クラスデザインです。ランダムに選び続けると、単純に正解率33%になる問題の系です。
 学習器の動作結果をイメージしていただくために、実際に3層DNNのツールを学習させた時の、テストデータの正解率の推移を下に図示します。

perform.JPG

 縦軸が正解率、横軸が学習エポックです。この図自体は、matplotlibで描画しています。エポックが増すにつれて正解率が上がっている様子を読み取れます。さらに進行すると、徐々に正解率が下がっていますが、これが過学習の現象です。今のデータの系で、構築した学習器を適用すると、大体66 ~ 69%の正解率を叩き出す結果になりました。

TensorFlowについて(モデル実装)

 最後に、私が実装を完遂するまでに、学んだことをまとめます。最適化ツールのDNNプログラムは、Pythonで書きました。2015年11月当時、機械学習ライブラリTensorFlowの対応言語がPython2系のみだっただからです。
 TensorFlowは、2015年11月初旬に、Googleがオープンソースとして提供しはじめました。TensorFlowが出現するまでにも、Pythonには機械学習専用のライブラリが複数存在しました。オリジナルのScikit-learnから、最近のChainerまで多彩です。TensorFlowがこれらのライブラリに比べて優れている点は、「機械学習パーツ(モジュール)の使いやすさ」、「計算の速さ」、「自動結果表示機能搭載」です。
 第一の機械学習パーツ(モジュール)の使いやすさは、個人的に最も評価が高いです。SGDやDropoutのような、機械学習に必要な計算過程の多くがモジュール化されており、一行書けばその処理をしてくれます。TensorFlowの構成デザインは、深層学習の計算モジュール(特にNN)と親和性が高いです。入力から出力までのデータ(スカラーでも多次元配列でも)を一律にテンソルとして扱い、データフローをイメージしやすく記述できます。TensorFlowには、「Graphクラス」なるものが構築されており、データフローの様子をTensorBoardという可視化ツールで図示することができます。
 第二の計算の速さの秘訣は、単純処理の仕組みにあります。TensorFlowは、学習器を構成するオブジェクト間の関係をPythonで記述するものの、Backendで処理される単純な行列計算などは、すべてC++で記述しています。そのため、多次元のネットワークでも、Chainerと匹敵するスピードで計算することができます。加えて、GPUで演算処理できる機能も付いています(導入には、NVIDIAのCUDAドライバを環境に導入する必要がある)。今回のツールの系は、十分CPUで処理できる分量でしたが、大量の画像処理といったような系では、GPUの並列処理が計算コストに大きく効いてきます。
 第三の自動結果表示機能搭載は、上述したTensorBoardの件です。TensorBoardは、Graphの他にも、テンソル変数化した数値の、エポックによる時系列的変化を図示することができます。

データ整形のポイント

(1)名義尺度の処理

 使用したデータには、数値を直接使えるものと、そうでないものがあります。
 使えるものは、例えば、記事の単語数などです。データ間の順序、差、比に意味があるタイプのものです(順序、間隔尺度、比尺度)。これらのデータは、特段考えずにそのまま適用できます。
 直接使えないものはカテゴリカル・データ(名義尺度)です。例えば、大カテゴリ(タレント、歌手アーティスト、・・・)のデータです。数値化を意図して、大カテゴリという一つの次元にタレント:1, 歌手アーティスト:2のように数値を振ると、データの意味が変わってしまいます。(歌手アーティストとタレントの間に「差がある」という、存在しない/意図しない意味が勝手に付加されてしまいます。)したがって、それぞれの成分に対して、該当するか・しないかの(0,1)値を振って、ひとつの次元に独立させる必要があります。この処理を、1-of-K符号化といいます。

(2)データの標準化

 数値化を完了したデータに対し、各次元内で標準化の処理をしました。データ内には、スケール感の異なる数値が次元をまたいで存在するからです。元のままデータを投入すると、学習が発散しやすくなったりします。
 似たような処理に、各次元における[0,1]区間へのRescalingや正規化もあります。

TensorFlowを扱う上で、とっつきにくかったこと

 TensorFlowの基本的な使い方・記法は、公式TutorialとQiita内にたくさん転がっています。ですので、ここではこのモデル実装時につまづきやすかった使い方に触れさせていただきます。

(1) tfファミリーの関数を作動させるには、必ずsession.runを実行しなければならない。
 たとえば、配列サイズ[3,3]の正規分布の乱数を受け取りたいとします。

python
ran = tf.truncated_normal([3,3], stddev=0.1)
print ran

 このコードで、ranは乱数を表示するでしょうか?答えはできません。上のtf関数の記述自体は間違っていません。しかし、動作させるためには、sessionクラスの立ち上げが必要になります(ran自体は、Tensorオブジェクトである旨のみコンピュータに認識されている)。次の記述で作動します。

python
ran = tf.truncated_normal([3,3], stddev=0.1)
sess = tf.InteractiveSession()
_ran = sess.run(ran)
print _ran

(2)学習の更新ごとに変化させたい変数は、Variable指定しなければならない
TensorFlowのtfクラスには、Variableというサブクラスがあります。学習で更新させる変数は、必ずVariable指定します。

python
W_encode = tf.Variable(tf.truncated_normal([max_k, input_dim, layer_sizes], stddev=0.1), name="Encode_weights")

(Variable指定の一例。W_encodeはNNの重みの変数なのだが、学習ごとに更新させるので、Variable指定している。)

(3)すべてのVariableを宣言しきってから、初期化宣言する。

python
    with tf.Session() as sess_ae:
        AE_training = training_control(
            AE_training_mode, autoencoder['cost'], eta_AE)
        init = tf.initialize_all_variables()
        sess_ae.run(init)

tf.initialize_all_variables()が初期化動作をする関数です。これもtf関数なので、session.run指定で初めて作動します。すべてのVariableが揃わない段階で、initializeすると、エラーを吐いていしまいます。

(4)placeholderは、学習ごとにデータを入れるための器である。
 TensorFlowには、placeholderという独特のオブジェクトがあります。英単語から連想されるとおり容れ物のイメージであり、学習ごとに使うデータを容れます。機械学習プログラムは、基本的に1エポック回すごとに、サンプリングしたデータを都度入力する段取りです。裏を返すと、都度入力するデータについては、「空っぽの容れ物」を用意しておく方が都合よく、その役割を有しているのが、placeholderです。

python
rank = tf.placeholder("float", shape=(None, NUM_CLASSES))
factor = tf.placeholder("float", shape=(None, INPUT_SIZE))

上記は、placeholder適用の例です。rankは教師データを入れる器、factorは入力データを入れる器です。学習のエポックごとに新しいデータを入れていくため、placeholder指定しています。

コード公開

※私のコードリテラシーが低く、可読性の低いコードだと認識でいますので、とても恥ずかしいですが、TensorFlowの流れを知っていただくために、載せました。参考程度に御覧ください。
※バグ自体はなく、整形したデータを入れれば作動します。
※TensorBoardまわりのコーディングが整理されたら更新する予定です。

python
# -*- coding: utf-8 -*-

# このコードは、2層のAEを積層した、Deep Networkです。

#-------------------------------------------------------------------------
# データと定数とインポート関係
#-------------------------------------------------------------------------

from __future__ import absolute_import
from __future__ import division

import sys
import tensorflow as tf
import numpy as np
import math
import random
import matplotlib.pyplot as plt
import os

# スクリプト実行のときに、時間計測をする
import time
import datetime

if __name__ == '__main__':
    t0 = time.clock()

# cdの変更
os.chdir("/media/sf_tf_output/data")


# データのインポート
# RDBとしてまとめて、データをインポートする。
t_input = np.loadtxt(open("C_1_1_5.csv", "r"), delimiter=",")


# ニューロンと入力次元の決定
INPUT_SIZE = 54
HIDDEN_UNIT_SIZE1 = 34
HIDDEN_UNIT_SIZE2 = 22
HIDDEN_UNIT_SIZE3 = 14
# クラス分類する場合。
NUM_CLASSES = 3
batch_size = 10

# ホールドアウトの際、訓練データをいくつもらうか?
TRAIN_DATA_SIZE = 18000

# 訓練のステップ数を設定する
AE_step1 = 30000
AE_step2 = 30000
AE_step3 = 30000
NN_step = 70000

# 学習係数を設定する。
eta_AE1 = 0.001
eta_AE2 = 0.001
eta_AE3 = 0.001
eta_alpha = 0.0000001
eta_NN = 0.0001

# maxout関数の決定
max_k_AE1 = 10
max_k_AE2 = 10
max_k_AE3 = 10

# スパース自己符号化器まわり
KL_beta = 1.0
KL_lou = 0.3
# L2正則化(重み減衰)用の定数λ
lambda_L2 = 0.0001
#AEのドロップアウト率
DROP_EN = 0.2
DROP_DE = 0.5

# AE制御モードの選択: 0: ノーマル, 1: 重み減衰, 2: スパース正則化, 3: 混合モデル
AE_regu_mode = 1
# 活性化関数モード選択: 0: sigmoid, 1: ReLU, 2: maxout
AE_acti_mode = 2

# 学習率制御の選択(パラメータ調整は、関数内で直接いじってください)
# 0~3 : SGD, Adagrad, Momentum, Adam
AE_training_mode = 3
NN_training_mode = 3

# plt書き出し用の受け皿
step1 = []
step2 = []
train_series = []
test_series = []
entropy_series = []


# データ行列の行数をごちゃ混ぜに入れ替える。
perm = np.arange(t_input.shape[0])
np.random.shuffle(perm)
t_input = t_input[perm]

# 学習データとテストデータに分割する。
[train_input, test_input] = np.vsplit(t_input, [TRAIN_DATA_SIZE])

# 教師データと入力データに分解する。
[rank_train, factor_train] = np.hsplit(train_input, [NUM_CLASSES])
[rank_test, factor_test] = np.hsplit(test_input, [NUM_CLASSES])


#-----------------------------------------------------
# AEのコーディング
#-----------------------------------------------------
#dropout制御用引数の定義
keep_prob_encode = tf.placeholder("float")
keep_prob_decode = tf.placeholder("float")

# maxout関数の定義
def maxout(x, W, b, maxout_size):
    h1 = None
    for ii in range(maxout_size):
        out = tf.matmul(x, W[ii, :, :]) + b[ii, :]
        if h1 == None:
            h1 = out
        else:
            h1 = tf.maximum(h1, out)
    return h1


def AE(x, layer_sizes, max_k):

    # 積み上げここから
    input_dim = int(x.get_shape()[1])
    input_batch = batch_size

    with tf.name_scope('Encode') as scope:
        x_drop = tf.nn.dropout(x, keep_prob_encode)
        x_random = x + tf.random_normal(
            [input_dim], mean=0.0, stddev=0.5, dtype=tf.float32)
        x_random_drop = tf.nn.dropout(x_random, keep_prob_encode)
        if AE_acti_mode == 2:
            W_encode = tf.Variable(tf.truncated_normal(
                [max_k, input_dim, layer_sizes], stddev=0.1), name="Encode_weights")
            b_encode = tf.Variable(tf.constant(
                0.1, shape=[max_k, layer_sizes]), name="Encode_biases")
        else:
            W_encode = tf.Variable(tf.truncated_normal(
                [input_dim, layer_sizes], stddev=0.1), name="Encode_weights")
            b_encode = tf.Variable(tf.constant(
                0.1, shape=[layer_sizes]), name="Encode_biases")

        if AE_acti_mode == 0:
            output_encode = tf.nn.sigmoid(
                tf.matmul(x_random_drop, W_encode) + b_encode)
        elif AE_acti_mode == 1:
            output_encode = tf.nn.relu(
                tf.matmul(x_random_drop, W_encode) + b_encode)
        elif AE_acti_mode == 2:
            output_encode = maxout(x_random_drop, W_encode, b_encode, max_k)
        elif AE_acti_mode == 3:
            output_encode = tf.nn.tanh(
                tf.matmul(x_random_drop, W_encode) + b_encode)
        output_encode_drop = tf.nn.dropout(output_encode, keep_prob_decode)

    with tf.name_scope('Decode') as scope:
        W_decode = tf.Variable(tf.truncated_normal(
            [layer_sizes, input_dim], stddev=0.1), name="Decode_weights")
        b_decode = tf.Variable(tf.constant(
            0.1, shape=[input_dim]), name="Decode_biases")
        # 入力が実数と2値の混合型なので、出力の逆リンク関数に恒等写像を使う。
        output_decode = tf.matmul(output_encode_drop, W_decode) + b_decode
    # スパース自己符号化器用の平均活性度KLの計算。
    # 平均活性度は、訓練データ数ベースで出すように言われているが、近似的にミニバッチ数で出す。
    # ユニットごとに、平均活性度(ave_activation)を計算する。
    ave_activation = 1. / batch_size * tf.reduce_sum(output_encode, 0)
    for j in range(layer_sizes):
        KL = 0.
        if ave_activation[j] <= 0.:
            tmp = 0.
        else:
            tmp = KL_beta * (KL_lou * (tf.log(KL_lou) - tf.log(ave_activation[j])) + (
                1 - KL_lou) * (tf.log(1 - KL_lou) - tf.log(1 - ave_activation[j])))
        KL += tmp
    # L2正則化項(重み減衰)をつくる
    # weight decay(重み減衰)は、層のWの分だけ、2乗ノルムを用意する。
    # 正則化項は、各層の重みテンソルについて2乗ノルムを計算し、最後足し合わせるだけでよい。なぜなら、逆誤差伝播の際、微分変数は各層のwに対応するためである。
    #(一つの式に足しあわせても、他の層は微分の結果ゼロになる!)
    weight_decay = lambda_L2 / 2 * \
        (tf.nn.l2_loss(W_encode) + tf.nn.l2_loss(W_decode))

    # 自己符号化器の正則化の選択
    if AE_regu_mode == 0:
        AE_reguralize = 0
    elif AE_regu_mode == 1:
        AE_reguralize = weight_decay
    elif AE_regu_mode == 2:
        AE_reguralize = KL
    elif AE_regu_mode == 3:
        AE_reguralize = weight_decay + KL

    return {
        'encoded': output_encode,
        'decoded': output_decode,
        'cost': 1. / 2 * (tf.reduce_sum(tf.square(x - output_decode), 1)) + AE_reguralize,
        'AE_weights': W_encode,
        'AE_biases': b_encode
    }


#---------------------------------------------------------------------
# NNのコーディング
#---------------------------------------------------------------------

# AEで抽出した変数を受け渡して使う。

def Neural(x, W, b, max_k, W_name, b_name):
    # Autoencoderの活性化関数がmaxoutのときは、NNの活性化関数も自動的にmaxoutになる。
    weight = tf.Variable(W, name=W_name)
    bias = tf.Variable(b, name=b_name)
    if AE_acti_mode == 2:
        output = maxout(x, weight, bias, max_k)
    else:
        output = tf.nn.sigmoid(
            tf.matmul(x, weight) + bias)
    return output


def NN(x, AE1_weight, AE1_bias, AE2_weight, AE2_bias, AE3_weight, AE3_bias):
    # 事前学習で獲得した特徴をもとに、NNにかける。そのための演算
    # ここのNNの重みは、ここで学習する点に注意する。
    with tf.name_scope('hidden1') as scope:
        x = tf.nn.dropout(x, keep_prob_encode)
        hidden1_output = Neural(x, AE1_weight, AE1_bias,
                                max_k_AE1, "hidden1_weight", "hidden1_bias")
        hidden1_output = tf.nn.dropout(hidden1_output, keep_prob_decode)
    with tf.name_scope('hidden2') as scope:
        hidden2_output = Neural(
            hidden1_output, AE2_weight, AE2_bias, max_k_AE2, "hidden2_weight", "hidden2_bias")
        hidden2_output = tf.nn.dropout(hidden2_output, keep_prob_decode)
    with tf.name_scope('hidden3') as scope:
        hidden3_output = Neural(
            hidden2_output, AE3_weight, AE3_bias, max_k_AE3, "hidden3_weight", "hidden3_bias")
        hidden3_output = tf.nn.dropout(hidden3_output, keep_prob_decode)
    with tf.name_scope('output') as scope:
        output_weight = tf.Variable(tf.truncated_normal(
            [HIDDEN_UNIT_SIZE3, NUM_CLASSES], stddev=0.1), name="output_weight")
        output_bias = tf.Variable(tf.random_uniform(
            shape=[NUM_CLASSES],
            minval=-1. / math.sqrt(HIDDEN_UNIT_SIZE2),
            maxval=1. / math.sqrt(HIDDEN_UNIT_SIZE2)),
            name="output_bias")
        # outputは、クラス数分に応じたベクトルになる。
        # 出力はソフトマックスで計算。
        nn_out = tf.nn.softmax(
            tf.matmul(hidden3_output, output_weight) + output_bias)
    return {
        'output': nn_out
    }


class Dataset(object):

    def __init__(self, rank, factor):
        assert rank.shape[0] == factor.shape[
            0], ("Input and teacher have the same number.")
        self.samples = rank.shape[0]
        self.index_in_epoch = 0
        self.epoch_completion = 0
        self._rank = rank
        self._factor = factor

    # ステップ数を返す関数
    def epoch_completion(self):
        return self.epoch_completion

    # 指定のデータ数を使ったのち、ランダムにサンプルを取得する関数(ランダムな復元抽出)
    # 訓練データをミニバッチごとにサンプリングする。データ上限まで、いったら、シャッフルして、ミニバッチサンプリングを再開する(ホールドアウト+ランダムサンプリングを加えた感じ)
    def next_batch(self, batch_size):
        start = self.index_in_epoch
        self.index_in_epoch += batch_size
        self.epoch_completion += 1
        # 総使用データが元データ数を上回った時、一回シャッフルする。また、データ分利用する。
        if (self.index_in_epoch > self.samples) or self.index_in_epoch == batch_size:
            perm = np.arange(self.samples)
            np.random.shuffle(perm)
            self._rank = self._rank[perm]
            self._factor = self._factor[perm]
            # start
            start = 0
            self.index_in_epoch = batch_size
        end = self.index_in_epoch
        return self._rank[start: end], self._factor[start: end]

Train = Dataset(rank_train, factor_train)

# AE学習の実行

#"training_control"は、学習率制御の手法を選択する関数。SGD, Adagrad, Momentum, Adamの4通りを準備。


def training_control(mode, loss, l_rate):
    if mode == 0:
        train_step = tf.train.GradientDescentOptimizer(
            learning_rate=l_rate).minimize(loss)
    elif mode == 1:
        train_step = tf.train.AdagradOptimizer(
            learning_rate=l_rate).minimize(loss)
    elif mode == 2:
        train_step = tf.train.MomentumOptimizer(
            learning_rate=l_rate, momentum=0.5).minimize(loss)
    elif mode == 3:
        train_step = tf.train.AdamOptimizer(
            learning_rate=l_rate,
            beta1=0.9,
            beta2=0.999,
            epsilon=1e-08,).minimize(loss)
    return train_step

# Autoencoderの学習を実行する関数(NNと違い、積層により複数回訓練するため、関数化しておく。)


def AE_test(obj, feed_train_x, AE_INPUT, AE_HIDDEN, AE_step, eta_AE,
            max_k):
    x = tf.placeholder("float", shape=(None, AE_INPUT))
    step_AE = []
    cost_register = []
    autoencoder = AE(x, AE_HIDDEN, max_k)
    with tf.Session() as sess_ae:
        AE_training = training_control(
            AE_training_mode, autoencoder['cost'], eta_AE)
        init = tf.initialize_all_variables()
        sess_ae.run(init)
        for i in range(AE_step):
            train_y, train_x = obj.next_batch(batch_size)
            sess_ae.run(AE_training, feed_dict={x: train_x,
             keep_prob_encode : (1 - DROP_EN), keep_prob_decode : (1 - DROP_DE)})
            if i % 100 == 0:
                cost = sess_ae.run(tf.reduce_mean(
                    autoencoder['cost']), feed_dict={x: train_x,
                     keep_prob_encode : 1, keep_prob_decode : 1})
                step_AE = np.append(step_AE, i)
                cost_register = np.append(cost_register, cost)
                eta_AE -= eta_alpha * AE_step
                print "step", i, " cost: ", cost
        trained_x = sess_ae.run(
            autoencoder['encoded'], feed_dict={x: feed_train_x,
             keep_prob_encode : 1, keep_prob_decode : 1})
        weights = sess_ae.run(
            autoencoder['AE_weights'], feed_dict={x: feed_train_x,
             keep_prob_encode : 1, keep_prob_decode : 1})
        biases = sess_ae.run(
            autoencoder['AE_biases'], feed_dict={x: feed_train_x,
             keep_prob_encode : 1, keep_prob_decode : 1})
        sess_ae.close()
    return {
        'trained_x': trained_x,
        'weights': weights,
        'biases': biases,
        'cost_register': cost_register,
        'step_AE': step_AE
    }

#-----------------------------------------------------------
# NNの計算
#-----------------------------------------------------------

if __name__ == '__main__':
    AE1 = AE_test(Train, factor_train, INPUT_SIZE,
                  HIDDEN_UNIT_SIZE1, AE_step1, eta_AE1,
                  max_k_AE1)
    trained_AE1 = AE1['trained_x']
    AE1_W = AE1['weights']
    AE1_b = AE1['biases']
    AE1_Train = Dataset(rank_train, trained_AE1)
    AE2 = AE_test(AE1_Train, trained_AE1, HIDDEN_UNIT_SIZE1,
                  HIDDEN_UNIT_SIZE2, AE_step2, eta_AE2,
                  max_k_AE2)
    trained_AE2 = AE2['trained_x']
    AE2_W = AE2['weights']
    AE2_b = AE2['biases']
    AE2_Train = Dataset(rank_train, trained_AE2)
    AE3 = AE_test(AE2_Train, trained_AE2, HIDDEN_UNIT_SIZE2,
                  HIDDEN_UNIT_SIZE3, AE_step3, eta_AE3,
                  max_k_AE3)
    trained_AE3 = AE3['trained_x']
    AE3_W = AE3['weights']
    AE3_b = AE3['biases']
    AE3_Train = Dataset(rank_train, trained_AE3)
    with tf.Session() as sess_nn:
        rank = tf.placeholder("float", shape=(None, NUM_CLASSES))
        factor = tf.placeholder("float", shape=(None, INPUT_SIZE))
        nn_output = NN(factor, AE1_W, AE1_b, AE2_W, AE2_b, AE3_W, AE3_b)
        cross_entropy = -tf.reduce_sum(rank * tf.log(nn_output['output']))
        NN_training = training_control(NN_training_mode, cross_entropy, eta_NN)
        # 先に使う変数(上でいうNeural)を呼び出してからinititalizeをかける。
        init = tf.initialize_all_variables()
        sess_nn.run(init)

  # 学習精度の評価関数の定義
        correct_prediction = tf.equal(
            tf.argmax(nn_output['output'], 1), tf.argmax(rank, 1))
        accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
        # ベストパフォーマンスデータを取得
        max_acc = 0.
        max_step = 0
        for step in range(NN_step):
            train_y, train_x = Train.next_batch(batch_size)
            feed_training = {factor: train_x, rank: train_y,
                keep_prob_encode : 1, keep_prob_decode : 1}
            feed_train = {factor: train_x, rank: train_y,
                keep_prob_encode : 1, keep_prob_decode : 1}
            feed_test = {factor: factor_test, rank: rank_test,
                keep_prob_encode : 1, keep_prob_decode : 1}
        # training_opを実行すれば、数珠繋がりでcross_entropyもoutputも自動計算される
        # この段階で、パラメータの更新が終わる
            sess_nn.run(NN_training, feed_dict=feed_train)
            # 100エポックごとに、結果を記録・表示させる。
            if step % 100 == 0:
                # 訓練誤差の計算
                train_result = sess_nn.run(accuracy, feed_dict=feed_training)
                print "Train accuracy at step %s: %f" % (step, train_result)
            # matplotlib用のグラフ元データ
                step1 = np.append(step1, step)
                train_series = np.append(train_series, train_result)
                test_result = sess_nn.run(accuracy, feed_dict=feed_test)
            # summary_str_test = test_result[0]
                print "Test accuracy at step %s: %f" % (step, test_result)
            # summary_writer.add_summary(summary_str_test, step)
            # matplotlib用のグラフ元データ
                step2 = np.append(step2, step)
                test_series = np.append(test_series, test_result)
                if test_result > max_acc:
                    max_step = step
                max_acc = max(max_acc, test_result)
                entropy_series = np.append(entropy_series, sess_nn.run(
                    cross_entropy, feed_dict=feed_train))

    t1 = time.clock()
    print "elapsed time: %f (sec)" % (t1 - t0)
    print "Max performance is ", max_acc
    print "When the perfomance is the best, steps are", max_step


# 画像データ格納
d = datetime.datetime.today()
filename = "{0:02d}".format(d.month)\
    + "{0:02d}".format(d.day)\
    + "{0:02d}".format(d.hour)\
    + "{0:02d}".format(d.minute)\
    + "{0:02d}".format(d.second)
os.mkdir(filename)
os.chdir("/media/sf_tf_output/data/" + filename)

# matplotlibの描画
plt.plot(AE1['step_AE'], AE1['cost_register'], color="r", linestyle="-")
plt.savefig("cost1.jpg")
plt.close()
plt.plot(AE2['step_AE'], AE2['cost_register'], color="m", linestyle="-")
plt.savefig("cost2.jpg")
plt.close()
plt.plot(AE3['step_AE'], AE3['cost_register'], color="k", linestyle="-")
plt.savefig("cost3.jpg")
plt.close()
plt.plot(step2, entropy_series, color="c", linestyle="-")
plt.savefig("entropy_GOSA.jpg")
plt.close()
plt.plot(step1, train_series, color="b", linestyle="-")
plt.savefig("train.jpg")
plt.close()
plt.plot(step2, test_series, color="g", linestyle="-")
plt.savefig("test.jpg")
plt.close()

#logの書き出し
a = {}
a[0] = {"elapsed time: (sec)" : (t1 - t0)}
a[1] = {"Max performance": max_acc}
a[2] = {"Max performance steps": max_step}
a[3] = {"HIDDEN_UNIT_SIZE1" : HIDDEN_UNIT_SIZE1}
a[4] = {"HIDDEN_UNIT_SIZE2" : HIDDEN_UNIT_SIZE2}
a[5] = {"HIDDEN_UNIT_SIZE3" : HIDDEN_UNIT_SIZE3}
a[6] = {"batch_size" : batch_size}
a[7] = {"TRAIN_DATA_SIZE" :TRAIN_DATA_SIZE}
a[8] = {"AE_step1" : AE_step1}
a[9] = {"NN_step" : NN_step}
a[10] = {"eta_AE1" : eta_AE1}
a[11] = {"eta_NN" : eta_NN}
a[12] = {"max_k_AE1" : max_k_AE1}
a[13] = {"KL_beta" : KL_beta}
a[14] = {"KL_lou" : KL_lou}
a[15] = {"lambda_L2" : lambda_L2}
a[16] = {"AE_regu_mode" : AE_regu_mode}
a[17] = {"AE_acti_mode" : AE_acti_mode}

f = open('log.txt' , 'w')
for i in range(len(a)):
    f.write(str(a[i]) + "\n")
f.close()