2. 觀點提取和聚類代碼詳解
阿新 • • 發佈:2019-01-16
opinion n) math hold 依存關系 sed words 根據 com
1. pyhanlp介紹和簡單應用
2. 觀點提取和聚類代碼詳解
1. 前言
本文介紹如何在無監督的情況下,對文本進行簡單的觀點提取和聚類。
2. 觀點提取
觀點提取是通過依存關系的方式,根據固定的依存結構,從原文本中提取重要的結構,代表整句的主要意思。
我認為比較重要的依存關系結構是"動補結構", "動賓關系", "介賓關系"3個關系。不重要的結構是"定中關系", "狀中結構", "主謂關系"。通過核心詞ROOT出發,來提取觀點。
觀點提取的主要方法如下,完整代碼請移步致github。
''' 關鍵詞觀點提取,根據關鍵詞key,找到關鍵處的rootpath,尋找這個root中的觀點,觀點提取方式和parseSentence的基本一樣。 支持提取多個root的觀點。 ''' def parseSentWithKey(self, sentence, key=None): #key是關鍵字,如果關鍵字存在,則只分析存在關鍵詞key的句子,如果沒有key,則不判斷。 if key: keyIndex = 0 if key not in sentence: return [] rootList = [] parse_result = str(self.hanlp.parseDependency(sentence)).strip().split('\n') # 索引-1,改正確,因為從pyhanlp出來的索引是從1開始的。 for i in range(len(parse_result)): parse_result[i] = parse_result[i].split('\t') parse_result[i][0] = int(parse_result[i][0]) - 1 parse_result[i][6] = int(parse_result[i][6]) - 1 if key and parse_result[i][1] == key: keyIndex = i for i in range(len(parse_result)): self_index = int(parse_result[i][0]) target_index = int(parse_result[i][6]) relation = parse_result[i][7] if relation in self.main_relation: if self_index not in rootList: rootList.append(self_index) # 尋找多個root,和root是並列關系的也是root elif relation == "並列關系" and target_index in rootList: if self_index not in rootList: rootList.append(self_index) if len(parse_result[target_index]) == 10: parse_result[target_index].append([]) #對依存關系,再加一個第11項,第11項是一個當前這個依存關系指向的其他索引 if target_index != -1 and not (relation == "並列關系" and target_index in rootList): parse_result[target_index][10].append(self_index) # 尋找key在的那一條root路徑 if key: rootIndex = 0 if len(rootList) > 1: target = keyIndex while True: if target in rootList: rootIndex = rootList.index(target) break next_item = parse_result[target] target = int(next_item[6]) loopRoot = [rootList[rootIndex]] else: loopRoot = rootList result = {} related_words = set() for root in loopRoot: # 把key和root加入到result中 if key: self.addToResult(parse_result, keyIndex, result, related_words) self.addToResult(parse_result, root, result, related_words) #根據'動補結構', '動賓關系', '介賓關系',選擇觀點 for item in parse_result: relation = item[7] target = int(item[6]) index = int(item[0]) if relation in self.reverse_relation and target in result and target not in related_words: self.addToResult(parse_result, index, result, related_words) # 加入關鍵詞 for item in parse_result: word = item[1] if word == key: result[int(item[0])] = word #對已經在result中的詞,按照在句子中原來的順序排列 sorted_keys = sorted(result.items(), key=operator.itemgetter(0)) selected_words = [w[1] for w in sorted_keys] return selected_words
通過這個方法,我們拿到了每個句子對應的觀點了。下面對所有觀點進行聚類。
2.1 觀點提取效果
原句 | 觀點 |
---|---|
這個手機是正品嗎? | 手機是正品 |
禮品是一些什麽東西? | 禮品是什麽東西 |
現在都送什麽禮品啊 | 都送什麽禮品 |
直接付款是怎麽付的啊 | 付款是怎麽付 |
如果不滿意也可以退貨的吧 | 不滿意可以退貨 |
3. 觀點聚類
觀點聚類的方法有幾種:
- 直接計算2個觀點的聚類。(我使用的方法)
- 把觀點轉化為向量,比較余弦距離。
我的方法是用difflib對任意兩個觀點進行聚類。我的時間復雜度很高\(O(n^2)\),用一個小技巧優化了下。代碼如下:
def extractor(self): de = DependencyExtraction() opinionList = OpinionCluster() for sent in self.sentences: keyword = "" if not self.keyword: keyword = "" else: checkSent = [] for word in self.keyword: if sent not in checkSent and word in sent: keyword = word checkSent.append(sent) break opinion = "".join(de.parseSentWithKey(sent, keyword)) if self.filterOpinion(opinion): opinionList.addOpinion(Opinion(sent, opinion, keyword)) ''' 這裏設置兩個閾值,先用小閾值把一個大數據切成小塊,由於是小閾值,所以本身是一類的基本也能分到一類裏面。 由於分成了許多小塊,再對每個小塊做聚類,聚類速度大大提升,thresholds=[0.2, 0.6]比thresholds=[0.6]速度高30倍左右。 但是[0.2, 0.6]和[0.6]最後的結果不是一樣的,會把一些相同的觀點拆開。 ''' thresholds = self.json_config["thresholds"] clusters = [opinionList] for threshold in thresholds: newClusters = [] for cluster in clusters: newClusters += self.clusterOpinion(cluster, threshold) clusters = newClusters resMaxLen = {} for oc in clusters: if len(oc.getOpinions()) >= self.json_config["minClusterLen"]: summaryStr = oc.getSummary(self.json_config["freqStrLen"]) resMaxLen[summaryStr] = oc.getSentences() return self.sortRes(resMaxLen)
3.1 觀點總結
對聚類在一起的觀點,提取一個比較好的代表整個聚類的觀點。
我的方法是對聚類觀點裏面的所有觀點進行字的頻率統計,對高頻的字組成的字符串去和所有觀點計算相似度,相似度最高的那個當做整個觀點聚類的總的觀點。
def getSummary(self, freqStrLen):
opinionStrs = []
for op in self._opinions:
opinion = op.opinion
opinionStrs.append(opinion)
# 統計字頻率
word_counter = collections.Counter(list("".join(opinionStrs))).most_common()
freqStr = ""
for item in word_counter:
if item[1] >= freqStrLen:
freqStr += item[0]
maxSim = -1
maxOpinion = ""
for opinion in opinionStrs:
sim = similarity(freqStr, opinion)
if sim > maxSim:
maxSim = sim
maxOpinion = opinion
return maxOpinion
3.2 觀點總結效果
聚類總結 | 所有觀點 |
---|---|
手機是全新正品 | 手機是全新正品 手機是全新 手機是不是正品 保證是全新手機 |
能送無線充電器 | 能送無線充電器 人家送無線充電器 送無線充電器 買能送無線充電器 |
可以優惠多少 | 可以優惠多少 你好可優惠多少 能優惠多少 可以優惠多少 |
是不是翻新機 | 是不是翻新機 不會是翻新機 手機是還是翻新 會不會是翻新機 |
花唄可以分期 | 花唄不夠可以分期 花唄分期可以 可以花唄分期 花唄可以分期 |
沒有給發票 | 我沒有發票 發票有開給我 沒有給發票 你們有給發票 |
4. 總結
以上我本人做的一些簡單的觀點提取和聚類,可以適用一些簡單的場景中。
2. 觀點提取和聚類代碼詳解