# Copyright 2016-2017 Tohgoroh Matsui All Rights Reserved.
#
# Changes:
# 2017-06-19 Changed for TensorFlow 1.2
#
#
# This includes software developed by Google, Inc.
# licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an 'AS IS' BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import tensorflow as tf
import numpy as np


# パラメーター
flags = tf.app.flags
FLAGS = flags.FLAGS
flags.DEFINE_integer('samples',       400,    'Number of training samples.')
flags.DEFINE_integer('max_steps',     1001,   'Number of steps to run trainer.')
flags.DEFINE_float(  'learning_rate', 0.001,  'Initial learning rate.')
flags.DEFINE_float(  'dropout',       0.9,    'Keep probability for training dropout.')
flags.DEFINE_string( 'data_dir',      'data', 'Directory for storing data.')
flags.DEFINE_string( 'summaries_dir', 'log',  'Summaries directory.')


def train():
  # CSVファイルの読み込み
  data = np.genfromtxt(FLAGS.data_dir + '/housing.csv', delimiter=",")

  # データをシャッフル
  np.random.shuffle(data)
  
  # 学習データ (train) とテストデータ (test) に分割
  x_train, x_test = np.vsplit(data[:, 0:13].astype(np.float32), [FLAGS.samples])
  y_train, y_test = np.vsplit(data[:,13:14].astype(np.float32), [FLAGS.samples])


  # セッション
  sess = tf.InteractiveSession()

  # 入力
  with tf.name_scope('input'):
    x  = tf.placeholder(tf.float32, shape=[None, 13])
  
  # 出力
  with tf.name_scope('output'):
    y_ = tf.placeholder(tf.float32, shape=[None, 1])
    
  # ドロップアウトのキープ率
  with tf.name_scope('dropout'):
    keep_prob = tf.placeholder("float")
    tf.summary.scalar('dropout_keep_probability', keep_prob)
  
  # 重み（係数）
  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)
    
  # for TensorBoard
  def variable_summaries(var, name):
    with tf.name_scope('summaries'):
      mean = tf.reduce_mean(var)
      tf.summary.scalar('mean/' + name, mean)
      with tf.name_scope('stddev'):
        stddev = tf.sqrt(tf.reduce_sum(tf.square(var - mean)))
      tf.summary.scalar('sttdev/' + name, stddev)
      tf.summary.scalar('max/' + name, tf.reduce_max(var))
      tf.summary.scalar('min/' + name, tf.reduce_min(var))
      tf.summary.histogram(name, var)


  # ニューラル・ネットワークの層
  def nn_layer(input_tensor, input_dim, output_dim, layer_name, relu=True):
    with tf.name_scope(layer_name):
      with tf.name_scope('weights'):
        weights = weight_variable([input_dim, output_dim])
        variable_summaries(weights, layer_name + '/weights')
      with tf.name_scope('biases'):
        biases = bias_variable([output_dim])
        variable_summaries(biases, layer_name + '/biases')
      with tf.name_scope('Wx_plus_b'):
        preactivate = tf.matmul(input_tensor, weights) + biases
        tf.summary.histogram(layer_name + '/pre_activations', preactivate)
      if relu:
        activations = tf.nn.relu(preactivate, name='activation')
      else:
        activations = preactivate
      tf.summary.histogram(layer_name + '/acctivations', activations)
      return activations
      

  # 入力層
  hidden1 = nn_layer(x, 13, 64, 'layer1')
  dropped1 = tf.nn.dropout(hidden1, keep_prob)
  
  # 中間層
  hidden2 = nn_layer(dropped1, 64, 64, 'layer2')
  dropped2 = tf.nn.dropout(hidden2, keep_prob)
  
  # 出力層
  y = nn_layer(dropped2, 64, 1, 'layer3', relu=False)


  # 損失
  with tf.name_scope('loss'):
    loss = tf.reduce_mean(tf.square(y_ - y))
    tf.summary.scalar('loss', loss)

  # 学習
  with tf.name_scope('train'):
    train_step = tf.train.AdagradOptimizer(FLAGS.learning_rate).minimize(loss)


  # for TensorBoard
  merged = tf.summary.merge_all()
  train_writer = tf.summary.FileWriter(FLAGS.summaries_dir + '/train', sess.graph)
  test_writer  = tf.summary.FileWriter(FLAGS.summaries_dir + '/test')
  tf.global_variables_initializer().run()


  # データとドロップアウト・キープ率の切り替え
  def feed_dict(train):
    if train:
      xs, ys = x_train, y_train
      k  = FLAGS.dropout
    else:
      xs, ys = x_test, y_test
      k  = 1.0
    return {x: xs, y_: ys, keep_prob: k}


  # 学習ルーチン
  for i in range(FLAGS.max_steps):
    # 学習
    sess.run(train_step, feed_dict=feed_dict(True))
    # 訓練データに対する評価
    summary, train_loss = sess.run([merged, loss], feed_dict=feed_dict(True))
    train_writer.add_summary(summary, i)
    # テスト・データに対する評価
    summary, test_loss, est = sess.run([merged, loss, y], feed_dict=feed_dict(False))
    test_writer.add_summary(summary, i)
    if i == 0 or i % np.power(10, np.floor(np.log10(i))) == 0:
      print('Train loss and test loss at step %s: %s, %s' % (i, train_loss, test_loss))


def main(_):
  if tf.gfile.Exists(FLAGS.summaries_dir):
    tf.gfile.DeleteRecursively(FLAGS.summaries_dir)
  tf.gfile.MakeDirs(FLAGS.summaries_dir)
  train()

if __name__ == '__main__':
  tf.app.run()
