# -*- coding: UTF-8 -*-
import numpy as np
import tensorflow as tf
import math


def generateLinearData(dimension, num):
    np.random.seed(1024)
    beta = np.array(range(dimension)) + 1
    x = np.random.random((num, dimension))
    epsilon = np.random.random((num, 1))
    # 将被预测值写成矩阵形式,会极大加快速度
    y = x.dot(beta).reshape((-1, 1)) + epsilon
    return x, y


def createLinearModel(dimension):
    np.random.seed(1024)
    # 定义 x 和 y
    x = tf.placeholder(tf.float64, shape=[None, dimension], name='x')
    # 写成矩阵形式会大大加快运算速度
    y = tf.placeholder(tf.float64, shape=[None, 1], name='y')
    # 定义参数估计值和预测值
    betaPred = tf.Variable(np.random.random([dimension, 1]))
    yPred = tf.matmul(x, betaPred, name='y_pred')
    # 定义损失函数
    loss = tf.reduce_mean(tf.square(yPred - y))
    model = {
        'loss_function': loss,
        'independent_variable': x,
        'dependent_variable': y,
        'prediction': yPred,
        'model_params': betaPred
    }
    return model


def stochasticGradientDescent(X, Y, model, learningRate=0.01,
                              miniBatchFraction=0.01, epoch=10000, tol=1.e-6):
    method = tf.train.GradientDescentOptimizer(learning_rate=learningRate)
    optimizer = method.minimize(model['loss_function'])

    # 增加日志
    tf.summary.scalar('loss_function1', model['loss_function'])
    tf.summary.histogram('params1', model['model_params'])
    tf.summary.scalar('first_param1', tf.reduce_mean(model['model_params'][0]))
    tf.summary.scalar('last_param1', tf.reduce_mean(model['model_params'][-1]))
    summary = tf.summary.merge_all()
    # 程序运行结束后执行 tensorboard --logdir logs/
    summaryWriter = createSummaryWriter('logs/sto_gradient_descent')


    # TF 开始运行
    sess = tf.Session()
    init = tf.global_variables_initializer()
    sess.run(init)

    # 迭代梯度下降法
    step = 0
    batchSize = int(X.shape[0] * miniBatchFraction)
    batchNum = int(math.ceil(1 / miniBatchFraction))
    prevLoss = np.inf
    diff = np.inf
    # 当损失函数的变动小于阈值或达到最大循环次数,则停止迭代
    while (step < epoch) & (diff > tol):
        for i in range(batchNum):
            # 选取小批次训练数据
            batchX = X[i * batchSize: (i+1) * batchSize]
            batchY = Y[i * batchSize: (i+1) * batchSize]
            # 迭代模型参数
            sess.run([optimizer],
                     feed_dict={
                         model['independent_variable']: batchX,
                         model['dependent_variable']: batchY
                     })
            # 计算损失函数

            _, summaryStr, loss = sess.run(
                [summary, model['loss_function']],
                feed_dict={
                    model['independent_variable']: X,
                    model['dependent_variable']: Y
                }
            )
            summaryWriter.add_summary(summaryStr, step)
            # 计算损失函数的变动
            diff = abs(prevLoss - loss)
            prevLoss = loss
            if diff <= tol:
                break
        step += 1



def gradientDescent(X, Y, model, learningRate=0.01, maxIter=10000, tol=1.e-6):
    # 确定最优算法
    method = tf.train.GradientDescentOptimizer(learning_rate=learningRate)
    optimizer = method.minimize(model['loss_function'])
    # 增加日志
    tf.summary.scalar('loss_function', model['loss_function'])
    tf.summary.histogram('params', model['model_params'])
    tf.summary.scalar('first_param', tf.reduce_mean(model['model_params'][0]))
    tf.summary.scalar('last_param', tf.reduce_mean(model['model_params'][-1]))
    summary = tf.summary.merge_all()
    # 程序运行结束后执行 tensorboard --logdir logs/
    summaryWriter = createSummaryWriter('logs/gradient_descent')

    # TF 开始运行
    sess = tf.Session()
    # 产生初始参数
    init = tf.global_variables_initializer()
    sess.run(init)

    # 迭代梯度下降
    step = 0
    prevLoss = np.inf
    diff = np.inf
    # 当损失函数的变动小于阈值或达到最大循环次数,则停止迭代
    while (step < maxIter) & (diff > tol):
        _, summaryStr, loss = sess.run(
            [optimizer, summary, model['loss_function']],
            feed_dict={
                model['independent_variable']: X,
                model['dependent_variable']: Y
            }
        )
        summaryWriter.add_summary(summaryStr, step)
        # 计算损失函数的变动
        diff = abs(prevLoss - loss)
        prevLoss = loss
        step += 1
    summaryWriter.close()


def createSummaryWriter(logPath):
    # 检查是否有老的文件,有的话清理
    if tf.gfile.Exists(logPath):
        tf.gfile.DeleteRecursively(logPath)
    summaryWriter = tf.summary.FileWriter(logPath, graph=tf.get_default_graph())
    return summaryWriter



def run():
    # 自变量个数
    dimension = 30
    num = 10000
    # 随机产生模型数据
    X, Y = generateLinearData(dimension, num)
    # 定义模型
    model = createLinearModel(dimension)
    # 使用梯度下降估计模型参数
    gradientDescent(X, Y, model)


if __name__ == '__main__':
    run()