文章阅读目录大纲
在将生物序列(如基因组或蛋白质序列)或文本数据转换为数值向量形式时,TF-IDF(Term Frequency-Inverse Document Frequency)和N-gram One-hot(又称Bag-of-n-grams)是两种经典且基础的文档嵌入算法。它们各自侧重于不同的特征提取方式,常被用于自然语言处理和生物信息学领域。
One-hot(Bag-of-n-grams)编码原理
One-hot编码是一种简单直观的表示方法,用于将离散的特征转换为二进制向量。在文本或序列的语境下,One-hot编码通常与词袋模型(Bag-of-Words, BoW)结合使用,因此也常被称作Bag-of-n-grams。其基本思想是忽略词或n-gram在文档中的顺序,仅统计其出现与否或出现次数,从而将文档表示为一个固定长度的向量。例如,我们假若将一条DNA序列当作为一个文档,每一个碱基当作为单词,则很显然我们可以将拿到的任意一条DNA序列使用{A,T,G,C}这4个碱基的字母组成的向量来表示,虽然我们处理的DNA序列的长读很可能是天差地别的,但是使用{A,T,G,C}这4个碱基来描述后,按照最简单的One-hot编码思路,都可以被统一的处理为4个元素长度的向量[1,1,1,1]。很明显这种简单的处理是不能够被我们所接受的,因为所有被处理后的序列的特异性都丢失了。
为了尝试解决上面的简单的单词编码规则对序列文档的特异性丢失的问题,在One-hot编码思路的基础上,增加了单词之间的先后顺序的关联关系,产生了N-gram模型方法。N-gram模型在Bag-of-Words基础上增加了对连续词组的考虑。例如,对于n=2(二元模型),会将连续的词对作为新的“单词”处理。这种N-gram模型方法在生物序列处理上就是称之为K-mer方法。对一条序列进行N-gram One-hot编码,其实就是生成kmer,然后基于kmer分布生成对应的one-hot向量,例如对于这里示例的三条DNA序列:ATCCCCC,GTGAGAAAAAGCC和GCCTATAATATAT,在kmer长读为2的时候,就可以生成下面所示的One-hot编码的向量:
[ AC AG AT CA CC CT GC GG TA TC TT]
ATCCCCC [ 0 0 1 0 1 0 0 0 0 1 0 ]
GTGAGAAAAAGCC [ 1 0 0 1 0 1 1 1 0 1 1 ]
GCCTATAATATAT [ 0 1 1 0 0 0 1 1 1 0 1 ]
将这些2-gram也加入词表后,再进行统计,可以捕获一定的上下文顺序信息。例如,增加2-gram后,Bag-of-2-grams的向量维度会增加,因为词表包含了更多的组合词。对于Bag-of-n-grams编码方法,很明显这种方法模型简单、计算快速,易于理解和实现。它是一种基于统计的表示方法,能够很快地提取到序列文档的特征。但是,由于其忽略了词序和语法信息,所以序列的特异性会很容易被消除。此外,当词表非常大时,向量维度会极高,导致维数灾难,并且向量极为稀疏,大部分元素为零。
TF-IDF加权
在上面所提到的Bag-of-n-grams模型中,认为所有特征(词或n-gram)的重要程度相同,这在很多情况下并不成立。例如在生物序列分析中,某种kmer在序列库中极为罕见,则这种kmer相比较于大量分布在不同序列上的kmer显然更有价值。一些关键词在当前文档中频繁出现,但在整个语料库中却很少见,这些词往往更能代表文档的主题。因此,在Bag-of-n-grams的基础上进行单词的加权处理,就产生了TF-IDF方法。在TF-IDF方法中,会通过两个部分的乘积来量化每个词的重要性:
TF(Term Frequency):表示一个词在文档中出现的频率,通常使用归一化的形式,以防止对长文档的偏置。计算公式如下:
TF(t, d) = (词 t 在文档 d 中出现的次数) / (文档 d 中所有词的总数)
IDF(Inverse Document Frequency):衡量一个词在所有文档中的普遍重要性,用于降低常用词的权重。如果一个词在很多文档中都出现,则其IDF值较低,反之则较高。IDF的计算通常采用对数形式,以平滑极端情况。公式如下:
IDF(t) = log(文档总数 / (包含词 t 的文档数 + 1))
最后,将上面的两个因子相乘,即可得到TF-IDF值:词的TF与IDF的乘积,反映了该词在当前文档中的重要性。公式为:
TF-IDF(t, d) = TF(t, d) * IDF(t)
现在我们假设一个文档可以通过一个字符串集合来表示,这个集合中的每一个字符串就是一个单词,那么,我们可以通过下面的代码统计出每一篇文档中的单词分布频数:
Dim doc As String() = {...}
' 单个文档的数据结构可以用频数字典来表示
Dim tf As Dictionary(Of String, Integer) = doc _
' 按照单词进行分组,然后统计出出现频数
.GroupBy(Function(a) a) _
.ToDictionary(Function(a) a.Key,
Function(a)
Return a.Count
End Function)
上面单个文档我们在这里可以直接用一个单词分布频数的字典来描述,那么一系列的文档的集合,就可以用下面的字典结构来表示:
''' <summary>
''' 存储 序列ID -> {K-mer: 出现次数}
''' </summary>
ReadOnly vecs As New Dictionary(Of String, Dictionary(Of String, Integer))
按照算法的定义,我们可以编写出下面的两个函数来实现TF-IDF中的两个计算因子的计算操作:
''' <summary>
''' get number of document which contains term <paramref name="v"/>
''' </summary>
''' <param name="v">term for count documents</param>
''' <returns></returns>
<MethodImpl(MethodImplOptions.AggressiveInlining)>
Public Function DF(v As String) As Integer
Return Aggregate seq As Dictionary(Of String, Integer)
In vecs.Values
Where seq.ContainsKey(v)
Into Count
End Function
''' <summary>
''' IDF (Inverse Document Frequency)
''' </summary>
''' <param name="v"></param>
''' <returns></returns>
Public Function IDF(v As String) As Double
Return std.Log((N + 1) / (DF(v) + 1)) + 1
End Function
在上面的第一个DF函数中,统计出某一个单词在哪些文档中出现,并计数。第二个IDF函数则是进行逆文档计算来评价当前分析的这个单词的重要性程度。由于DF函数的结果在IDF的公式的分母中,很明显,单词在越多的文档中出现,其IDF结果值越低。即某一个单词在不同的文档间出现的频率越高,则这个单词的重要性程度越低。
所以我们可以从上面的计算公式中总结出TF-IDF加权算法的核心思想:TF-IDF认为,如果一个词在当前文档中出现频率(TF)很高,而在其他文档中出现频率很低(IDF高),那么这个词对于区分当前文档内容具有很高的代表性,应给予较高权重。相反,如果词在所有文档中都普遍出现,则其IDF值接近0,TF-IDF值也会很低,甚至可以忽略这类词。基于IDF加权,能够有效突出文档中具有区分性的关键词,降低常见无意义词的影响,提高后续分类或检索的准确性。
但是,由于TF-IDF方法仍然基于词频统计,没有考虑词与词之间的语义关系,可能无法捕捉文档的深层含义。对于非常短的文档,可能因词频过低而影响TF-IDF的计算。此外,IDF的计算需要整个语料库的统计信息,这意味着如果语料库规模小或不够全面,IDF的准确性可能受影响。
基于k-mer分解的生物序列向量嵌入
将生物序列(如DNA、RNA或蛋白质序列)转换为数值向量的一种常用方法是k-mer分解。k-mer是指序列中长度为k的连续子串,也称为k-mers或k-grams。例如,对于DNA序列“ATGCA”,k=3时,其3-mers包括“ATG”、“TGC”、“GCA”。k-mer方法在生物信息学中应用广泛,因为它可以高效地从大规模序列数据中提取特征,并且k-mer的分布和频率往往蕴含生物序列的内在功能和结构信息。
k-mer分解将一个长序列切割成一系列重叠或不重叠的短序列片段,从而将序列表示为这些k-mer的集合或频率分布。对于长度为L的序列,如果采用滑动窗口方式,可以提取出N = L - k + 1个k-mers。每个k-mer可以看作一个“单词”,序列本身可以看作由这些“单词”组成的“文档”。这样,生物序列就转化为了一种形式上的文本,可以应用TF-IDF等自然语言处理技术来提取特征。
ReadOnly vec As New TFIDF
ReadOnly k As Integer
ReadOnly type As SeqTypes
Public Sub Add(seq As FastaSeq)
' get biological sequence data
Dim chars As String = seq.SequenceData
If type <> SeqTypes.Protein Then
' fix of the nucleotide sequence order
' make nucleotide seuqnece always in forward order
chars = NucleicAcid.Canonical(chars)
End If
' generate kmer from the given sequence data
' and add as document data into TF-IDF algorithm
Call vec.Add(seq.Title, KSeq.KmerSpans(chars, k))
End Sub
''' <summary>
''' split the given sequence as kmer span for a speicific biological sequence.
''' </summary>
''' <param name="seq_str"></param>
''' <param name="k"></param>
''' <returns></returns>
Public Shared Iterator Function KmerSpans(seq_str As String, k As Integer) As IEnumerable(Of String)
For i As Integer = 0 To seq_str.Length - k
Yield seq_str.Substring(i, length:=k)
Next
End Function
例如,上面的示例代码就实现了想TF-IDF计算算法模块中添加一条fasta序列为kmer文档的过程。在上面的函数实现中,对于基因组的核酸序列,由于DNA序列有forward和互补的顺序,所以对于DNA序列需要进行Canonical操作,将序列的方向统一为forward方向。然后基于滑窗(窗口大小为k)操作从原始序列字符串中提取出子集,得到kmer列表,既可以将生物序列转换为kmer单词集合,用于添加进入TF-IDF计算模块中。
从k-mer到One-hot向量
在获取了序列的所有k-mers后,可以将每个不同的k-mer视为一个“单词”,构建一个全局的k-mer词表。然后,对每个序列(相当于一篇文档)统计其k-mer出现情况,即可得到该序列的One-hot或词频向量表示。基于在上面所展示的代码,可以通过下面的函数来生成全局的kmer词表:
Private Function WordsFromDocument() As IEnumerable(Of String)
Dim pull = From seq As Dictionary(Of String, Integer)
In vecs.Values
Select seq.Keys
Return From word As String
In pull.IteratesALL
Distinct
Order By word
End Function
上面的代码实现逻辑非常简单:由于我们在上面的代码中,将序列集合看作为单词的出现频数的字典集合,在频数字典中,键名就是kmer单词。所以在这里我们将所有的文档对应的频数字典中的kmer单词键名提取出来,去重后排序,就可以构建出一个全局的kmer词表。至此,我们就可以循环扫描一遍这个全局的单词表,然后针对每一个单词都提取出在不同的文档中的出现次数和IDF加权结果,既可以得到了该kmer单词的向量化嵌入结果:
''' <summary>
''' generates the TF-IDF matrix
''' </summary>
''' <returns>
''' A dataframe object with row is sequence and column is word data
''' </returns>
Public Function TfidfVectorizer(Optional L2normalized As Boolean = False) As DataFrame
Dim m As New DataFrame With {
.rownames = vecs.Keys.ToArray
}
Dim seqs As SeqValue(Of Dictionary(Of String, Integer))() = m.rownames _
.Select(Function(id) vecs(id)) _
.SeqIterator _
.ToArray
For Each v As String In TqdmWrapper.Wrap(Words)
' add column
' row is sequence id
Call m.add(v, From seq As SeqValue(Of Dictionary(Of String, Integer))
In seqs.AsParallel
Let tf As Integer = seq.value.TryGetValue(v, [default]:=0)
Let ordinal As Integer = seq.i
Let embedding As Double = tf * IDF(v)
Order By ordinal Ascending
Select embedding)
Next
If L2normalized Then
' data populated by rows
Dim rows As NamedCollection(Of Double)() = m _
.foreachRow(Function(r)
Dim vec As New Vector(From xi As Object In r Select CDbl(xi))
Dim norm As Double = std.Sqrt((vec ^ 2).Sum)
' get normalized sequence row data vector
Return New NamedCollection(Of Double)(r.name, vec / norm)
End Function) _
.ToArray
m = DataFrame.FromRows(rows, m.featureNames)
End If
Return m
End Function
至此,我们将生物序列通过上述方法嵌入为向量后,可以开展多种分析。这些向量表示将复杂的生物数据转换为计算机易于处理的形式,为各种下游应用奠定基础。嵌入向量的一个直接应用是计算对象之间的相似度或距离。由于向量表示了序列或功能的特征,因此可以通过余弦相似度、欧氏距离等度量计算两个向量之间的接近程度。同时,嵌入向量作为特征,也可以输入各种机器学习模型进行分类或回归预测。
- TF-IDF与N-gram One-hot文档嵌入算法原理 - 2026年2月10日
- CenterStar多序列比对算法 - 2026年1月8日
- 建立KEGG的KO序列数据库 - 2026年1月4日


No responses yet