Risk Minimization with Overdue Balance

Risk Minimization with Overdue Balance#

import numpy as np
import pandas as pd
import tensorflow as tf
from tensorflow.keras.layers import Input, Dense, Dropout, Concatenate, Attention
from tensorflow.keras.models import Model
from sklearn.model_selection import train_test_split, KFold
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_squared_error, accuracy_score, confusion_matrix, classification_report
import matplotlib.pyplot as plt
import seaborn as sns

# Set random seed for reproducibility
np.random.seed(42)

# Generate synthetic dataset
n_samples = 100

# Generate ovd_days (overdue days)
ovd_days = np.random.randint(0, 120, size=n_samples)

# Generate overdue amount based on ovd_days (overdue)
ovd_amount = np.where(ovd_days > 0, np.random.uniform(100, 1000, size=n_samples), 0)

# Define the target variable 'default' with imbalance (less than 1% default)
default = np.where(np.random.rand(n_samples) < 0.01, 1, 0)

# Generate additional 16 features (e.g., income, age, credit score, etc.)
additional_features = {
    f'feature_{i}': np.random.uniform(0, 1, size=n_samples)
    for i in range(1, 17)
}

# Create a DataFrame
data = pd.DataFrame({
    'ovd_days': ovd_days,
    'ovd_amount': ovd_amount,
    'default': default
})

# Add additional features to the DataFrame
for feature_name, values in additional_features.items():
    data[feature_name] = values

# Data exploration and sanity check
print(data.describe())
print(data.isnull().sum())
print("Class distribution for 'default':")
print(data['default'].value_counts(normalize=True))

# Plot class imbalance
sns.countplot(x='default', data=data)
plt.title('Class Distribution of Default')
plt.show()

# Separate features and targets
X = data.drop(columns=['ovd_amount', 'default'])
y_classification = data['default']
y_regression = data['ovd_amount']

# Split the data into training, test, and holdout sets
X_train, X_temp, y_train_class, y_temp_class, y_train_reg, y_temp_reg = train_test_split(
    X, y_classification, y_regression, test_size=0.3, random_state=42
)
X_test, X_holdout, y_test_class, y_holdout_class, y_test_reg, y_holdout_reg = train_test_split(
    X_temp, y_temp_class, y_temp_reg, test_size=0.5, random_state=42
)

# Scale the features
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
X_holdout_scaled = scaler.transform(X_holdout)

# Define function to create model architectures
def create_model(model_type):
    input_layer = Input(shape=(X_train_scaled.shape[1],))
    shared = Dense(64, activation='relu')(input_layer)
    shared = Dropout(0.3)(shared)
    
    if model_type == 'MTAN':
        # MTAN model architecture with attention mechanism
        attention = Attention()([shared, shared])
        shared = Concatenate()([shared, attention])
        classification_branch = Dense(16, activation='relu')(shared)
        classification_output = Dense(1, activation='sigmoid', name='classification_output')(classification_branch)
        regression_branch = Dense(16, activation='relu')(shared)
        regression_output = Dense(1, activation='linear', name='regression_output')(regression_branch)
    elif model_type == 'Cross-Stitch':
        # Cross-Stitch model architecture
        cross_stitch_layer = Dense(32, activation='relu')(shared)
        classification_branch = Dense(32, activation='relu')(cross_stitch_layer)
        classification_output = Dense(1, activation='sigmoid', name='classification_output')(classification_branch)
        regression_branch = Dense(32, activation='relu')(cross_stitch_layer)
        regression_output = Dense(1, activation='linear', name='regression_output')(regression_branch)
    elif model_type == 'MTL-DNN':
        # MTL-DNN model architecture with deeper layers
        shared = Dense(128, activation='relu')(shared)
        classification_branch = Dense(64, activation='relu')(shared)
        classification_output = Dense(1, activation='sigmoid', name='classification_output')(classification_branch)
        regression_branch = Dense(64, activation='relu')(shared)
        regression_output = Dense(1, activation='linear', name='regression_output')(regression_branch)
    elif model_type == 'HPS':
        # HPS model architecture (hard parameter sharing)
        classification_branch = Dense(16, activation='relu')(shared)
        classification_output = Dense(1, activation='sigmoid', name='classification_output')(classification_branch)
        regression_branch = Dense(16, activation='relu')(shared)
        regression_output = Dense(1, activation='linear', name='regression_output')(regression_branch)
    
    model = Model(inputs=input_layer, outputs=[classification_output, regression_output])
    model.compile(
        optimizer='adam',
        loss={'classification_output': 'binary_crossentropy', 'regression_output': 'mse'},
        metrics={'classification_output': 'accuracy', 'regression_output': 'mae'}
    )
    return model

# Define model types
model_types = ['MTAN', 'Cross-Stitch', 'MTL-DNN', 'HPS']

# Perform cross-validation and evaluate models
kf = KFold(n_splits=5, shuffle=True, random_state=42)

results = []

for model_type in model_types:
    print(f"\nEvaluating model: {model_type}")
    fold_accuracies = []
    fold_mses = []
    
    for train_index, val_index in kf.split(X_train_scaled):
        X_fold_train, X_fold_val = X_train_scaled[train_index], X_train_scaled[val_index]
        y_fold_train_class, y_fold_val_class = y_train_class.iloc[train_index], y_train_class.iloc[val_index]
        y_fold_train_reg, y_fold_val_reg = y_train_reg.iloc[train_index], y_train_reg.iloc[val_index]
        
        # Create and train the model
        model = create_model(model_type)
        model.fit(
            X_fold_train,
            {'classification_output': y_fold_train_class, 'regression_output': y_fold_train_reg},
            validation_data=(X_fold_val, {'classification_output': y_fold_val_class, 'regression_output': y_fold_val_reg}),
            epochs=10,
            batch_size=32,
            verbose=0
        )
        
        # Evaluate on validation set
        val_preds = model.predict(X_fold_val)
        val_class_preds = (val_preds[0] > 0.5).astype(int).flatten()
        val_reg_preds = val_preds[1].flatten()
        
        accuracy = accuracy_score(y_fold_val_class, val_class_preds)
        mse = mean_squared_error(y_fold_val_reg, val_reg_preds)
        fold_accuracies.append(accuracy)
        fold_mses.append(mse)
    
    avg_accuracy = np.mean(fold_accuracies)
    avg_mse = np.mean(fold_mses)
    results.append({'Model': model_type, 'Accuracy': avg_accuracy, 'MSE': avg_mse})
    print(f"{model_type} - Cross-Validation Accuracy: {avg_accuracy:.4f}")
    print(f"{model_type} - Cross-Validation MSE: {avg_mse:.4f}")
    
    # Evaluate on holdout set
    holdout_preds = model.predict(X_holdout_scaled)
    holdout_class_preds = (holdout_preds[0] > 0.5).astype(int).flatten()
    holdout_reg_preds = holdout_preds[1].flatten()
    
    holdout_accuracy = accuracy_score(y_holdout_class, holdout_class_preds)
    holdout_mse = mean_squared_error(y_holdout_reg, holdout_reg_preds)
    results[-1]['Holdout_Accuracy'] = holdout_accuracy
    results[-1]['Holdout_MSE'] = holdout_mse
    print(f"Evaluating model on holdout set: {model_type}")
    print(f"{model_type} - Binary Accuracy: {holdout_accuracy:.4f}")
    print(f"{model_type} - Features Prediction MSE: {holdout_mse:.4f}")

# Convert results to DataFrame and plot for easy comparison
results_df = pd.DataFrame(results)

# Plot model comparison
fig, axes = plt.subplots(1, 2, figsize=(15, 6))
sns.barplot(x='Model', y='Accuracy', data=results_df, ax=axes[0])
axes[0].set_title('Model Comparison - Cross-Validation Accuracy')
axes[0].set_ylim(0.95, 1.0)
sns.barplot(x='Model', y='MSE', data=results_df, ax=axes[1])
axes[1].set_title('Model Comparison - Cross-Validation MSE')
plt.tight_layout()
plt.show()

# Holdout set performance
fig, axes = plt.subplots(1, 2, figsize=(15, 6))
sns.barplot(x='Model', y='Holdout_Accuracy', data=results_df, ax=axes[0])
axes[0].set_title('Model Comparison - Holdout Accuracy')
axes[0].set_ylim(0.95, 1.0)
sns.barplot(x='Model', y='Holdout_MSE', data=results_df, ax=axes[1])
axes[1].set_title('Model Comparison - Holdout MSE')
plt.tight_layout()
plt.show()
         ovd_days  ovd_amount     default   feature_1   feature_2   feature_3  \
count  100.000000  100.000000  100.000000  100.000000  100.000000  100.000000   
mean    57.290000  521.560060    0.020000    0.491455    0.520535    0.559295   
std     34.084356  265.139375    0.140705    0.279351    0.312287    0.307534   
min      1.000000  104.969905    0.000000    0.014393    0.010838    0.011354   
25%     31.250000  305.724173    0.000000    0.265685    0.281990    0.303574   
50%     59.000000  484.591963    0.000000    0.520286    0.553967    0.583512   
75%     87.250000  781.173654    0.000000    0.702778    0.794555    0.844001   
max    116.000000  988.198243    1.000000    0.990054    0.990505    0.999718   

        feature_4   feature_5   feature_6   feature_7   feature_8   feature_9  \
count  100.000000  100.000000  100.000000  100.000000  100.000000  100.000000   
mean     0.432970    0.481340    0.515847    0.448254    0.508675    0.538588   
std      0.275124    0.281296    0.289133    0.289906    0.296913    0.292093   
min      0.014545    0.051824    0.004632    0.010996    0.021269    0.017874   
25%      0.183861    0.241114    0.250972    0.201410    0.248852    0.264138   
50%      0.400013    0.435397    0.535428    0.423319    0.534214    0.558295   
75%      0.635269    0.733431    0.753085    0.665799    0.746253    0.800449   
max      0.980033    0.989960    0.996874    0.984402    0.987668    0.997934   

       feature_10  feature_11  feature_12  feature_13  feature_14  feature_15  \
count  100.000000  100.000000  100.000000  100.000000  100.000000  100.000000   
mean     0.524193    0.471828    0.511864    0.506697    0.488865    0.479238   
std      0.308144    0.293155    0.296533    0.278509    0.279970    0.301545   
min      0.006386    0.009771    0.004940    0.005759    0.023639    0.011031   
25%      0.252341    0.200431    0.237170    0.301686    0.233271    0.205039   
50%      0.599410    0.486051    0.511026    0.529383    0.520009    0.454793   
75%      0.777409    0.710317    0.745670    0.716644    0.710958    0.761420   
max      0.997125    0.996334    0.986630    0.999414    0.994139    0.996697   

       feature_16  
count  100.000000  
mean     0.512624  
std      0.294719  
min      0.012108  
25%      0.262452  
50%      0.524355  
75%      0.770576  
max      0.979970  
ovd_days      0
ovd_amount    0
default       0
feature_1     0
feature_2     0
feature_3     0
feature_4     0
feature_5     0
feature_6     0
feature_7     0
feature_8     0
feature_9     0
feature_10    0
feature_11    0
feature_12    0
feature_13    0
feature_14    0
feature_15    0
feature_16    0
dtype: int64
Class distribution for 'default':
default
0    0.98
1    0.02
Name: proportion, dtype: float64
_images/297a55b628c0e5169b3fea99827d8af321f5257812e58dbf61d640f4df3320a4.png
Evaluating model: MTAN
1/1 [==============================] - 0s 131ms/step
1/1 [==============================] - 0s 148ms/step
1/1 [==============================] - 0s 139ms/step
1/1 [==============================] - 0s 193ms/step
1/1 [==============================] - 0s 221ms/step
MTAN - Cross-Validation Accuracy: 0.9714
MTAN - Cross-Validation MSE: 334973.8320
1/1 [==============================] - 0s 95ms/step
Evaluating model on holdout set: MTAN
MTAN - Binary Accuracy: 1.0000
MTAN - Features Prediction MSE: 299751.3324

Evaluating model: Cross-Stitch
1/1 [==============================] - 0s 130ms/step
1/1 [==============================] - 0s 176ms/step
1/1 [==============================] - 0s 127ms/step
1/1 [==============================] - 0s 142ms/step
1/1 [==============================] - 0s 156ms/step
Cross-Stitch - Cross-Validation Accuracy: 0.9714
Cross-Stitch - Cross-Validation MSE: 335785.5626
1/1 [==============================] - 0s 46ms/step
Evaluating model on holdout set: Cross-Stitch
Cross-Stitch - Binary Accuracy: 1.0000
Cross-Stitch - Features Prediction MSE: 300668.7235

Evaluating model: MTL-DNN
1/1 [==============================] - 0s 101ms/step
1/1 [==============================] - 0s 136ms/step
1/1 [==============================] - 0s 142ms/step
1/1 [==============================] - 0s 121ms/step
1/1 [==============================] - 0s 164ms/step
MTL-DNN - Cross-Validation Accuracy: 0.9714
MTL-DNN - Cross-Validation MSE: 331800.2659
1/1 [==============================] - 0s 85ms/step
Evaluating model on holdout set: MTL-DNN
MTL-DNN - Binary Accuracy: 1.0000
MTL-DNN - Features Prediction MSE: 294710.4139

Evaluating model: HPS
1/1 [==============================] - 0s 126ms/step
1/1 [==============================] - 0s 120ms/step
1/1 [==============================] - 0s 119ms/step
1/1 [==============================] - 0s 152ms/step
1/1 [==============================] - 0s 101ms/step
HPS - Cross-Validation Accuracy: 0.9714
HPS - Cross-Validation MSE: 336172.2703
1/1 [==============================] - 0s 69ms/step
Evaluating model on holdout set: HPS
HPS - Binary Accuracy: 1.0000
HPS - Features Prediction MSE: 300673.4797
_images/6b39a232555159e4fa92abe36320bf61b95880ca33b25f22ebff1fe378ac9bf2.png _images/6ce185089fbbff61c7886f61d6ed0fb206b05d305bd8ecbb1a38d4e9df2999a2.png