NLP.001 词向量
0.序言
深度学习方面的理论还有很多没有写,但是只能告一段落了,后面有时间会再去写.但我们学了深度学习的目的肯定还是引用。接下来几篇文章就会详细去写关于NLP相关的知识。
首先,无论是ML还是DL首要的就是如何将信息转化成计算机能识别并进行处理的形式。这里就是 向量(Tensor) ,如何将文字转化成向量呢,通常包括是 分词->编码->映射 这几步。下面我将详细讲解这个过程
1.分词
你是不是认为我要讲jieba下面一些方法函数的使用,那只能说明你还不够了解我(qwq…)我要讲的是原理,看底层源码,哥们。。。。
1.基于dp原理的分词
先来看一下jieba源代码里面的方法:
def calc(self, sentence, DAG, route):
N = len(sentence)
route[N] = (0, 0)
logtotal = log(self.total)
#wjs:py小课堂qaq:这里利用了两层for循环,外面一层决定倒着遍历句子中每个单词(下标),内循环决定了每个单词到下一个能到达单词的对应词频
#这里把每种情况相乘并且取max
for idx in xrange(N - 1, -1, -1):
#wjs:这里变成取对数操作
#or 1是为了防止词频是Long long型的
route[idx] = max((log(self.FREQ.get(sentence[idx:x + 1]) or 1) -
logtotal + route[x + 1][0], x) for x in DAG[idx])
大概解释一下,就是对于每个确定的句子,里面的每个单词都对应了在词典(外部导入)里面的一个固定的词频,那么由于 古典概型,假设每个词之间都是相互独立的,那么每一个词在句子中对应的概率p就等于n(wordi)/n(word1)+n(word2)+n(word3)…+n(wordn),
现在对这个句子进行分词,那么会产生很多可能,假设从最后一个字开始划分,那么它可能到达的位置就有很多很多(可能与下一个结合为一个词,也可能与下下个…bulabula,qwq),然而最后一个字划分结束的位置又会影响到下一次词的划分(划分过的字不能再次使用哈)。那么总会有一种最优的情况,使得整体的概率最大
这种相邻状态存在转移关系,并且求解全局最优的算法,就是背包(dp)啊!!!
2.TF—IDF模型
我们在对句子或者文档进行关键词提取的时候,如何去衡量一个词在 整篇文档整体中的重要性呢。
出现的越多一定就越重要吗,如果如此那怎么区别不同类别文章呢?
出现的越少一定就越重要吗,如果如此那该如何计算呢?
把以上两种情况折中一下,就得到了我们我们的 TF—IDF
TF:一个词在一条句子中出现的频率/这条句子中词的总数
IDF:ln(一个训练文档中句子的总数/包含该词的句子总数)
TF-IDF:TF(wi)*IDF(wi)
2.编码
1.one—hot
ML对于文字的编码一直只停留在one_hot阶段,即对于一句话中有n个单词的话,会对应生成一个
n*n的 01 矩阵,
从上到下每一行代表着单词顺序的从前到后,
从左到右,出现1的那一列的列坐标即代表着对应的那一个字
这里引用一下大佬的图片:【Transformer系列】深入浅出理解Embedding(词嵌入)_transformer embedding-CSDN博客
![在这里插入图片描述](https://img-blog.csdnimg.cn/2573254f39d24d3bb11f3a23b9ec0d52.png#pic_center)
![在这里插入图片描述](https://img-blog.csdnimg.cn/2be302cf05184458a59ee46cb6a71c20.png#pic_center)
对应到torch中对应的 F.one_hot() 函数中,看下官方文档怎么说的
翻译一下,就是生成一个 len(input) 行, num_classes 列的矩阵。这个矩阵中除了在input中每一个值的最后一个维度作为索引对应的地方是1,其余地方都是0。
下面是官方文档给出的例子
torch.nn.functional.one_hot — PyTorch 2.3 documentation
>>> F.one_hot(torch.arange(0, 5) % 3)
tensor([[1, 0, 0],
[0, 1, 0],
[0, 0, 1],
[1, 0, 0],
[0, 1, 0]])
>>> F.one_hot(torch.arange(0, 5) % 3, num_classes=5)
tensor([[1, 0, 0, 0, 0],
[0, 1, 0, 0, 0],
[0, 0, 1, 0, 0],
[1, 0, 0, 0, 0],
[0, 1, 0, 0, 0]])
>>> F.one_hot(torch.arange(0, 6).view(3,2) % 3)
tensor([[[1, 0, 0],
[0, 1, 0]],
[[0, 0, 1],
[1, 0, 0]],
[[0, 1, 0],
[0, 0, 1]]])
2.如何降低01矩阵的维度?
假如有一串 one_hot 代码如下所示:
[1, 1, 0, 0]#文本1
[1, 0, 0, 1]#文本2
[0, 1, 1, 0]#文本3
我们可以转换为
[0, 1] # 文本序号/列下标
[0, 3] #这个代表第二个句子中第0列和第3列的值不是0(是1)
[1, 2]
这样再去对01矩阵进行读取的时候只需要访问第二个矩阵即知道所有1所在的位置,方便进行操作
3.映射
1.向量
高中的时候我们都学过向量,基本定义就是 一个矢量在坐标系中的表达 ,其巧妙之处就是将一个矢量转换成了坐标的形式,很方便做数学上 标量的计算
回到最开始提出的问题,何为向量呢?什么又是词向量?其实很简单,我们将所有输入的语句标准化之后,要投入到使用层面,即对它们之间的关系要做运算从而有一个 可视化,可计算的标准,但只停留在one_hot编码远远不够的,因为其是一个稀疏矩阵,很多维度上的信息都是无用的(都是0),所以我们为了更加全面的去分析不同句子之间的联系,要做的第一步就是
将一个稀疏矩阵转换成相同维度的稠密矩阵
2.weight矩阵
1.全连接(nn.Linear())
在前面神经网络中,通过加入linear使数据有了weight,即有了可优化的对象。但在这里的Linear()的主要作用有两点
1.控制输出矩阵特征的数量
先看官方文档:Linear — PyTorch 2.3 documentation
下面列出一个例子,将一个稀疏矩阵映射到稠密矩阵上
import torch.nn as nn
import torch
import torch.nn.functional as F
import numpy as np
def t0() :
features = [
[1, 1, 0, 0, 0],
[1, 1, 1, 0, 0],
[0, 1, 1, 0, 0],
[0, 0, 0, 1, 1],
[0, 0, 1, 0, 1],
[0, 0, 1, 1, 0],
[0, 0, 1, 1, 1],
]
features = np.asarray(features, dtype=np.float32)
features = torch.tensor(features)
m=nn.Linear(5,5)
output=m(features)
print(output)
if __name__ == '__main__':
t0()
'''tensor([[ 0.0301, -0.6789, 0.7991, 0.2303, -1.1344],
[ 0.0171, -1.0850, 0.4215, -0.0491, -1.0711],
[ 0.1171, -0.9259, 0.0714, -0.4162, -0.7722],
[-0.1048, 0.4798, 0.7283, -0.1678, -0.1963],
[ 0.1736, -0.3083, 0.3366, -0.0535, -0.0304],
[-0.5325, -0.3701, -0.0686, -0.8803, -0.4750],
[-0.1178, 0.0737, 0.3507, -0.4471, -0.1330]],
grad_fn=<AddmmBackward0>)'''
但是毕竟映射不能保证学习到原数据所有的特征,所以是有误差滴,所以才要BP鸭…
2.embedding(嵌入)
还是先看官方文档: Embedding — PyTorch 2.3 documentation
解读:
1.最重要也是必须输入的两个参数 就是 num_embeddings 和embedding_dim 这两个数字中后者可以随意输入,前者 需要保证输入的值比input中所有的值要大,因为只有如此才能保证输入的值做为索引的时候不会超出范围
2.Output: (∗,𝐻)(∗,H), where * is the input shape and 𝐻=embedding_dimH=embedding_dim 这句话的意思就是表明embedding 在原先的基础上增加了一维,维度里面有embedding_dim个元素 。
作用 :
1.大体来说就是将原数组在原来的顺序上,在不同的 embeddeing_dim上的相同下标的值拿出来作为新的维度
替代原来input里面的元素 。
2.大体原理和linear差不多,只不过 Linear不改变矩阵的维度而embedding将矩阵进行了升维操作。 但都与原矩阵之间存在着映射关系和差异
import torch.nn as nn
import torch
import torch.nn.functional as F
import numpy as np
from torch.nn import Embedding
def t0() :
features = [
[1, 1, 0, 0, 0], # 文本1
[1, 1, 1, 0, 0], # 文本2
[0, 1, 1, 0, 0], # 文本3
[0, 0, 0, 1, 1], # 文本4
[0, 0, 1, 0, 1], # 文本5
[0, 0, 1, 1, 0], # 文本6
[0, 0, 1, 1, 1] # 文本7
]
features = np.asarray(features, dtype=np.float32)
features = torch.LongTensor(features)
emb = Embedding(num_embeddings=3, embedding_dim=2)
#m=nn.Linear(5,5)
#output=m(features)
output = emb(features)
print(emb.weight.size())
print(features.size())
print(output.size())
#torch.Size([3, 2])
#torch.Size([7, 5])
#torch.Size([7, 5, 2])
print(emb.weight.T)
'''tensor([[0.8567, -0.6881, -0.5146],
[-1.1263, -1.0687, 1.8672]], grad_fn= < PermuteBackward0 >)'''
print(output)
'''tensor([[[-1.2768, -0.2975],
[-1.2768, -0.2975],
[0.6933, -1.1181],
[0.6933, -1.1181],
[0.6933, -1.1181]],
[[-1.2768, -0.2975],
[-1.2768, -0.2975],
[-1.2768, -0.2975],
[0.6933, -1.1181],
[0.6933, -1.1181]],
[[0.6933, -1.1181],
[-1.2768, -0.2975],
[-1.2768, -0.2975],
[0.6933, -1.1181],
[0.6933, -1.1181]],
[[0.6933, -1.1181],
[0.6933, -1.1181],
[0.6933, -1.1181],
[-1.2768, -0.2975],
[-1.2768, -0.2975]],
[[0.6933, -1.1181],
[0.6933, -1.1181],
[-1.2768, -0.2975],
[0.6933, -1.1181],
[-1.2768, -0.2975]],
[[0.6933, -1.1181],
[0.6933, -1.1181],
[-1.2768, -0.2975],
[-1.2768, -0.2975],
[0.6933, -1.1181]],
[[0.6933, -1.1181],
[0.6933, -1.1181],
[-1.2768, -0.2975],
[-1.2768, -0.2975],
[-1.2768, -0.2975]]], grad_fn= < EmbeddingBackward0 >)'''
if __name__ == '__main__':
t0()
[0.6933, -1.1181]],
[[0.6933, -1.1181],
[0.6933, -1.1181],
[-1.2768, -0.2975],
[-1.2768, -0.2975],
[-1.2768, -0.2975]]], grad_fn= < EmbeddingBackward0 >)'''
if __name__ == '__main__':
t0()