import numpy as np
import sklearn as sk
from sklearn.pipeline import Pipeline

from diffprivlib import models

X_train = np.loadtxt("https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.data",
                        usecols=(0, 4, 10, 11, 12), delimiter=", ")
y_train = np.loadtxt("https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.data",
                        usecols=14, dtype=str, delimiter=", ")

X_test = np.loadtxt("https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.test",
                        usecols=(0, 4, 10, 11, 12), delimiter=", ", skiprows=1)

y_test = np.loadtxt("https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.test",
                        usecols=14, dtype=str, delimiter=", ", skiprows=1)
# Must trim trailing period "." from label
y_test = np.array([a[:-1] for a in y_test])

"""
Explanation: Training differentially private pipelines
We start by importing the required libraries and modules and collecting the data that we need from the Adult dataset.
End of explanation
"""


pipe = Pipeline([
    ('scaler', sk.preprocessing.StandardScaler()),
    ('pca', sk.decomposition.PCA(2)),
    ('lr', sk.linear_model.LogisticRegression(solver="lbfgs"))
])

"""
Explanation: Pipeline with no privacy
To begin, let's train and test a scikit-learn pipeline without any privacy guarantees. We first use StandardScaler to normalise the data to zero mean and unit variance, then use PCA to reduce the dimensionality of the system, and then use LogisticRegression as a classifier.
End of explanation
"""


pipe.fit(X_train, y_train)
baseline = pipe.score(X_test, y_test)
print("Non-private test accuracy: %.2f%%" % (baseline * 100))

"""
Explanation: We now train the model, and save the test accuracy as a baseline.
End of explanation
"""


dp_pipe = Pipeline([
    ('scaler', models.StandardScaler(bounds=([17, 1, 0, 0, 1], [90, 160, 10000, 4356, 99]))),
    ('pca', models.PCA(2, data_norm=5, centered=True)),
    ('lr', models.LogisticRegression(data_norm=5))
])

dp_pipe.fit(X_train, y_train)
print("Differentially private pipeline accuracy (epsilon=3): %.2f%%" % (dp_pipe.score(X_test, y_test) * 100))

"""
Explanation: Differentially private pipeline
Using diffprivlib, we can now train a differentially private pipeline. We use the same components as in our pipeline above, but with each component satisfying differential privacy. We decide on the bounds and data_norm parameters by trial and error for this example. In practice, these hyperparameters should be chosen using non-sensitive data, i.e. from metadata provided by the data owner.
End of explanation
"""


epsilons = np.logspace(-3, 0, 500)
pipe_accuracy = []

for epsilon in epsilons:
    _eps = epsilon / 3
    dp_pipe.set_params(scaler__epsilon=_eps, pca__epsilon=_eps, lr__epsilon=_eps)
    
    dp_pipe.fit(X_train, y_train)
    pipe_accuracy.append(dp_pipe.score(X_test, y_test))

"""
Explanation: Let's now train the pipeline across a range of epsilons.
End of explanation
"""


import pickle

pickle.dump((epsilons, baseline, pipe_accuracy), open("pipeline_accuracy_500.p", "wb" ) )

"""
Explanation: Let's save the results so they can be used later.
End of explanation
"""


import matplotlib.pyplot as plt
import pickle

epsilons, baseline, pipe_accuracy = pickle.load(open("pipeline_accuracy_500.p", "rb"))

plt.semilogx(epsilons, pipe_accuracy, label="Differentially private pipeline", zorder=10)
plt.plot(epsilons, np.ones_like(epsilons) * baseline, dashes=[2,2], label="Non-private pipeline", zorder=5)
plt.title("Differentially private pipeline accuracy")
plt.xlabel("epsilon")
plt.ylabel("Accuracy")
plt.ylim(0, 1)
plt.xlim(epsilons[0], epsilons[-1])
plt.legend(loc=4)
plt.show()

"""
Explanation: Results
We can now plot the results, showing that non-private accuracy is matched from approximately epsilon = 0.1.
End of explanation
"""


