发布于 

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_namebrand_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

模型构建

首先做模型的输入,通过CountVectorizerTfidfVectorizer生成词频的矩阵, Tfidf 的效果更优,因为考虑了各词在所有字段钟出现的次数,生成的词频矩阵是带有权重的。

vectorizer = FeatureUnion([
('name', CountVectorizer(ngram_range=(1, 2), max_features=50000, preprocessor=build_preprocessor_1('name'))),
('category_name', CountVectorizer(token_pattern='.+', preprocessor=build_preprocessor_1('category_name'))),
('brand_name', CountVectorizer(token_pattern='.+', preprocessor=build_preprocessor_1('brand_name'))),
('shipping', CountVectorizer(token_pattern='\d+', preprocessor=build_preprocessor_1('shipping'))),
('item_condition_id', CountVectorizer(token_pattern='\d+', preprocessor=build_preprocessor_1('item_condition_id'))),
('item_description', TfidfVectorizer(ngram_range=(1, 3),max_features=100000, preprocessor=build_preprocessor_1('item_description'))),
])

利用岭回归,实现价格预测。

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

通过对数据集的了解和对样例代码的学习,我们了解到优化这个问题的答案有三个角度可以入手:

  1. 数据预处理:怎样处理缺失值?数据该怎样结合?
  2. 形成词频矩阵时进行优化:调整 CountVectorizerTfidfVectorizer 的参数
  3. 模型的选择和优化:尝试岭回归之外的模型、调整模型参数。

二、尝试更多的模型

在上面的样例代码中,利用岭回归模型得到的结果是3.01左右。经过之前课上的提示和网上的资料查找,我们准备再去尝试一下 MLP模型和 Lgmb模型。在粗略的尝试了两个模型之后我们决定进一步利用 MLP 进行下一步的优化。

MLP

MLP 模型的结果如下:

LGBM

Lgbm 模型的结果如下:

MLPLGBM 结合

  1. 特征处理
  • 导入数据集
# 读文件
train = pd.read_csv('data/train.csv', sep='\t')
test = pd.read_csv('data/test.csv', sep='\t')
# 训练数据和测试数据一起处理
df = pd.concat([train, test], axis=0)
  • 缺失值处理
#对缺失值进行处理
df['category_name'] = df['category_name'].fillna('MISS').astype(str)
df['brand_name'] = df['brand_name'].fillna('missing').astype(str)
df['item_description'] = df['item_description'].fillna('No')
#数据类型处理
df['shipping'] = df['shipping'].astype(str)
df['item_condition_id'] = df['item_condition_id'].astype(str)
  • 特征向量化

使用 sklearn 库中的 CountVectorizer 类将文本特征进行向量化处理,并使用 FeatureUnion 进行特征联合

vectorizer = FeatureUnion([
('name', CountVectorizer(
ngram_range=(1, 2),
max_features=100000,
preprocessor=build_preprocessor('name'))),
('category_name', CountVectorizer(
token_pattern='.+',
preprocessor=build_preprocessor('category_name'))),
('brand_name', CountVectorizer(
token_pattern='.+',
preprocessor=build_preprocessor('brand_name'))),
('shipping', CountVectorizer(
token_pattern='\d+',
preprocessor=build_preprocessor('shipping'))),
('item_condition_id', CountVectorizer(
token_pattern='\d+',
preprocessor=build_preprocessor('item_condition_id'))),
('item_description', TfidfVectorizer(
ngram_range=(1, 3),
max_features=200000,
preprocessor=build_preprocessor('item_description'),
stop_words='english')),
])
  1. 模型构建

对特征分别使用岭回归模型,Lgbm 模型和 mlp 模型进行训练,在本地测试得到的解分别为3.01,3.00,0.26

  • 岭回归模型

def ridge_classify(train_data,train_label):
#模型
model = Ridge(
solver='auto',
fit_intercept=True,
alpha=0.4,
max_iter=100,
normalize=False,
tol=0.05)
#训练
model.fit(train_data, train_label)
return model
  • lgbm模型
def lgbm_classify(train_data,train_label):
params = {
'learning_rate': 0.75,
'application': 'regression',
'max_depth': 3,
'num_leaves': 100,
'verbosity': -1,
'metric': 'RMSE',
}

train_X, valid_X, train_y, valid_y = train_test_split(train_data, train_label, test_size=0.1, random_state=144)
d_train = lgb.Dataset(train_X, label=train_y)
d_valid = lgb.Dataset(valid_X, label=valid_y)
watchlist = [d_train, d_valid]

model = lgb.train(params, train_set=d_train, num_boost_round=2200, valid_sets=watchlist, \
early_stopping_rounds=50, verbose_eval=100)
return model
  • mlp 模型

MLP 模型由两个全连接层和一个dropout层组成,本质上就是一个多隐藏层的网络

def mlp_model(train_data,train_label,row_train):
model = Sequential()
# 全连接层
model.add(Dense(64, input_shape=(row_train,), activation='relu'))
# DropOut层
model.add(Dropout(0.4))
# 全连接层+分类器
model.add(Dense(1, activation='relu'))

model.compile(loss='mean_squared_logarithmic_error',
optimizer='adam',
metrics=['accuracy']
)

model.fit(train_data, train_label,
batch_size=300,
epochs=1,
)

return model.predict(X_test)

三、形成词频矩阵时进行优化

在样例代码中我们尝试了将所有 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 object


item_condition_idshipping 直接作为输入考虑,而 name, category_name, brand_name, item_description 考虑不同的组合进行尝试。

在此之前,我们找到了一个数据可视化的实例教程,对数据的属性进行分析。
通过详细观察数据得到最优的输入组合:

train.head()

  1. price
    通过数据可视化后的观察我们得知为什么要对 pricelog1p 处理,这样使 price 分布更优。

  2. category_name
    尝试对该属性进行拆分,分成各种子类并查看相应数据。

  3. item_description

不同的输入组合

  1. 在样例代码中只是简单地将各个属性结合在一起进行文本分析,即name + item_condition_id + category_name + brand_name + shipping + item_description(6个输入)
  2. 尝试name, item_condition_id, shipping,category_name + item_description, brand_name(5个输入)
  3. 尝试name, item_condition_id, shipping, category_name + brand_name + item_description(4个输入)
  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的权重,让最终结果更好。

最终源码及实验结果

  1. 数据预处理

    # 数据处理
    # 属性共有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']]
  2. 构建模型

    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]
  3. 训练模型并预测结果

    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模型下的其他优化方向

  1. 可以观察到在item_desciption 的词云中,有诸如shippingfree等词,这些词可能代表着免运费等含义,与shipping属性有一定的重复,将它作为特征词训练模型会造成干扰。
  2. 单个关键词可能包含的信息不全面,关键词之间可能有很大的关联。
  3. 在最终的模型中MLP采用了四层感知机,感知机的层数和每层的输入规模还可以做进一步调参。

实验心得

这次实验的难度非常大,不知道从何入手。

在仔细研究了课程中给的样例代码和数据可视化分析的内容之后,对数据集和预测的方法都有了初步的了解。

因为对MLPLightgbm等模型非常不熟悉,所以从输入的角度入手,在不同属性的组合之处进行尝试,得到了最终的较为优秀的结果。

在之后的学习中应该更加深入地学习和了解模型,尽量能够自己独立完成创建模型,而不是修改其他已经写好的模型。

参考资料

[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

[4].https://wklchris.github.io/Py3-pandas.html#%E7%BB%9F%E8%AE%A1%E4%BF%A1%E6%81%AFdfdescribe-svalue_counts—unique

[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

[12]. https://www.jianshu.com/p/c532424541ad

[13]. https://www.jiqizhixin.com/articles/2017-11-13-7