背景介绍
Home Depot 产品相关性预测 kaggle竞赛:https://www.kaggle.com/c/home-depot-product-search-relevance HomeDepot是美国一家家具建材商品网站,用户通过在搜索框中输入关键词,得到相关商品和服务,如输入floor,得到不同材料的地板商品、地板清洗商品、地板安装服务等。kaggle竞赛目的是通过设计一种模型,能够更好的匹配用户搜索关键词,得到相关性更高的产品和服务。
导入数据
import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestRegressor,BaggingRegressor
from nltk.stem.snowball import SnowballStemmer
#导入train,test,product_descriptions数据集
df_train=pd.read_csv(r'E:\python\kaggle-product-search-relevance-master\train.csv',encoding='ISO-8859-1')
df_test=pd.read_csv(r'E:\python\kaggle-product-search-relevance-master\test.csv',encoding='ISO-8859-1')
df_desc=pd.read_csv(r'E:\python\kaggle-product-search-relevance-master\product_descriptions.csv')
数据简介:
训练数据:
product_uid:产品的编号
product_title:产品标题描述
search_term:用户搜索的关键词
relevance: 用户搜索结果的评分。test数据集里没有这列,这个是要预测的Label。
产品信息:
product_uid:产品编号
product_description:产品详细描述
这个数据也挺重要的,可以并到训练数据里构建特征。
合并数据:
#合并训练数据及测试数据
df_all=pd.concat([df_train.assign(is_train=1),df_test.assign(is_train=0)],axis=0,ignore_index=True)
#把产品描述加进来
df_all=pd.merge(df_all,df_desc,how='left',on='product_uid')
文本预处理
stemmer=SnowballStemmer('english')
def str_stemmer(s):
return ' '.join([stemmer.stem(word) for word in s.lower().split()])
#为了计算关键词的有效性,可以直接看出现了多少次
def str_common_word(str1,str2):
return sum(int(str2.find(word)>0) for word in str1.split())
#数据处理
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))
文本特征
#关键词的长度
df_all['len_of_query']=df_all['search_term'].map(lambda x:len(x.split())).astype(np.int64)
#标题中有多少关键词重合
df_all['commons_in_title']=df_all.apply(lambda x:str_common_word(x['search_term'],x['product_title']),axis=1)
#描述中有多少关键词重合
df_all['commons_in_desc']=df_all.apply(lambda x:str_common_word(x['search_term'],x['product_description']),axis=1)
#Levenshtein文本相似度
import Levenshtein
df_all['dist_in_title']=df_all.apply(lambda x:Levenshtein.ratio(x['search_term'],x['product_title']),axis=1)
df_all['dist_in_desc']=df_all.apply(lambda x:Levenshtein.ratio(x['search_term'],x['product_description']),axis=1)
#TF-IDF
'''
我们首先搞一个新的column,叫all_texts, 里面是所有的texts。
(我并没有算上search term, 因为他们不是一个结构完整的句子,
可能会影响tfidf的学习)。为了防止句子格式不完整,我们也强制给他们
加上句号。
注意:这里我们最严谨的做法是把train/test先分开,
然后只在train上做tfidf的学习,并在test上直接转化。
但由于kaggle提前拿到test,这里姑且把文本内容汇总一起,
实际项目中,往往是没法提前拿到test的。
'''
df_all['all_texts']=df_all['product_title']+'.'+df_all['product_description']+'.'
#然后,我们取出所有的单字,做成一个我们的单词字典
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)
#下面写一个类,扫便我们所有的语料,并且转化成简单的单词的个数计算(Bag-of-Words)
class MyCorpus(object):
def __iter__(self):
for x in df_all['all_texts'].values:
yield dictionary.doc2bow(list(tokenize(x,errors='ignore')))
corpus=MyCorpus()
#有了标准形式的语料库,就可以把已经变成BoW向量的数组,做一次TFIDF计算
from gensim.models.tfidfmodel import TfidfModel
tfidf=TfidfModel(corpus)
#判断两个句子的相似度:把其中一个作为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,我们只需要数值
return float(sim[0])
#计算TFIDF相似度
df_all['tfidf_cos_sim_in_title']=df_all.apply(lambda x:cos_sim(x['search_term'],x['product_title']),axis=1)
df_all['tfidf_cos_sim_in_desc']=df_all.apply(lambda x:cos_sim(x['search_term'],x['product_description']),axis=1)
#Word2Vec
#w2v和tfidf不同,对tfidf而言,只需要知道一整段text中包含了哪些word元素就行了。
#而w2v要考虑到句子层级的split,以及语境前后的考虑.
import nltk
#句子分割,把长文本分割成list of 句子,再把句子变成list of 单词
tokenizer=nltk.data.load('tokenizers/punkt/english.pickle')
sentences=[tokenizer.tokenize(x) for x in df_all['all_texts'].values]
#其实这些sentences不需要层级关系,我们把list of lists给flatten了
sentences=[y for x in sentences for y in x]
#把单词分好
from nltk.tokenize import word_tokenize
w2v_corpus=[word_tokenize(x) for x in sentences]
#训练model
from gensim.models.word2vec import Word2Vec
model=Word2Vec(w2v_corpus,size=128,window=5,min_count=5,workers=4)
#TFIDF是针对每个句子都可以有的,而w2v是针对每个单词的
#这里我们平均化一个句子的w2v向量,算作整个text的平均vector
#先拿到全部的vocabulary
vocab=model.wv.vocab
#得到任意text的vector
def get_vector(text):
res=np.zeros([128])
count=0
for word in word_tokenize(text):
if word in vocab:
res+=model[word]
count+=1
return res/count
#计算两个text的平均w2v的cosine similarity
from scipy import spatial
def w2v_cos_sim(text1,text2):
w2v1=get_vector(text1)
w2v2=get_vector(text2)
sim=1-spatial.distance.cosine(w2v1,w2v2)
if str(sim)=='nan':
sim=1
else:
pass
return float(sim)
#构建w2v相似度特征
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)
重塑训练/测试数据集
#删除相关特征
df_all=df_all.drop(['search_term','product_title','product_description','all_texts'],axis=1)
#重塑训练/测试集
df_train=df_all.loc[df_all['is_train']==1]
df_test=df_all.loc[df_all['is_train']==0]
#记录下测试集的id
test_ids=df_test['id']
#分离出y_train
y_train=df_train['relevance'].values
#把原集中的label删去
X_train=df_train.drop(['id','relevance','is_train'],axis=1).values
X_test=df_test.drop(['id','relevance','is_train'],axis=1).values
建立模型
这里用个简单的随机森林模型,参数也没有细调。可以再拿其它模型试试,并更细致的调整参数。
#建立模型
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')
上传结果
rf=RandomForestRegressor(n_estimators=30,max_depth=9)
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)