[PYTHON] [SIGNATER] Défi pour la prévision des prix de l'hébergement pour les services d'hébergement privé

introduction

En raison de l'influence de Corona, le temps passé à la maison a augmenté, donc à partir d'avril de cette année, j'ai commencé à travailler dur sur l'apprentissage automatique. Dans le processus, j'ai eu de nombreuses opportunités de travailler sur les concours Kaggle et SIGNATE, j'ai donc décidé d'écrire un article sur Qiita en tant que sortie. Cette fois, nous travaillerons sur l'exercice de SIGNATE "Prévision du prix de l'hébergement pour le service d'hébergement privé". Le but est de créer une référence pour une analyse et une compréhension plus approfondies. Le code créé cette fois-ci est laissé au format Jupyter Notebook en ici.

1. Comprendre les problèmes

Dans cette tâche, nous travaillerons à la construction d'un modèle qui prédit le prix de l'hébergement pour chaque propriété en utilisant les données de propriété publiées sur Airbnb, qui est un service d'hébergement privé. Chez Airbnb, les propriétaires fixent les prix des chambres en fonction de la taille et de l'emplacement de la chambre, mais il semble qu'il n'est pas facile de fixer des prix raisonnables.

2, EDA (analyse exploratoire des données)

Importez les bibliothèques requises et chargez respectivement les données d'entraînement, les données de vérification et les données de soumission.

#Importer la bibliothèque
import numpy as np
import pandas as pd
from pandas import DataFrame, Series
import matplotlib.pyplot as plt
import seaborn as sns

from scipy import stats
from scipy.stats import norm,skew

from sklearn.preprocessing import LabelEncoder
import lightgbm as lgb

import warnings
warnings.filterwarnings('ignore')

#Lire les données
train = pd.read_csv('train.csv')
test = pd.read_csv('test.csv')
sub = pd.read_csv('sample_submit.csv',names=('id','pred'))

#Spécifiez le nombre de lignes comme variable
ntrain = train.shape[0]
ntest = test.shape[0]

#Vérifiez le nombre de données
train.shape, test.shape

#((55583, 29), (18528, 28))

Il peut être confirmé que les données d'apprentissage contiennent 55583 éléments et les données de vérification contiennent 18528 éléments. Étant donné que le prix de l'hébergement ** y ** que vous souhaitez enfin prédire n'est inclus que dans les données d'entraînement, le nombre de colonnes est supérieur à celui des données de vérification. Affichez les 5 premières lignes de données d'entraînement et de validation.

train.head()

スクリーンショット 2020-09-23 19.24.27.png

test.head()

スクリーンショット 2020-09-23 19.24.43.png ** commodités ** et ** nom ** sont comme des chaînes. Étant donné que le classificateur ne peut pas traiter les données de caractères, il est nécessaire d'envisager des contre-mesures. Lorsque les 29 colonnes de données d'apprentissage ont été vérifiées, les colonnes contenant des chaînes de caractères qui ne pouvaient pas être traitées par conversion de catégorie étaient les suivantes.

Nom de l'en-tête Explication
amenities Agréments
description Explication
name Nom de la propriété
thumbnail_url Lien d'image de pouce

Visualisez la distribution de la variable objectif ** y (prix de la chambre) **.

sns.distplot(train['y']);

スクリーンショット 2020-09-20 20.40.45.png Comme vous pouvez le voir dans le plan du concours, le type de problème est la régression. Pour la régression, il est important que les valeurs de la variable objective suivent une distribution normale. Nous pouvons voir que ** y ** est loin de la distribution normale, nous devons donc nous en occuper. Vérifiez également la distorsion et la netteté actuelles.

#Afficher la distorsion et la netteté
print("asymétrie: %f" % train['y'].skew())
print("kurtosis: %f" % train['y'].kurt())

#asymétrie: 4.264338
#kurtosis: 26.030945

La ** distorsion ** était de 4,26 et la ** netteté ** était de 26,03. C'est assez biaisé. Pour les principales variables catégorielles, nous explorerons également la relation avec ** y ** en utilisant le diagramme des moustaches. (accommodates)

var = 'accommodates'
data = pd.concat([train['y'], train[var]], axis=1)
f, ax = plt.subplots(figsize=(16, 8))
fig = sns.boxplot(x=var, y="y", data=data)
fig.axis(ymin=0, ymax=2100);
plt.xticks(rotation=90);

スクリーンショット 2020-09-20 20.52.50.png → La propriété avec le prix médian le plus élevé est une propriété pouvant accueillir 16 personnes. Le côté hôte (le côté qui loue la propriété) semble avoir tendance à fixer le prix de l'hébergement plus haut lorsque la propriété a une grande capacité. (bathrooms)

var = 'bathrooms'
data = pd.concat([train['y'], train[var]], axis=1)
f, ax = plt.subplots(figsize=(16, 8))
fig = sns.boxplot(x=var, y="y", data=data)
fig.axis(ymin=0, ymax=2100);
plt.xticks(rotation=90);

スクリーンショット 2020-09-20 20.55.03.png (bedrooms)

var = 'bedrooms'
data = pd.concat([train['y'], train[var]], axis=1)
f, ax = plt.subplots(figsize=(16, 8))
fig = sns.boxplot(x=var, y="y", data=data)
fig.axis(ymin=0, ymax=2100);
plt.xticks(rotation=90);

スクリーンショット 2020-09-20 20.55.18.png (beds)

var = 'beds'
data = pd.concat([train['y'], train[var]], axis=1)
f, ax = plt.subplots(figsize=(16, 8))
fig = sns.boxplot(x=var, y="y", data=data)
fig.axis(ymin=0, ymax=2100);
plt.xticks(rotation=90);

スクリーンショット 2020-09-20 20.55.37.png Il semble que plus il y a de variables, plus le prix est élevé. Ici, à titre d'exemple basé sur les résultats de la visualisation, on suppose que l'ajout de ** {bath | bed} rooms ** peut améliorer la précision.

3, prétraitement des données

Au stade «obtention des données», nous savons que les variables objectives ne suivent pas une distribution normale. En guise de contre-mesure, prenez le logarithme de ** y ** et placez-le dans une pseudo distribution normale. Faisons également un diagnostic avec un diagramme QQ normal en prenant un résidu pour voir s'il s'approche d'une distribution normale.

#Avant le traitement
sns.distplot(train['y'] , fit=norm);

#Obtenir les paramètres
(mu, sigma) = norm.fit(train['y'])
print( '\n mu = {:.2f} and sigma = {:.2f}\n'.format(mu, sigma))

#Visualisation
plt.legend(['Normal dist. ($\mu=$ {:.2f} and $\sigma=$ {:.2f} )'.format(mu, sigma)],loc='best')
plt.ylabel('Frequency')
plt.title('y distribution')

#Application de tracés QQ réguliers
fig = plt.figure()
res = stats.probplot(train['y'], plot=plt)
plt.show()

スクリーンショット 2020-09-21 22.41.37.png

#Après le traitement
# log1p(fonction numpy)Appliquer, prendre le journal
train["y"] = np.log1p(train["y"])

#Vérifiez la distribution après l'application
sns.distplot(train['y'] , fit=norm);

#Obtenir les paramètres
(mu, sigma) = norm.fit(train['y'])
print( '\n mu = {:.2f} and sigma = {:.2f}\n'.format(mu, sigma))

#Visualisation
plt.legend(['Normal dist. ($\mu=$ {:.2f} and $\sigma=$ {:.2f} )'.format(mu, sigma)],loc='best')
plt.ylabel('Frequency')
plt.title('y distribution')

#Application de tracés QQ réguliers
fig = plt.figure()
res = stats.probplot(train['y'], plot=plt)
plt.show()

スクリーンショット 2020-09-21 22.42.06.png Si vous prenez le logarithme, vous pouvez voir qu'il est proche de la distribution normale. Vous pouvez voir que les résidus de ** y ** sont alignés sur la ligne rouge à 45 degrés, bien qu'il y ait quelques écarts dans le graphique QQ normal. Par conséquent, on peut dire que l'erreur de la variable objective suit également la distribution normale.

#Extraire la colonne y
train_y = train['y']
train_y.shape

#(55583,)

#Combinez les données d'entraînement et les données de validation
all_data = pd.concat((train, test)).reset_index(drop=True)
all_data.drop(['y','id'], axis=1, inplace=True)
print("all_data size : {}".format(all_data.shape))

#all_data size : (74111, 27)

Lors de l'analyse des données, le traitement des valeurs manquantes suit toujours. Traitons à nouveau les valeurs manquantes dans chaque colonne.

#Vérifiez le pourcentage de valeurs manquantes
all_data_na = (all_data.isnull().sum() / len(all_data)) * 100
all_data_na = all_data_na.drop(all_data_na[all_data_na == 0].index).sort_values(ascending=False)[:30]
missing_data = pd.DataFrame({'Missing Ratio' :all_data_na})
missing_data.head(15)

スクリーンショット 2020-09-20 21.06.46.png

Il semble que 13 variables ont des valeurs manquantes. Décidons comment reconstituer ou supprimer des données tout en regardant les données d'origine et les résultats de visualisation. Cette fois, je vais laisser tomber ** thumbnail_url ** et ** zipcode **.

#Convertir toutes les variables liées à l'année / au mois en type virgule flottante et remplir les valeurs manquantes avec 0
for c in ('first_review','last_review','host_since'):
    all_data[c] = pd.to_datetime(all_data[c])
    all_data[c] = pd.DatetimeIndex(all_data[c])
    all_data[c] = np.log(all_data[c].values.astype(np.float64))
    all_data[c] = all_data[c].fillna(0)

#Remplissez les valeurs manquantes avec 1
for c in ('bathrooms','beds','bedrooms'):
    all_data[c] = all_data[c].fillna(1)

#Remplissez les valeurs manquantes avec Aucun
for c in ('host_response_rate','neighbourhood','host_identity_verified','host_has_profile_pic'):
    all_data[c] = all_data[c].fillna('None')

#Remplir avec la médiane
all_data['review_scores_rating'].fillna(all_data['review_scores_rating'].median(),inplace=True)

#Supprimer les colonnes inutilisées
all_data = all_data.drop(['thumbnail_url','zipcode'],axis=1)

Vous avez maintenant traité les valeurs manquantes. Vérifiez le type de données ici.

all_data.dtypes

スクリーンショット 2020-09-23 22.17.09.png Il existe de nombreux types d'objets. Étant donné que le modèle ne peut pas être entraîné tel quel, ** Label Encoder ** est utilisé.

#Encodage des étiquettes
cols =  ('bed_type','cancellation_policy','city','cleaning_fee','host_identity_verified','host_has_profile_pic','host_response_rate','instant_bookable','property_type','room_type','neighbourhood')

for c in cols:
    lbl = LabelEncoder() 
    lbl.fit(list(all_data[c].values)) 
    all_data[c] = lbl.transform(list(all_data[c].values))

#Vérifier le type de données
all_data.dtypes

スクリーンショット 2020-09-23 22.21.08.png J'ai pu le convertir en un type presque numérique.

Traitement du langage naturel (NLP)

De nombreux concours traitant des données textuelles sont organisés chez kaggle et SIGNATE. Puisqu'il gère des langues naturelles telles que le japonais et l'anglais, il s'appelle Natural Language Processing (NLP) et a été établi comme un domaine de l'apprentissage automatique. Par rapport aux données de table, il n'y a pas de grande différence dans l'étape d'apprentissage / prédiction car elle ne s'écarte pas du cadre de l'apprentissage automatique supervisé en raison de la nature de la concurrence. D'autre part, en ce qui concerne le prétraitement, il existe différents types tels que l'extraction de la racine de mot et la vectorisation de mot. Cette fois, nous allons traiter avec une méthode simple qui ne compte que le nombre de caractères de la phrase.

#Comptez le nombre de caractères dans la colonne cible
for c in ('amenities','description','name'):
    all_data[c] = all_data[c].apply(lambda x: sum(len(word) for word in str(x).split(" ")))

all_data.dtypes

スクリーンショット 2020-10-05 23.57.53.png Vous pouvez voir que ce sont tous des types numériques. À propos, l'histogramme de ** description ** est affiché à titre d'exemple.

#Description de l'histogramme
plt.hist(all_data['description'],alpha=0.5)
plt.xlabel('description')
plt.ylabel('count')
plt.show()

スクリーンショット 2020-10-06 0.12.21.png Vous pouvez voir que la très grande majorité des propriétés ont une description de 800 caractères ou plus. Enfin, ajoutons les ** {bath | bed} rooms ** que nous avions émis l'hypothèse au moment de l'EDA.

#Créer une nouvelle quantité de fonctionnalités
#Additionnez le nombre de salles de bain et de chambres
all_data['total_rooms'] = all_data['bathrooms'] + all_data['bedrooms']

4, apprentissage

Cette fois, nous utiliserons LightGBM, qui est le plus souvent utilisé dans les compétitions récemment, dans GBDT (gradient boosting tree) pour effectuer de l'apprentissage à la prédiction.

#Diviser en données d'entraînement et données de vérification
train = all_data[:ntrain]
test = all_data[ntrain:]

#Apprendre avec le régresseur LGBM
model = lgb.LGBMRegressor(num_leaves=100,learning_rate=0.05,n_estimators=1000)
model.fit(train,train_y)

#Prédire les données de validation avec le modèle entraîné
pred = np.expm1(model.predict(test))

Dans feature_importances_, vérifiez quelles variables contribuent au modèle.

#Visualisez l'importance variable
ranking = np.argsort(-model.feature_importances_)
f, ax = plt.subplots(figsize=(11, 9))
sns.barplot(x=model.feature_importances_[ranking],y=train.columns.values[ranking], orient='h')
ax.set_xlabel('feature importance')
plt.tight_layout()
plt.show()

スクリーンショット 2020-10-06 0.15.39.png Les deux principaux niveaux d'importance étaient la latitude et la longitude. Enfin, écrivez-le dans un fichier csv et soumettez-le.

#Soumission
sub['pred'] = pred
sub.to_csv('sub.csv',index=False,header=None)

5, résultat

À la suite du prétraitement et de l'apprentissage, il a été classé 44/163 (au 6 octobre 2020). C'est un résultat décent, mais il y a place à l'amélioration à tous égards. (Utilisation des fonctionnalités supprimées, traitement du langage naturel, sélection de modèle, vérification des intersections, réglage des paramètres, ensemble ...) Des résultats encore meilleurs peuvent être obtenus par une telle ingéniosité, j'espère donc que cet article vous aidera.

6, références

・ Kaggle "House Prices: Advanced Regression Techniques" a publié le cahier "Stacked Regressions: Top 4% on LeaderBoard"

Recommended Posts

[SIGNATER] Défi pour la prévision des prix de l'hébergement pour les services d'hébergement privé