也是好久没写博客了,前段时间一直在找工作,没有做什么实质性的工作。最近工作也定下了,百度流量质量控制部的反作弊算法团队,不算是百度的什么土豪团队,但是99%以上的流量收入都是要从这个团队过一遍的,团队资历实力可见一斑。
好了不吹b了,说说这个阶段要做的东西:从服务产品的评论中挖掘服务产品的特征,不理解的话举个例子:
“这饭店环境还真是不错,就是菜码有点太大了!”
很明显,加了高亮部分的文字蕴含着这个服务产品的两个特征,那么我要做的其实就是把这样的特征找出来,可以归结于数据挖掘范畴。
这个事做成了之后要做什么我先不说,因为这是我的毕业课题,透露太多了也不好。博客上我只会放处理的大致流程和遇到的问题,源码部分公开。
做学术研究嘛肯定是要有数据源的,数据源老师给提供了一个,即Yelp Dataset Challenge中的数据源:https://www.yelp.com/dataset_challenge? 感兴趣的朋友可以去看看,数据质量非常高,略强于阿里天池。
(二)英文分词、赋词性
我之前做的都是中文分词,看到是英文分词给我开心坏了,空格不都打好的么。赋词性这块就不行了,因为不可能搞个词典挨个去查,我就用了nltk英文NLP处理包,这玩意以前没用过,具体代码在最后面放着,注释非常全,自己研究就行了。下面我主要说一说nltk的词性标注,这个标注还是挺奇怪的,在官网没有找到词性标注表,跟国内的北大几级标注那些又不一样,去翻了一下http://blog.csdn.net/heyongluoyao8/article/details/43731743#reply这个人的博客,做个归纳,方便以后查找。
1. ? ? CC ? ? ?Coordinating conjunction 连接词
2. ? ? CD ? ? Cardinal number ?基数词
3. ? ? DT ? ? Determiner ?限定词(如this,that,these,those,such,不定限定词:no,some,any,each,every,enough,either,neither,all,both,half,several,many,much,(a) few,(a) little,other,another.
4. ? ? EX ? ? Existential there 存在句
5. ? ? FW ? ? Foreign word 外来词
6. ? ? IN ? ? Preposition or subordinating conjunction 介词或从属连词
7. ? ? JJ ? ? Adjective 形容词或序数词
8. ? ? JJR ? ? Adjective,comparative 形容词比较级
9. ? ? JJS ? ? Adjective,superlative 形容词最高级
10. ? ? LS ? ? List item marker 列表标示
11. ? ? MD ? ? Modal 情态助动词
12. ? ? NN ? ? Noun,singular or mass 常用名词 单数形式
13. ? ? NNS ? ? Noun,plural ?常用名词 复数形式
14. ? ? NNP ? ? Proper noun,singular ?专有名词,单数形式
15. ? ? NNPS ? ? Proper noun,plural ?专有名词,复数形式
16. ? ? PDT ? ? Predeterminer 前位限定词
17. ? ? POS ? ? Possessive ending 所有格结束词
18. ? ? PRP ? ? Personal pronoun 人称代词
19. ? ? PRP$ ? ? Possessive pronoun 所有格代名词
20. ? ? RB ? ? Adverb 副词
21. ? ? RBR ? ? Adverb,comparative 副词比较级
22. ? ? RBS ? ? Adverb,superlative 副词最高级
23. ? ? RP ? ? Particle 小品词
24. ? ? SYM ? ? Symbol 符号
25. ? ? TO ? ? to 作为介词或不定式格式
26. ? ? UH ? ? Interjection 感叹词
27. ? ? VB ? ? Verb,base form 动词基本形式
28. ? ? VBD ? ? Verb,past tense 动词过去式
29. ? ? VBG ? ? Verb,gerund or present participle 动名词和现在分词
30. ? ? VBN ? ? Verb,past participle 过去分词
31. ? ? VBP ? ? Verb,non-3rd person singular present 动词非第三人称单数
32. ? ? VBZ ? ? Verb,3rd person singular present 动词第三人称单数
33. ? ? WDT ? ? Wh-determiner 限定词(如关系限定词:whose,which.疑问限定词:what,which,whose.)
34. ? ? WP ? ? ?Wh-pronoun 代词(who whose which)
35. ? ? WP$ ? ? Possessive wh-pronoun 所有格代词
36. ? ? WRB ? ? Wh-adverb ? 疑问代词(how where when)
(二)挖掘产品特征(10.13 Baseline)
挖掘这些特征就要分析自然语言的结构了,在上面的例子中(分词后):这/ 饭店/ 环境/ 还/ 真是/ 不错,就是/ 菜码/ 有点/ 太大/ 了!我们可以很清楚的发现,表特征的词往往都是名词(NN/NNS),而这些特征的周围必定跟随一个形容词(也可能连带一个或几个程度副词),看了一下Yelp的评论数据也确实如此。根据这个线索就可以编码了。
编码的时候要注意一个问题就是先找形容词(JJ/JJR/JJS)再找特征,但是表程度的这个形容词往往不在这个特征的旁边,那么久要设置一个滑窗,在滑窗范围内寻找这个特征(我暂时设置的是滑窗=5),这种办法个人感觉简单粗暴,但是缺点是只能找单个词的特征,无法寻找一类短语特征(比如dish size),这个问题在以后的探索中慢慢解决。现在先不着急,先搞个baseline看看效果怎么样再说,我把餐饮行业的feature都拿到了,一共92326个,feature出现次数在1000以下的我认为是稀疏特征,直接扔掉,剩下494个特征,我取前50个展示一下吧:
('food',203900) ('place',126807) ('service',110508) ('time',98767) ('restaurant',47986) ('staff',41384) ('menu',36441) ('experience',35822) ('meal',32812) ('thing',32057) ('night',30665) ('sauce',28831) ('bit',28180) ('pizza',26407) ('order',24406) ('bar',24077) ('price',23429) ('chicken',22599) ('bread',21457) ('hour',21134) ('dish',20586) ('side',20291) ('way',20077) ('lunch',19849) ('flavor',19529) ('dinner',19046) ('day',18674) ('server',18649) ('salad',18648) ('rice',18287) ('nothing',17406) ('something',17184) ('selection',16780) ('quality',16727) ('everything',15665) ('meat',15177) ('table',15061) ('location',15022) ('atmosphere',14962) ('cheese',14741) ('steak',14428) ('sushi',14326) ('area',14032) ('taste',13685) ('breakfast',13576) ('visit',13576) ('waitress',12394) ('soup',12300) ('sandwich',12280) ('beer',12261)
个人感觉这种baseline算法还是非常靠谱的,我们能看到的都是真真切切的特征,没有噪音。
(三)有什么改进的地方
这50个特征有很多是一类,比如soup和sandwich,都表示的餐饮中的一个菜,那么未来能否使用wiki大规模语料库将这些近义词合并成一个类目会成为一个比较大的工作量;再有就是挖出来的feature都是一个词,看看能不能挖出一个短语吧。
(四)源码
# coding:utf-8 import nltk,os,sys # # 进度条 # def View_Bar(flag,sum): rate = float(flag) / sum rate_num = rate * 100 if flag % 15.0 == 0: print '\r%.2f%%: ' %(rate_num),# \r%.2f后面跟的两个百分号会输出一个'%' sys.stdout.flush() # # 筛选 指定类型的 店家 # def Filter_Business(path,type): # path是business表路径,type:待筛选店家的categories lines = open(path,'r').readlines() filter_bussiness_id = {} # 筛选完成的商家id的字典 {"5UmKMjUEUNdYWqANhGckJw":None,"UsFtqoBl7naz8AVUBZMjQQ":None,} flag = 0 # 进度条 for line in lines: line = line.split('"categories": ') categories = line[1].split(',"city"')[0] # ["Fast Food","Restaurants"] business_id = line[0].split(',"full_address"')[0].split('"business_id": ')[1] if type in categories: filter_bussiness_id[business_id[1:-1]] = 0 flag += 1 print ('筛选数据进度: ' + str(flag/85901.0)) print ('字典: ' + str(filter_bussiness_id)) print ('字典长度: ' + str(len(filter_bussiness_id))) return filter_bussiness_id # # 提取指定类型店家的 review # def Filter_Business_Review(path,filter_business_id): # path是review文件路径 lines = open(path,'r').readlines() f = open('/Users/John/Desktop/yelp_dataset_challenge_academic_dataset/review_text.txt','w') flag = 0 # 进度条 for line in lines: dict = eval(line) business_id = dict['business_id'] # 该评论对应的商家 if filter_business_id.has_key(business_id) == True: text = dict['text'].replace('\n','') f.write(text + '\n') flag += 1 print (flag/2685066.0) return 0 # # # 词性标注 # # # def Tag_Word(path): # path 是所有用户的评论文件路径 lines = open(path,'r').readlines() tags = [] # 保存每个文章分词后的词性 [ [('Excellent','JJ'),('food','NN'),('.','.')],# [('Superb','NNP'),('customer',('service','.')] ] feature_word = [] # 提出的服务价值分布特征 # 分词、赋词性 f = open('/Users/John/Desktop/yelp_dataset_challenge_academic_dataset/word_tagged_sentences.txt','w') # 保存一下词性标注后的结果 flag = 0 # 进度条 for text in lines: sentences = nltk.sent_tokenize(text) # 将文本拆分成句子列表 # 先对每个句子进行分词,在对这个句子进行词性标注(这样效果比较好) for sentence in sentences: word = nltk.word_tokenize(sentence) # 先对句子进行分词 ['Excellent','food','.'] word_tagged = nltk.pos_tag(word) # 再对这个分好的句子进行词性标注 [('Excellent','.')] for item in word_tagged: # 将标注好的词写入文件中 f.write(item[0] + '/' + item[1] + ' ') # 'Excellent/JJ food/NN ./. ' f.write('\n') # 这里我认为每个能展现feature的评论都是蕴含在一句话中的,因此每句话一行,到时候找feature的时候也是一行一行的去找 flag += 1 print ('分词进度: ' + str(flag/2687201.0)) return 0 # # # 筛选 feature 词汇 # # # def Featuer_Word(path,window): # path 是词性标注后的评论句子 flag = 0 # 进度条 lines = open(path,'r').readlines() len_lines = float(len(lines)) tagged_sentences = [] # 保存所有标注好的句子 # [ [(“'Excellent',# [('Superb','.')] ] feature_list = [] # 挖到的feature # 设置一个滑窗,寻找距离这个滑窗最近的一个NN、NNS def Slip_Window_Func(tagged_sentence,i,window): len_sentence = len(tagged_sentence) feature = '' k = 1 while k <= window: # 同时向目标词两边找 NN\NNS if i-k >= 0: if tagged_sentence[i-k][1] == ('NN' or 'NNS'): feature = tagged_sentence[i - k][0] if i+k < len_sentence: if tagged_sentence[i+k][1] == ('NN' or 'NNS'): feature = tagged_sentence[i + k][0] if feature == '': k += 1 continue else: break return feature # 数据预处理 flag = 0 # 进度条 print ('数据预处理进度: ') for line in lines: # 预处理一下字符串 'Excellent/JJ food/NN ./. \n' sentence = line[:-3].split(' ') # ['Excellent/JJ','food/NN','./.'] tagged_sentence = [] # 标注好的一个句子 [('Excellent','.')] for item in sentence: tagged_sentence.append(item.split('/')) tagged_sentences.append(tagged_sentence) flag += 1 View_Bar(flag,len_lines) # if flag == 100: # break print('') # 使用滑窗window确定 feature flag = 0 # 进度条 print ('feature挖掘进度: ') for tagged_sentence in tagged_sentences: for i,tagged_word in enumerate(tagged_sentence): # ('Excellent','JJ') if tagged_word[1] == ('JJ' or 'JJR' or 'JJS'): # 如果遇到形容词、比较级、最高级的话 feature = Slip_Window_Func(tagged_sentence,5) # 设置一个滑窗,寻找距离这个滑窗最近的一个NN、NNS if feature != '' and feature_list != []: # 如果挖到了feature的话 if feature != feature_list[-1]: # 这一步是防止挖到有滑窗交集的feature feature_list.append(feature) elif feature != '' and feature_list == []: feature_list.append(feature) else: continue flag += 1 View_Bar(flag,len_lines) print ('所有的feature:') print (feature_list) # 将feature词汇保存一下 f = open('/Users/John/Desktop/yelp_dataset_challenge_academic_dataset/feature.txt','w') for item in feature_list: f.write(str(item) + '\n') print('feature词汇保存完毕') # # 对 feature 词汇进行再清洗 # def Feature_Data_Cleaning(path): # path是装有feature词汇的文件路径 lines = open(path,'r').readlines() feature_dict = {} # 保存feature的字典 # 把原始文件放到字典中 for feature in lines: feature = feature[:-1] if feature_dict.has_key(feature) == False: # 如果字典里没有这个feature feature_dict[feature] = 1 # 赋一下key-value对 else: # 如果有这个feature feature_dict[feature] += 1 # 对字典排序 feature_dict = sorted(feature_dict.iteritems(),key=lambda asd:asd[1],reverse=True) # 对value进行降序排序 print ('原始feature数目: ' + str(len(lines))) print ('放到dict中的数目:' + str(len(feature_dict))) # 将feature字典保存成文件 f = open('/Users/John/Desktop/yelp_dataset_challenge_academic_dataset/feature_dict.txt','w') for item in feature_dict: f.write(str(item) + '\n') return 0 # 只筛选 餐厅 类型的服务行业 # filter_business_id = Filter_Business('/Users/John/Desktop/yelp_dataset_challenge_academic_dataset/yelp_academic_dataset_business.json',"Restaurants") # 保存review # Filter_Business_Review('/Users/John/Desktop/yelp_dataset_challenge_academic_dataset/yelp_academic_dataset_review.json',filter_business_id) # 词性标注 # Tag_Word('/Users/John/Desktop/yelp_dataset_challenge_academic_dataset/review_text.txt') # 筛选feature词汇 # Featuer_Word('/Users/John/Desktop/yelp_dataset_challenge_academic_dataset/word_tagged_sentences.txt',window=5) # 对feature自会进行在清洗 Feature_Data_Cleaning('/Users/John/Desktop/yelp_dataset_challenge_academic_dataset/feature.txt')