前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >算法集锦(17) | 推荐系统 | 基于机器学习的商品定价系统

算法集锦(17) | 推荐系统 | 基于机器学习的商品定价系统

作者头像
用户7623498
发布2020-08-04 16:41:04
9780
发布2020-08-04 16:41:04
举报

Mercari是日本最大的社区购物应用程序,其深知一个问题,就是想给卖家提供定价建议非常困难。因为卖家可以在Mercari的市场上放置任何东西。

本次算法分享,我们将建立一个自动建议正确产品价格的模型。数据集可以从Kaggle下载。为了验证结果,我只需要train.tsv。

让我们开始吧!

代码语言:javascript
复制
 1import gc
 2import time
 3import numpy as np
 4import pandas as pd
 5import matplotlib.pyplot as plt
 6import seaborn as sns
 7from scipy.sparse import csr_matrix, hstack
 8from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
 9from sklearn.preprocessing import LabelBinarizer
10from sklearn.model_selection import train_test_split, cross_val_score
11from sklearn.metrics import mean_squared_error
12import lightgbm as lgb
13df = pd.read_csv('train.tsv', sep = '\t')

将数据随机分为训练集和测试集。我们只对EDA进行训练。

代码语言:javascript
复制
1msk = np.random.rand(len(df)) < 0.8
2train = df[msk]
3test = df[~msk]
4train.shape, test.shape
5train.head()
代码语言:javascript
复制
1train.info()

价 格

代码语言:javascript
复制
1train.price.describe()

商品的价格被扭曲了,绝大部分的价格在10-20之间。我们对价格进行对数变换。

代码语言:javascript
复制
1plt.subplot(1, 2, 1)
2(train['price']).plot.hist(bins=50, figsize=(12, 6), edgecolor = 'white', range = [0, 250])
3plt.xlabel('price', fontsize=12)
4plt.title('Price Distribution', fontsize=12)
5plt.subplot(1, 2, 2)
6np.log(train['price']+1).plot.hist(bins=50, figsize=(12,6), edgecolor='white')
7plt.xlabel('log(price+1)', fontsize=12)
8plt.title('Price Distribution', fontsize=12)

物 流

通过分析,可见超过55%的商品物流费用由买方支付。

代码语言:javascript
复制
1train['shipping'].value_counts() / len(train)

我们关心的是,物流与价格之间有什么联系呢?

代码语言:javascript
复制
 1shipping_fee_by_buyer = train.loc[df['shipping'] == 0, 'price']
 2shipping_fee_by_seller = train.loc[df['shipping'] == 1, 'price']
 3fig, ax = plt.subplots(figsize=(18,8))
 4ax.hist(shipping_fee_by_seller, color='#8CB4E1', alpha=1.0, bins=50, range = [0, 100],
 5       label='Price when Seller pays Shipping')
 6ax.hist(shipping_fee_by_buyer, color='#007D00', alpha=0.7, bins=50, range = [0, 100],
 7       label='Price when Buyer pays Shipping')
 8plt.xlabel('price', fontsize=12)
 9plt.ylabel('frequency', fontsize=12)
10plt.title('Price Distribution by Shipping Type', fontsize=15)
11plt.tick_params(labelsize=12)
12plt.legend()
13plt.show()
代码语言:javascript
复制
1print('The average price is {}'.format(round(shipping_fee_by_seller.mean(), 2)), 'if seller pays shipping');
2print('The average price is {}'.format(round(shipping_fee_by_buyer.mean(), 2)), 'if buyer pays shipping')

如果卖方付运费,平均价格是22.58。

如果买方付运费,平均价格是30.11。

对价格进行对数变换后,我们再进行比较。

代码语言:javascript
复制
 1fig, ax = plt.subplots(figsize=(18,8))
 2ax.hist(np.log(shipping_fee_by_seller+1), color='#8CB4E1', alpha=1.0, bins=50,
 3       label='Price when Seller pays Shipping')
 4ax.hist(np.log(shipping_fee_by_buyer+1), color='#007D00', alpha=0.7, bins=50,
 5       label='Price when Buyer pays Shipping')
 6plt.xlabel('log(price+1)', fontsize=12)
 7plt.ylabel('frequency', fontsize=12)
 8plt.title('Price Distribution by Shipping Type', fontsize=15)
 9plt.tick_params(labelsize=12)
10plt.legend()
11plt.show()

很明显,当买方支付运费时,平均价格会更高。

类别名称

代码语言:javascript
复制
1print('There are', train['category_name'].nunique(), 'unique values in category name column')

类别名称列中有1265个唯一值。最常见的10个类别名称是:

代码语言:javascript
复制
1train['category_name'].value_counts()[:10]

商品状况与价格

代码语言:javascript
复制
1sns.boxplot(x = 'item_condition_id', y = np.log(train['price']+1), data = train, palette = sns.color_palette('RdBu',5))

在每个商品状况id之间的平均价格似乎是不同的。

在以上探索性数据分析之后,我决定使用所有的特性来构建我们的模型。

LightGBM

在微软的DMTK项目的保护伞下,LightGBM是一个使用基于树的学习算法的梯度增强框架。它被设计成分布式和高效的,具有以下优点:

  • 更快的训练速度和更高的效率
  • 降低内存使用
  • 更好的精度
  • 并行和GPU学习支持
  • 能够处理大规模数据

因此,我们对该工具进行尝试。

参数设置

代码语言:javascript
复制
1NUM_BRANDS = 4000
2NUM_CATEGORIES = 1000
3NAME_MIN_DF = 10
4MAX_FEATURES_ITEM_DESCRIPTION = 50000

我们需要修正的列中有缺失的值:

代码语言:javascript
复制
1print('There are %d items that do not have a category name.' %train['category_name'].isnull().sum())

有5083项没有类别名称:

代码语言:javascript
复制
1print('There are %d items that do not have a brand name.' %train['brand_name'].isnull().sum())

有506370个项目没有品牌名称:

代码语言:javascript
复制
1print('There are %d items that do not have a description.' %train['item_description'].isnull().sum())

有3个项目没有描述。

LightGBM Helper函数:

代码语言:javascript
复制
 1def handle_missing_inplace(dataset): 
 2    dataset['category_name'].fillna(value='missing', inplace=True) 
 3    dataset['brand_name'].fillna(value='missing', inplace=True) 
 4    dataset['item_description'].replace('No description yet,''missing', inplace=True) 
 5    dataset['item_description'].fillna(value='missing', inplace=True)
 6def cutting(dataset):
 7    pop_brand = dataset['brand_name'].value_counts().loc[lambda x: x.index != 'missing'].index[:NUM_BRANDS]
 8    dataset.loc[~dataset['brand_name'].isin(pop_brand), 'brand_name'] = 'missing'
 9    pop_category = dataset['category_name'].value_counts().loc[lambda x: x.index != 'missing'].index[:NUM_CATEGORIES]
10def to_categorical(dataset):
11    dataset['category_name'] = dataset['category_name'].astype('category')
12    dataset['brand_name'] = dataset['brand_name'].astype('category')
13    dataset['item_condition_id'] = dataset['item_condition_id'].astype('category')

删除价格为0的行:

代码语言:javascript
复制
1df = pd.read_csv('train.tsv', sep = '\t')
2msk = np.random.rand(len(df)) < 0.8
3train = df[msk]
4test = df[~msk]
5test_new = test.drop('price', axis=1)
6y_test = np.log1p(test["price"])
7train = train[train.price != 0].reset_index(drop=True)

合并训练数据和新的测试数据。

代码语言:javascript
复制
1nrow_train = train.shape[0]
2y = np.log1p(train["price"])
3merge: pd.DataFrame = pd.concat([train, test_new])

训练准备

代码语言:javascript
复制
1handle_missing_inplace(merge)
2cutting(merge)
3to_categorical(merge)

计算向量化名称和类别名称列。

代码语言:javascript
复制
1cv = CountVectorizer(min_df=NAME_MIN_DF)
2X_name = cv.fit_transform(merge['name'])
3cv = CountVectorizer()
4X_category = cv.fit_transform(merge['category_name'])

TF-IDF Vectorize item_description列。

代码语言:javascript
复制
1tv = TfidfVectorizer(max_features=MAX_FEATURES_ITEM_DESCRIPTION, ngram_range=(1, 3), stop_words='english')
2X_description = tv.fit_transform(merge['item_description'])

标签binarize brand_name列。

代码语言:javascript
复制
1lb = LabelBinarizer(sparse_output=True)
2X_brand = lb.fit_transform(merge['brand_name'])

item_condition_idshipping列创建虚拟变量。

代码语言:javascript
复制
1X_dummies = csr_matrix(pd.get_dummies(merge[['item_condition_id', 'shipping']], sparse=True).values)

创建稀疏合并。

代码语言:javascript
复制
1sparse_merge = hstack((X_dummies, X_description, X_brand, X_category, X_name)).tocsr()

删除文档频率小于等于1的特性。

代码语言:javascript
复制
1mask = np.array(np.clip(sparse_merge.getnnz(axis=0) - 1, 0, 1), dtype=bool)
2sparse_merge = sparse_merge[:, mask]

将训练数据和测试数据从稀疏合并中分离出来。

代码语言:javascript
复制
1X = sparse_merge[:nrow_train]
2X_test = sparse_merge[nrow_train:]

为lightGBM创建数据集。

代码语言:javascript
复制
1train_X = lgb.Dataset(X, label=y)

将我们的参数指定为一个命令。

代码语言:javascript
复制
1params = {
2        'learning_rate': 0.75,
3        'application': 'regression',
4        'max_depth': 3,
5        'num_leaves': 100,
6        'verbosity': -1,
7        'metric': 'RMSE',
8    }
  • 当我们处理回归问题时,使用“regression”作为应用。
  • 使用“RMSE”作为度量,因为这是一个回归问题。
  • “num_leaves”=100,因为我们的数据比较大。
  • 使用“max_depth”避免过度拟合。
  • 使用“冗长”来控制LightGBM的冗长程度(<0:致命)。
  • “learning_rate”决定了每棵树对最终结果的影响。

开始训练

训练一个模型需要一个参数列表和数据集。

代码语言:javascript
复制
1gbm = lgb.train(params, train_set=train_X, num_boost_round=3200, verbose_eval=100)

预 测

代码语言:javascript
复制
1y_pred = gbm.predict(X_test, num_iteration=gbm.best_iteration)

评 估

代码语言:javascript
复制
1from sklearn.metrics import mean_squared_error
2print('The rmse of prediction is:', mean_squared_error(y_test, y_pred) ** 0.5)

预测的RMSE为:0.4616。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-07-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 决策智能与机器学习 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档