Day 19 - [语料库模型] 07-程序码: 余弦相似性

嗨,昨天语料库模型建好了,下一步要如何使用呢? 我们要如何比对输入的句子与语料库中的哪一句最相似呢?

相似度计算方式

计算两个点之间存在的差异大小,主要有两种方式,欧式距离与余弦相似度。
详细的介绍参考: 欧氏距离与余弦相似度的比较

欧式距离

主要计算空间中点与点之间的距离。
距离越大,相似性越低,反之亦然。

欧式距离

余弦相似度

主要计算向量间的夹角大小。
夹角越大,相似性越低,反之亦然。

余弦相似度

而余弦相似度的公式由「内积公式」推导而来。

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 与每个句子的相似性。

结语

终於写完了,这篇应该本系列是最难的一篇了,我早上还先复习了数学呢。

参考资料



<<:  19 APCS 观念题考试技巧

>>:  EP 25: Validation by Data Annotation Validators in TopStore App

Day 30 - 每日产生观察名单

本篇重点 每日下载及更新Kbar资料 每日产生观察名单 结论 每日下载及更新Kbar资料 每天下载及...

[Day12] Android - Kotlin笔记:JetPack - Fragments在Navigation中的参数传递(Safe Args)

Fragments在Navigation中的参数传递 - SafeArgs 继上篇我们得知如何运用N...

Day02-Linux档案结构简介

Linux 因为之後会部署在 Linux 上 所以要先大概了解一下档案结构 鸟哥的 Linux 私房...

Day 13: 人工智慧在音乐领域的应用 (AI作曲的历史发展)

今天我们正式开始聊聊AI作曲相关方面的话题~ AI作曲历史 如果要追朔到最早的自动作曲 ,大家可以猜...

硬碟管理实作

昨天介绍完硬碟管理,来实作吧~ TIPS: Array在unRaid指的是资料池,并非POOL(在u...