iT邦幫忙

2021 iThome 鐵人賽

DAY 19
0

嗨,昨天語料庫模型建好了,下一步要如何使用呢? 我們要如何比對輸入的句子與語料庫中的哪一句最相似呢?

相似度計算方式

計算兩個點之間存在的差異大小,主要有兩種方式,歐式距離與餘弦相似度。
詳細的介紹參考: 歐氏距離與餘弦相似度的比較

歐式距離

主要計算空間中點與點之間的距離。
距離越大,相似性越低,反之亦然。

歐式距離

餘弦相似度

主要計算向量間的夾角大小。
夾角越大,相似性越低,反之亦然。

餘弦相似度

而餘弦相似度的公式由「內積公式」推導而來。

formula

餘弦相似度無關乎向量大小,重點是向量之間的方向。

  • 方向一致時餘弦值為 1
  • 直角時餘弦相似度的值為 0
  • 方向相反時餘弦值為 -1

詞向量

此段參考 机器是这样理解语言 - 词向量 | 莫煩Python

比較

大部分時候,我們注重的不一定是點與點之間的距離,更重要的是兩個向量的方向是否接近。這時候,餘弦相似度就比歐式距離適合。

程式碼

莫煩 Python 的程式碼: https://github.com/MorvanZhou/NLP-Tutorials
我修改過的版本: https://gitlab.com/graduate_lab415/nlp/-/blob/master/main.py

執行

"""test"""
q = sys.argv[1]
# q = "拐杖去哪裡申請"
# q = "I like kitty"
if CUT_METHOD == "ckip":
    tmp_q = [q]
    q = tmp_q

scores = docs_score(q)
d_ids = scores.argsort()[-3:][::-1]
if LOG:
    print("\n[{}] top 3 docs for '{}':\n{}".format(CUT_METHOD, (q if CUT_METHOD != "ckip" else q[0]), [docs[i] for i in d_ids]))

第一句 q 是讀入要比較的句子,sys.argv[1] 就是用來讀取執行程式碼時輸入的參數,過幾天的文章會寫到。我如果還記得,再回來補連結...
第一個 if 那邊是因為 CKIP 和 Jieba 轉換完的格式不一樣,需要做個轉換,統一一下。
docs_score(q) 計算 q 和其他所有句子的餘弦相似性。

Python List 操作方式

a = [1, 2, 3, 4, 5]
a[::-1] #[::-1] 是倒置,輸出: [5, 4, 3, 2, 1]
a[-3] #[-3] 是倒數第 3 個,輸出: 3
a[-3:] #[-3:] 是從倒數第三個到最後一個,輸出: [3, 4, 5]
a[-3:][::-1] #輸出: [5, 4, 3]

計算分數(餘弦相似度)

def docs_score(query, len_norm=False):
    """斷詞"""
    if CUT_METHOD == "jieba":
        q_words = CutSentence.cut_jeiba(re.sub('[??()()「」,,、。::_“”→]', ' ', query), log=LOG)  # use jieba
    elif CUT_METHOD == "ckip":
        q_words = CutSentence.cut_ckip(query, log=LOG)  # use ckip
        temp_arr = np.array(q_words)
        temp_arr = temp_arr.reshape(len(q_words[0]))
        q_words = temp_arr
    else:
        q_words = query.replace(",", "").split(" ")

    """label"""
    if LABEL:
        q_words = add_label(q_words)

    """add unknown words"""
    unknown_v = 0
    for v in set(q_words):
        if v not in v2i:
            v2i[v] = len(v2i)
            i2v[len(v2i) - 1] = v
            unknown_v += 1
    if unknown_v > 0:
        _idf = np.concatenate((idf, np.zeros((unknown_v, 1), dtype=np.float)), axis=0)
        _tf_idf = np.concatenate((tf_idf, np.zeros((unknown_v, tf_idf.shape[1]), dtype=np.float)), axis=0)
    else:
        _idf, _tf_idf = idf, tf_idf
    counter = Counter(q_words)
    q_tf = np.zeros((len(_idf), 1), dtype=np.float)  # [n_vocab, 1]
    for v in counter.keys():
        q_tf[v2i[v], 0] = counter[v]
    q_vec = q_tf * _idf  # [n_vocab, 1]

    q_scores = cosine_similarity(q_vec, _tf_idf)  # 拿q的向量和所有向量比對
    if len_norm:
        len_docs = [len(d) for d in docs_words]
        q_scores = q_scores / np.array(len_docs)
    return q_scores

一個新拿到的 q (這個 function 中叫 query),進行比較前,也要經過段詞、加 label,再檢查 query 有沒有新的詞,有的話就要加到 v2ii2v 裡面。將 query 轉換成向量 q_vec 後才能算相似性唷。

最後 q_scores = cosine_similarity(q_vec, _tf_idf) 計算 query 與每個句子的相似性。

結語

終於寫完了,這篇應該本系列是最難的一篇了,我早上還先複習了數學呢。

參考資料



上一篇
Day 18 - [語料庫模型] 06-程式碼: TF、IDF、TF-IDF
下一篇
Day 20 - [語料庫模型] 08-繪製語料庫模型Heatmap圖
系列文
長照小幫手 - 從 0 開始建置 Chatbot 的筆記 & 走錯路的心得31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言