Price Suggestion Chanllenge
实验题目
题目背景
考虑到网上销售的产品数量,产品定价在规模上变得更加困难。服装有很强的季节性定价趋势,受品牌影响很大,而电子产品价格根据产品规格波动。如何根据以往信息进行合理定价,有效地帮助商家进行商品的销售是一个有意义的问题。
分析目标
通过给出的商品描述、商品类别和品牌信息,并结合训练数据中的商品价格来给新商品定价格。Eg :
显然 Versace
的衣服价格上应该远高于美特斯邦威的衣服,并且在商品描述中,可以发现两者描述有细微差别。
本 project 旨在对文本信息进行分析,提取文本信息中重要信息,推导出和价格之间的潜在关系
数据字段分析
数据集
train.csv
训练集 (含price)test.csv
测试集 (不含price) ;label_test.csv
测试集 中对应的price
f_test.csv
最终的评价数据集 (不含price
)
评价指标
评价的使用的是 Mean Squared Logarithmic Error
: 计算的方式如下
其中$n$代表测试集的样本数;$p_i$代表的是预测的商品价格值;$\alpha_i$代表实际的销售价格。
作业要求
提交的最后文件内容为:
- 最终代码文件(请写清楚使用了那些库,以及相应库的版本,可使用
pip list
命令查看版本,确保能顺利运行) - 在
f_test.csv
数据集上的结果 - 分析文档
请不要是简单的代码粘贴,加入分析过程
将你对于数据的理解记录下来,简单来说,缺失值处理这种基本操作
写出你的尝试的各种方法,为了解决rank太低的情况下分数太低
写一份同学负责哪一部分代码,每一部分没有区别,主要是为了给代码风格打分。
提交结果文件格式
- 结果文件名为
- 提交格式
- 第一行为 test_id \t price 的表头
- 接下来的每行为id \t predict_price
实验过程
这次实验难度很大,我们所有参考资料均在实验报告的末尾注明
一、样例代码的学习
首先尝试了给出的样例代码,了解了解决这个问题的大致思路。解决这个价格预测问题的主要过程是:导入数据和数据探索、数据预处理、模型构建、价格预测和测评。
导入数据和数据探索
导入数据和初步了解数据train_data = pd.read_csv('../data/4/train.csv', sep="\t")
test_data = pd.read_csv('../data/4/test.csv',sep='\t')
train_data.info()<class 'pandas.core.frame.DataFrame'>
RangeIndex: 300000 entries, 0 to 299999
Data columns (total 8 columns):
train_id 300000 non-null int64
name 300000 non-null object
item_condition_id 300000 non-null int64
category_name 298719 non-null object
brand_name 171929 non-null object
price 300000 non-null float64
shipping 300000 non-null int64
item_description 300000 non-null object
dtypes: float64(1), int64(3), object(4)
memory usage: 18.3+ MB
数据预处理
首先处理属性,训练数据首先要删去price
,再去掉没有用处的 train_id
或者 test_id
。通过观察上面的数据属性可知 category_name
和 brand_name
有数据缺失,样例代码直接用 missing
填充。def featureProcessing(df):
# delete the data that will not be used
df = df.drop(['price', 'test_id', 'train_id'], axis=1)
# deal with the missing value with a default value
df['category_name'] = df['category_name'].fillna('missing').astype(str)
df['brand_name'] = df['brand_name'].fillna('missing').astype(str)
df['item_description'] = df['item_description'].fillna('No')
# convert the data : int -> str
df['shipping'] = df['shipping'].astype(str)
df['item_condition_id'] = df['item_condition_id'].astype(str)
return df
模型构建
首先做模型的输入,通过CountVectorizer
和 TfidfVectorizer
生成词频的矩阵, Tfidf
的效果更优,因为考虑了各词在所有字段钟出现的次数,生成的词频矩阵是带有权重的。
vectorizer = FeatureUnion([ |
利用岭回归,实现价格预测。def ridgeClassify(train_data, train_label):
ridgeClf = Ridge(
solver='auto',
fit_intercept=True,
alpha=0.5,
max_iter=500,
normalize=False,
tol=0.05)
# 训练
ridgeClf.fit(train_data, train_label)
return ridgeClf
通过对数据集的了解和对样例代码的学习,我们了解到优化这个问题的答案有三个角度可以入手:
- 数据预处理:怎样处理缺失值?数据该怎样结合?
- 形成词频矩阵时进行优化:调整
CountVectorizer
和TfidfVectorizer
的参数 - 模型的选择和优化:尝试岭回归之外的模型、调整模型参数。
二、尝试更多的模型
在上面的样例代码中,利用岭回归模型得到的结果是3.01左右。经过之前课上的提示和网上的资料查找,我们准备再去尝试一下 MLP
模型和 Lgmb
模型。在粗略的尝试了两个模型之后我们决定进一步利用 MLP
进行下一步的优化。
MLP
MLP
模型的结果如下:
LGBM
Lgbm
模型的结果如下:
MLP
和 LGBM
结合
- 特征处理
- 导入数据集
# 读文件 |
- 缺失值处理
#对缺失值进行处理 |
- 特征向量化
使用 sklearn
库中的 CountVectorizer
类将文本特征进行向量化处理,并使用 FeatureUnion
进行特征联合
vectorizer = FeatureUnion([ |
- 模型构建
对特征分别使用岭回归模型,Lgbm
模型和 mlp
模型进行训练,在本地测试得到的解分别为3.01,3.00,0.26
- 岭回归模型
|
lgbm
模型
def lgbm_classify(train_data,train_label): |
mlp
模型
MLP
模型由两个全连接层和一个dropout层组成,本质上就是一个多隐藏层的网络
def mlp_model(train_data,train_label,row_train): |
三、形成词频矩阵时进行优化
在样例代码中我们尝试了将所有 CountVectorizer
替换为 TdidfVectorizer
,然后利用岭模型进行预测,但是结果并没有优化很多,仅仅到2.9而已。
在后面利用 MLP
时完全舍弃了 CountVectorizer
只利用 TdidfVectorizer
。
四、优化数据预处理过程
我们对上面基本已经完善的 MLP
进行优化的方式是尝试不同特征的组合。
数据属性分析(详见Price Suggestion Challenge1.ipynb
)
首先对属性进行分析:item_condition_id 300000 non-null int64
shipping 300000 non-null int64
name 300000 non-null object
category_name 298719 non-null object
brand_name 171929 non-null object
item_description 300000 non-null objectitem_condition_id
和 shipping
直接作为输入考虑,而 name
, category_name
, brand_name
, item_description
考虑不同的组合进行尝试。
在此之前,我们找到了一个数据可视化的实例教程,对数据的属性进行分析。
通过详细观察数据得到最优的输入组合:train.head()
price
通过数据可视化后的观察我们得知为什么要对price
做log1p
处理,这样使price
分布更优。category_name
尝试对该属性进行拆分,分成各种子类并查看相应数据。item_description
不同的输入组合
- 在样例代码中只是简单地将各个属性结合在一起进行文本分析,即
name
+item_condition_id
+category_name
+brand_name
+shipping
+item_description
(6个输入) - 尝试
name
,item_condition_id
,shipping
,category_name
+item_description
,brand_name
(5个输入) - 尝试
name
,item_condition_id
,shipping
,category_name
+brand_name
+item_description
(4个输入) - 尝试
name
,item_condition_id
,shipping
,name
+category_name
+brand_name
+item_description
(4个输入)
四种组合作为输入的结果非常相近,除了组合1MSLE
在0.4左右,组合2和3 在0.21 左右,组合4最终能跑到0.17左右。组合4实际上加大了name
的权重,让最终结果更好。
最终源码及实验结果
数据预处理
# 数据处理
# 属性共有8个,删去price,train_id对结果没有影响。
def data_preprocess(df):
df['name'] = df['name'].fillna('') + ' ' + df['brand_name'].fillna('')
df['text'] = (df['item_description'].fillna('') + ' ' + df['name'] + ' ' + df['category_name'].fillna(''))
return df[['name', 'text', 'shipping', 'item_condition_id']]构建模型
def fit_predict(xs, y_train):
X_train, X_test = xs
# 配置tf.Session的运算方式,比如gpu运算或者cpu运算
config = tf.ConfigProto(
# 设置多个操作并行运算的线程数
intra_op_parallelism_threads=1, use_per_session_threads=1, inter_op_parallelism_threads=1)
# Session提供了Operation执行和Tensor求值的环境。
with tf.Session(graph=tf.Graph(), config=config) as sess, timer('fit_predict'):
ks.backend.set_session(sess)
model_in = ks.Input(shape=(X_train.shape[1],), dtype='float32', sparse=True)
# ks.layers.Dense 表示输出空间的维度
# Dense全连接层,相当于直接添加一层
# activation 是按逐个元素计算的激活函数
out = ks.layers.Dense(192, activation='relu')(model_in)
out = ks.layers.Dense(64, activation='relu')(out)
out = ks.layers.Dense(64, activation='relu')(out)
out = ks.layers.Dense(1)(out)
model = ks.Model(model_in, out)
model.compile(loss='mean_squared_error', optimizer=ks.optimizers.Adam(lr=3e-3))
for i in range(3):
with timer(f'epoch {i + 1}'):
model.fit(x=X_train, y=y_train, batch_size=2 ** (11 + i), epochs=1, verbose=0)
return model.predict(X_test)[:, 0]训练模型并预测结果
def main():
vectorizer = make_union(# 把所有的transformers组装成一个FeatureUnion. n_jobs表示可以同时进行
# FunctionTransformer 实现自定义转换,validate=False 时没有输入验证
# TfidfVectorizer函数,仅考虑按照词频排列前max_feature位的词,token_pattern='\w+'至少匹配一位的词
make_pipeline(FunctionTransformer(itemgetter('name'), validate=False), TfidfVectorizer(max_features=100000, token_pattern='\w+')),
make_pipeline(FunctionTransformer(itemgetter('text'), validate=False), TfidfVectorizer(max_features=100000, token_pattern='\w+')),
make_pipeline(FunctionTransformer(itemgetter(['shipping', 'item_condition_id']), validate=False),
FunctionTransformer(to_records, validate=False), DictVectorizer()),
n_jobs=4)
# StandardScaler()进行数据标准化。保存训练集中的参数(均值、方差)直接使用其对象转换测试集数据。
y_scaler = StandardScaler()
# with 语句适用于对资源进行访问的场合,确保不管使用过程中是否发生异常都会执行必要的“清理”操作,释放资源,比如文件使用后自动关闭、线程中锁的自动获取和释放等。
with timer('process train'):
train = pd.read_csv('train.csv', sep='\t')
test = pd.read_csv('test.csv', sep='\t')
# 删去'price'属性
train = train[train['price'] > 0].reset_index(drop=True)
# 将price数据进行标准化
y_train = y_scaler.fit_transform(np.log1p(train['price'].values.reshape(-1, 1)))
X_train = vectorizer.fit_transform(data_preprocess(train)).astype(np.float32)
print(f'X_train: {X_train.shape} of {X_train.dtype}')
with timer('process valid'):
X_test = vectorizer.transform(data_preprocess(test)).astype(np.float32)
with ThreadPool(processes=4) as pool:
Xb_train, Xb_test = [x.astype(np.bool).astype(np.float32) for x in [X_train, X_test]]
xs = [[Xb_train, Xb_test], [X_train, X_test]] * 2
# 预测模型
y_pred = np.mean(pool.map(partial(fit_predict, y_train=y_train), xs), axis=0)
y_pred = np.expm1(y_scaler.inverse_transform(y_pred.reshape(-1, 1))[:, 0])
# print(type(y_pred))
# 输出预测结果到csv
test_id = np.array(range(0, len(y_pred)))
dataframe = pd.DataFrame({'test_id': test_id, 'price': y_pred})
dataframe.to_csv("res.csv", index=False, sep='\t')
# print('Valid MSLE: {:.4f}'.format(mean_squared_log_error(valid['price'], y_pred)))
最终实验结果达到了0.179。
在MLP
模型下的其他优化方向
- 可以观察到在
item_desciption
的词云中,有诸如shipping
和free
等词,这些词可能代表着免运费等含义,与shipping
属性有一定的重复,将它作为特征词训练模型会造成干扰。 - 单个关键词可能包含的信息不全面,关键词之间可能有很大的关联。
- 在最终的模型中
MLP
采用了四层感知机,感知机的层数和每层的输入规模还可以做进一步调参。
实验心得
这次实验的难度非常大,不知道从何入手。
在仔细研究了课程中给的样例代码和数据可视化分析的内容之后,对数据集和预测的方法都有了初步的了解。
因为对MLP
,Lightgbm
等模型非常不熟悉,所以从输入的角度入手,在不同属性的组合之处进行尝试,得到了最终的较为优秀的结果。
在之后的学习中应该更加深入地学习和了解模型,尽量能够自己独立完成创建模型,而不是修改其他已经写好的模型。
参考资料
[1].https://ahmedbesbes.com/how-to-mine-newsfeed-data-and-extract-interactive-insights-in-python.html
[2]. https://github.com/pjankiewicz/mercari-solution
[3].https://www.kaggle.com/thykhuely/mercari-interactive-eda-topic-modelling
[5].https://zh.wikipedia.org/wiki/%E5%A4%9A%E5%B1%82%E6%84%9F%E7%9F%A5%E5%99%A8
[6].https://blog.csdn.net/weixin_39807102/article/details/81912566
[7].https://github.com/maiwen/NLP
[8]. https://zh.wikipedia.org/wiki/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F
[9].https://blog.csdn.net/u012609509/article/details/72911564
[10]. https://www.kaggle.com/tunguz/more-effective-ridge-lgbm-script-lb-0-44823
[11]. https://qiita.com/kazuhirokomoda/items/1e9b7ebcacf264b2d814