本文主要是学习参考莫烦老师的教学,对老师课程的学习,记忆笔记。
原文链接
文章目录
书山有路勤为径,学海无涯苦作舟。
零、吃水不忘挖井人
请支持老师的原文
原文链接
一、搜索引擎
1.检索文字信息
搜索引擎会遍历所有网页,对获取的网页中的标题,时间、正文的内容信息给予不同权重,提前构造成索引,将其存储在数据库之中。
在用户在检索的时候,搜索引擎就会只在自己的数据库中进行检索。但网页有更新的时候,spider会定时的爬到网页,进行更新。
2.2 检索图片,视频信息
在深度学习还没有普及的时候,对于图片和视频的信息,大多都是通过文字来记录的,通过对图片和视频的信息打上标签,实现检索。
现今的深度学习提供了新的思路,利用模型从非文字的信息,提取出来计算机可以识别的数字的信息。
多模态搜索:将用户的输入的文字信息转化为数字信息,与图片和视频的数字信息,进行匹配。对比两者数字之间的关联性,找到最相似的内容。
深度学习的缺点:由于其网络复杂,所以计算起来费时,费力,效率低。
2.3 倒排索引(快速检索)
NLP可以实现,计算机对于人类语言的理解。
倒排索引可以实现计算机,不用理解人类的语言就可以快速的检索到需要的信息。
倒排索引是一种批量召回的技术,快速在海量的数据中,初步召回基本复合检索要求的信息。
- 正排索引
对所有的文件信息进行检索,找到复合要求的信息。
- 倒排索引
在第一拿到所有的材料的时候,对所有资料通读,构建关键词与文章的对应关系。当用户在检索特定词的时候,之间返回该关键词下的所有文章。
在海量的数据面前,倒排索引找到的文章可能还是海量的,所以我们还要实现对于文章的排序,取得排名靠前的文章列表。
处理匹配排序最有名的算法:TF-IDF
2.4 匹配排序TF-IDF
2.4.1 TF-IDF原理
批量召回的数据,可能还是海量的,所以还需要对数据进行筛选。
对召回的内容再做一个问题与内容的相似度的排序,只返回头部的内容。相似度打分
TFIDF
将用户的关键词与文章中的关键词进行比对的时候,我们就需要找到文章中的关键词。哪些词是重要的,文章可以用哪些词进行表达。
我们就需要利用TFIDF实现对于文章关键词的筛选提取:
TF:越重要,出现的频率越大,但是有些口语化的词汇出现的次数也很频繁。
就需要IDF区分力进行词的区分。
IDF :实现对于全局中区分哪些词是重要的,那些词不重要。
两者结合实现表达一篇文章
全局文章的关键词的
2.4.2 检索中TFIDF的实现
在检索的时候,机器会利用词表,计算出用户提问的问句的TFIDF的值
再计算问句,与每篇文章的COS距离。
就是将问句与文章都投射到一个向量空间当中,实现文章的相似度的计算。
每篇文章都可转换成向量的形式,如下所示:
每一篇文章都是向量空间中的一个点,问句也是向量空间中的一个点。
2.4.3 TFIDF的数学表达形式
TFIDF用词语的数字向量来代表一篇文档,当比较文档时,就是在比较这些向量的相似性。
可以将所有的文章都转换为一个N维空间中的一个N维向量。
比如下面有三篇文档,每篇文档中都不断重复着“莫烦”,“Python”,“最棒”这三个词。通过对每个词计算TF-IDF后,我们可以用这这些TF-IDF值代表这三篇文档,也就是说,每篇文档就是一个三维向量。
如果把向量展示在图上,就会有类似下面图案的样子,加上一个计算向量相似度的方式,比如cosine距离,我们就能判段哪些文档在这个三维空间上比较相似了。图中两个蓝色向量离得越近,就代表他们越像。
二、搜索引擎代码实现
2.1 初步假定15篇文章
import numpy as np
from collections import Counter
import itertools
docs = [
"it is a good day, I like to stay here",
"I am happy to be here",
"I am bob",
"it is sunny today",
"I have a party today",
"it is a dog and that is a cat",
"there are dog and cat on the tree",
"I study hard this morning",
"today is a good day",
"tomorrow will be a good day",
"I like coffee, I like book and I like apple",
"I do not like it",
"I am kitty, I like bob",
"I do not care who like bob, but I like kitty",
"It is coffee time, bring your cup",
]
将文档的单词转换成ID形式,这样便于后续通过ID进行统计。
docs_words = [d.replace(",", "").split(" ") for d in docs]
vocab = set(itertools.chain(*docs_words))
v2i = {v: i for i, v in enumerate(vocab)}
i2v = {i: v for v, i in v2i.items()}
2.2 TF-IDF
构建每一篇文档的代表,也就是它的TF-IDF向量表示
在计算TF-IDF时,我们会拆开来计算,因为其实IDF的值是可以复用的,当我们计算过一个庞大数据库中的IDF后, 可以把带有这个数据库数据分布的IDF拿到其他地方使用,有点像机器学习当中的与训练模型的意思。
举个例子,如果有专门在金融类数据或科技类数据上训练过的NLP的预训练模型, 那么当我们想在科技类文档中使用的当然就是科技类的预训练模型,把金融类的预训练模型放在科技类上使用肯定会存在数据分布上的偏差。同理,IDF预计算表也是这样一个意思。
词w 的IDF本质计算 IDF=log(所有文档数/所有文档中 词w 数), 当然还有很多种变异的计算方式。代码如下:
idf_methods = {
"log": lambda x: 1 + np.log(len(docs) / (x+1)),
"prob": lambda x: np.maximum(0, np.log((len(docs) - x) / (x+1))),
"len_norm": lambda x: x / (np.sum(np.square(x))+1),
}
def get_idf(method="log"):
# inverse document frequency: low idf for a word appears in more docs, mean less important
df = np.zeros((len(i2v), 1))
for i in range(len(i2v)):
d_count = 0
for d in docs_words:
d_count += 1 if i2v[i] in d else 0
df[i, 0] = d_count
idf_fn = idf_methods.get(method, None)
if idf_fn is None:
raise ValueError
return idf_fn(df) # [n_vocab, 1]
IDF的计算有很多中方法,这里我提供了3种可以选择。一个文档中的词,如果IDF值约小,就说明它越没有意义,在其他文档中出现频率也很高。 最后出来的IDF表是一个所有词语的重要程度表,shape=[n_vocab, 1]. 接下来我们再来计算每篇文档中的TF值。
词w 在 文档d 的TF本质计算 TF=文档d 中 词w 总数
tf_methods = {
"log": lambda x: np.log(1+x),
"augmented": lambda x: 0.5 + 0.5 * x / np.max(x, axis=1, keepdims=True),
"boolean": lambda x: np.minimum(x, 1),
"log_avg": lambda x: (1 + safe_log(x)) / (1 + safe_log(np.mean(x, axis=1, keepdims=True))),
}
def get_tf(method="log"):
# term frequency: how frequent a word appears in a doc
_tf = np.zeros((len(vocab), len(docs)), dtype=np.float64) # [n_vocab, n_doc]
for i, d in enumerate(docs_words):
counter = Counter(d)
for v in counter.keys():
_tf[v2i[v], i] = counter[v] / counter.most_common(1)[0][1]
weighted_tf = tf_methods.get(method, None)
if weighted_tf is None:
raise ValueError
return weighted_tf(_tf) # [n_vocab, n_doc]
TF 是用所有词组成的每篇文档向量表示,shape=[n_vocab, n_doc], 通过TF-IDF的乘积,就能得到每篇文档的TF-IDF向量表示了。
tf = get_tf() # [n_vocab, n_doc]
idf = get_idf() # [n_vocab, 1]
tf_idf = tf * idf # [n_vocab,
最终计算出来的TF-IDF实际是一个词语和文章的矩阵,代表着用词语向量表示的文章
2.3 问题向量化
有了这些向量表示,当我们进行搜索时,只需要将搜索的问题向量化,把搜索向量在文档向量上进行距离计算,就能算出来哪些文档更贴近搜索向量了。
尝试搜索 I get a coffee cup, 并返回15篇文档当中最像这句搜索的前3篇文档。
def cosine_similarity(q, _tf_idf):
unit_q = q / np.sqrt(np.sum(np.square(q), axis=0, keepdims=True))
unit_ds = _tf_idf / np.sqrt(np.sum(np.square(_tf_idf), axis=0, keepdims=True))
similarity = unit_ds.T.dot(unit_q).ravel()
return similarity
def docs_score(q):
... 为了突出重点,我省略处理部分,请见Github中的全量代码
q_vec = q_tf * _idf # [n_vocab, 1]
q_scores = cosine_similarity(q_vec, _tf_idf)
... 为了突出重点,我省略处理部分,请见Github中的全量代码
return q_scores
q = "I get a coffee cup"
scores = docs_score(q)
d_ids = scores.argsort()[-3:][::-1]
print("\ntop 3 docs for '{}':\n{}".format(q, [docs[i] for i in d_ids]))
三、TFIDF的其他应用
3.1 挑选文章关键词
TF-IDF就是一张将词语重要程度转换成向量的文档展示方式,那么在这些向量中, 必定会有主导型元素,而这些元素其实就是这篇文档中很重要的关键词了。
我们能给这些文档挑选关键词,下面的功能就是给前三篇文档挑两个关键词。
def get_keywords(n=2):
for c in range(3):
col = tf_idf[:, c]
idx = np.argsort(col)[-n:]
print("doc{}, top{} keywords {}".format(c, n, [i2v[i] for i in idx]))
四、sklearn实现TFIDF
4.1 稀疏矩阵问题
问题:当文档的数量庞大的时候,记录TFIDF的时候,有很多值都是空值,因为有些文档不会出现那些词。如果我们把完整的一个tfidf矩阵放在计算机内存中,这些空值也会占据内存空间,这样的矩阵就是稀疏矩阵。
解决:计算将这些稀疏矩阵转换为密集矩阵,只要记录下,矩阵中那些有值的数字的行列位置,以及它的具体的ifidf的数值是多少。
在sklearn中就是使用这种机制实现对于矩阵的存储。
4.2 sklearn代码实现
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
#-------------------------------------------文档集-----------------------------
docs = [
"it is a good day, I like to stay here",
"I am happy to be here",
"I am bob",
"it is sunny today",
"I have a party today",
"it is a dog and that is a cat",
"there are dog and cat on the tree",
"I study hard this morning",
"today is a good day",
"tomorrow will be a good day",
"I like coffee, I like book and I like apple",
"I do not like it",
"I am kitty, I like bob",
"I do not care who like bob, but I like kitty",
"It is coffee time, bring your cup",
]
#----------------------------------计算tfidf--------------------------------------
vectorizer = TfidfVectorizer()
tf_idf = vectorizer.fit_transform(docs) #训练文档的tfidf
q = "I get a coffee cup"
qtf_idf = vectorizer.transform([q]) #基于文档的矩阵向量将问题转换为对应的向量
res = cosine_similarity(tf_idf, qtf_idf) #计算cos距离
res = res.ravel().argsort()[-3:]
print("\ntop 3 docs for '{}':\n{}".format(q, [docs[i] for i in res[::-1]]))
文档的tfidf的矩阵结果:
五、搜索引擎的拓展
5.1 tfidf的全局的局限性
TF-IDF 中的 IDF 带有全局属性,但是这个全局也只是全世界语料数据集的一部分
我们无法获取互联网上所有的信息,最多只能获取到一个子集,一部分的信息而已。 所以IDF并不是完美的IDF。
如果是专业领域搜索,我们的IDF就必须注重专业领域的词汇。
举个例子, 如果我们想要医学领域的搜索,如果我们拿着IDF娱乐领域获得的IDF,那么在医学领域就不会有什么好效果。
原因也很简单
- 1)娱乐领域没有那么多医学词汇
- 2)在医学领域重要的词,娱乐领域中这些词的频率与出现位置分布肯定有很大不同。
5.2机器学习的应用
TF-IDF就是对文档向量化的表示,机器学习或者深度学习中的输入都数值向量化的矩阵。
我们也能用 TF-IDF 作为文档的训练数据,将文档向量化表示后,当做神经网络的输入或者经典机器学习模型的输入,对这篇文档进行后续处理,比如分类,回归等拟合过程。
5.3 搜索引擎全流程
一个完整搜索引擎包含多个环节。
从一个query(搜索问句)开始,包含了很多query预处理部分,比如分词,敏感词过滤, 纠错,拼音识别,查询改写,查询补全等等。
再通过各种的召回策略,比如TF-IDF/ElasticSearch 召回,得到候选答案,最后再做一些业务层面的过滤处理, 才能到达你的搜索结果展示框里