Kaggle - Home Depot Product Search Relevance 进阶篇

Github: https://github.com/yjfiejd/Product_search_relevance_NLP-/blob/master/product_search_relevance_advanced.ipynb (可查看jupyter notebook)
  • 基础篇与进阶篇最大的区别:特征提取的方式不同
  • 基础篇中特征的提取太粗糙:1)关键字常长度 2)用搜索词中的单词在产品title、产品介绍中出现的次数来衡量
  • 进阶篇的特征提取:采用了3中方法,在原有文本上共提取出了6个新的特征
  1. Levenshtein : 用来计算字符串之间的距离 -> 2个新特征 -> 搜索词句子与产品title,搜索词与产品描述直接的距离
  2. TF-idf构造特征: -> 2个新特征
    1. 新建all_text列(放入product_title+product_description,组合为一条句子)
    2. 把all_text列中每一个格子的句子拆分为单词(gensim.utils库中的Tokenize) -> 包含221877单词的大字典 -> 语料库用iterator实现,用class来实现
    3. 得到标准形式的语料库 TfidModel(corpus)
    4. 用MatrixSimilarity计算相似度,计算两条句子的相似度 -> 增加两个特征 -> 搜索词与产品title、搜索词与产品描述的相似度
  3. 通过Word2Vec来评判距离,搜索词与产品title,产品描述的 -> 2个新特征
    1. nltk也是自带一个强大的句子分割器。【调用工具】
    2. 我们先把长文本搞成list of 句子,再把句子变成list of 单词:【文本->句子】
    3. 我们把list of lists 给 flatten了。【句子 -> 扁平化flatten】
    4. 我们把句子里的单词给分好。可以用刚刚Gensim的tokenizer, 也可以用nltk的word_tokenizer 【句子 -> 单词】
    5. 训练我们的预料库,成为词向量 【单词 -> 训练语料库model】
    6. 可以得到每个单词的向量,但是每一格句子中由多个单词组成,把每个单词向量取平均,
    7. 计算两个句子的vector的相似度, 用cosine similarity,用scipy的spatial功能
    8. 应用在两列上, 得到新的2个特征:df_all.apply(lambda x: w2v_cos_sim(x['search_term'], x['product_title']), axis=1)
    9. 去掉不需要的column,给drop掉, 留下新特征

代码如下:

# -*- coding:utf8 -*-
# @TIME : 2018/4/27 上午6:15
# @Author : Allen
# @File : product_search_relevance_advanced.py

#目的:给出输入关键字与搜索结果,评价搜索准确度
#读取数据
df_train = pd.read_csv('train.csv', encoding = "ISO-8859-1")
df_test = pd.read_csv('test.csv', encoding = "ISO-8859-1")
df_desc = pd.read_csv('product_descriptions.csv')
#处理思路
#1,导入包、数据  -> 合并数据格式concat,merge,
#2,文本预处理 -> 【简单方法】:看输入词是在搜索结果中出现几次,需要先统一数据集格式 -> str_stemmer and str_commond_words 处理数据
#3,自制文本特征 -> 关键词长度/搜索词语与title和describtion中重复词语数 -> 去掉之前的英文,保留自制特征
#4,重塑训练/测试集 -> 拆分出X_train, X_test, y_train, 去除label
#5,建立模型:Ridge回归模型RandomForestRegressor 找出最佳参数max_depth=7 通过多种参数导入 -> 画图
#6,上传结果:生成csv文件
#########1) 导入需要用的库
import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestClassifier, BaggingRegressor
from nltk.stem.snowball import SnowballStemmer
import os
# 解决路径报错问题:SyntaxError: (unicode error) 'unicodeescape':https://www.cnblogs.com/renfanzi/p/6307074.html
f = r'C:\Users\xiaochen.liu\Desktop\product_search_relevance'
os.chdir(f)
#读取数据
df_train = pd.read_csv('train.csv', encoding = "ISO-8859-1")
df_test = pd.read_csv('test.csv', encoding = "ISO-8859-1")
df_desc = pd.read_csv('product_descriptions.csv')
#合并测试集与训练集,便于统一文本预处理
#PANDAS 数据合并与重塑(concat篇):https://blog.csdn.net/stevenkwong/article/details/52528616
#PANDAS 数据合并与重塑(join/merge篇):https://blog.csdn.net/stevenkwong/article/details/52540605
df_all = pd.concat((df_train, df_test), axis=0, ignore_index=True)
df_all.head()
#print(df_all.shape)
#把描述信息加入表,how='left'表示左边全部保留,on表示以什么为基准对齐
df_all = pd.merge(df_all, df_desc, how='left', on='product_uid')
df_all.head()
#############2) 文本预处理,把表格的语句处理为计算机能懂的格式,这里使用NLTK
stemmer = SnowballStemmer('english')
#把每一列中的每一条产品说明中的英文句子,小写lower() -> 分割split() -> 提取词干stemmer.stem -> 整合join
def str_stemmer(s):
    return " ".join([stemmer.stem(word) for word in s.lower().split()])
#计算"关键词次数" : 统计两个字符串中的相同单词的个数
#python find()方法:http://www.runoob.com/python/att-string-find.html
def str_common_word(str1, str2):
    return sum(int(str2.find(word)>=0) for word in str1.split())
#把每一个column都跑一遍,用str_stemmer清洁所有的文本内容
#这3行我跑了10几分钟 - 为渣渣电脑默哀一分钟
df_all['search_term'] = df_all['search_term'].map(lambda x: str_stemmer(x))
df_all['product_title'] = df_all['product_title'].map(lambda x: str_stemmer(x))
df_all['product_description'] = df_all['product_description'].map(lambda x: str_stemmer(x))
##############3) 【区别】进阶版文本特征
# 新特征介绍:Levenshtein 计算两个字符串单词的距离
import Levenshtein
Levenshtein.ratio('hello', 'hello world')
#【新特征1】: search_term 和 product_title比较
df_all['dist_in_title'] = df_all.apply(lambda x:Levenshtein.ratio(x['search_term'],x['product_title']), axis=1)
#【新特征2】: search_term 和 product_description比较
df_all['dist_in_desc'] = df_all.apply(lambda x:Levenshtein.ratio(x['search_term'],x['product_description']), axis=1)
#【新特征3】: 采用TF-iDF构造新的特征: -> 【这里新增了2个特征】
#1)新建all_text列(放入product_title+product_description,组合为一条句子)
#2)把all_text列中每一个格子的句子拆分为单词(gensim.utils库中的Tokenize) -> 包含221877单词的大字典 -> 语料库用iterator实现,用class来实现
#3)得到标准形式的语料库 TfidModel(corpus),
#4) 用MatrixSimilarity计算相似度,计算两条句子的相似度 -> 增加两个特征 -> 搜索词与产品title、搜索词与产品描述的相似度


#把训练集,先fit里面的文本信息,然后在来transform转换成TIIDF的格式  
#X_train = feature_extraction.fit_transform(train["combined_news"].values)  
#这里因为feature_extraction记住了训练的格式,转换测试集的格式  
#X_test = feature_extraction.transform(test["combined_news"].values)  
#y_train = train["Label"].values  
#y_test = test["Label"].values  

#新建一列,保存为所有的文本除了search term, 加上句号
df_all['all_texts'] = df_all['product_title'] + ' . ' + df_all['product_description'] + ' . '
#打印看一下效果
df_all['all_texts'][:5]
# 有了组合好的句子,可以分词了准备
# 分词:这里我们用gensim,为了更加细致的分解TFIDF的步骤动作;其实sklearn本身也有简单好用的tfidf模型
# Tokenize可以用各家或者各种方法,就是把长长的string变成list of tokens。包括NLTK,SKLEARN都有自家的解决方案

from gensim.utils import tokenize
from gensim.corpora.dictionary import Dictionary
#得到了一个很多单词的大词典
dictionary = Dictionary(list(tokenize(x, errors='ignore')) for x in df_all['all_texts'].values)
print(dictionary)
#这个类所做的事情也很简单,就是扫便我们所有的语料,并且转化成简单的单词的个数计算
class MyCorpus(object):
    def __iter__(self):
        for x in df_all['all_texts'].values:
            yield dictionary.doc2bow(list(tokenize(x, errors='ignore')))

# 这里这么折腾一下,仅仅是为了内存friendly。面对大量corpus数据时,你直接存成一个list,会使得整个运行变得很慢。
# 所以我们搞成这样,一次只输出一组。但本质上依旧长得跟 [['sentence', '1'], ['sentence', '2'], ...]一样
corpus = MyCorpus()
#有了我们标准形式的语料库,我们于是就可以init我们的TFIDFmodel了。这里做的事情,就是把已经变成BoW向量的数组,做一次TFIDF的计算。
from gensim.models.tfidfmodel import TfidfModel
tfidf = TfidfModel(corpus)
#示例:这下我们看看一个普通的句子放过来长什么样子:
tfidf[dictionary.doc2bow(list(tokenize('hello world, good morning', errors='ignore')))]
#怎么判断两个句子的相似度呢?
#这里有个trick,因为我们得到的tfidf只是『有这个字,就有这个值』,并不是一个全部值。
#也就是说,两个matrix可能size是完全不一样的。
#想用cosine计算的同学就会问了,两个matrix的size都不fix,怎么办?
#咦,这里就注意咯。他们的size其实是一样的。只是把全部是0的那部分给省略了对吧?
#于是,我们只要拿其中一个作为index。扩展开全部的matrixsize,另一个带入,就可以计算了

from gensim.similarities import MatrixSimilarity

# 先把刚刚那句话包装成一个方法
def to_tfidf(text):
    res = tfidf[dictionary.doc2bow(list(tokenize(text, errors='ignore')))]
    return res

# 然后,我们创造一个cosine similarity的比较方法
def cos_sim(text1, text2):
    tfidf1 = to_tfidf(text1)
    tfidf2 = to_tfidf(text2)
    index = MatrixSimilarity([tfidf1],num_features=len(dictionary))
    sim = index[tfidf2]
    # 本来sim输出是一个array,我们不需要一个array来表示,
    # 所以我们直接cast成一个float
    return float(sim[0])
#举例说明
text1 = 'hello world'
text2 = 'hello from the other side'
cos_sim(text1, text2)
#计算搜索词语与产品title相似度
df_all['tfidf_cos_sim_in_title'] = df_all.apply(lambda x: cos_sim(x['search_term'], x['product_title']), axis=1)
#计算搜索词与产品描述description相似度
df_all['tfidf_cos_sim_in_desc'] = df_all.apply(lambda x: cos_sim(x['search_term'], x['product_description']), axis=1)
#【新特征4】:通过Word2Vec来评判距离,搜索词与产品title,产品描述的。

import nltk
#1)nltk也是自带一个强大的句子分割器。【调用工具】
tokenizer = nltk.data.load('tokenizers/punkt/english.pickle')
#2)我们先把长文本搞成list of 句子,再把句子变成list of 单词:【文本->句子】
sentences = [tokenizer.tokenize(x) for x in df_all['all_texts'].values]
#3)我们把list of lists 给 flatten了。【句子 -> 扁平化flatten】
sentences = [y for x in sentences for y in x] #一共1998321个句子。
#4)我们把句子里的单词给分好。可以用刚刚Gensim的tokenizer, 也可以用nltk的word_tokenizer 【句子 -> 单词】
from nltk.tokenize import word_tokenize
w2v_corpus = [word_tokenize(x) for x in sentences]
#5) 训练我们的预料库,成为词向量 【单词 -> 训练语料库model】
from gensim.models.word2vec import Word2Vec
model = Word2Vec(w2v_corpus, size=128, window=5, min_count=5, workers=4)

#6) 可以得到每个单词的向量,但是每一格句子中由多个单词组成,把每个单词向量取平均,
vocab = model.vocab
#得到任意text句子的vector(就是取平均)
def get_vector(text):
    reso = np.zeros([128])
    count = 0
    for word in word_tokenize(text):
        res += model[word]
        count+=1
    return res/count
#测试一下,每条句子的vector:
print(get_vector('life is like a box of chocolate'))
#7) 计算两个句子的vector的相似度, 用cosine similarity,用scipy的spatial功能
from scipy import spatial
def w2v_cos_sim(text1, text2):
    try:
        w2v1 = get_vector(text1)
        w2v2 = get_vector(text2)
        sim = 1 - spatial.distance.cosine(w2v1, w2v2)
#9) 去掉不需要的column,给drop掉
df_all = df_all.drop(['search_term','product_title','product_description','all_texts'],axis=1)

return float(sim) except: return float(0)#测试一下两个句子之间的距离w2v_cos_sim('hello world', 'hello from the other side')
#8) 应用在两列上, 得到新的2个特征
df_all['w2v_cos_sim_in_title'] = df_all.apply(lambda x: w2v_cos_sim(x['search_term'], x['product_title']), axis=1)
df_all['w2v_cos_sim_in_desc'] = df_all.apply(lambda x: w2v_cos_sim(x['search_term'], x['product_description']), axis=1)

*************************后面部分与基础篇一样**************************

################4) 把之前合并起来处理的训练集,测试集拿出来
#分离训练集合,测试集合
df_train = df_all.loc[df_train.index]
df_test = df_all.loc[df_test.index]
#记录测试ID
test_ids = df_test['id']
#分离出y_train
y_train = df_train['relevance'].values
#删除原集label
X_train = df_train.drop(['id', 'relevance'], axis=1).values
X_test = df_test.drop(['id', 'relevance'], axis = 1).values
################5) 建立模型 : RF
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import cross_val_score

#循环测试不同的树的深度
params = [1,3,5,6,7,8,9,10]
test_scores = []
for param in params:
    clf = RandomForestRegressor(n_estimators=30, max_depth=param)
    test_score = np.sqrt(-cross_val_score(clf, X_train, y_train, cv=5, scoring='neg_mean_squared_error'))
    test_scores.append(np.mean(test_score))
#画图
import matplotlib.pyplot as plt
%matplotlib inline
plt.plot(params, test_scores)
plt.title("Param vs CV Error");

#这里会得到一个最佳的树深度
################6) 上传结果
rf = RandomForestRegressor(n_estimators=30, max_depth=6)
rf.fit(X_train, y_train)
y_pred = rf.predict(X_test)
pd.DataFrame({"id": test_ids, "relevance": y_pred}).to_csv('submission.csv',index=False)






















阅读更多
上一篇Kaggle - Home Depot Product Search Relevance 基础篇
下一篇RNN_lstm 循环神经网络 - 分类任务
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭