今天我們要使用昨天說到的Lin similarity來計算字與字之間的相似度。我們將會使用Brown Corpus作為訓練文集,以及Wordnet中的文字關係架構來計算文字相似度。今天的任務會分成三部分:
今天我們會利用一個常見的Word Similarity Dataset:Similarity-353。我已經把dataset存起來放到Github上,大家可以在這裡下載,記得把dataset和這個notebook放在同一個資料夾裡面。
這個Dataset長這樣:
Word 1 Word 2 Human (mean) 1 2 3 4 5 6 7 8 9 10 11 12 13
love sex 6.77 9 6 8 8 7 8 8 4 7 2 6 7 8
tiger cat 7.35 9 7 8 7 8 9 8.5 5 6 9 7 5 7
tiger tiger 10.00 10 10 10 10 10 10 10 10 10 10 10 10 10
book paper 7.46 8 8 7 7 8 9 7 6 7 8 9 4 9
computer keyboard 7.62 8 7 9 9 8 8 7 7 6 8 10 3 9
除了第一列是header之外,其他每一列都是資料。資料中第一行是文字1、第二行是文字2、第三行是黃金標籤(人類審查),之後的每一行是由不同的審查員審查後給的評分。
這裡我們資料存進Python dictionary中,以文字1和文字2的tuple為key,以黃金標籤為value,長得就像:{("tiger", "cat"): 7.35, ...}。 這個dataset中存著許多稀有字,所以我們需要把他們去除掉,讓我們的dataset跟訓練資料更能夠配合。所以第一步驟我們要做的就是去除稀有字,來產生一個小一些的測試集供我們未來測試文字相似度。
首先,我們基於文字在Brown Corpus中的document frequency來判斷該字是不是稀有字。我們把Brown Corpus中的每個段落視為一個檔案,利用NLTK Brown內建的 paras
方法來讀取,同時去掉那些非英文字母的字,接著進行lower case和lemmatization。若是文字1和文字2中其中一個字的document frequency小於8,那麼我們就將之視為稀有字,並且把這個文字組合移除。
import nltk
from nltk.corpus import brown
from nltk.corpus import wordnet
#nltk.download('brown')
#nltk.download('wordnet')
# filtered_gold_standard 儲存移除稀有自後的文字組合與黃金標籤
filtered_gold_standard = {}
from collections import Counter
doc_freqs = Counter()
# lemmatize
lemmatizer = nltk.stem.wordnet.WordNetLemmatizer()
def lemmatize(word):
lemma = lemmatizer.lemmatize(word,'v')
if lemma == word:
lemma = lemmatizer.lemmatize(word,'n')
return lemma
# 移除有稀有字的文字組合
def remove_word_pair(dictionary, li):
for key in list(dictionary.keys()):
if key[0] not in li or key[1] not in li:
dictionary.pop(key, None)
# 讀取資料,同時將文字組合與相似度存進filtered_gold_standard
# 也將每一個獨特自行存到doc_freqs Counter
with open("set1.tab") as f:
next(f)
for line in f:
line_list = line.split("\t")
filtered_gold_standard[(line_list[0], line_list[1])] = float('%.2f' % float(line_list[2]))
for i in range(2):
doc_freqs[line_list[i]] = 0
bwn_par = brown.paras() # 未處理的Brown Corpus段落
norm_par = [] # 預處理過的Brown Corpus段落
# 進行預處理
for i in range(len(bwn_par)):
para_sentence = []
for j in range(len(bwn_par[i])):
para_sentence.append([lemmatize(word.lower()) for word in bwn_par[i][j] if word.isalpha()])
norm_par.append(sum(para_sentence,[]))
# 計算每個處理過的字的document frequency
for word in doc_freqs:
for i in range(len(norm_par)):
if word in norm_par[i]:
doc_freqs[word] += 1
# 將document frequency < 8 的稀有字去除
for key in list(doc_freqs.keys()):
if doc_freqs[key] < 8:
doc_freqs.pop(key,None)
remove_word_pair(filtered_gold_standard, doc_freqs)
print(len(filtered_gold_standard))
print(filtered_gold_standard)
第二段預處理,我們要將有太多的歧異的字去除。這裡我們會根據NLTK中WordNet所提供的資料,將那些沒有單一主要字義的字去掉。我們定義單一主要字義為:1) 只有一個意思(也就是只有一個synset),或2) 最常見的意思是第二常見的意思之count在四倍以上(WordNet有提供 count()
方法)。除了去除沒有單一主要字義的字,我們也要去掉那些單一主要字義不是名詞的字(在Synset的資料中也會顯示)。只要文字組合中有一個字是需要去除的,整個文字組合都需要被移除。
我們會將經過第二段預處理的文字組合與相似度存進 final_gold_standard。
# final_gold_standard 儲存最終版本的文字組合與相似度
final_gold_standard = {}
import operator
# primary_sense 儲存文字與主要字義的組合
primary_sense = {}
# word_types 儲存所有字型
word_types = []
# 將所有自行加到 word_types 中
final_gold_standard = filtered_gold_standard.copy()
for key in list(filtered_gold_standard.keys()):
for i in range(2):
if key[i] not in word_types:
word_types.append(key[i])
# 檢查這個字是否符合單一主要字義標準,若是,則留下該字
def should_keep(word):
lemma_count = {} # k: (i, j, .pos()), v: .count()
for i in range(len(wordnet.synsets(word))):
for j in range(len(wordnet.synsets(word)[i].lemmas())):
if wordnet.synsets(word)[i].lemmas()[j].name().lower() == word:
lemma_count[(i,j,wordnet.synsets(word)[i].pos())] = wordnet.synsets(word)[i].lemmas()[j].count()
else:
lemma_count[(i,j,wordnet.synsets(word)[i].pos())] = 0
# 照著count數排序
lemma_count = sorted(lemma_count.items(), key=operator.itemgetter(1), reverse=True)
# 將是名詞且有單一主要字義的字留下
if len(lemma_count) > 0:
if len(wordnet.synsets(word)) == 1 and lemma_count[0][0][2] == 'n':
primary_sense[word] = lemma_count[0][0][0]
return True
elif len(lemma_count) > 1 and lemma_count[0][1] >= (lemma_count[1][1] * 4) and lemma_count[0][0][2] == 'n':
primary_sense[word] = lemma_count[0][0][0]
return True
return False
word_types = [word for word in word_types if should_keep(word)]
# 將沒有達到條件的文字組合移除
remove_word_pair(final_gold_standard, word_types)
print(len(final_gold_standard))
print(final_gold_standard)
前面已經有黃金標籤(人工審查)的相似度了。現在,我們要使用Lin similarity來計算。我們用Brown corpus中的information content (IC)來計算。
from nltk.corpus import wordnet_ic
#nltk.download('wordnet_ic')
# lin_similarities 儲存文字組合與lin similarity
lin_similarities = {}
brown_ic = wordnet_ic.ic('ic-brown.dat')
for key in list(final_gold_standard.keys()):
word_x, word_y = wordnet.synsets(key[0]), wordnet.synsets(key[1])
sense_x, sense_y = primary_sense[key[0]], primary_sense[key[1]]
lin_similarities[key] = word_x[sense_x].lin_similarity(word_y[sense_y], brown_ic)
print(lin_similarities)
最後,我們使用Pearson correlation co-efficient來比對Lin similarity和黃金標籤的關聯度。利用Scipy (scipy.stats
) 裡面的 pearsonr
方法來計算。Pearson correlation co-efficient的結果會落於-1到1之間,若是完美正相關則為1。
from scipy.stats import pearsonr
def cal_pearson_cor(dict2):
dict1 = final_gold_standard
array1 = list(dict1.values())
array2 = list(dict2.values())
return pearsonr(array1, array2)
print(cal_pearson_cor(lin_similarities)[0])
今天的Jupyter Notebook在這裡。