PS:要转载请注明出处,本人版权所有。
PS: 这个只是基于《我自己》的理解,
如果和你的原则及想法相冲突,请谅解,勿喷。
环境说明
无
前言
本文是这个系列第二篇,它们是:
- 《大模型基础补全计划(一)---重温一些深度学习相关的数学知识》https://www.cnblogs.com/Iflyinsky/p/18717317
本系列虽然是大模型相关内容,但是主要还是大语言模型,对于大语言模型来说,其输入是我们值得关心的。
自然语言的基本单位是词或者字,对于模型来说,是没有办法直接输入文字的,因此我们需要一种方法将文字转换为LLM所能接受的格式,我们将词转换为向量的表达这种技术叫做词嵌入(word embedding)。
下面我们介绍一种在后续例子中会出现的一种直观词嵌入方法:one_hot向量。
one_hot向量
其含义非常的简单,如果有5个不同的词,那么生成5个0,1的向量即可,例如:([1,0,0,0,0])或者([0,0,0,0,1]),通过这样简单的向量,就可以标识所有的字。
虽然其看起来简单,但是有些缺陷,例如:现在我们来考虑一个问题,我们用one_hot向量A表示‘似’,用one_hot向量B表示‘像’,然后我们求其余弦相似度 (cos{theta} = frac{A^TB}{|A||B|} = 0) ,难道现在我们可以说‘似’和‘像’是无关联的吗?
根据上面的这个疑问,很明显要解决这个问题,需要把词映射为像人脸识别中的人脸特征向量这样的特征向量。下面我们介绍word2vec这个工具,注意词向量的表达有很多越来越好的方法,这里我们只需要了解一个基本即可。
word2vec
word2vec工具可以将每个词映射为固定长度的向量,这些向量能够表达词的相似性关系。如果大家做过人脸识别,那就对这个相似性概念一点也不陌生。
word2vec工具包含了两个模型:跳元模型(skip-gram),连续词袋(CBOW)。这些模型的训练依赖于条件概率,且由于是不带标签的数据,他们是自监督模型。
下面我们只简单分析一个简单的:跳元模型(skip-gram)。
跳元模型(skip-gram)
跳元模型假设一个词可以用来在文本序列中生成其周围的单词。我们以文本序列:“自太古以来”为例,给定中心词“古”,给定上下文窗口是2,跳元模型考虑生成上下文词“自”,“太”,“以”,“来”的条件概率是:(P(“自”,“太”,“以”,“来”|“古”) = P(“自”|“古”)*P(“太”|“古”)*P(“以”|“古”)*P(“来”|“古”))。
在跳元模型中,对于词表V中索引为i的的词(w_i),其有两个向量(v_i)和(u_i),他们分别表示为(w_i)做为中心词、上下文词时的向量。此时我们给定中心词(w_c),生成上下文词(w_o)的条件概率可以使用u,v向量的点积和softmax来建模:(P(w_o|w_c) = frac{exp(u_o^T v_c)}{sum_{iin{V}} exp(u_i^T v_c)})
现在我们给定词表V,时间步t处的词表示为(w_t),给定上下文窗口是m,跳元模型的似然函数是在给定任何中心词的情况下生成所有上下文词的概率:(prodlimits_{t-1}^{T} prodlimits_{-mle j le m, j ne 0} P(w_{t+j}|w_{t}))
然后我们就通过最大化似然函数来学习模型参数,相当于最小化负对数似然函数,然后得到损失函数是:(-sumlimits_{t-1}^{T} sumlimits_{-mle j le m, j ne 0} log(P(w_{t+j}|w_{t})))。
最后当我们使用随机梯度下降来最小化损失时,我们选取一个短的序列来计算该序列的梯度。注意,我们的模型定义为:(P(w_o|w_c) = frac{exp(u_o^T v_c)}{sum_{iin{V}} exp(u_i^T v_c)}),我们给定(v_c),求(v_o)的梯度。
为了方便计算,我们对模型取对数,可得到:(log(P(w_o|w_c)) = u_o^T v_c - log(sum_{iin{V}} exp(u_i^T v_c)))
然后我们求相对于(v_c)的微分,可以得到:(frac{partial}{partial v_c}(log(P(w_o|w_c))) = frac{partial}{partial v_c}(u_o^T v_c) - frac{partial}{partial v_c}(log(sum_{iin{V}} exp(u_i^T v_c))) = u_o - frac{1}{sum_{iin{V}} exp(u_i^T v_c)} * frac{partial}{partial v_c}(sum_{iin{V}} exp(u_i^T v_c)) = u_o - frac{1}{sum_{iin{V}} exp(u_i^T v_c)} * (sum_{jin{V}} exp(u_j^T v_c)*u_j) = u_o - sumlimits_{jin{V}}(frac{exp(u_j^T v_c)*u_j}{sum_{iin{V}} exp(u_i^T v_c)}) = u_o - sumlimits_{jin{V}}(frac{exp(u_j^T v_c)}{sum_{iin{V}} exp(u_i^T v_c)})*u_j = u_o - sumlimits_{jin{V}} P(w_j|w_c)u_j)
注意中间关于对数的求导计算:令(f(v_c) = sum_{iin{V}} exp(u_i^T v_c)),可以通过
(frac{partial}{partial v_c}(log f(v_c)) = u_o - frac{1}{f(v_c)} * frac{partial}{partial v_c}(f(v_c)))得到上面的结果。
Tokenizer
注意,在这些把词生成向量的过程,我们上文已经提到了。但是这里忽略了一个大问题,就是我们默认将语句拆分了词或者字。
因此在做向量化之前,有一个关键的动作是分词,从2025/03现在来看,分词的主要作用是将字转换(浓缩为)为token id。现在简单理解就是:tokenid可能是一个字、词或者零点几个字、词。
以后有机会再挖这个的坑吧,现在先简单这样理解。
后记
虽然现在有更加先进的模型代替了这些基础的模型,但是对于我们初学者来说,可以通过这样的一个简单的模型来知道词嵌入过程做了什么是非常有意义的。
此外从上面的过程我们可以知道,我们在用大语言模型时,需要做预处理文字,非常的像使用CV模型前,对图像进行预处理。而这个预处理过程就是:分词+向量化。
参考文献
