0% found this document useful (0 votes)
11 views24 pages

German Credit Risk Classification Study

The document outlines a case study on credit risk classification using the German Credit Data Set, focusing on minimizing financial losses by accurately identifying loan applicants' credit risks. It emphasizes the importance of recall for the 'Bad' class due to the high cost of misclassifying bad credit risks as good, and discusses various classification metrics and techniques for handling imbalanced data. The study also details data preprocessing, model architecture, and evaluation best practices for effective classification in a business context.

Uploaded by

rupali.mbaa24119
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
11 views24 pages

German Credit Risk Classification Study

The document outlines a case study on credit risk classification using the German Credit Data Set, focusing on minimizing financial losses by accurately identifying loan applicants' credit risks. It emphasizes the importance of recall for the 'Bad' class due to the high cost of misclassifying bad credit risks as good, and discusses various classification metrics and techniques for handling imbalanced data. The study also details data preprocessing, model architecture, and evaluation best practices for effective classification in a business context.

Uploaded by

rupali.mbaa24119
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Case Study: German Credit Risk Classification

German Credit Data Set, serving as the foundation for a credit risk classification case study aligned with **CRISP-DM to
clearly define the problem, specify business objectives, and understand the cost structure.

Data Source and Goal


[Link]

The data was collected by a German bank and provided by Professor Dr. Hans Hofmann. It classifies loan applicants as
either Good Credit Risk or Bad Credit Risk.

Business Objective
The primary business objective is to minimize financial loss due to loan defaults by accurately identifying applicants
who are likely to default (Bad Credit Risk).

Target Variable (Business Outcome)


Business Term Data Value Count Proportion

Good Credit Risk (Loan Approved - Repaid) 1 700 70%

Bad Credit Risk (Loan Approved - Defaulted) 2 300 30%

Note: The dataset is inherently imbalanced, with a 70:30 ratio of good to bad credit risks.

Business Cost Matrix (Misclassification Penalties)


In credit risk, not all errors are equally costly. Approving a bad applicant (False Positive) is far more damaging than
rejecting a good applicant (False Negative). The original dataset requires the use of a Cost Matrix

Actual \ Predicted 1 (Predicted Good) (Approve Loan) 2 (Predicted Bad) (Reject Loan)

1 (Actual Good) (Profitable) 0 (Correct) 1 (Lost Opportunity - Cost: 1)

2 (Actual Bad) (Default Risk) 5 (Loan Default - Cost: 5) 0 (Correct)

Key Business Insight from Cost Matrix

It is 5 times worse to classify a customer as Good when they are Bad (i.e., approving a defaulter), than it
is to classify a customer as Bad when they are Good (i.e., mistakenly rejecting a profitable customer).
Therefore, the model must be optimized to minimize False Positives (misclassifying actual bad risk as
good).

In this business scenario, Recall for the "Bad" class is critically important. Missing a bad credit risk
(False Negative) is the most expensive mistake we can make.

Data Set Structure


Metric Detail

Total Instances 1000

Total Attributes 20

Attribute Mix 7 Numerical, 13 Categorical

Missing Values None ('No missing values')


Numerical Features (7 Attributes)
These attributes represent quantitative measures and require scaling (e.g., Standardization) during preprocessing to
prevent features with large magnitudes from dominating the model.

Attribute Description Units/Scale

A2 Duration in month Months

A5 Credit amount DM (Deutsche Mark)

A8 Installment rate in % of disposable income Percentage

A11 Present residence since Years

A13 Age in years Years

A16 Number of existing credits at this bank Count

A18 Number of people being liable to provide maintenance for Count

Categorical Features (13 Attributes)


These attributes represent symbolic categories and MUST be One-Hot Encoded (OHE) for use in an Artificial Neural
Network (ANN).

Attribute Description Example Categories

A1 Status of existing checking account A11 ($\lt$ 0 DM), A14 (no checking account)

A3 Credit history A30 (no/paid duly), A34 (critical account)

A4 Purpose A40 (car new), A43 (radio/TV), A49 (business)

A6 Savings account/bonds A61 ($\lt$ 100 DM), A65 (unknown/no savings)

A7 Present employment since ]A71 (unemployed), A75 ($\ge$ 7 years)

A9 Personal status and sex A92 (female), A93 (male: single)

A10 Other debtors / guarantors A101 (none), A103 (guarantor)

A12 Property A121 (real estate), A124 (unknown/no property)

A14 Other installment plans ]A141 (bank), A143 (none)

A15 Housing A151 (rent), A152 (own)

A17 Job A171 (unskilled/unemployed), A173 (skilled employee)

A19 Telephone A191 (none), A192 (yes)

A20 Foreign worker A201 (yes), A202 (no)

Classification Basics
Definition: A supervised learning task where the goal is to predict a discrete class label for a given input. It is like
teaching a child to sort fruits into baskets (Apples, Oranges, Bananas).

Types of Classification:
Binary Classification: Two possible outcomes.

Example: Loan vs. No Loan, Fraudulent vs. Legitimate, Good Credit Vs Bad Credit.
Output layer uses 1 neuron with sigmoid activation (outputs a probability between 0 and 1).
Multi-Class Classification: More than two classes; each sample belongs to exactly one class.

Example: Digit recognition (0-9), E-commerce, Customer service - product category classification.
Output layer uses n neurons (where n = number of classes) with softmax activation (outputs a
probability distribution across all classes).
Multi-Label Classification: More than two classes; each sample can belong to multiple classes simultaneously.

Example: Image tagging (a photo can contain a "beach," "sunset," and "dog") as in Google Lens model.
Output layer uses n neurons with sigmoid activation (each outputs an independent probability).

Classification in a Business Setting


Definition: A process that uses data to predict a categorical outcome that directly impacts business operations, risk,
and profitability.

In this case study output is the probability that the applicant is a "Good" credit risk.

Classification Performance Measures


The Confusion Matrix
A table used to describe the performance of a classification model on a set of test data for which the true values are
known.

For Binary Classification:

Actual \ Predicted Predicted Positive Predicted Negative

Actual Positive True Positive (TP) False Negative (FN)

Actual Negative False Positive (FP) True Negative (TN)

Key Terms:

True Positive (TP): You predicted positive, and it's true.


True Negative (TN): You predicted negative, and it's true.
False Positive (FP): You predicted positive, and it's false. (Type I Error)
False Negative (FN): You predicted negative, and it's false. (Type II Error)

Core Metrics: Formulas & Interpretation


Accuracy
The proportion of total predictions that were correct.
Use when your classes are well-balanced.
Formula: Accuracy = (TP + TN) / (TP + TN + FP + FN)

Precision
The proportion of positive predictions that were actually correct. "How precise are our positive predictions?"
Use when the cost of False Positives (FP) is high (e.g., spam detection, recommending unsafe videos).
Formula: Precision = TP / (TP + FP)

Recall (Sensitivity, True Positive Rate - TPR)


The proportion of actual positives that we correctly identified. "How many of the true positives did we catch?"
Use when the cost of False Negatives (FN) is high (e.g., disease screening, fraud detection).
Formula: Recall = TP / (TP + FN)

F1-Score
The harmonic mean of Precision and Recall. A single score that balances both concerns.
Use when you need a single metric to compare models and you have an imbalanced dataset.
Formula: F1-Score = 2 * (Precision * Recall) / (Precision + Recall)
Trade-off: Precision and Recall are often in a trade-off. Increasing one typically decreases the other.

For the case study


"Bad" as Positive (Risk Management View) is taken
Positive Class = "Bad" Credit (the risky ones we need to catch)
Negative Class = "Good" Credit (the safe ones)

Actual \ Predicted Predicted Bad (Reject) Predicted Good (Approve)

True Positive (TP) False Negative (FN)


Actual Bad (Positive) Correctly rejected a bad loan Wrongly approved a bad loan
Cost: 0 Cost: 5

False Positive (FP) True Negative (TN)


Actual Good (Negative) Wrongly rejected a good loan Correctly approved a good loan
Cost: 1 Cost: 0

**Why Recall is Critically Important:**

Recall (for "Bad" as Positive) = TP / (TP + FN)


Recall measures: "Of all the actual bad credit risks, what percentage did we correctly identify and reject?"
High Recall means we're catching most of the dangerous applicants
Low Recall means we're missing bad credit risks and incurring the $5 cost repeatedly

ROC Curve & AUC


ROC Curve (Receiver Operating Characteristic)
A plot that shows the trade-off between True Positive Rate (Recall) and False Positive Rate (FPR) at various
classification thresholds.
False Positive Rate (FPR): FPR = FP / (FP + TN)
The curve is created by varying the decision threshold from 0 to 1 and plotting TPR vs. FPR at each point.
A random classifier would lie along the diagonal line (dashed line).

AUC (Area Under the ROC Curve)


A single number that summarizes the ROC curve's performance.
Interpretation:
AUC = 1.0: Perfect classifier.
AUC = 0.5: No better than random guessing (the diagonal line).
AUC > 0.8: Generally considered a good model.
The probability that the model ranks a random positive example more highly than a random negative example. It
measures the model's ranking power, not its calibrated probability.

Other Important Measures


Specificity (True Negative Rate): TNR = TN / (TN + FP) = 1 - FPR
Log Loss (Cross-Entropy Loss): Measures the uncertainty of your probabilities by comparing them to the true
labels. A lower log loss is better. This is often the direct loss function used in training ANNs for classification.

For this case study


If we only maximize recall, we could create a disastrous business model:

Extreme Example: The "Reject Everyone" Model

# This model has perfect recall for the "Bad" class


def reject_everyone_model(X):
return [Link](len(X)) # Predict "Bad" for everyone
# Results:
# - Recall for Bad class = 1.0 (perfect! catches all bad customers)
# - Precision for Bad class = 0.05 (terrible! only 5% of rejections are correct)
# - Business outcome: BANKRUPTCY (we reject 95% of good customers)
A model can achieve high recall by being overly paranoid and rejecting too many good customers.

ROC-AUC as the "Ranking Quality" Metric


"What's the probability that a randomly chosen Bad customer gets a higher risk score than a randomly chosen
Good customer?"

AUC = 1.0: Perfect ranking - all Bad customers get higher risk scores than all Good customers
AUC = 0.8: Good ranking - 80% of the time, Bad customers get higher risk scores than Good customers
AUC = 0.5: Random guessing - no ranking ability

ROC-AUC tells us how well the model distinguishes between truly risky vs. safe customers, regardless of our
chosen threshold.

How Recall and ROC-AUC Work Together

Recall Answers: "Can we catch enough bad customers?" ROC-AUC Answers: "How well does our model separate
good from bad customers?"

The Ideal Scenario:

High ROC-AUC (>0.8): Model has strong inherent ability to distinguish risks
High Recall (>0.8): We've set our threshold to catch most bad customers

Sample Scenarios for better understanding

ROC-
Scenario Recall Business Interpretation
AUC

High Model has poor ranking ability; achieving high recall by being overly conservative and
Danger Low (0.6)
(0.9) rejecting many good customers

High Low Model can distinguish risks well, but we're using the wrong threshold and missing too
Ineffective
(0.85) (0.5) many bad customers

High High
Excellent Model has strong ranking ability AND we're catching most bad customers
(0.85) (0.9)

The Problem of Imbalanced Classes


Scenario: When one class significantly outnumbers the other(s).

Example: 99% "Not Fraud" and 1% "Fraud" transactions.

Why is it a problem?

A naive model that always predicts the majority class can achieve very high accuracy but is useless.
The model becomes biased towards the majority class and fails to learn the patterns of the minority class.

Handling Imbalanced Data


1. Resampling Techniques
Oversampling: Increase the number of minority class samples.
Method: Duplicate existing samples or use SMOTE (Synthetic Minority Over-sampling Technique) to create
synthetic samples.
Undersampling: Decrease the number of majority class samples.
Risk: You might lose important information from the majority class.
2. Algorithm-Level Techniques
Use Appropriate Metrics: Stop using Accuracy! Use Precision, Recall, F1-Score, and AUC instead.
Adjust Class Weights: Tell the model to "pay more attention" to the minority class.
In sklearn and [Link] , you can set the class_weight parameter to 'balanced' or a
custom dictionary.

3. Data-Level Techniques
Collect more data for the minority class, if possible.

4. Anomaly Detection
Frame the problem as anomaly detection, where the minority class is treated as an "anomaly."

Other Salient Points to consider for ANN (Deep Learning)


Classification
Data Preprocessing
Feature Scaling: Crucial for ANNs. Use Standardization ( StandardScaler ) or Normalization ( MinMaxScaler )
to ensure stable and fast convergence.
Categorical Encoding: For input features, use one-hot encoding or embedding layers.

Model Architecture & Training


Output Layer Activation:
Binary: sigmoid
Multi-Class: softmax
Loss Function:
Binary: binary_crossentropy
Multi-Class: categorical_crossentropy (one-hot encoded labels) or
sparse_categorical_crossentropy (integer labels)
Overfitting:
Use L1/L2 Regularization in your Dense layers.
Use Dropout layers to randomly turn off neurons during training.
Use Early Stopping to halt training when validation performance stops improving.

Threshold Tuning
The default threshold for classification is 0.5. You can adjust this threshold to favor Precision or Recall based on
your business problem.
Use the ROC curve or Precision-Recall curve to select an optimal threshold.

Evaluation Best Practices


Always use a stratified train-test split to preserve the class distribution in your splits.
Use K-Fold Cross-Validation (with stratification) for a more robust evaluation, especially on small datasets.

Businesss and Data Understanding


The business and Data Undestanding has been defined earlier.

In [1]: import pandas as pd


import numpy as np
import seaborn as sns
import [Link] as plt
from sklearn.model_selection import train_test_split
from [Link] import StandardScaler
from sklearn.linear_model import LogisticRegression
from [Link] import accuracy_score, confusion_matrix, classification_report, roc_curve,roc_auc_sco

In [2]: import tensorflow as tf


import keras
from [Link] .models import Sequential
from [Link] import Dense
from keras import Input
from [Link] import load_model

Data Preparation
In [3]: # Data Pre-processing
column_names = [f'A{i}' for i in range(1, 21)] + ['Risk']
# Load data from the provided content or URL
data = pd.read_csv('[Link]', sep=' ', header=None)
[Link] = column_names

X = [Link]('Risk', axis=1)
# Target: 0 (Good Risk), 1 (Bad Risk)
y = data['Risk'].apply(lambda x: 0 if x == 1 else 1)

# Identify feature types


categorical_cols = [f'A{i}' for i in [1, 3, 4, 6, 7, 9, 10, 12, 14, 15, 17, 19, 20]]
numerical_cols = [col for col in [Link] if col not in categorical_cols]

# Preprocessing: Convert all data to numerical format


X_cat = pd.get_dummies(X[categorical_cols], drop_first=True)
scaler = StandardScaler()
X_num_scaled = scaler.fit_transform(X[numerical_cols])
X_num_scaled = [Link](X_num_scaled, columns=numerical_cols)

X_processed = [Link]([X_num_scaled, X_cat], axis=1)


input_dim = X_processed.shape[1]

# Split the data


X_train, X_test, y_train, y_test = train_test_split(
X_processed, y, test_size=0.2, random_state=42, stratify=y
)

In [4]: len(X_train)

Out[4]: 800

In [5]: len(X_test)

Out[5]: 200

Modelling

Baseline Model - Logistic Regression


In [6]: # Initialize and Train Logistic Regression
log_model = LogisticRegression(solver='liblinear', random_state=42)
log_model.fit(X_train, y_train)

# Predict probabilities and calculate AUC


log_pred_proba = log_model.predict_proba(X_test)[:, 1]
log_auc = roc_auc_score(y_test, log_pred_proba)
log_accuracy = log_model.score(X_test, y_test)

print("2. Baseline Model Performance (Logistic Regression) ")


print(f"Logistic Regression Accuracy: {log_accuracy:.4f}")
print(f"Logistic Regression ROC-AUC: {log_auc:.4f}")

--- 2. Baseline Model Performance (Logistic Regression) ---


Logistic Regression Accuracy: 0.7850
Logistic Regression ROC-AUC: 0.8043
Artificial Neural Network Model
Defining the Neural Network Architecture : Sequential model
The sequential model is the simplest way to build a neural network using keras. The addition of layers one after the
other in sequence defines the neural network architecture.

In [7]: # Initialize the Sequential model


ann_model = Sequential(name="Simple_Credit_ANN")

ann_model.add([Link](shape=(input_dim,)))
# Layer 1: Input Layer (and 1st Hidden Layer)
ann_model.add(Dense(units=32, activation='relu', name = "Hidden_layer"))

# Layer 2: Output Layer


ann_model.add(Dense(units=1, activation='sigmoid', name = "Output_layer"))

print(" ANN Model Architecture ")


ann_model.summary()

--- ANN Model Architecture ---


Model: "Simple_Credit_ANN"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ Layer (type) ┃ Output Shape ┃ Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ Hidden_layer (Dense) │ (None, 32) │ 1,568 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ Output_layer (Dense) │ (None, 1) │ 33 │
└──────────────────────────────────────┴─────────────────────────────┴─────────────────┘
Total params: 1,601 (6.25 KB)
Trainable params: 1,601 (6.25 KB)
Non-trainable params: 0 (0.00 B)

Observe the core building block of neural network model which is the "layer", a data-processing module. The data is
being "filtered" so as to pass on next data which is useful for further layers.

Precisely, layers extract representations out of the data fed into them -- hopefully representations that are more
meaningful for the problem at hand. So usually deep learning models consists of chaining together simple layers which
will implement a form of progressive "data distillation".

Two Dense layers, which are densely-connected (also called "fully-connected") neural layers. The second (and last)
layer is a 1 neuron "sigmoid" layer, which implies that it will return probability scores ( the score from zero to
probabilities being 1). Score will be the probability that the current risk is bad risk (1) or good risk(0).

Before training, few more things to be provided:

A loss function: is the way in which deep learning model measures the performance on its training data, and thus
getting feedback to tweak the learning in right direction.
An optimizer: this is the mechanism through which the deep learning model will update itself based on the data it
"sees" and its loss function.
Metrics to monitor during training and testing.

In this model we would use accuracy and AUC to montior first.

Compile and Fit the model

In [8]: # Compile the model


ann_model.compile(
optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy', [Link](name='auc')]
)

print("\n--- ANN Training (The Deep Learning Cycle) ---")


# Train the model
history = ann_model.fit(
X_train, y_train,
epochs=50,
batch_size=32,
validation_split=0.1, # Use 10% of training data for validation tracking - Net training samples = 800
verbose=1 # Set to 1 to show the epoch-by-epoch learning process
)

# Evaluate the model


ann_loss, ann_accuracy, ann_auc = ann_model.evaluate(X_test, y_test, verbose=0)
--- ANN Training (The Deep Learning Cycle) ---
Epoch 1/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 5s 52ms/step - accuracy: 0.6708 - auc: 0.5495 - loss: 0.6281 - val_accuracy: 0.6
875 - val_auc: 0.6422 - val_loss: 0.6000
Epoch 2/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 14ms/step - accuracy: 0.7028 - auc: 0.6230 - loss: 0.5890 - val_accuracy: 0.6
875 - val_auc: 0.7131 - val_loss: 0.5844
Epoch 3/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 1s 22ms/step - accuracy: 0.7028 - auc: 0.6941 - loss: 0.5657 - val_accuracy: 0.7
000 - val_auc: 0.7284 - val_loss: 0.5700
Epoch 4/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 15ms/step - accuracy: 0.7125 - auc: 0.7324 - loss: 0.5485 - val_accuracy: 0.7
125 - val_auc: 0.7429 - val_loss: 0.5550
Epoch 5/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 14ms/step - accuracy: 0.7194 - auc: 0.7604 - loss: 0.5321 - val_accuracy: 0.7
125 - val_auc: 0.7531 - val_loss: 0.5477
Epoch 6/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 14ms/step - accuracy: 0.7194 - auc: 0.7803 - loss: 0.5191 - val_accuracy: 0.7
125 - val_auc: 0.7589 - val_loss: 0.5413
Epoch 7/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 15ms/step - accuracy: 0.7403 - auc: 0.7913 - loss: 0.5076 - val_accuracy: 0.7
125 - val_auc: 0.7636 - val_loss: 0.5316
Epoch 8/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.7444 - auc: 0.7997 - loss: 0.4980 - val_accuracy: 0.7
375 - val_auc: 0.7676 - val_loss: 0.5273
Epoch 9/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.7431 - auc: 0.8085 - loss: 0.4893 - val_accuracy: 0.7
375 - val_auc: 0.7655 - val_loss: 0.5282
Epoch 10/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.7514 - auc: 0.8171 - loss: 0.4802 - val_accuracy: 0.7
500 - val_auc: 0.7640 - val_loss: 0.5235
Epoch 11/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.7583 - auc: 0.8215 - loss: 0.4736 - val_accuracy: 0.7
500 - val_auc: 0.7611 - val_loss: 0.5216
Epoch 12/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 13ms/step - accuracy: 0.7625 - auc: 0.8284 - loss: 0.4664 - val_accuracy: 0.7
625 - val_auc: 0.7593 - val_loss: 0.5253
Epoch 13/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.7694 - auc: 0.8335 - loss: 0.4605 - val_accuracy: 0.7
625 - val_auc: 0.7615 - val_loss: 0.5231
Epoch 14/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 14ms/step - accuracy: 0.7667 - auc: 0.8368 - loss: 0.4553 - val_accuracy: 0.7
750 - val_auc: 0.7604 - val_loss: 0.5201
Epoch 15/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.7708 - auc: 0.8414 - loss: 0.4499 - val_accuracy: 0.7
750 - val_auc: 0.7644 - val_loss: 0.5191
Epoch 16/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.7833 - auc: 0.8451 - loss: 0.4455 - val_accuracy: 0.7
750 - val_auc: 0.7582 - val_loss: 0.5217
Epoch 17/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 1s 24ms/step - accuracy: 0.7764 - auc: 0.8503 - loss: 0.4398 - val_accuracy: 0.7
625 - val_auc: 0.7604 - val_loss: 0.5212
Epoch 18/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.7819 - auc: 0.8537 - loss: 0.4358 - val_accuracy: 0.7
250 - val_auc: 0.7651 - val_loss: 0.5182
Epoch 19/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 1s 23ms/step - accuracy: 0.7944 - auc: 0.8574 - loss: 0.4309 - val_accuracy: 0.7
375 - val_auc: 0.7662 - val_loss: 0.5176
Epoch 20/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 15ms/step - accuracy: 0.7889 - auc: 0.8607 - loss: 0.4270 - val_accuracy: 0.7
375 - val_auc: 0.7647 - val_loss: 0.5245
Epoch 21/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.7861 - auc: 0.8623 - loss: 0.4235 - val_accuracy: 0.7
125 - val_auc: 0.7596 - val_loss: 0.5204
Epoch 22/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.7944 - auc: 0.8669 - loss: 0.4186 - val_accuracy: 0.7
125 - val_auc: 0.7636 - val_loss: 0.5233
Epoch 23/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 1s 20ms/step - accuracy: 0.8028 - auc: 0.8702 - loss: 0.4145 - val_accuracy: 0.7
000 - val_auc: 0.7662 - val_loss: 0.5218
Epoch 24/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.8125 - auc: 0.8730 - loss: 0.4106 - val_accuracy: 0.7
000 - val_auc: 0.7665 - val_loss: 0.5216
Epoch 25/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 15ms/step - accuracy: 0.8125 - auc: 0.8762 - loss: 0.4066 - val_accuracy: 0.7
000 - val_auc: 0.7665 - val_loss: 0.5253
Epoch 26/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 14ms/step - accuracy: 0.8083 - auc: 0.8789 - loss: 0.4034 - val_accuracy: 0.7
250 - val_auc: 0.7633 - val_loss: 0.5233
Epoch 27/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.8181 - auc: 0.8819 - loss: 0.3995 - val_accuracy: 0.7
250 - val_auc: 0.7669 - val_loss: 0.5216
Epoch 28/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.8194 - auc: 0.8836 - loss: 0.3963 - val_accuracy: 0.7
375 - val_auc: 0.7687 - val_loss: 0.5203
Epoch 29/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 17ms/step - accuracy: 0.8236 - auc: 0.8868 - loss: 0.3927 - val_accuracy: 0.7
250 - val_auc: 0.7658 - val_loss: 0.5226
Epoch 30/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 10ms/step - accuracy: 0.8319 - auc: 0.8903 - loss: 0.3886 - val_accuracy: 0.7
375 - val_auc: 0.7676 - val_loss: 0.5218
Epoch 31/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.8319 - auc: 0.8916 - loss: 0.3859 - val_accuracy: 0.7
375 - val_auc: 0.7673 - val_loss: 0.5216
Epoch 32/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 18ms/step - accuracy: 0.8292 - auc: 0.8942 - loss: 0.3820 - val_accuracy: 0.7
375 - val_auc: 0.7644 - val_loss: 0.5269
Epoch 33/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 1s 23ms/step - accuracy: 0.8347 - auc: 0.8956 - loss: 0.3805 - val_accuracy: 0.7
500 - val_auc: 0.7680 - val_loss: 0.5181
Epoch 34/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 15ms/step - accuracy: 0.8431 - auc: 0.8995 - loss: 0.3761 - val_accuracy: 0.7
375 - val_auc: 0.7687 - val_loss: 0.5220
Epoch 35/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.8389 - auc: 0.9007 - loss: 0.3727 - val_accuracy: 0.7
500 - val_auc: 0.7647 - val_loss: 0.5232
Epoch 36/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 1s 20ms/step - accuracy: 0.8486 - auc: 0.9020 - loss: 0.3699 - val_accuracy: 0.7
375 - val_auc: 0.7695 - val_loss: 0.5249
Epoch 37/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.8472 - auc: 0.9055 - loss: 0.3659 - val_accuracy: 0.7
500 - val_auc: 0.7695 - val_loss: 0.5232
Epoch 38/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.8514 - auc: 0.9080 - loss: 0.3624 - val_accuracy: 0.7
500 - val_auc: 0.7687 - val_loss: 0.5243
Epoch 39/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 13ms/step - accuracy: 0.8458 - auc: 0.9101 - loss: 0.3596 - val_accuracy: 0.7
375 - val_auc: 0.7698 - val_loss: 0.5263
Epoch 40/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 17ms/step - accuracy: 0.8472 - auc: 0.9111 - loss: 0.3568 - val_accuracy: 0.7
500 - val_auc: 0.7684 - val_loss: 0.5314
Epoch 41/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.8514 - auc: 0.9139 - loss: 0.3525 - val_accuracy: 0.7
500 - val_auc: 0.7698 - val_loss: 0.5282
Epoch 42/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 14ms/step - accuracy: 0.8597 - auc: 0.9154 - loss: 0.3503 - val_accuracy: 0.7
500 - val_auc: 0.7742 - val_loss: 0.5267
Epoch 43/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 14ms/step - accuracy: 0.8569 - auc: 0.9174 - loss: 0.3473 - val_accuracy: 0.7
500 - val_auc: 0.7702 - val_loss: 0.5304
Epoch 44/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 10ms/step - accuracy: 0.8569 - auc: 0.9204 - loss: 0.3439 - val_accuracy: 0.7
500 - val_auc: 0.7698 - val_loss: 0.5310
Epoch 45/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.8625 - auc: 0.9218 - loss: 0.3409 - val_accuracy: 0.7
250 - val_auc: 0.7651 - val_loss: 0.5341
Epoch 46/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.8639 - auc: 0.9233 - loss: 0.3381 - val_accuracy: 0.7
250 - val_auc: 0.7669 - val_loss: 0.5310
Epoch 47/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 11ms/step - accuracy: 0.8597 - auc: 0.9260 - loss: 0.3344 - val_accuracy: 0.7
500 - val_auc: 0.7684 - val_loss: 0.5340
Epoch 48/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 10ms/step - accuracy: 0.8667 - auc: 0.9272 - loss: 0.3319 - val_accuracy: 0.7
250 - val_auc: 0.7695 - val_loss: 0.5328
Epoch 49/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.8708 - auc: 0.9293 - loss: 0.3289 - val_accuracy: 0.7
250 - val_auc: 0.7676 - val_loss: 0.5320
Epoch 50/50
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 9ms/step - accuracy: 0.8736 - auc: 0.9312 - loss: 0.3254 - val_accuracy: 0.72
50 - val_auc: 0.7662 - val_loss: 0.5390

# You can try to use below the RMSprop optimizer with learning rate

[Link](optimizer=[Link](1e-8),
loss='binary_crossentropy',
metrics=['accuracy', [Link](name='auc')])

steps_per_epoch = ceil(720/32) = 23. Batch_size default = 32, alternative parameter is steps_per_epoch in fit method.
Either batch_size or steps_per_epoch can be used

In [9]: # Results for comparison


comparison_results = [Link]({
'Model': ['Logistic Regression (Baseline)', 'Simple ANN (Deep Learning)'],
'Accuracy':[log_accuracy, ann_accuracy],
'ROC-AUC Score': [log_auc, ann_auc]
})

print("\n Model Comparison: DL vs. Traditional ML")


print(comparison_results)

--- Model Comparison: DL vs. Traditional ML ---


Model Accuracy ROC-AUC Score
0 Logistic Regression (Baseline) 0.785 0.804286
1 Simple ANN (Deep Learning) 0.780 0.804226

Logistic Regression performed better. In this case, the complexity of an ANN wasn't justified. More layers, More
units/neurons or Regularization would be needed to unlock the full potential of deep learning.

In [10]: from [Link] import EarlyStopping

In [11]: # Initialize the Sequential model


ann_deep_model = Sequential(name="Deeper_Credit_ANN")

ann_deep_model.add([Link]( shape = (input_dim,)))


# Layer 1: Hidden Layer (64 neurons)
ann_deep_model.add(Dense(units=64, activation='relu'))

# Layer 2: New Hidden Layer (32 neurons)


ann_deep_model.add(Dense(units=32, activation='relu'))

# Layer 3: Output Layer (1 neuron)


ann_deep_model.add(Dense(units=1, activation='sigmoid'))

# Compile the model


ann_deep_model.compile(
optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy', [Link](name='auc')]
)

# Display the new architecture


print(" Deeper ANN Architecture (3 Hidden Layers)")
ann_deep_model.summary()

--- Deeper ANN Architecture (3 Hidden Layers) ---


Model: "Deeper_Credit_ANN"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ Layer (type) ┃ Output Shape ┃ Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ dense (Dense) │ (None, 64) │ 3,136 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_1 (Dense) │ (None, 32) │ 2,080 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_2 (Dense) │ (None, 1) │ 33 │
└──────────────────────────────────────┴─────────────────────────────┴─────────────────┘
Total params: 5,249 (20.50 KB)
Trainable params: 5,249 (20.50 KB)
Non-trainable params: 0 (0.00 B)

In [12]: # Define the Early Stopping Callback Monitors 'val_loss' (validation loss) to minimize overfitting.
# patience=10: Waits for 10 epochs without improvement before stopping.
early_stopper_deep = EarlyStopping(
monitor='val_loss',
patience=10,
restore_best_weights=True,
verbose=1
)

# Train the deeper model


print("\n Training with Early Stopping (Max 100 Epochs)")
history_deep = ann_deep_model.fit(
X_train, y_train,
epochs=100,
batch_size=32,
validation_split=0.1,
callbacks=[early_stopper_deep], # <-- Apply the stopper here
verbose=0
)
print(f"Training Complete. Model halted after {len(history_deep.history['loss'])} epochs.")

# Evaluate the deeper model on the test set


deep_loss, deep_accuracy, deep_auc = ann_deep_model.evaluate(X_test, y_test, verbose=0)

--- Training with Early Stopping (Max 100 Epochs) ---


Epoch 21: early stopping
Restoring model weights from the end of the best epoch: 11.
Training Complete. Model halted after 21 epochs.

In [13]: comparison_data = {
'Model': [
'Logistic Regression',
'Simple ANN (2-Layer)',
'Deeper ANN + Early Stopping'
],
'Test Accuracy': [log_accuracy, ann_accuracy, deep_accuracy],
'ROC-AUC Score': [log_auc, ann_auc, deep_auc]
}

comparison_df = [Link](comparison_data)

# Re-sort to show best performance first (based on ROC-AUC)


comparison_df = comparison_df.sort_values(by='ROC-AUC Score', ascending=False)

print("\n Model Performance Comparison ")


print(comparison_df)

# Business Insight
best_model = comparison_df.iloc[0]['Model']
best_auc = comparison_df.iloc[0]['ROC-AUC Score']

print(f"\n The {best_model} achieved the highest ROC-AUC score of {best_auc:.4f}.")


--- Model Performance Comparison ---
Model Test Accuracy ROC-AUC Score
0 Logistic Regression 0.785 0.804286
1 Simple ANN (2-Layer) 0.780 0.804226
2 Deeper ANN + Early Stopping 0.735 0.775238

The Logistic Regression achieved the highest ROC-AUC score of 0.8043.

While the deeper network did not achieve the highest AUC, the results are comparable, demonstrating that for this
specific dataset, a minimal structure is often sufficient.

Overfitting/Underfitting diagnosis
In [14]: import [Link] as plt
import seaborn as sns

print(" Learning Curves for Deeper ANN ")

# A. Plot Training & Validation Loss


[Link](figsize=(12, 5))
[Link](1, 2, 1)
[Link](history_deep.history['loss'], label='Training Loss')
[Link](history_deep.history['val_loss'], label='Validation Loss')
[Link]('Model Loss: Training vs. Validation')
[Link]('Epoch')
[Link]('Loss (Binary Crossentropy)')
[Link]()
[Link](True)

# B. Plot Training & Validation AUC


[Link](1, 2, 2)
# Note: The metric name for AUC is 'auc' in the history object
[Link](history_deep.history['auc'], label='Training AUC')
[Link](history_deep.history['val_auc'], label='Validation AUC')
[Link]('Model AUC: Training vs. Validation')
[Link]('Epoch')
[Link]('AUC Score')
[Link]()
[Link](True)

plt.tight_layout()
[Link]()

# --- C. Final Status Check ---


# Get final scores from the restored best weights
best_val_loss = min(history_deep.history['val_loss'])
final_epochs = len(history_deep.history['loss'])

print(f"\nFinal Epochs Run: {final_epochs}")


print(f"Best Validation Loss: {best_val_loss:.4f}")

--- Learning Curves for Deeper ANN ---


Final Epochs Run: 21
Best Validation Loss: 0.5255

Overfitting/Underfitting diagnosis based on the plots:

The Training Loss continues to decrease while the Validation Loss stops decreasing and starts increasing rapidly after a
certain point. Similarly, the Training AUC continues to rise while the Validation AUC plateaus or starts to fall. The model
has started to "memorize" the noise and specifics of the training data. This is called overfitting. The divergence point is
where generalization ability peaked. The Early Stopping did not mitigated the worst effects of overfitting. This indicates
patience needs to be reduced.

Regularization approach to avoid overfitting.


In [15]: from [Link] import Dense, Dropout

In [16]: # Model Definition: ANN_regularized


# We use the Deeper ANN architecture but apply more aggressive regularization

# Initialize the Sequential model


ann_regularized = Sequential(name="ANN_regularized")

# Layer 1: Input layer


ann_regularized.add([Link]( shape = (input_dim,)))

# Layer 2: Hidden Layer (64 neurons)


ann_regularized.add(Dense(units=64, activation='relu'))

# Layer 3: Dropout (Increased Rate: 0.3)


ann_regularized.add(Dropout(0.3))

# Layer 4: Hidden Layer (32 neurons)


ann_regularized.add(Dense(units=32, activation='relu'))

# Layer 5: Dropout (Increased Rate: 0.3)


ann_regularized.add(Dropout(0.3))

# Layer 6: Output Layer (1 neuron)


ann_regularized.add(Dense(units=1, activation='sigmoid'))

# Compile the model


ann_regularized.compile(
optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy', [Link](name='auc')]
)

# Define the Early Stopping Callback with stricter patience


early_stopper = EarlyStopping(
monitor='val_loss',
patience=5, # <--- Reduced Patience
restore_best_weights=True,
verbose=1
)

# Training the Model


print("\n Training ANN_regularized ")
# Using 100 epochs, allowing Early Stopping to halt the training quickly
history_regularized = ann_regularized.fit(
X_train, y_train,
epochs=100,
batch_size=32,
validation_split=0.1,
callbacks=[early_stopper],
verbose=0
)

--- Training ANN_regularized ---


Epoch 25: early stopping
Restoring model weights from the end of the best epoch: 20.
In [17]: from [Link] import roc_auc_score

# Evaluate the new model


regularized_loss, regularized_accuracy, regularized_auc = ann_regularized.evaluate(X_test, y_test, verbose

print("\n Model Performance (Post-Regularization)")


comparison_data = {
'Metric': ['Test Accuracy', 'ROC-AUC Score', 'Epochs Run'],
'ANN_regularized': [
f"{regularized_accuracy:.4f}",
f"{regularized_auc:.4f}",
len(history_regularized.history['loss'])
]
}

comparison_df = [Link](comparison_data)

print(comparison_df)

--- Model Performance (Post-Regularization) ---


Metric ANN_regularized
0 Test Accuracy 0.7650
1 ROC-AUC Score 0.7860
2 Epochs Run 25

Result: Performance is comparable or slightly worse. The model stopped after fewer epochs. This confirms that the
previous training run was too long and highlights the effectiveness of Early Stopping in saving computation time.

Wider ANN (breadth) approach to increase the hypothesis search space.


In [18]: from [Link] import Recall

In [19]: model_wide = Sequential(name="ANN_Wide_Model")


model_wide.add([Link]( shape = (input_dim,)))

# Wider Hidden Layer (128 units)


# This layer provides high capacity to learn patterns.
model_wide.add(Dense(units=128, activation='relu'))

# Dropout Regularization (High Capacity requires strong control)


model_wide.add(Dropout(0.3))

# Hidden Layer (64 units)


model_wide.add(Dense(units=64, activation='relu'))

# Dropout Regularization
model_wide.add(Dropout(0.3))

# Output Layer
model_wide.add(Dense(units=1, activation='sigmoid'))

# Compile the model


model_wide.compile(
optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy', Recall(), [Link](name='auc')]
)

# Display the final, most complex architecture


print(" Wide ANN Architecture (Max Capacity) ")
model_wide.summary()

--- Wide ANN Architecture (Max Capacity) ---


Model: "ANN_Wide_Model"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ Layer (type) ┃ Output Shape ┃ Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ dense_6 (Dense) │ (None, 128) │ 6,272 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout_2 (Dropout) │ (None, 128) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_7 (Dense) │ (None, 64) │ 8,256 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout_3 (Dropout) │ (None, 64) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_8 (Dense) │ (None, 1) │ 65 │
└──────────────────────────────────────┴─────────────────────────────┴─────────────────┘
Total params: 14,593 (57.00 KB)
Trainable params: 14,593 (57.00 KB)
Non-trainable params: 0 (0.00 B)

In [20]: # Define the Early Stopping Callback (stricter control)


# Patience is set low to prevent the performance decay seen in earlier models.
early_stopper_final = EarlyStopping(
monitor='val_loss',
patience=5,
restore_best_weights=True,
verbose=0
)

# Training the Final Model


print("\n Training Wide ANN (Optimal Control) ")
history_final_wide = model_wide.fit(
X_train, y_train,
epochs=100,
batch_size=32,
validation_split=0.1,
callbacks=[early_stopper_final],
verbose=0
)

--- Training Wide ANN (Optimal Control) ---

In [21]: from [Link] import roc_auc_score

# Evaluate the final model


wide_loss, wide_accuracy, wide_recall, wide_auc = model_wide.evaluate(X_test, y_test, verbose=0)

# Final Comparison
# Assuming the best prior AUC was approx 0.7860 (from ANN_regularized)

print("\n--- Model Evaluation ---")


print(f" Model (Wide ANN) Test Accuracy: {wide_accuracy:.4f}")
print(f" Model (Wide ANN) ROC-AUC Score: {wide_auc:.4f}")
print(f" Model (Wide ANN) Recall Score: {wide_recall:.4f}")

--- Model Evaluation ---


Model (Wide ANN) Test Accuracy: 0.8050
Model (Wide ANN) ROC-AUC Score: 0.8020
Model (Wide ANN) Recall Score: 0.5500

Assessment: The wider network was not so necessary or slightly over-parameterized. The final performance is
comparable, proving that the simpler, less complex ANN may also be sufficient for the data complexity.

Cost-Sensitive Training of ANN Classifier for imbalance class


Goal is to increase Generalization & Statistical Accuracy with Minimizing Financial Cost (FN×5)

Method which we start with is Early Stopping + Dropout Class plus class weighting, train it using the Class Weights that
reflect the 5:1 cost matrix.

Helps the model pay more attention to underrepresented classes during training
In [22]: from [Link] import class_weight
from [Link] import confusion_matrix, roc_auc_score, recall_score

In [23]: # Define Cost-Sensitive Weights

# Target: y (0=Good, 1=Bad)


# For binary classification: Weight for class i = total_samples / (n_classes * count(class_i))
# For example Class 0 weight = 1000 / (2 * 800) = 0.625 , Class 1 weight = 1000 / (2 * 200) = 2.5
class_weights_array = class_weight.compute_class_weight(
'balanced',
classes=[Link](y_train),
y=y_train
)
class_weights = {i: class_weights_array[i] for i in range(2)}

# Apply Cost Matrix Factor: FN (Class 1 error) is 5x more costly than FP (Class 0 error)
cost_factor = 5
class_weights[1] = class_weights[1] * cost_factor # Weight of Bad Risk class (1) is scaled by 5

print(f"Calculated Cost-Sensitive Class Weights: {class_weights}")

Calculated Cost-Sensitive Class Weights: {0: np.float64(0.7142857142857143), 1: np.float64(8.33333333333333


4)}

In [24]: # Defining the same architecture as wide ANN one


model_final = Sequential(name="ANN_Final_Model")
model_final.add([Link]( shape = (input_dim,)))
# This layer provides high capacity to learn patterns.
model_final.add(Dense(units=128, activation='relu'))

# Dropout Regularization (High Capacity requires strong control)


model_final.add(Dropout(0.3))

# Hidden Layer (64 units)


model_final.add(Dense(units=64, activation='relu'))

# Dropout Regularization
model_final.add(Dropout(0.3))

# Output Layer
model_final.add(Dense(units=1, activation='sigmoid'))

# Compile the model


model_final.compile(
optimizer='adam',
loss='binary_crossentropy',
metrics=['accuracy', Recall(), [Link](name='auc')]
)

# Display the final, most complex architecture


print(" Wide ANN Architecture (Max Capacity) ")
model_final.summary()

--- Wide ANN Architecture (Max Capacity) ---


Model: "ANN_Final_Model"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ Layer (type) ┃ Output Shape ┃ Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ dense_9 (Dense) │ (None, 128) │ 6,272 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout_4 (Dropout) │ (None, 128) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_10 (Dense) │ (None, 64) │ 8,256 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dropout_5 (Dropout) │ (None, 64) │ 0 │
├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤
│ dense_11 (Dense) │ (None, 1) │ 65 │
└──────────────────────────────────────┴─────────────────────────────┴─────────────────┘
Total params: 14,593 (57.00 KB)
Trainable params: 14,593 (57.00 KB)
Non-trainable params: 0 (0.00 B)

In [25]: # Train Model with Cost-Sensitivity

early_stopper_cost = EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True, verbose=1)

print("\n Training COST-SENSITIVE Model ")


model_final.fit(
X_train, y_train,
epochs=100,
batch_size=32,
validation_split=0.1,
callbacks=[early_stopper_cost],
class_weight=class_weights, # <--- Final Optimization Technique
verbose=1
)
print("Training Complete: Cost-Sensitive Model Ready.")
--- Training COST-SENSITIVE Model ---
Epoch 1/100
23/23 ━━━━━━━━━━━━━━━━━━━━ 5s 50ms/step - accuracy: 0.3194 - auc: 0.5637 - loss: 1.4833 - recall_1: 0.9535
- val_accuracy: 0.3125 - val_auc: 0.5884 - val_loss: 1.3219 - val_recall_1: 1.0000
Epoch 2/100
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 12ms/step - accuracy: 0.2986 - auc: 0.6159 - loss: 1.3259 - recall_1: 1.0000
- val_accuracy: 0.3125 - val_auc: 0.6251 - val_loss: 1.1353 - val_recall_1: 1.0000
Epoch 3/100
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 13ms/step - accuracy: 0.3042 - auc: 0.6751 - loss: 1.2697 - recall_1: 1.0000
- val_accuracy: 0.3125 - val_auc: 0.6713 - val_loss: 1.1152 - val_recall_1: 1.0000
Epoch 4/100
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 13ms/step - accuracy: 0.3167 - auc: 0.7611 - loss: 1.1712 - recall_1: 1.0000
- val_accuracy: 0.3125 - val_auc: 0.6833 - val_loss: 1.0407 - val_recall_1: 1.0000
Epoch 5/100
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 15ms/step - accuracy: 0.3319 - auc: 0.7287 - loss: 1.2047 - recall_1: 0.9953
- val_accuracy: 0.3625 - val_auc: 0.7062 - val_loss: 1.0509 - val_recall_1: 1.0000
Epoch 6/100
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 14ms/step - accuracy: 0.3931 - auc: 0.7828 - loss: 1.1166 - recall_1: 0.9860
- val_accuracy: 0.4375 - val_auc: 0.7127 - val_loss: 0.9783 - val_recall_1: 0.9600
Epoch 7/100
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 14ms/step - accuracy: 0.4389 - auc: 0.7866 - loss: 1.0948 - recall_1: 1.0000
- val_accuracy: 0.4625 - val_auc: 0.7207 - val_loss: 0.9817 - val_recall_1: 0.9600
Epoch 8/100
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.4347 - auc: 0.8136 - loss: 1.0416 - recall_1: 0.9907
- val_accuracy: 0.4750 - val_auc: 0.7247 - val_loss: 0.9233 - val_recall_1: 0.8800
Epoch 9/100
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 15ms/step - accuracy: 0.5306 - auc: 0.8109 - loss: 1.0576 - recall_1: 0.9814
- val_accuracy: 0.4875 - val_auc: 0.7251 - val_loss: 0.9716 - val_recall_1: 0.8400
Epoch 10/100
23/23 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.4861 - auc: 0.8325 - loss: 0.9892 - recall_1: 0.9907
- val_accuracy: 0.5125 - val_auc: 0.7553 - val_loss: 0.9059 - val_recall_1: 0.8400
Epoch 11/100
23/23 ━━━━━━━━━━━━━━━━━━━━ 1s 22ms/step - accuracy: 0.5750 - auc: 0.8355 - loss: 0.9743 - recall_1: 0.9860
- val_accuracy: 0.5125 - val_auc: 0.7527 - val_loss: 0.9775 - val_recall_1: 0.8800
Epoch 12/100
23/23 ━━━━━━━━━━━━━━━━━━━━ 1s 28ms/step - accuracy: 0.5556 - auc: 0.8428 - loss: 0.9493 - recall_1: 0.9860
- val_accuracy: 0.5625 - val_auc: 0.7509 - val_loss: 0.8915 - val_recall_1: 0.8400
Epoch 13/100
23/23 ━━━━━━━━━━━━━━━━━━━━ 1s 20ms/step - accuracy: 0.6097 - auc: 0.8624 - loss: 0.8929 - recall_1: 0.9860
- val_accuracy: 0.6125 - val_auc: 0.7662 - val_loss: 0.8326 - val_recall_1: 0.8400
Epoch 14/100
23/23 ━━━━━━━━━━━━━━━━━━━━ 1s 22ms/step - accuracy: 0.6083 - auc: 0.8688 - loss: 0.8777 - recall_1: 0.9860
- val_accuracy: 0.5500 - val_auc: 0.7571 - val_loss: 0.9567 - val_recall_1: 0.8400
Epoch 15/100
23/23 ━━━━━━━━━━━━━━━━━━━━ 1s 22ms/step - accuracy: 0.6153 - auc: 0.8781 - loss: 0.8613 - recall_1: 0.9860
- val_accuracy: 0.5625 - val_auc: 0.7625 - val_loss: 0.9043 - val_recall_1: 0.8400
Epoch 16/100
23/23 ━━━━━━━━━━━━━━━━━━━━ 1s 20ms/step - accuracy: 0.6292 - auc: 0.8754 - loss: 0.8482 - recall_1: 0.9860
- val_accuracy: 0.6000 - val_auc: 0.7687 - val_loss: 0.8378 - val_recall_1: 0.8400
Epoch 17/100
23/23 ━━━━━━━━━━━━━━━━━━━━ 1s 24ms/step - accuracy: 0.6444 - auc: 0.8805 - loss: 0.8284 - recall_1: 0.9767
- val_accuracy: 0.5875 - val_auc: 0.7593 - val_loss: 0.8987 - val_recall_1: 0.8800
Epoch 18/100
23/23 ━━━━━━━━━━━━━━━━━━━━ 1s 20ms/step - accuracy: 0.6514 - auc: 0.8963 - loss: 0.7834 - recall_1: 0.9860
- val_accuracy: 0.5875 - val_auc: 0.7651 - val_loss: 0.8744 - val_recall_1: 0.8400
Epoch 18: early stopping
Restoring model weights from the end of the best epoch: 13.
Training Complete: Cost-Sensitive Model Ready.

In [26]: # Evaluation: Calculate Cost and Compare

# Generate predictions
P_pred_final = model_final.predict(X_test)
Y_pred_class_final = (P_pred_final > 0.5).astype(int)

# Calculate Cost Metrics


cm_final = confusion_matrix(y_test, Y_pred_class_final)
tn, fp, fn, tp = cm_final.ravel()

# Total Misclassification Cost = (FN * 5) + (FP * 1)


total_cost = (fn * 5) + (fp * 1)
final_recall = recall_score(y_test, Y_pred_class_final)
print("\n Final Business Performance Summary ")
print(f"Total Misclassification Cost: {total_cost} Units")
print(f"Recall (Bad Risk - FN Reduction): {final_recall:.4f}")
print("Cost Matrix (Final):")
cm_df_final = [Link](cm_final,
index=['Actual Good (0)', 'Actual Bad (1)'],
columns=['Predicted Good (0)', 'Predicted Bad (1)'])
print(cm_df_final)

7/7 ━━━━━━━━━━━━━━━━━━━━ 0s 25ms/step

--- Final Business Performance Summary ---


Total Misclassification Cost: 114 Units
Recall (Bad Risk - FN Reduction): 0.9000
Cost Matrix (Final):
Predicted Good (0) Predicted Bad (1)
Actual Good (0) 56 84
Actual Bad (1) 6 54

In [27]: cm_final

Out[27]: array([[56, 84],


[ 6, 54]])

In [28]: [Link](figsize=(8, 6))


[Link](cm_final, annot=True, fmt='d', cmap='Blues',
xticklabels=['Predicted Good (0)', 'Predicted Bad (1)'],
yticklabels=['Actual Good (0)', 'Actual Bad (1)'])
[Link]('Confusion Matrix: German Credit Risk ')
[Link]('True Class')
[Link]('Predicted Class')
[Link]()

Interpretation:

Top Left (TN): Correctly Identified Good Credit


Top Right (FP): False Positive (Type I Error) - Predicted Bad, but Actual Good. This is a LOST OPPORTUNITY
(Business Cost: 1).
Bottom Right (TP): Correctly Identified Bad Credit (Loan Rejected/No Loss)
Bottom Left (FN): False Negative (Type II Error) - Predicted Good, but Actual Bad. This is the CRITICAL ERROR
(Business Cost: 5 - Loan Default).

In [29]: print("\n Classification Report ")


# Using the target labels 0 (Good) and 1 (Bad)
print(classification_report(y_test, Y_pred_class_final, target_names=['Good Risk (0)', 'Bad Risk (1)']))

--- Classification Report ---


precision recall f1-score support

Good Risk (0) 0.90 0.40 0.55 140


Bad Risk (1) 0.39 0.90 0.55 60

accuracy 0.55 200


macro avg 0.65 0.65 0.55 200
weighted avg 0.75 0.55 0.55 200

Interpretation of Critical Metrics (for class 'Bad Risk (1)'):

1. Recall (Bad Risk): Measures the model's ability to find ALL positive samples.
How many of the ACTUAL Bad Risks did we correctly identify?
Maximize this to minimize the costliest error (False Negatives/Loan Defaults).
2. Precision (Bad Risk): Measures the accuracy of positive predictions.
Of all the applicants we PREDICTED as Bad Risk, how many were actually Bad?
Maximize this to avoid rejecting too many good customers (False Positives/Lost Opportunity).

In [30]: print("\n ROC-AUC and ROC Curve (Model Discriminatory Power) ")

# Calculate AUC
roc_auc = roc_auc_score(y_test, P_pred_final)
print(f"ROC AUC Score: {roc_auc:.4f}")

# Calculate ROC Curve points


fpr, tpr, thresholds = roc_curve(y_test, P_pred_final)

# Plot ROC Curve


[Link](figsize=(8, 6))
[Link](fpr, tpr, label=f'AUC = {roc_auc:.4f}')
[Link]([0, 1], [0, 1], 'r--')
[Link]([0.0, 1.0])
[Link]([0.0, 1.05])
[Link]('False Positive Rate (1 - Specificity)')
[Link]('True Positive Rate (Recall)')
[Link]('Receiver Operating Characteristic (ROC) Curve')
[Link](loc="lower right")
[Link]()

--- ROC-AUC and ROC Curve (Model Discriminatory Power) ---


ROC AUC Score: 0.8054
Interpretation:

AUC is the probability that the model ranks a randomly chosen positive case (Bad Risk) higher than a randomly
chosen negative case (Good Risk).
A value close to 1.0 indicates excellent discriminative power. A value of 0.5 is no better than random guessing.

Deployment
Save the model

In [31]: # Model Saving and Prediction - The modern, recommended way to save Keras models is the Keras v3 format (.
# which saves the architecture, weights, compilation info (loss/optimizer state) efficiently as a single a

print("\n Model Saving and Loading ")

model_filename = 'german_credit_ann.keras'

# Save the Model


model_final.save(model_filename)
print(f"Model saved successfully in .keras format as: {model_filename}")

--- Model Saving and Loading ---


Model saved successfully in .keras format as: german_credit_ann.keras

Load the model

In [32]: # Load the Model


reconstructed_model = load_model(model_filename)
print("Model loaded successfully.")

Model loaded successfully.

Predictions

In [33]: # Make Prediction with the loaded model (Let us make predictions on the first 5 test samples)
sample_predictions = reconstructed_model.predict(X_test.head(5))
sample_results = [Link]({
'Probability_Bad_Risk': sample_predictions.flatten(),
'Predicted_Class': (sample_predictions > 0.5).astype(int).flatten(),
'Actual_Class': y_test.head(5).values
})

print("\nSample Predictions on Loaded Model:")


print(sample_results)

1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 166ms/step

Sample Predictions on Loaded Model:


Probability_Bad_Risk Predicted_Class Actual_Class
0 0.761553 1 0
1 0.640403 1 0
2 0.896385 1 1
3 0.566097 1 0
4 0.736858 1 1

You might also like