#!/usr/bin/env python
# coding: utf-8

# In[1]:


import pandas as pd
import numpy as np


# In[2]:


import kagglehub
path = kagglehub.dataset_download("dhruvildave/english-handwritten-characters-dataset")


# In[3]:


df = pd.read_csv("english.csv")
df


# In[4]:


from PIL import Image
from tqdm import tqdm

images = []

for path in tqdm(df['image']):
    with Image.open(path) as img:
        img_grey = img.convert('L').resize((28, 28))
        normalised = 1.0 - (np.array(img_grey, dtype=np.float32).flatten() / 255.0)
        images.append(normalised)


# In[5]:


X = np.array(images)
n = X.shape[0] # 28 * 28
X = np.c_[np.ones(n), X]

y, label = pd.factorize(df["label"])


# In[6]:


from sklearn.model_selection import train_test_split

x_train, x_test, y_train, y_test = train_test_split(X, y, test_size=0.2)


# In[7]:


# perpcetron

def step_activation(z):
    return np.where(z >= 0, 1, -1)

def train_bin_pla(X, y, lr=0.1, epochs=50):
    n_samp, n_feat = X.shape

    w = np.zeros(n_feat)

    for epoch in range(epochs):
        errors = 0

        for i in range(n_samp):
            z = np.dot(X[i], w)
            y_pred = step_activation(z)

            if y_pred != y[i]:
                w += lr * y[i] * X[i]
                errors += 1

        # early stop
        if errors == 0:
            break

    return w

def fit_pla(X, y, n_class, lr=0.1, epochs=50):
    n_feat = X.shape[1]

    W = np.zeros((n_class, n_feat))

    for clas in tqdm(range(n_class)):
        y_binary = np.where(y == clas, 1, -1)
        W[clas] = train_bin_pla(X, y_binary)

    return W

def predict_pla(X, W):
    scores = np.dot(X, W.T)
    return np.argmax(scores, axis=1), scores


# In[8]:


def plot_confusion_matrix(y_true, y_pred, labels, title="Confusion Matrix"):
    cm = confusion_matrix(y_true, y_pred)
    fig, ax = plt.subplots(figsize=(10, 10))
    ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=labels).plot(
        ax=ax, cmap="coolwarm", colorbar=False
    )
    plt.xticks(rotation=90)
    plt.title(title)
    plt.tight_layout()
    plt.show()


# In[9]:


# Train One-vs-Rest PLA
num_classes = len(np.unique(y_train))
W = fit_pla(x_train, y_train, n_class=num_classes, lr=0.01, epochs=100)

train_preds_pla, _ = predict_pla(x_train, W)
test_preds_pla, test_scores_pla = predict_pla(x_test, W)


# In[10]:


import matplotlib.pyplot as plt
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
plot_confusion_matrix(y_test, test_preds_pla, label, title="Confusion Matrix - PLA")


# In[12]:


from sklearn.neural_network import MLPClassifier

mlp_clf = MLPClassifier(
    hidden_layer_sizes=(64, 32),
    activation="relu",
    solver='adam',
    max_iter=500,
    random_state=42
)

mlp_clf.fit(x_train, y_train)


# In[15]:


from sklearn.metrics import classification_report, accuracy_score

predictions = mlp_clf.predict(x_test)
print("Accuracy:", accuracy_score(y_test, predictions))
print(classification_report(y_test, predictions))


# In[16]:


plot_confusion_matrix(y_test, predictions, label, title="Confusion Matrix - PLA")

