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).
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é.
À 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) |
OS: Windows10 Processeur: core i7 5500U Mémoire: 16 Go Environnement Anaconda3 (Python 3.7.6)
Bank_Prediction ├ notebook/ ●●●.ipynb ├ input/ train.csv、test.csv └ Sortie / Sortie du résultat de la prédiction ici
Créez un modèle de prédiction dans l'ordre suivant.
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.
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")
train.info()
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.
train.describe()
train.hist(figsize=(20,20), color='r')
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.
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)
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.
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=[])
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).
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())
J'ai pu confirmer le nombre d'éléments inclus dans chaque variable catégorielle.
À partir de là, nous effectuerons un prétraitement des données pour créer un modèle de prédiction.
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])
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)
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).
** (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.
Cette fois, je n'ai pas sous-échantillonné, mais en ai fait 2. Je me suis référé à la page suivante.
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.
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.
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
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