import diffprivlib.models as dp
import numpy as np
from sklearn.linear_model import LogisticRegression

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=", ")

np.unique(y_train)

"""
Explanation: Logistic Regression with Differential Privacy
We start by importing the required libraries and modules and collecting the data that we need from the Adult dataset.
End of explanation
"""


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])

np.unique(y_test)

"""
Explanation: Let's also collect the test data from Adult to test our models once they're trained.
End of explanation
"""


clf = LogisticRegression(solver="lbfgs")
clf.fit(X_train, y_train)

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

"""
Explanation: Logistic Regression with no privacy
To begin, let's first train a regular (non-private) logistic regression classifier, and test its accuracy.
End of explanation
"""


dp_clf = dp.LogisticRegression()
dp_clf.fit(X_train, y_train)

print("Differentially private test accuracy (epsilon=%.2f): %.2f%%" % 
     (dp_clf.epsilon, dp_clf.score(X_test, y_test) * 100))

"""
Explanation: Differentially private logistic regression
Using the diffprivlib.models.LogisticRegression module of diffprivlib, we can train a logistic regression classifier while satisfying differential privacy.
If we don't specify any parameters, the model defaults to epsilon = 1 and data_norm = None. If the norm of the data is not specified at initialisation (as in this case), the norm will be calculated on the data when .fit() is first called and a warning will be thrown as it causes a privacy leak. To ensure no additional privacy leakage, we should specify the data norm explicitly as an argument, and choose the bounds indepedently of the data (i.e. using domain knowledge).
Additionally, the high data_norm that is read from the data in this instance gives poor results, with accuracy only slightly better than random. This is as a result of the large amount of noise requires to protect data spread over a large domain. By clipping the data to a smaller domain, accuracy improves markedly, as demonstrated below.
End of explanation
"""


dp_clf = dp.LogisticRegression(epsilon=float("inf"), data_norm=1e5)
dp_clf.fit(X_train, y_train)

print("Agreement between non-private and differentially private (epsilon=inf) classifiers: %.2f%%" % 
     (dp_clf.score(X_test, clf.predict(X_test)) * 100))

"""
Explanation: By setting epsilon = float("inf"), we can produce the same result as the non-private logistic regression classifer.
End of explanation
"""


accuracy = []
epsilons = np.logspace(-3, 1, 500)

for eps in epsilons:
    dp_clf = dp.LogisticRegression(epsilon=eps, data_norm=100)
    dp_clf.fit(X_train, y_train)
    accuracy.append(dp_clf.score(X_test, y_test))

"""
Explanation: Tradeoff of accuracy and privacy
We can also visualise the tradeoff between accuracy and epsilon using matplotlib.
End of explanation
"""


import pickle

pickle.dump((epsilons, baseline, accuracy), open("lr_accuracy_500.p", "wb" ) )

"""
Explanation: Let's save the results using pickle so we can reproduce the plot easily in the future.
End of explanation
"""


import matplotlib.pyplot as plt
import pickle

epsilons, baseline, accuracy = pickle.load(open("lr_accuracy_500.p", "rb"))

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

"""
Explanation: Let's plot the results using matplotlib. The discontinuty observed near epsilon = 10 is an artifact of the model. Because of the norm-clipping applied to the dataset before training (data_norm=100), the accuracy plateaus without reaching the non-private baseline.
End of explanation
"""


