import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

from models import linear_model, logistic_model, log_cost, log_cost_dev, gd_update
from models import binary_confusion_matrix, std_normalize, binary_accuracy, create_parameters, data_normalize
from sklearn.model_selection import train_test_split

%matplotlib inline

"""
Explanation: Iris Classification with Logistic Regression
End of explanation
"""


df = pd.read_csv('./data/iris.csv')
df = df.reindex(np.random.permutation(df.index))
df.info()

df['IsSetosa'] = df['Species'].apply(lambda a: 1.0 if a=='Iris-setosa' else 0)
data = df[['SepalLengthCm', 'SepalWidthCm', 'PetalLengthCm', 'PetalWidthCm', 'IsSetosa']]
data.head()

train, test = train_test_split(data, test_size=0.2)
train_X = np.array(train[['SepalLengthCm', 'SepalWidthCm', 'PetalLengthCm', 'PetalWidthCm']])
train_y = np.array(train[['IsSetosa']])

np.mean(train_X, axis=0)

train_stds, train_means = std_normalize(train_X)

np.mean(train_X, axis=0)

np.std(train_X, axis=0)

"""
Explanation: 1), prepare data
End of explanation
"""


feature_size = train_X.shape[1]
sample_count = train_X.shape[0]

W, b = create_parameters(feature_size)

threshold = 0.5
lr = 0.01

for epoch in range(0, 1000):
    h = logistic_model(train_X, W, b)
    dW, db = log_cost_dev(train_X, train_y, h)
    W, b = gd_update(W, b, dW, db, lr)
    if (epoch + 1) % 100 == 0:
        cur_cost = log_cost(h, train_y)
        conf = binary_confusion_matrix(h, train_y, threshold=threshold)
        print('epoch: {0}, cost: {1}, conf: {2}'.format(epoch + 1, cur_cost, conf))

predictions = logistic_model(train_X, W, b)
final_cost = log_cost(predictions, train_y)
conf = binary_confusion_matrix(predictions, train_y, threshold=threshold)
print('training finished!')
print('final cost: {0}, conf: {1}'.format(final_cost, conf))

"""
Explanation: 2), train
End of explanation
"""


test_X = np.array(test[['SepalLengthCm', 'SepalWidthCm', 'PetalLengthCm', 'PetalWidthCm']])
test_y = np.array(test[['IsSetosa']])
data_normalize(test_X, train_stds, train_means)

test_h = logistic_model(test_X, W, b)
test_cost = log_cost(test_h, test_y)
test_conf = binary_confusion_matrix(test_h, test_y, threshold=threshold)
print('test cost: {0}, conf: {1}'.format(test_cost, test_conf))

"""
Explanation: 3). try test data
End of explanation
"""


df['Species'].unique()

df['IsSetosa'] = df['Species'].apply(lambda a: 1.0 if a=='Iris-setosa' else 0)
df['IsVericolor'] = df['Species'].apply(lambda a: 1.0 if a=='Iris-versicolor' else 0)
df['IsVirginica'] = df['Species'].apply(lambda a: 1.0 if a=='Iris-virginica' else 0)
data = df[['SepalLengthCm', 'SepalWidthCm', 'PetalLengthCm', 'PetalWidthCm', 'IsSetosa', 'IsVericolor', 'IsVirginica']]

train, test = train_test_split(data, test_size=0.2)
train_X = np.array(train[['SepalLengthCm', 'SepalWidthCm', 'PetalLengthCm', 'PetalWidthCm']])
train_y0 = np.array(train[['IsSetosa']])
train_y1 = np.array(train[['IsVericolor']])
train_y2 = np.array(train[['IsVirginica']])
train_y_all = np.array(train[['IsSetosa', 'IsVericolor', 'IsVirginica']])

test_X = np.array(test[['SepalLengthCm', 'SepalWidthCm', 'PetalLengthCm', 'PetalWidthCm']])
test_y_all = np.array(test[['IsSetosa', 'IsVericolor', 'IsVirginica']])

x_means, x_stds = std_normalize(train_X)
data_normalize(test_X, x_means, x_stds)

"""
Explanation: so, this is only for Setosa, we want generalize binary classification to multi-classies
Iris, one-vs-all
1), prepare data again
End of explanation
"""


def train_lr_classifier(X, y, lr=0.01, threshold=0.5, epochs=1000, step_size=100):
    feature_size = X.shape[1]
    sample_count = y.shape[0]
    W, b = create_parameters(feature_size)
    
    for epoch in range(0, epochs):
        h = logistic_model(X, W, b)
        dW, db = log_cost_dev(X, y, h)
        W, b = gd_update(W, b, dW, db, lr)
        if (epoch + 1) % step_size == 0:
            cur_cost = log_cost(h, y)
            conf = binary_confusion_matrix(h, y, threshold=threshold)
            print('epoch: {0}, cost: {1}, conf: {2}'.format(epoch + 1, cur_cost, conf))

    predictions = logistic_model(X, W, b)
    final_cost = log_cost(predictions, y)
    conf = binary_confusion_matrix(predictions, y, threshold=threshold)
    print('training finished!')
    print('final cost: {0}, conf: {1}'.format(final_cost, conf))
    return W, b

m0 = train_lr_classifier(train_X, train_y0, lr=0.01, threshold=0.5)

m1 = train_lr_classifier(train_X, train_y1, lr=0.01, threshold=0.5, epochs=50000, step_size=10000)

m2 = train_lr_classifier(train_X, train_y2, lr=0.01, threshold=0.5, epochs=50000, step_size=10000)

"""
Explanation: 2), define some utils
End of explanation
"""


import models as ml

feature_size = train_X.shape[1]
sample_count = train_X.shape[0]
class_count = train_y_all.shape[1]

W, b = ml.create_parameters(feature_size, class_count)

for epoch in range(0, 100000):
    h = ml.softmax_regression_model(train_X, W, b)
    dW, db = ml.crossentropy_cost_dev(train_X, train_y_all, h)
    W, b = ml.gd_update(W, b, dW, db, lr=0.01)
    if (epoch + 1) % 10000 == 0:
        cur_cost = ml.crossentropy_cost(h, train_y_all)
        cur_acc = ml.categorical_accuracy(h, train_y_all)
        print('epoch: {0}, cost: {1}, acc: {2}'.format(epoch + 1, cur_cost, cur_acc))

predictions = ml.softmax_regression_model(train_X, W, b)
final_cost = ml.crossentropy_cost(predictions, train_y_all)
final_acc = ml.categorical_accuracy(predictions, train_y_all)
print('training finished!')
print('train cost: {0}, acc: {1}'.format(final_cost, final_acc))

test_h = ml.softmax_regression_model(test_X, W, b)
test_cost = ml.crossentropy_cost(test_h, test_y_all)
test_acc = ml.categorical_accuracy(test_h, test_y_all)
print('test cost: {0}, acc: {1}'.format(test_cost, test_acc))

"""
Explanation: Classify multi-class with Softmax
What is softmax?
$$
\begin{equation}
Softmax(x_j) = \frac{e^{x_j}}{\sum_{i=1}^m e^{x_{i}}}
\end{equation}
$$
See details in models.py
End of explanation
"""


np.argmax(ml.softmax_regression_model(train_X[0:4], W, b), axis=1)

np.argmax(train_y_all[0:4], axis=1)

"""
Explanation: let's take a look
End of explanation
"""


