Il peut être difficile de savoir ce qui en vaut vraiment la peine. Moins de détails peuvent faire une grande différence de prix. Par exemple, l'un de ces chandails coûte 335 $ et l'autre 9,99 $. Pouvez-vous deviner lequel est lequel?
Compte tenu du nombre de produits vendus en ligne, la tarification des produits est encore plus difficile. Le prix des vêtements a de fortes tendances saisonnières de prix et est fortement influencé par le nom de la marque, mais le prix des appareils électroniques fluctue en fonction des spécifications du produit.
La plus grande application d'achat communautaire du Japon est profondément consciente de ce problème. Il est difficile de proposer une bonne offre de prix au vendeur, car le vendeur peut mettre n'importe quoi ou n'importe quoi sur le marché Mercari.
Le Mercari Price Suggestion Challenge est un concours qui évalue le «prix raisonnable» d'un produit à partir des données produit réellement mises en vente. Les données produit incluent le nom du produit, la description du produit, l'état du produit, le nom de la marque, le nom de la catégorie, etc., et sur cette base, l'apprentissage automatique est utilisé pour prédire le prix de vente.
L'ensemble de données produit est publié par la version nord-américaine de Mercari, donc tout le monde peut l'obtenir. https://www.kaggle.com/c/mercari-price-suggestion-challenge/data
Cette fois, j'aimerais utiliser ces données pour estimer le prix approprié.
train.tsv a les données de 1,5 million d'articles actuellement répertoriés. Toutes les notations sont en anglais en raison de la version nord-américaine des données Mercari. Le produit est décrit à partir de 8 colonnes.
colonne | La description |
---|---|
train_id | ID de publication de l'utilisateur |
name | Nom du produit |
item_condition_id | État du produit |
category_name | Catégorie de produit |
brand_name | marque |
price | Prix de vente (dollar) |
shipping | Frais de port (exposant ou acheteur) |
item_description | Description du produit |
Ces données sont divisées en train et test, et le prix de vente est prédit par l'apprentissage automatique.
L'environnement d'exécution est sur Google Colaboratory. Étant donné que le nombre de données est extrêmement important, cela prendra du temps à moins que ce ne soit dans un environnement GPU.
Veuillez vous référer ici pour Goggle Colboratory Présentation et instructions de Google Colaboratory (TensorFlow et GPU peuvent être utilisés)
RSMLE est utilisé lorsque vous souhaitez exprimer la distribution proche de la ** distribution log normale ** et de l'erreur ** entre la valeur mesurée et la valeur prédite sous forme de rapport ou de rapport ** au lieu d'une largeur.
En regardant la figure ci-dessus, l'histogramme des prix du produit ressemble à une distribution normale logarithmique. Aussi, par exemple Les largeurs d'erreur de (1000, 5000) et (100000, 104000) sont égales à 4000, mais les taux d'erreur sont différents et cette différence est importante.
A partir de là, le prix estimé semble convenir à la méthode d'évaluation par RMSLE.
Non seulement train.tsv mais aussi test.tsv sont publiés, mais comme il n'a pas d'étiquette de réponse correcte, les données obtenues en supprimant environ 10000 éléments de train.tsv sont utilisées comme données de test.
Données globales (* 1482535, 8 ) -> (train_df ( 1472535, 8 ), test_df ( 10000, 7 *))
Il existe de nombreux espaces dans les catégories, les marques et les descriptions de produits. En apprentissage automatique, il est normal de traiter les défauts, alors remplissez les espaces avec la fonction suivante. En raison de l'absence, la marque «manquante» représentait 42% du total.
def handle_missing_inplace(dataset):
dataset['category_name'].fillna(value="Other", inplace=True)
dataset['brand_name'].fillna(value='missing', inplace=True)
dataset['item_description'].fillna(value='None', inplace=True)
La marque est coupée avant la conversion de type. Puisqu'il existe environ 5000 types de marques, les noms de marque qui apparaissent très peu de fois ne sont pas très utiles pour l'apprentissage, alors entrez le même «manquant» que le blanc.
pop_brands = df["brand_name"].value_counts().index[:NUM_BRANDS]
df.loc[~df["brand_name"].isin(pop_brands), "brand_name"] = "missing"
Après avoir coupé environ la moitié, le nombre minimum d'apparitions était de 4 fois.
Convertit les données texte en type de catégorie. En effet, des variables factices sont créées lors du traitement ultérieur.
def to_categorical(dataset):
dataset['category_name'] = dataset['category_name'].astype('category')
dataset['brand_name'] = dataset['brand_name'].astype('category')
dataset['item_condition_id'] = dataset['item_condition_id'].astype('category')
Applique CountVectorizer aux noms de produits et aux noms de catégories. Pour faire simple, CountVectorizer est vectorisé en fonction du nombre d'occurrences. Par exemple, si vous exécutez Count Vectorizer sur les trois noms de produit «MLB Cincinnati Reds T Shirt Size XL», «AVA-VIV Blouse» et «Leather Horse Statues», ils seront vectorisés comme suit.
De plus, comme le nom du produit est saisi par le vendeur, il peut y avoir des erreurs typographiques dans le mot ou des mots fixes ou des chiffres qui n'apparaissent que dans des phrases spécifiques. Dans cet esprit, ajoutez l'option min_df à CountVectorizer. min_df signifie exclure les mots qui apparaissent moins que min_df%.
count_name = CountVectorizer(min_df=NAME_MIN_DF)
X_name = count_name.fit_transform(df["name"])
count_category = CountVectorizer()
X_category = count_category.fit_transform(df["category_name"])
Contrairement à CountVectorizer, TfidfVectorizer considère non seulement le nombre d'occurrences d'un mot mais également la rareté du mot. Par exemple, les mots qui existent dans chaque phrase tels que «desu» et «masu», et les acronymes tels que «a» et «the» apparaissent fréquemment en anglais et sont fortement entraînés par ces mots dans Count Vectorizer. Au lieu de cela, il est utilisé lorsque vous souhaitez effectuer une vectorisation en se concentrant sur l'importance des mots.
En d'autres termes, TfidfVecotrizer signifie "pondérer les mots qui apparaissent fréquemment dans un document et rarement dans un autre document de grande importance".
A partir des points ci-dessus, la description du produit sera vectorisée par TfidfVectorizer.
Ensuite, le tableau est comme indiqué ci-dessus et la valeur tfidf est fortement attachée à l'acronyme et au connectif. Spécifiez stop_word = 'english'
car ces mots n'ont toujours pas de sens dans l'apprentissage.
Ensuite, la figure de gauche montre les 10 dernières valeurs tfidf. Si la valeur tfidf est extrêmement petite, cela n'a pas beaucoup de sens, alors supprimez-la. Aussi, au lieu de prendre tfidf pour un mot, prenez tfidf pour des mots consécutifs. Par exemple, définissons n-gramme avec le dicton "une pomme par jour éloigne le médecin" (une pomme par jour sans médecin).
n-gram(1, 2)
{'an': 0, 'apple': 2, 'day': 5, 'keeps': 9, 'the': 11,'doctor':7,'away': 4,
'an apple': 1, 'apple day': 3, 'day keeps': 6, 'keeps the': 10,
'the doctor': 12, 'doctor away': 8}
n-gram(1, 3)
{'an': 0, 'apple': 3, 'day': 7, 'keeps': 12, 'the': 15, 'doctor': 10,'away': 6,
'an apple': 1, 'apple day': 4, 'day keeps': 8, 'keeps the': 13,'the doctor': 16,
'doctor away': 11, 'an apple day': 2, 'apple day keeps': 5, 'day keeps the': 9,
'keeps the doctor': 14, 'the doctor away': 17}
De cette manière, à mesure que la plage de n grammes augmente, les caractéristiques du texte sont capturées plus en détail et des données utiles sont acquises. Avec l'ajout d'options, cela ressemble à la figure de droite.
Le tfidf final ressemblera à la figure ci-dessous. Celui avec la valeur tfidf la plus élevée est "description", qui peut être vu comme étant influencé par "Pas encore de description". Vous pouvez voir que les articles neufs et d'occasion tels que «neuf» et «utilisé» affectent également le prix.
tfidf_descp = TfidfVectorizer(max_features = MAX_FEAT_DESCP,
ngram_range = (1,3),
stop_words = "english")
X_descp = tfidf_descp.fit_transform(df["item_description"])
Comme je l'ai mentionné plus tôt, il existe environ 5 000 types de marques et, à la suite du processus de découpe, il existe environ 2 500 types de marques.
Marquez-les avec 0 ou 1. Comme il y a beaucoup de données, définissez sparse_output = True
et exécutez.
label_brand = LabelBinarizer(sparse_output=True)
X_brand = label_brand.fit_transform(df["brand_name"])
Les variables factices sont une technique permettant de convertir des données non numériques en nombres. Plus précisément, il convertit les données non numériques en une séquence de nombres avec seulement «0» et «1». Ici, des variables factices sont créées pour l'état du produit et les frais d'expédition.
X_dummies = scipy.sparse.csr_matrix(pd.get_dummies(df[[
"item_condition_id", "shipping"]], sparse = True).values, dtype=int)
Maintenant que nous avons traité toutes les colonnes, nous allons combiner toutes les séquences et les appliquer au modèle.
X = scipy.sparse.hstack((X_dummies,
X_descp,
X_brand,
X_category,
X_name)).tocsr()
Comme tous les paramètres ne peuvent pas être expliqués, certains paramètres sont résumés brièvement.
option | desc |
---|---|
alpha | Degré de normalisation pour éviter le surapprentissage |
max_iter | Nombre maximum d'itérations d'apprentissage |
tol | Sous réserve d'une augmentation du score de tol ou plus |
alpha
Il est possible de construire un modèle qui s'adapte excessivement aux données données et provoque une petite erreur pour les données d'entraînement données, mais il est appelé "** surentraînement " qu'il n'est pas possible de faire une prédiction appropriée pour des données inconnues. dire. Par conséquent, le surapprentissage peut être évité en définissant des restrictions sur l'apprentissage des paramètres. Une telle limitation est appelée " normalisation **".
option | description |
---|---|
n_esimators | Nombre d'arbres déterminés |
learning_rate | Poids de chaque arbre |
max_depth | Profondeur maximale de chaque arbre |
num_leaves | Nombre de feuilles |
min_child_samples | Nombre minimum de données contenues dans le nœud final |
n_jobs | Nombre de processus parallèles |
learning_rate
n_estimatiors
Ridge
Commencez par rechercher la valeur optimale de alpha. Déplacez alpha dans la plage de 0,05 à 75 pour visualiser l'effet sur la précision
D'après la figure, la valeur minimale * RMSLE 0,4745938085035464 * a été obtenue lorsque alpha = 3,0.
Ensuite, suite à la vérification du nombre maximum de recherches max_iter dans toutes les plages, aucune amélioration de la précision n'a été obtenue. De plus, plus la valeur tol est élevée, moins elle est précise.
D'après ce qui précède, le paramètre Ridge est modélisé avec alpha = 3.
LGBM Pour le réglage des paramètres LGBM, je me suis référé aux documents. https://lightgbm.readthedocs.io/en/latest/Parameters-Tuning.html
Il semble être une pratique courante de commencer par définir learning_rate et n_estimatiors comme première étape dans l'ajustement des paramètres de LGBM. Pour améliorer la précision, il semble que learning_rate soit petit et n_estimatiors soit grand. Déplacez learning_rate dans la plage de 0,05 à 0,7 pour ajuster n_estimatiors.
Ensuite, après avoir défini learning_rate et n_estimatiors, déplacez num_leaves.
(num_leaves = 20) RMSLE 0.4620242411418184 ↓ (num = 31) RMSLE 0.4569169142862856 ↓ (num = 40) RMSLE 0.45587232757584967
Dans l'ensemble, nous avons constaté que l'augmentation de num_leaves améliorait également la précision. Globalement ici signifie même lorsque d'autres paramètres sont ajustés.
Cependant, lors de l'ajustement de chaque paramètre, si num_leaves était trop élevé, un ** sur-appariement ** se produirait et, dans certains cas, un bon score ne pourrait pas être obtenu. J'ai dû bien l'ajuster avec d'autres paramètres.
Lorsque learning_rate = 0,7 max_depth = 15, num_leaves = 30 RMSLE 44.650714399639845
Le modèle final LGBM ressemble à ceci:
lgbm_params = {'n_estimators': 1000, 'learning_rate': 0.4, 'max_depth': 15,
'num_leaves': 40, 'subsample': 0.9, 'colsample_bytree': 0.8,
'min_child_samples': 50, 'n_jobs': 4}
Rideg + LGBM est utilisé pour calculer la valeur prédite. LGBM a un meilleur score que Ridge, mais en combinant les deux modèles, vous pouvez améliorer la précision. Ridge RMSL error on dev set: 0.47459370995217937
LGBM RMSL error on dev set: 0.45317097672035855
Ridge + LGBM RMSL error on dev set: 0.4433081424824549
Cette précision correspond à une plage d'erreur estimée de 18,89 à 47,29 pour un produit à 30 $.
price est la valeur prédite par Ridge + LGBM, et real_price est la valeur mesurée. Sur les 10 000 données de test, il y en avait environ 7553 avec des erreurs de moins de 10 $.
Tracé résiduel avec journal
Répartition des prix réels et estimés
J'ai simplement pris la différence, mais il y a environ 90 produits qui ont une différence de 100 dollars ou plus entre la valeur prévue et la valeur mesurée. Étant donné que cet ensemble de données est des données d'il y a deux ans, on peut voir que le nombre de données est petit et qu'il n'est pas possible de bien prévoir car Apple Watch etc. sont des produits relativement nouveaux. C'est aussi Mercari. C'est bien, mais tout ne peut pas être bien prédit à cause du prix basé sur des valeurs personnelles. Le sac d'entraîneur a en fait été vendu pour environ 9 $ ...
import numpy as np
import pandas as pd
import scipy
from sklearn.linear_model import Ridge
from lightgbm import LGBMRegressor
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.preprocessing import LabelBinarizer
NUM_BRANDS = 2500
NAME_MIN_DF = 10
MAX_FEAT_DESCP = 10000
print("Reading in Data")
df = pd.read_csv('train.tsv', sep='\t')
print('Formatting Data')
shape = df.shape[0]
train_df = df[:shape-10000]
test_df = df[shape-10000:]
target = test_df.loc[:, 'price'].values
target = np.log1p(target)
print("Concatenate data")
df = pd.concat([train_df, test_df], 0)
nrow_train = train_df.shape[0]
y_train = np.log1p(train_df["price"])
def handle_missing_inplace(dataset):
dataset['category_name'].fillna(value="Othe", inplace=True)
dataset['brand_name'].fillna(value='missing', inplace=True)
dataset['item_description'].fillna(value='None', inplace=True)
print('Handle missing')
handle_missing_inplace(df)
def to_categorical(dataset):
dataset['category_name'] = dataset['category_name'].astype('category')
dataset['brand_name'] = dataset['brand_name'].astype('category')
dataset['item_condition_id'] = dataset['item_condition_id'].astype('category')
print('Convert categorical')
to_categorical(df)
print('Cut')
pop_brands = df["brand_name"].value_counts().index[:NUM_BRANDS]
df.loc[~df["brand_name"].isin(pop_brands), "brand_name"] = "missing"
print("Name Encoders")
count_name = CountVectorizer(min_df=NAME_MIN_DF)
X_name = count_name.fit_transform(df["name"])
print("Category Encoders")
count_category = CountVectorizer()
X_category = count_category.fit_transform(df["category_name"])
print("Descp encoders")
tfidf_descp = TfidfVectorizer(max_features = MAX_FEAT_DESCP,
ngram_range = (1,3),
stop_words = "english")
X_descp = tfidf_descp.fit_transform(df["item_description"])
print("Brand encoders")
label_brand = LabelBinarizer(sparse_output=True)
X_brand = label_brand.fit_transform(df["brand_name"])
print("Dummy Encoders")
X_dummies = scipy.sparse.csr_matrix(pd.get_dummies(df[[
"item_condition_id", "shipping"]], sparse = True).values, dtype=int)
X = scipy.sparse.hstack((X_dummies,
X_descp,
X_brand,
X_category,
X_name)).tocsr()
print("Finished to create sparse merge")
X_train = X[:nrow_train]
X_test = X[nrow_train:]
model = Ridge(solver='auto', fit_intercept=True, alpha=3)
print("Fitting Rige")
model.fit(X_train, y_train)
print("Predicting price Ridge")
preds1 = model.predict(X_test)
def rmsle(Y, Y_pred):
assert Y.shape == Y_pred.shape
return np.sqrt(np.mean(np.square(Y_pred - Y )))
print("Ridge RMSL error on dev set:", rmsle(target, preds1))
def rmsle_lgb(labels, preds):
return 'rmsle', rmsle(preds, labels), False
train_X, valid_X, train_y, valid_y = train_test_split(X_train, y_train, test_size=0.3, random_state=42)
lgbm_params = {'n_estimators': 1000, 'learning_rate': 0.4, 'max_depth': 15,
'num_leaves': 40, 'subsample': 0.9, 'colsample_bytree': 0.8,
'min_child_samples': 50, 'n_jobs': 4}
model = LGBMRegressor(**lgbm_params)
print('Fitting LGBM')
model.fit(train_X, train_y,
eval_set=[(valid_X, valid_y)],
eval_metric=rmsle_lgb,
early_stopping_rounds=100,
verbose=True)
print("Predict price LGBM")
preds2 = model.predict(X_test)
print("LGBM RMSL error on dev set:", rmsle(target, preds2))
preds = (preds1 + preds2) / 2
print("Ridge + LGBM RMSL error on dev set:", rmsle(target, preds))
test_df["price1"] = np.expm1(preds1)
test_df['price2']=np.exp(preds2)
test_df['price']= np.expm1(preds)
test_df['real_price'] = np.expm1(target)
Suite à l'estimation du juste prix, nous avons obtenu un meilleur score que prévu. Je pense que la précision se serait améliorée un peu plus si la valeur min_df, le réglage de la plage de n-grammes, etc. avaient été modifiés dans la partie de pré-traitement, et si le texte n'était pas seulement appliqué à tfidf mais apportait des corrections plus détaillées. .. De plus, les valeurs des produits sont différentes pour chaque personne, vous ne pouvez donc prévoir que dans une certaine mesure. Si vous le trouvez utile, veuillez me donner un bon bouton!
Recommended Posts