import numpy as np
import seaborn as sns
import pandas as pd
from sklearn.metrics import accuracy_score, confusion_matrix, classification_report
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt

# Load the dataset
csv_file_path = "garments_worker_productivity.csv"
df = pd.read_csv(csv_file_path)

# Inspect the distribution
print(df["actual_productivity"].describe())

# Transform ‘actual_productivity’ to a binary variable
threshold = 0.5
df["productivity_class"] = (df["actual_productivity"] >= threshold).astype(int)

# Check the transformation
print(df["productivity_class"].value_counts())

# Visualize the distribution of classes
sns.countplot(x="productivity_class", data=df)
plt.title("Distribution of Productivity Classes")
plt.show()

# Define features and target
X = df.drop(["actual_productivity", "productivity_class"], axis=1)
y = df["productivity_class"]

# Convert categorical variables into dummies
X = pd.get_dummies(X)

# Split the data into training and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Normalize - Fit StandardScaler on X_train and transform both X_train and X_test 
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# Display normalized data
print(pd.DataFrame(X_train_scaled, columns=X_train.columns).head())

# Confirm the splitting
print(f"Training Set Size: {len(X_train)}")
print(f"Test Set Size: {len(X_test)}")

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def sigmoid_derivative(x):
    return sigmoid(x) * (1 - sigmoid(x))

class NeuralNetwork:
    def __init__(self, x, y, hidden_neurons=20):
        # Initialize parameters
        self.input = x
        self.weights1 = np.random.rand(self.input.shape[1], hidden_neurons)
        self.bias1 = np.random.rand(1, hidden_neurons)
        self.weights2 = np.random.rand(hidden_neurons, 1)
        self.bias2 = np.random.rand(1, 1)
        self.y = y
        self.output = np.zeros(self.y.shape)

    def feedforward(self):
        # Perform feedforward operation
        self.layer1 = sigmoid(np.dot(self.input, self.weights1) + self.bias1)
        self.output = sigmoid(np.dot(self.layer1, self.weights2) + self.bias2)

    def backprop(self):
        # Perform backpropagation
        error = self.y - self.output
        
        # Calculate gradients for weights and biases based on error
        d_weights2 = np.dot(self.layer1.T, (2 * error * sigmoid_derivative(self.output)))
        d_bias2 = np.sum(2 * error * sigmoid_derivative(self.output), axis=0, keepdims=True)

        d_layer1 = np.dot(2 * error * sigmoid_derivative(self.output), self.weights2.T)
        d_weights1 = np.dot(self.input.T, (d_layer1 * sigmoid_derivative(self.layer1)))
        d_bias1 = np.sum(d_layer1 * sigmoid_derivative(self.layer1), axis=0, keepdims=True)
        
        # Update parameters
        self.weights1 += d_weights1
        self.bias1 += d_bias1
        self.weights2 += d_weights2
        self.bias2 += d_bias2

    def train(self, epochs=1000):
        # Train the neural network for a given number of epochs
        for epoch in range(epochs):
            self.feedforward()
            self.backprop()

    def predict(self, X):
        # Function to perform prediction using the neural network
        self.input = X
        self.feedforward()
        return self.output

# Function to create and train the network
def create_and_train_nn(X_train, y_train, hidden_neurons, epochs=1000):
    y_train_np = y_train.to_numpy().reshape(-1, 1)  # Convert Series to numpy array and reshape
    nn = NeuralNetwork(X_train, y_train_np, hidden_neurons=hidden_neurons)
    nn.train(epochs=epochs)
    return nn

# Function to evaluate network performance
def evaluate_nn(nn, X_test, y_test):
    y_test_np = y_test.to_numpy().reshape(-1, 1)  # Convert Series to numpy array and reshape
    predictions = (nn.predict(X_test) > 0.5).astype(int)
    accuracy = accuracy_score(y_test_np, predictions.flatten())
    confusion_mat = confusion_matrix(y_test_np, predictions.flatten())
    classification_rep = classification_report(y_test_np, predictions.flatten(), zero_division=0)
    return accuracy, confusion_mat, classification_rep

# Initialize network configurations
hidden_neurons_configs = [10, 20, 30]
results = {}

for neurons in hidden_neurons_configs:
    nn = create_and_train_nn(X_train_scaled, y_train, neurons, epochs=1500)
    accuracy, confusion_mat, class_report = evaluate_nn(nn, X_test_scaled, y_test)
    results[neurons] = {
        "Neural Network": nn,
        "Accuracy": accuracy,
        "Confusion Matrix": confusion_mat,
        "Classification Report": class_report
    }

# Print results for each configuration
for config, result in results.items():
    print(f"Results for {config} Hidden Neurons:")
    print("Accuracy:", result["Accuracy"])
    print("Confusion Matrix:\n", result["Confusion Matrix"])
    print("Classification Report:\n", result["Classification Report"])
    print()

does this code accomplish these tasks?:
1.	Background and Dataset
The garment industry is one of the key examples of the industrial globalization of this modern era. There are many manual operations in this business, which requires a lot of effort. Employee performance in terms of production and delivery in clothing manufacturing enterprises is a major factor in meeting the enormous demand for textile products around the world. Hence, monitoring, evaluating, and forecasting the productivity performance of the factory work teams is highly desired by the decision-makers in the garment business.
In this study, you will use Productivity Prediction of Garment Employees dataset to classify it as satisfactory or unsatisfactory productivity.
The dataset consists of 1197 instances and 15 features. For dataset description see UCI Machine Learning Library at:
 https://archive.ics.uci.edu/dataset/597/productivity+prediction+of+garment+employees
This study consists of two parts:
•	Part 1 is about implementing and training a multilayer feedforward neural network to classify productivity data as satisfactory or unsatisfactory.
•	Part 2 is about applying computational intelligence techniques to classify the data based on various features. 
2.	Part 1 Tasks
A.	Dataset Preparation
2.1	The original class label (actual_productivity) is a continuous variable. You must first transform it to a discrete variable so that you can apply Binary Classification on it. Visualize the distribution of features and classes, normalize the data, and divide it into training and test sets.
B.	Developing ANN from Scratch
2.2	Construct a 3-layers feedforward network with X input neurons, 20 hidden neurons, and Y output neurons. You must identify the values of X and Y yourself (Hint: Lecture # 3 ANN architecture design). Identify an appropriate activation function and justify your choice.
2.3	Write a computer program using a high-level programming language of your choice (Python with NumPy or C/C++ or Java) to train and test the neural network using the Backpropagation algorithm from scratch WITHOUT using any machine learning library. 
C.	Performance Evaluation
2.4	Evaluate the NN using state-of-the-art evaluation metrics. 
D.	Performance Comparison
2.5	Train a new neural network classifier with 10 hidden neurons and evaluate its performance. Repeat the process for training and testing neural network classifier with 30 hidden neurons.
2.6	Contrast the differences and compare among the performance of three trained neural network classifiers (i.e., with 20, 10 and 30 hidden neurons). Justify your observations.