[PYTHON] SIGNATE [1st _Beginner Limited Competition] Résolution du ciblage des clients bancaires

introduction

Chez SIGNATE, l'une des plates-formes nationales pour les compétitions d'apprentissage automatique, j'ai participé au «[1er concours _Beginner Limited] Ciblage des clients bancaires» qui s'est tenu en août 2020, j'ai donc également utilisé mon propre mémorandum comme solution. Je vais le décrire. ** De plus, nous n'avons pas de solution particulièrement originale. J'espère que cela sera utile pour les débutants en machine learning ** (le texte est long).

Compétition limitée débutant

Dans SIGNATE, les titres sont attribués en fonction des résultats du concours, mais le titre au moment de l'inscription dans SIGNATE sera "Begginer". Cette compétition était une compétition à laquelle seules les personnes de la classe inférieure de Beginer pouvaient participer (il semble que ce soit la première fois pour la compétition limitée de Beginer).

Normalement, vous participerez au concours pour le prochain titre Intermédiaire à partir du début, et si vous entrez dans le top 60% même une fois, vous serez promu, mais dans ce concours, si vous atteignez le score spécifié, vous serez automatiquement promu intermédiaire à ce stade. Ce sera une compétition à cet effet.

Je n'ai également enregistré que SIGNATE et j'étais un débutant, alors j'ai participé.

piramid.png

Aperçu de la compétition

À la suite d'une campagne menée par une banque, le fait qu'un client ait ouvert ou non un compte est prédit sur la base des données d'attribut client et des informations de contact des campagnes précédentes. Il s'agit d'un problème dit de «classification» dans l'apprentissage automatique.

Les données fournies sont les suivantes. Les données du train étaient de 27100 enregistrements et les données d'essai étaient de 18050 enregistrements.

colonne Nom de l'en-tête Type de données La description
0 id int Numéro de série de la ligne
1 age int âge
2 job varchar Occupation
3 marital varchar Célibataire/marié
4 education varchar Niveau d'éducation
5 default varchar Y a-t-il un défaut (oui), no)
6 balance int Solde moyen annuel (€)
7 housing varchar Prêt logement (oui), no)
8 loan varchar Prêt personnel (oui), no)
9 contact varchar Méthode de contact
10 day int Date du dernier contact
11 month char Dernier mois de contact
12 duration int Heure du dernier contact (secondes)
13 compaign int Nombre de contacts dans la campagne en cours
14 pdays int Jours écoulés: jours après le contact avec la campagne précédente
15 previous int Enregistrement de contact: nombre de contacts avec les clients avant la campagne en cours
16 poutcome varchar Résultats de la campagne précédente
17 y boolean Demander ou non un dépôt fixe (1:Oui, 0:Aucun)

Environnement d'exécution

OS: Windows10 Processeur: core i7 5500U Mémoire: 16 Go Environnement Anaconda3 (Python 3.7.6)

Structure du répertoire

Bank_Prediction  ├ notebook/ ●●●.ipynb  ├ input/ train.csv、test.csv └ Sortie / Sortie du résultat de la prédiction ici

Flux de création de modèle prédictif

Créez un modèle de prédiction dans l'ordre suivant.

    1. EDA (analyse exploratoire des données)
  1. Prétraitement des données
    1. Apprentissage et prédiction Quatre. résultat

1. 1. EDA (analyse exploratoire des données)

Tout d'abord, nous effectuerons une analyse pour confirmer la structure et les caractéristiques des données données. Par souci de simplicité dans l'article, je vais omettre le résultat EDA des données de test.

Lire les données

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from pandas.plotting import scatter_matrix
import seaborn as sns

#Définissez le nombre maximum de colonnes d'affichage sur 50
pd.set_option('display.max_columns', 50)

#Lire diverses données
train = pd.read_csv("../input/train.csv")
test = pd.read_csv("../input/test.csv")

Confirmation du nombre d'enregistrements, du nombre de fonctionnalités, du type de données, de la présence de valeurs manquantes

train.info()

info.png Le nombre d'enregistrements est de 27100, le nombre d'entités est de 18 et il est clair quelles caractéristiques sont des variables numériques et des variables catégorielles. De plus, il semble qu'il n'y ait aucune valeur manquante dans les données données cette fois. Puisque les données cette fois sont comme les données créées pour le concours, ce sont de belles données sans valeurs manquantes, mais dans le cas de données basées sur la réalité, il est courant d'effectuer un traitement complémentaire avec beaucoup de valeurs manquantes.

Confirmation des statistiques de base

train.describe()

describe.png

Vérifiez l'histogramme de chaque fonction

train.hist(figsize=(20,20), color='r')

histgram.png Bien que y indique si un compte a été ouvert ou non, on constate que le nombre d'ouvertures (1) est très faible par rapport au nombre de non ouverts (0), ce qui entraîne des données déséquilibrées.

Coefficient de corrélation

colormap = plt.cm.RdBu
plt.figure(figsize=(14,12))
plt.title('Pearson Correlation of Features', y=1.05, size=15)
sns.heatmap(train.select_dtypes(exclude='object').astype(int).corr(),linewidths=0.1,vmax=1.0, vmin=-1.0, 
            square=True, cmap=colormap, linecolor='white', annot=True)

corr.png Parmi les quantités de fonctionnalités, le précédent (le nombre de contacts avec les clients jusqu'à présent) semble avoir le plus de corrélation avec l'ouverture ou non d'un compte.

Confirmation de la répartition entre les quantités de caractéristiques


g = sns.pairplot(train, hue='y', palette = 'seismic',size=1.2,diag_kind = 'kde',diag_kws=dict(shade=True),plot_kws=dict(s=10) )
g.set(xticklabels=[])

histgram2.png

Le bleu est la répartition des clients qui n'ont pas ouvert de compte, et le rouge est la répartition des clients qui ont ouvert un compte. En regardant l'âge de l'histogramme diagonal, il semble que les plus jeunes sont plus susceptibles de ne pas ouvrir de compte. Il existe également une différence de distribution le jour (dernier jour de contact).

Vérifiez le nombre d'éléments dans chaque variable catégorielle


for _ in range(len(train.select_dtypes(include='object').columns)):
    print(train.select_dtypes(include='object').columns.values[_])
    print(len(train.select_dtypes(include='object').iloc[:,_].value_counts().to_dict()))
    print(train.select_dtypes(include='object').iloc[:,_].value_counts().to_dict())

category.png J'ai pu confirmer le nombre d'éléments inclus dans chaque variable catégorielle.

2. Prétraitement des données

À partir de là, nous effectuerons un prétraitement des données pour créer un modèle de prédiction.

Ajout de fonctionnalités

Tout d'abord, pour les variables catégorielles, nous avons ajouté une fonctionnalité qui combine trois fonctionnalités liées aux prêts.

Des fonctionnalités ont également été ajoutées pour les variables numériques. Ici, nous avons ajouté le montant de fonctionnalité qui est la différence entre la moyenne de chaque montant de fonctionnalité existante et le montant de fonctionnalité de chaque enregistrement. Il semble que les performances de généralisation puissent être améliorées en ajoutant une quantité de caractéristiques au carré ou une quantité de caractéristiques au cube en tant que nouvelle quantité de caractéristiques, mais cette fois je ne l'ai pas essayée.

#Fusionner les données de train et les données de test
train2 =  pd.concat([train,test],axis=0)

#Ajout de fonctionnalités
train2['default_housing_loan'] = train2['default'].str.cat([train2['housing'],train2['loan']], sep='_')
train2['age_median'] = train2['age'] - train2['age'].median()
train2['day_median'] = train2['day'] - train2['day'].median()
train2['duration_median'] = train2['duration'] - train2['duration'].median()
train2['campaign_median'] = train2['campaign'] - train2['campaign'].median()
train2['previous_median'] = train2['previous'] - train2['previous'].median()

Label Encoding Les variables catégorielles ne peuvent pas être entrées dans le modèle de prédiction en tant que données d'apprentissage telles quelles, elles doivent donc être codées. Il existe plusieurs méthodes de codage, mais comme l'algorithme utilisé pour la formation cette fois-ci est un arbre d'amplification de gradient, le codage par étiquette est utilisé (un codage à chaud est meilleur pour résoudre des problèmes tels que la "régression").

Voici un exemple de codage d'étiquette du montant matrimonial.

married → 0 single → 1 divorced → 2

#Label Encoding
from sklearn.preprocessing import LabelEncoder

category = train2.select_dtypes(include='object')

for col in list(category):
  le = LabelEncoder()
  le.fit(train2[col])
  le.transform(train2[col])
  train2[col] = le.transform(train2[col])

3. 3. Apprentissage et prédiction

Maintenant que le prétraitement des données pour les données données est terminé, nous continuerons à nous entraîner et à prévoir. L'algorithme utilisé pour la formation est LightGBM. Cette fois, 20 modèles ont été créés en changeant les nombres aléatoires lors de la division en données d'entraînement et données de vérification, et la moyenne de chaque valeur prédite a été prise comme résultat final de la prédiction (Random Seed Average). Les hyper paramètres sont réglés par Oputuna.

~~ De plus, ** à cause de données déséquilibrées, j'ai spécifié "'class_weight': 'shared'" dans les paramètres de LightGBM **. ~~ ** (Correction) AUC n'était pas nécessaire car il s'agit d'un indice d'évaluation qui n'est pas affecté par le biais des données. De plus, c'était LightGBM Classiefier qui pouvait spécifier class_weight en tant que paramètre. ** **

train&predict



#Importer lightgbm
import optuna.integration.lightgbm as lgb #Réglage haut de gamme avec Optuna
from sklearn.model_selection import  train_test_split
import datetime

#Divisez le train2 fusionné en train et testez à nouveau
train = train2[:27100]
test = train2[27100:].drop(['y'],axis=1)

#Obtenez les valeurs des variables objectives et explicatives du train
target = train['y'].values
features = train.drop(['id','y'],axis=1).values

#données de test
test_X = test.drop(['id'],axis=1).values

lgb_params = {'objective': 'binary',
              'metric': 'auc', #L'indice d'évaluation spécifié par le concours est AUC
              #'class_weight': 'balanced' #Je n'en ai pas besoin ici
             }

#Moyenne aléatoire des graines 20 fois
for _ in range(20):

    #Divisez le train en données d'entraînement et données de vérification
    (features , val_X , target , val_y) = train_test_split(features, target , test_size = 0.2)


    #Création d'un jeu de données pour LightGBM
    lgb_train = lgb.Dataset(features, target,feature_name = list(train.drop(['id','y'],axis=1))) #Pour apprendre
    lgb_eval = lgb.Dataset(val_X, val_y, reference=lgb_train) #Pour booster
    
    #Spécification des variables catégorielles
    categorical_features = ['job', 'marital', 'education', 'default', 'balance','month',
                            'housing', 'loan','poutcome', 'default_housing_loan']

    #Apprentissage
    model = lgb.train(lgb_params, lgb_train, valid_sets=lgb_eval,
                      categorical_feature = categorical_features,
                      num_boost_round=1000,
                      early_stopping_rounds=20,
                      verbose_eval=10)

    pred = model.predict(test_X) #Valeur de probabilité d'application de compte
    
    #Stocker chaque résultat de prédiction
    if _ == 0:
        output = pd.DataFrame(pred,columns=['pred' + str(_+1)])
        output2 = output
    
    else:
        output2 = pd.concat([output2,output],axis=1)
    
    #Fin de pour

#Moyenne de chaque résultat de prédiction
df_mean = output2.mean(axis='columns')
df_result = pd.concat([test['id'],df_mean],axis=1)

#Exporter avec l'heure attachée au nom du fichier
now = datetime.datetime.now()
df_result.to_csv('../output/submission' + now.strftime('%Y%m%d_%H%M%S') + '.csv',index=None,header=None)

Quatre. résultat

Le score (AUC) spécifié dans le concours était de 0,85, mais mon ** score final était de 0,855 **. J'ai été promu intermédiaire avec succès. ** Le classement final était 62e sur 787 personnes **, ce qui n'était ni mauvais ni extrêmement bon.

Au fait, la transition de la partition est la suivante.

** 0,8470: pas de moyenne de semences aléatoire ** ↓ (+0.0034) ** 0,8504: Moyenne aléatoire des semences 5 fois ** ↓ (+0.0035) ~~ ** 0.8539: Spécification de "'class_weight': 'balancé'" ** ~~ ↓ (+0.0016) ** 0,8555: Moyenne aléatoire des semences 20 fois **

~~ Dans mon cas, je pense que la spécification de "'class_weight': 'balancé'" était assez efficace. ~~

De plus, même si je l'ai corrigé dans le code posté sur Qiita, il y a eu une erreur fatale, donc j'ai l'impression que j'aurais pu monter jusqu'à environ 0.857 sans elle (un peu décevant).

D'ailleurs, sur le forum (tableau d'affichage de la compétition), il était écrit que si vous effectuez 100 fois la moyenne aléatoire des semences, le score augmentera considérablement. J'aurais dû augmenter le nombre moyen de fois (je n'étais pas prêt à apprendre pendant 10 heures lol).

Traitement des données déséquilibrées

** (Correction) Comme décrit ci-dessus, cet indice d'évaluation AUC est un indice d'évaluation qui n'est pas affecté par le biais des données, il n'était donc pas nécessaire de prendre en compte cette fois. De plus, c'était LightGBM Classiefier qui pouvait spécifier class_weight en tant que paramètre. ** **

J'ai remarqué que les données d'entraînement cette fois étaient des données déséquilibrées. Lors de l'entraînement avec des données déséquilibrées, il est facile de prédire que le modèle de prédiction est un exemple négatif, le traitement suivant est donc courant.

    1. Sous-échantillonner le nombre de cas négatifs en fonction du nombre de cas positifs
  1. Pondérer le nombre d'échantillons pendant l'entraînement sans sous-échantillonnage

Cette fois, je n'ai pas sous-échantillonné, mais en ai fait 2. Je me suis référé à la page suivante.

Il est préférable de définir le poids de classe lors de la classification des données biaisées dans une forêt aléatoire

Si vous souhaitez implémenter un sous-échantillonnage de 1, la page suivante vous sera utile.

Sous-échantillonnage + ensachage avec LightGBM - un mémorandum de u ++

D'ailleurs, d'après mon expérience, le sous-échantillonnage ou la pondération est bon dépend du problème. Par conséquent, il est recommandé d'essayer les deux une fois et d'adopter celui avec le meilleur score.

D'autres choses que j'ai essayées

J'ai aussi essayé le pseudo étiquetage, mais je ne l'ai pas utilisé car il n'était pas très efficace dans cette compétition.

D'après les histoires d'autres personnes qui ont participé à la compétition, l'encodage et l'empilement de cibles ne sont pas très efficaces, il semble donc que c'était une bonne compétition d'attaquer les orthodoxes avec un seul modèle.

Supplément

Puisque ce concours a le même sujet dans les exercices SIGNATE, vous pouvez télécharger les données de la page suivante et vérifier le fonctionnement du code. Si vous souhaitez réellement le déplacer, veuillez.

[Question pratique] Ciblage des clients de la Banque

finalement

Bien que ce soit une compétition limitée pour les débutants, c'était une compétition très enrichissante avec beaucoup de choses à apprendre. À l'avenir, je voudrais défier le MoA de Kaggle (compétition de dynamique pharmaceutique) et le concours Splatoon de ProbSpace. Au fait, j'ai également postulé pour le programme de développement des ressources humaines AI "AI QUEST" parrainé par le ministre de l'Économie, du Commerce et de l'Industrie, donc si j'ai la chance de le réussir, je vais être occupé tous les jours.

P.S. Il a fallu beaucoup de temps pour dessiner la pyramide des titres SIGNATE ...

Recommended Posts

SIGNATE [1st _Beginner Limited Competition] Résolution du ciblage des clients bancaires
SIGNATE [1st _Beginner Limited Competition] Participation au ciblage des clients de la banque
Signate_ Revue du 1er Concours Limité Débutant
[SIGNATE] Ciblage des clients de la banque @ apprentissage
Signer 2nd _Beginner Limited Competition Review
JOI2019 / 2020 1ère qualification 3ème Comment résoudre les problèmes A et B
Comment résoudre des équations linéaires simultanées
Résoudre des puzzles et 15 puzzles