# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
from scipy import optimize
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc",
                      size=14)    # 解决windows环境下画图汉字乱码问题


def LogisticRegression():
    data = loadtxtAndcsv_data("data2.txt", ",", np.float64)
    X = data[:, 0:-1]
    y = data[:, -1]

    plot_data(X, y)  # 作图

    X = mapFeature(X[:, 0], X[:, 1])  # 映射为多项式
    initial_theta = np.zeros((X.shape[1], 1))  # 初始化theta
    initial_lambda = 0.1  # 初始化正则化系数，一般取0.01,0.1,1.....

    # 计算一下给定初始化的theta和lambda求出的代价J
    J = costFunction(initial_theta, X, y, initial_lambda)

   # print J  #输出一下计算的值，应该为0.693147
    # result = optimize.fmin(costFunction, initial_theta, args=(X,y,initial_lambda))    #直接使用最小化的方法，效果不好
    '''调用scipy中的优化算法fmin_bfgs（拟牛顿法Broyden-Fletcher-Goldfarb-Shanno）
    - costFunction是自己实现的一个求代价的函数，
    - initial_theta表示初始化的值,
    - fprime指定costFunction的梯度
    - args是其余测参数，以元组的形式传入，最后会将最小化costFunction的theta返回 
    '''
    result = optimize.fmin_bfgs(
        costFunction, initial_theta, fprime=gradient, args=(X, y, initial_lambda))
    p = predict(X, result)  # 预测
   # print u'在训练集上的准确度为%f%%'%np.mean(np.float64(p==y)*100)   # 与真实值比较，p==y返回True，转化为float

    X = data[:, 0:-1]
    y = data[:, -1]
    plotDecisionBoundary(result, X, y)  # 画决策边界


# 加载txt和csv文件
def loadtxtAndcsv_data(fileName, split, dataType):
    return np.loadtxt(fileName, delimiter=split, dtype=dataType)

# 加载npy文件


def loadnpy_data(fileName):
    return np.load(fileName)

# 显示二维图形


def plot_data(X, y):
    pos = np.where(y == 1)  # 找到y==1的坐标位置
    neg = np.where(y == 0)  # 找到y==0的坐标位置
    # 作图
    plt.figure(figsize=(15, 12))
    plt.plot(X[pos, 0], X[pos, 1], 'ro')        # red o
    plt.plot(X[neg, 0], X[neg, 1], 'bo')        # blue o
    plt.title(u"两个类别散点图", fontproperties=font)
    plt.show()

# 映射为多项式


def mapFeature(X1, X2):
    degree = 2                     # 映射的最高次方
    out = np.ones((X1.shape[0], 1))  # 映射后的结果数组（取代X）
    '''
    这里以degree=2为例，映射为1,x1,x2,x1^2,x1x2,x2^2
    '''
    for i in np.arange(1, degree+1):
        # i = 1, 2
        for j in range(i+1):
            # 矩阵直接乘相当于matlab中的点乘.*
            # 幂次的和为i
            temp = X1**(i-j)*(X2**j)
            out = np.hstack((out, temp.reshape(-1, 1)))
    return out

# 代价函数


def costFunction(initial_theta, X, y, inital_lambda):
    m = len(y)
    J = 0

    h = sigmoid(np.dot(X, initial_theta))    # 计算h(z)
    theta1 = initial_theta.copy()           # 因为正则化j=1从1开始，不包含0，所以复制一份，前theta(0)值为0
    theta1[0] = 0

    temp = np.dot(np.transpose(theta1), theta1)
    J = (-np.dot(np.transpose(y), np.log(h))-np.dot(np.transpose(1-y),
                                                    np.log(1-h))+temp*inital_lambda/2)/m   # 正则化的代价方程
    return J

# 计算梯度


def gradient(initial_theta, X, y, inital_lambda):
    m = len(y)
    grad = np.zeros((initial_theta.shape[0]))

    h = sigmoid(np.dot(X, initial_theta))  # 计算h(z)
    theta1 = initial_theta.copy()
    theta1[0] = 0

    grad = np.dot(np.transpose(X), h-y)/m+inital_lambda/m*theta1  # 正则化的梯度
    return grad


# S型函数
def sigmoid(z):
    h = np.zeros((len(z), 1))    # 初始化，与z的长度一置
    h = 1.0/(1.0+np.exp(-z))
    return h


# 画决策边界
def plotDecisionBoundary(theta, X, y):
    pos = np.where(y == 1)  # 找到y==1的坐标位置
    neg = np.where(y == 0)  # 找到y==0的坐标位置
    # 作图
    plt.figure(figsize=(15, 12))
    plt.plot(X[pos, 0], X[pos, 1], 'ro')        # red o
    plt.plot(X[neg, 0], X[neg, 1], 'bo')        # blue o
    plt.title(u"决策边界", fontproperties=font)

    #u = np.linspace(30,100,100)
    #v = np.linspace(30,100,100)

    u = np.linspace(-1, 1.5, 50)  # 根据具体的数据，这里需要调整
    v = np.linspace(-1, 1.5, 50)

    z = np.zeros((len(u), len(v)))
    for i in range(len(u)):
        for j in range(len(v)):
            z[i, j] = np.dot(mapFeature(u[i].reshape(1, -1),
                                        v[j].reshape(1, -1)), theta)    # 计算对应的值，需要map

    z = np.transpose(z)
    # 画等高线，范围在[0,0.01]，即近似为决策边界
    plt.contour(u, v, z, [0, 0.01], linewidth=2.0)
    # plt.legend()
    plt.show()

# 预测


def predict(X, theta):
    m = X.shape[0]
    p = np.zeros((m, 1))
    p = sigmoid(np.dot(X, theta))    # 预测的结果，是个概率值

    for i in range(m):
        if p[i] > 0.5:  # 概率大于0.5预测为1，否则预测为0
            p[i] = 1
        else:
            p[i] = 0
    return p


# 测试逻辑回归函数
def testLogisticRegression():
    LogisticRegression()


if __name__ == "__main__":
    testLogisticRegression()
