自然语言处理(NLP)
这是英语NLP特有的问题.
在需要将英文文本分割成句子的时候, 会发现只通过标点符号并不足以分割句子:
人名, 域名, 小数点, 省略号, 句号全都共用同一个符号
在需要将英文文本分割成句子的时候, 会发现只通过标点符号并不足以分割句子:
人名, 域名, 小数点, 省略号, 句号全都共用同一个符号
..指一个词所有形式的集合, 表示一种关联关系.
例如"go"的lexeme是集合
{"go", "goes", "going", "went", "gone"}.指约定好的代表相关lexeme的一个词.
例如约定"go"作为代表, 表示"go"和"went"等词共有的lexeme.
词干提取器被称作Stemmer.
与词形还原相比, 更接近工程领域.
现实中的信息检索普遍使用词干提取而不是词形还原:
词形还原可能存在不准确的情况, 而词干提取不存在不准确的情况,
因为它会将具有相同词干的不同词都映射到同一个词元上, 这有效提高了召回率.
词干提取器往往比词性还原器更快, 更小, 更简单.
现实中的信息检索普遍使用词干提取而不是词形还原:
词形还原可能存在不准确的情况, 而词干提取不存在不准确的情况,
因为它会将具有相同词干的不同词都映射到同一个词元上, 这有效提高了召回率.
词干提取器往往比词性还原器更快, 更小, 更简单.
实现方式:
- 基于规则, 例如将以"ing"结尾的单词根据规则变换.
- 基于语料, 从一个单词映射到对应的lemma.
https://snowballstem.org/
一种可以编译为多种语言的字符串处理语言, 专用于创建词干提取算法.
为Porter Stemmer算法的后继者.
该算法是最常用的英语词干算法, 被广泛使用.
来自Hunspell拼写检查库的词典.
一个词典包括两个文件:
.aff词缀(affix).dic词典(dictionary)
# 根据语言导出带有词干的词典, 需要先安装对应的aspell词典# http://aspell.net/man-html/Working-With-Affix-Info-in-Word-Lists.htmlaspell --lang en dump master | aspell --lang en munchhttps://github.com/michmech/lemmatization-lists
一个可以免费下载的大型英语词汇数据库, 提供同义词集.
官方的最新版本是2006年发布的, 由于年代久远, 被批评为缺乏维护, 内容过时, 导致世界各地出现分叉.
官方的最新版本是2006年发布的, 由于年代久远, 被批评为缺乏维护, 内容过时, 导致世界各地出现分叉.
https://github.com/globalwordnet/english-wordnet
一个WorldNet的英语分叉, 每年发布一个新版本.
将一个词转为lemma的过程被称作词形还原(lemmatization, 动词为lemmatize).
词形还原需要理解词在上下文中的词性, 从而准确确定词的词元.
词形还原需要理解词在上下文中的词性, 从而准确确定词的词元.
与词干提取相比, 更接近学术领域.
词袋是对文档进行分词后, 根据词频/TF-IDF等属性创建的高维稀疏向量.
为基于机器学习的嵌入模型出现之前的主流向量化方法.
缺陷:
- 需要词典来处理同义词, 否则同义词会稀释统计特征.
- 无法捕捉深层的语义特征.
例如, 文档中使用代词来指代概念时, 概念的统计特征会被稀释. - 无法处理多义词的情况.
例如, "亚马逊"可以指企业也可以指地点, "福特"可以是人名也可以是企业名. - 语序信息会丢失.
例如, 无法区分"狗咬人"和"人咬狗".
对原文进行基于统计的抽样, 提取文本中重要的句子作为摘要.
这种方法的缺点在于很容易造成有效信息的丢失.
抽象法在理解原文的基础上, 生成摘要文本.
通常基于机器学习技术.
已知通过排除现有词典里的词汇来分辨新词是一个不够准确的方法, 仍然需要大量人力来修正, 无法赶上新词出现的速度.
在这种前提下, 只有无监督词库生成方案是可行的,
而中文领域几乎所有的无监督词库生成研究都建立在matrix67的这篇博客文章的基础之上.
在这种前提下, 只有无监督词库生成方案是可行的,
而中文领域几乎所有的无监督词库生成研究都建立在matrix67的这篇博客文章的基础之上.
值得注意的是, 无监督词库生成普遍会生成不符合人类习惯的词汇.
例如算法可能更倾向于认为"的电影"比"电影院"更像一个词汇, 而这仅仅是因为"的电影"在统计意义上比"电影院"有优势.
直觉化的做法是人工修复无监督词库, 但很快就会意识到词汇太多根本无法修复的问题.
合理的做法是将"的电影"也视作正常的词汇,
因为统计意义上, 它已经是一个真正的词汇, 用于机器的词典本身并没有必要去迎合或匹配人类的习惯.
例如算法可能更倾向于认为"的电影"比"电影院"更像一个词汇, 而这仅仅是因为"的电影"在统计意义上比"电影院"有优势.
直觉化的做法是人工修复无监督词库, 但很快就会意识到词汇太多根本无法修复的问题.
合理的做法是将"的电影"也视作正常的词汇,
因为统计意义上, 它已经是一个真正的词汇, 用于机器的词典本身并没有必要去迎合或匹配人类的习惯.
正向词库生成是从小词开始, 尝试组成大词的词库生成方式.
这类生成方式的主要问题是它需要存储大量的中间数据用于统计, 语料库越大, 存储压力就越大.
复杂度至少为
在数据量庞大的情况下, 内存很容易不够用, 而且由于读写的碎片性很强, 会频繁发生内存交换, 导致效率极其低下.
这类生成方式的主要问题是它需要存储大量的中间数据用于统计, 语料库越大, 存储压力就越大.
复杂度至少为
O(n^2).在数据量庞大的情况下, 内存很容易不够用, 而且由于读写的碎片性很强, 会频繁发生内存交换, 导致效率极其低下.
朴素方案的方法很符合直觉:
- 1.将所有文本按N-gram进行切分, 对于中文一般切分到四元就够了.
- 2.统计每个词汇的词频.
- 3.设置一个阈值, 将出现频率在该值以上的词视作词汇.
例如, 可以通过80/20法则将前20%的词视作词汇.
逆向词库生成法只需要分别执行一次1-gram和2-gram的统计,
然后根据统计结果将句子拆分成更小的片段, 将最后无法拆分的片段视作词汇.
这种方法无论是性能还是内存需求都要比正向词库生成好得多, 复杂度可以看作为
采用这种方法时, 由于无法事先算好高于2-gram的信息熵, 以及考虑到存储成本, 不会计算信息熵.
然后根据统计结果将句子拆分成更小的片段, 将最后无法拆分的片段视作词汇.
这种方法无论是性能还是内存需求都要比正向词库生成好得多, 复杂度可以看作为
O(n).采用这种方法时, 由于无法事先算好高于2-gram的信息熵, 以及考虑到存储成本, 不会计算信息熵.
参考: https://blog.csdn.net/gzdmcaoyc/article/details/50001801
(actual_term_frequency / irrelevant_term_frequency) <= alpha或
actual_term_frequency <= irrelevant_term_frequency * alphaalpha的取值范围为大于等于1的整数, alpha越高词汇拆分得越深, 通常也意味着词库体积的缩小.
alpha的取值完全依赖于经验, 取1是完全可以的, 但由于没有计算信息熵, 拆分结果会出现一些名词与副词的组合, 需要进行过滤.
alpha的取值完全依赖于经验, 取1是完全可以的, 但由于没有计算信息熵, 拆分结果会出现一些名词与副词的组合, 需要进行过滤.
参考: https://spaces.ac.cn/archives/3913
通过观察生成的词库, 不难假设一个"完美"的词库能够符合幂律分布:
词频最高的词汇与词频一般的词汇有着巨大的差距, 位于长尾部分的低词频词汇的数量也理所当然的多.
词频最高的词汇与词频一般的词汇有着巨大的差距, 位于长尾部分的低词频词汇的数量也理所当然的多.
通过将词频去重后排序, 像找中位数那样寻找一个可以用于分隔基准值(例如百分比1%, 4%, 10%, 20%, 50%, 80%),
然后删掉词频小于此基准值的结果, 就可以生成一个有可用性的词库.
然后删掉词频小于此基准值的结果, 就可以生成一个有可用性的词库.
词频是该词在整个语料库里出现的次数, 由
该词语的出现次数/语料库里所有词语的出现次数 得出.在新词发现时, 用来衡量片段内部搭配的固定程度.
朴素无监督词库生成没有考虑到将词汇拼接在一起的情况.
显然如果一些词总是一前一后出现, 意味着这些词很可能本身就是一个词的不同部分.
例如, "电影"是一个词, "电影"又普遍与"院"一前一后出现, 那么有理由将"电影院"作为一个单独的词汇输出.
显然如果一些词总是一前一后出现, 意味着这些词很可能本身就是一个词的不同部分.
例如, "电影"是一个词, "电影"又普遍与"院"一前一后出现, 那么有理由将"电影院"作为一个单独的词汇输出.
如果两个一前一后出现的词汇本身毫无关系, 它们恰好以这种情况出现的概率应该是
该概率应该等同于用这两个词拼接出来的词的词频, 可得出
如果实际上长词的词频高于
通过为
P(先出现的词) * P(后出现的词).该概率应该等同于用这两个词拼接出来的词的词频, 可得出
P(合并后得到的长词) = P(先出现的词) * P(后出现的词).如果实际上长词的词频高于
P(先出现的词) * P(后出现的词), 则说明这个长词可能真实存在.通过为
长词词频 / P(先出现的词) * P(后出现的词) 设置一个阈值, 可以将这些长词过滤出来.用于新词发现时, 用来衡量片段外部左右搭配的丰富程度, 搭配越多, 信息熵越大.
可以通过设置一个阈值, 将此阈值之上的词汇视作单独词汇, 将此阈值之下的词汇视作其他词汇的一部分.
可以通过设置一个阈值, 将此阈值之上的词汇视作单独词汇, 将此阈值之下的词汇视作其他词汇的一部分.
由于词语左右两边都可能发生组合, 因此存在"左信息熵"和"右信息熵".
当需要判断一个词的信息熵时, 一般取较小的值作为信息熵.
当需要判断一个词的信息熵时, 一般取较小的值作为信息熵.
字节对编码是一种词汇表大小固定的数据压缩技术.
由于词汇表大小固定, BPE在深度学习NLP任务中被广泛用作分词器训练.
由于词汇表大小固定, BPE在深度学习NLP任务中被广泛用作分词器训练.
BPE可以在字符串熵工作, 也可以在字节序列上工作,
现代BPE实践通常在字节序列上工作, 以确保不会有字符是未知的.
现代BPE实践通常在字节序列上工作, 以确保不会有字符是未知的.
https://en.wikipedia.org/wiki/Entropy_(information_theory)
信息熵描述"听说新消息后, 关于相关事件的不确定性的减少量".
信息熵可以有效过滤掉重复/空洞的内容.
由于信息熵的计算不涉及语义分析, 不能用来判断内容的品质.
由于信息熵的计算不涉及语义分析, 不能用来判断内容的品质.
function getEntropy(text: string): number { const words = tokenize(text) const wordToCount = new Map<string, number>() for (const word of words) { wordToCount.set( word , (wordToCount.get(word) ?? 0) + 1 ) } let entropy = 0 for (const count of wordToCount.values()) { const wordFrequency = count / words.length entropy += -wordFrequency * Math.log2(wordFrequency) } return entropy}根据情况, 可以像拉丁字母那样直接使用逐汉字, 连续二汉字, 连续三汉字进行索引.
与专门的中文分词方法相比, term数量太大, 查询效率较低.
与专门的中文分词方法相比, term数量太大, 查询效率较低.
配合基于统计的方法, 则可以借由N-gram来生成词典, 从而减少term数量.
通过查找词典里已有的词进行切分.
词语越长, 匹配的优先级就越高.
与其他方法相比, 在速度上很有优势.
词语越长, 匹配的优先级就越高.
与其他方法相比, 在速度上很有优势.
- 基于词典规则的系统无法正确处理有二义性的短语.
- 无法处理未记载于词典里的新词.
实际整理词典时会发现, 中文环境中的新词几乎是源源不断地被创造出来, 想要通过人工完善词典是不可能的.
估计中文的词汇量超过40万个, 这对于查询来说也很困难, 为此必须使用专门的数据结构.
以下算法的性能瓶颈在于"判断词典是否有这个词", 因此有多种实现方式:
- Set: 牺牲时间换空间
- Map: 牺牲空间换时间
- 字典树(Trie): 仅在静态语言里可用(内存结构有优势)的推荐做法, 需要提前编译
- 前缀树(Prefix Trie): 在字典树的基础上上进一步加快速度, 编译时间更长.
- AC自动机(Aho-Corasick): 优于前缀树, 内部含有后缀树.
- BinTrie: 首字散列其余二分的字典树, 出自HanLP.
- 双数组字典树(DAT, Double Array Trie): 速度最快的结构, 实现难度大, 编译难度大.
DAT的经典实现是Darts(Double-ARray Trie System)
该实现通常会暴露一个类似commonPrefixSearch的方法, 用以进行前缀匹配,
用于切分时, 只需要创建一层循环, 将子字符串投入匹配:
用于切分时, 只需要创建一层循环, 将子字符串投入匹配:
for i in 0..text.len() { for (id, length) in dat.common_prefix_search(&text[i..]) { // ... }}- AC双数组字典树(ACDAT, Aho Corasick Double Array Trie):
由HanLP作者提出的概念, 融合了AC自动机和DAT, 其性能表现不如DAT, 作者认为是由汉语的特殊性造成的.
朴素完全切分是最简单的完全切分(找出文本中的所有单词)算法.
其具体做法是创建两层嵌套循环, 第一层作为起点索引, 第二层作为终点索引,
遍历被查找字符串中的所有子文本, 输出词典中的所有匹配项.
遍历被查找字符串中的所有子文本, 输出词典中的所有匹配项.
由于复杂度是
O(n^2), 性能最差, 稍微长一点的文本就会造成性能问题.完全切分方式之一, 通过AC自动机找到所有匹配项.
完全切分方式之一, 通过ACDAT找到所有匹配项.
性能最高的完全切分方式, 通过DAT找到所有匹配项.
在分词时正向遍历匹配最长的词, 每当成功匹配到一个最长词后, 从这个词之后的字符继续匹配, 直到结束.
正向最长匹配的逆向版本, 在分词时逆向遍历匹配最长的词.
在实践中效果比正向最长匹配好很多, 但仍然不完美.
在实践中效果比正向最长匹配好很多, 但仍然不完美.
为解决正向, 逆向最长匹配的问题而出现的一种融合了正向逆向两种方法的匹配规则:
同时进行正向和逆向匹配.
如果结果的词数量不同, 则取数量更少的那个作为结果.
如果结果的词数量相同, 则取字符更少的作为结果.
如果字符数量也相同, 返回逆向最长匹配的结果.
同时进行正向和逆向匹配.
如果结果的词数量不同, 则取数量更少的那个作为结果.
如果结果的词数量相同, 则取字符更少的作为结果.
如果字符数量也相同, 返回逆向最长匹配的结果.
在实践中效果和逆向最长匹配差不多, 有时甚至会比逆向最长匹配差.
https://github.com/NLP-LOVE/Introduction-NLP
https://github.com/NLP-LOVE/ML-NLP
https://github.com/NLP-LOVE/ML-NLP
在深度学习的情况下, 训练模型不需要预先分词,
不分词(逐字符)的训练结果比分词的要好.
不分词(逐字符)的训练结果比分词的要好.
由Google于2019年发布的NLP模型.
它直接对中文文本内容进行基于字符的tokenize.
它直接对中文文本内容进行基于字符的tokenize.
句子中出现了不存在于词典中的词.
能够从文本里发现词与词的关系, 从而发现新词的模型.
最流行的方案是将单字分为Begin(B), Middle(M), End(E), Single(S)四种类型.
根据词性将词分类.
- 汉语中一个单词具有多个词性是很常见的
- 依赖人力进行标注, 可用的语料库很少
- 学界对于标注规范存在分歧
一种被广泛使用的排名函数.
给定一个查询和一个文档, BM25可得出查询与文档之间的相关性.
给定一个查询和一个文档, BM25可得出查询与文档之间的相关性.
语料库是结构化的大量文本资料, 用于NLP研究.
维基百科上搜集的语料库列表:
https://zh.m.wikipedia.org/zh-hans/%E8%AF%AD%E6%96%99%E5%BA%93
https://en.wikipedia.org/wiki/List_of_text_corpora
https://zh.m.wikipedia.org/zh-hans/%E8%AF%AD%E6%96%99%E5%BA%93
https://en.wikipedia.org/wiki/List_of_text_corpora
维基百科提供各语言的百科数据库下载, 数据量大(仅中文就有超过400万个页面), 内容重复性低, 很容易相信它是一个很好的语料来源.
维基百科的内容使用了Wikitext标记语言, 存在大量自定义的模板, 在提取文本时需要过滤掉它们.
维基百科的内容使用了Wikitext标记语言, 存在大量自定义的模板, 在提取文本时需要过滤掉它们.
中文: https://dumps.wikimedia.org/zhwiki/
英文: https://dumps.wikimedia.org/enwiki/
英文: https://dumps.wikimedia.org/enwiki/
维基百科的数据量相当大, 因此转储使用bz2算法压缩.
bz2压缩能够将XML文件压缩至1/5, 但由于压缩的原因, 必须解压整个bz2文件才能读取信息.
bz2压缩能够将XML文件压缩至1/5, 但由于压缩的原因, 必须解压整个bz2文件才能读取信息.
multistream bz2解决了必须整个解压bz2文件才能读取信息的问题, 通过将未压缩的数据拆分为块的方式实现分块压缩, 最后得到一个分块压缩的bz2文件.
通过只解压相应的块, 就可以在不解压整个bz2文件的情况下获取数据.
通过只解压相应的块, 就可以在不解压整个bz2文件的情况下获取数据.
为了索引页面, 维基百科的转储提供一个用
索引文件的格式为:
: 作为分隔符的index文件存储内容的偏移值以起到索引的效果.索引文件的格式为:
块的起始字节:文件的ID:文章标题, 由于分块压缩, 会有多个文章使用相同的"块的起始字节".参考: https://alchemy.pub/wikipedia
https://github.com/attardi/wikiextractor
用于从维基转储里提取文本内容的CLI程序.
https://github.com/attardi/wikiextractor/issues/287
https://conferences.unite.un.org/UNCorpus/Home/Index/zh
多语言对齐语料库, 适用于机器翻译.
https://files.pushshift.io/reddit/submissions/
词典的最佳来源是权威词典, 例如各种人工编撰出版的词典;
其次是学术结果, 例如知识图谱;
然后才是维基百科之类的众包内容源, 以及输入法词库这样的经过实战的内容源;
而互联网上由一般用户分发的词典基本上是不可用的垃圾, 绝对不应使用.
其次是学术结果, 例如知识图谱;
然后才是维基百科之类的众包内容源, 以及输入法词库这样的经过实战的内容源;
而互联网上由一般用户分发的词典基本上是不可用的垃圾, 绝对不应使用.
Unix系统的
/usr/share/dict 目录包含系统上安装的词典, 此处的词典会被一些需要词典的程序使用.Debian系发行版通过wordlist包提供词典.
https://freedict.org/
内容距离上次更新久远.
提供的语言到语言到词典, 数据需要从TEI格式里手动分离.
提供的语言到语言到词典, 数据需要从TEI格式里手动分离.
http://wordlist.aspell.net/
可供Hunspell和Aspell使用的词典.
没有提供中文词典.
没有提供中文词典.
北京大学语言计算与机器学习研究组推出的中文分词工具包.
准确率高于jieba和THULAC.
准确率高于jieba和THULAC.
性能比jieba差, 内存占用量也高于jieba.
清华大学自然语言处理与社会人文计算实验室推出的中文分词工具包.
准确率高于jieba.
准确率高于jieba.
据称jieba的词性标注参照了 ICTCLAS 汉语词性标注集.
jieba项目已知存在诸多问题:
- 技术陈旧.
- 算法存在缺陷.
- 自带词典的词性标注错误百出.
- 词典收录了一些莫明其妙的词汇.
- 词典大小不匹配: idf词典的内容少于主词典.
- 用户词典优先级太高, 会覆盖掉默认词典里相同的词, 这导致一些原本有词性的词丢失词性标注.
- 分词功能不支持自定义停用词.
- API缺乏实用性:
- 无法在不分词的前提下查询词典里的项目, 例如查找单个词汇的td-idf值.
- cutForSearch(在不使用HMM的情况下)和cutAll会把单词切分为字母, 简直无法理解.
- cut系列的API太高级, 以至于无法生成适合连续短语搜索的分词结果.
- 尽管jieba的各种实现速度都比较快, 但准确性往往大幅弱于其他解决方案.
- cppjieba的实现没有足够的内存优化, 载入字典后会占用大量内存.
jieba自带的词典长期没有更新, 最旧的词典已经8年没有改动了.
dict: 主词典, 带权重和词性标签.
hmmDict: 隐式马尔科夫模型, 建议使用默认词典.
userDict: 用户词典, 建议自己根据需要定制.
idfDict: 关键词抽取所需的idf信息.
stopWordDict: 关键词抽取所需的停用词列表.
hmmDict: 隐式马尔科夫模型, 建议使用默认词典.
userDict: 用户词典, 建议自己根据需要定制.
idfDict: 关键词抽取所需的idf信息.
stopWordDict: 关键词抽取所需的停用词列表.
词典需是UTF-8编码的文件, 格式如下:
{词语} {词频(可省略)} {词性(可省略}}示例:
创新办 3 i云计算 5凱特琳 nz停用词词典只适用于TFIDF和TextRank, 不可用于分词.
停用词词典没有外部接口, 需要修改源代码才能实现.
停用词词典没有外部接口, 需要修改源代码才能实现.
专门开发了更有效率的DAT实现cedarwood以提升性能,
但其性能(v0.4.5)已经远慢于其他实现:
但其性能(v0.4.5)已经远慢于其他实现:
- yada(v0.5.0, 性能为cedarwood的2~3倍)
- daachorse(v0.4.3, 性能为cedarwood的4~5倍)
该实现的分词存在一些问题, 无法分词
https://github.com/messense/jieba-rs/issues/83
test-1 这样的文本:https://github.com/messense/jieba-rs/issues/83
停用词词典只适用于KeyExtractor和TextRankExtractor, 不可用于分词.
使用最新NLP技术的NLP项目.
由Java编写, 使用经典NLP技术实现.
由Python编写, 使用深度学习技术实现(即使用Python的原因).
载入模型的速度相当慢, 只支持按句子分词.
对于开放和封闭两个模型来说, 一条简单的句子在CPU下需要几十毫秒乃至数百毫秒才能完成分词,
实在难以作为一个分词组件使用.
对于开放和封闭两个模型来说, 一条简单的句子在CPU下需要几十毫秒乃至数百毫秒才能完成分词,
实在难以作为一个分词组件使用.
Python主要的NLP工具库之一, 包含大量可用的功能.
被认为适合研究人员.
被认为适合研究人员.
官方提供多种语料库的下载.
Python主要的NLP工具库之一, 只提供最好的方法.
自称为工业级, 一开始就以投入生产为目的开发, 并且有较好的准确度.
自称为工业级, 一开始就以投入生产为目的开发, 并且有较好的准确度.
在官方的速度比较中, 性能远优于Stanza, Flair, UDPipe.
中文分词时底层使用逐字符分割, jieba, pkuseg等.
官方提供多种语言的模型的下载.
Java主要的NLP工具库.
深度学习NLP工具库.
Hugging Face旗下的Rust分词库, 基于NLP模型.
https://www.hankcs.com/nlp/corpus/several-revenue-segmentation-system-used-set-of-source-tagging.html
从一个文档中找到代表其核心内容的关键词.
应用最广泛的关键词提取方法.
TF-IDF是词频和逆向文档频率的乘积, 旨在定义文档中词语(或短语)的重要性.
TF-IDF的值与词语在文档中出现的次数成正比, 与在整个语料库里出现的次数成反比.
在整个语料库里常见的词语(主要是日常用语)会被降权, 在少数文档中被反复提及的词语(例如专用术语)会被提权.
在整个语料库里常见的词语(主要是日常用语)会被降权, 在少数文档中被反复提及的词语(例如专用术语)会被提权.
TF-IDF = 词频 * 逆向文档频率词频 = 该文档中词语出现的次数 / 该文档的总词数 对于不同的文档, TF的值会改变.
在实际使用中, 很可能会想要把词频向下缩放, 以避免因为它的值太大而对TF-IDF产生影响:
注: log参数加1是为了让结果非负.
词频 = log10(1 + 该文档中词语出现的次数 / 该文档的总词数)注: log参数加1是为了让结果非负.
参考: https://jmotif.github.io/sax-vsm_site/morea/algorithm/TFIDF.html
逆向文档频率 = 文档总数 / (出现该词语的文档数量 + 1) 注: 分母加1是为了防止除零错误.
一个词汇的IDF值越高, 越说明这个词很少被使用.
对于相同的语料库, IDF的值是不变的.
对于相同的语料库, IDF的值是不变的.
实践中, 会将分子加1, 保证逆向文档频率的比值在任何情况下都大于等于1:
逆向文档频率 = (文档总数 + 1) / (出现该词语的文档数量 + 1) 还可能会想要把逆向文档频率向下缩放, 以避免因为它的值太大而对TF-IDF产生影响:
由于log的参数总是大于等于1, 不需要额外加1以保证结果非负.
该式子下, IDF将大于等于0.
逆向文档频率 = log10((文档总数 + 1) / (出现该词语的文档数量 + 1)) 由于log的参数总是大于等于1, 不需要额外加1以保证结果非负.
该式子下, IDF将大于等于0.
在IDF为0的极端情况下(文档总数等于出现该词语的文档数量), TF的作用会被完全消除.
因此, 还会在结果上加1使其总是大于等于1:
因此, 还会在结果上加1使其总是大于等于1:
逆向文档频率 = log10((文档总数 + 1) / (出现该词语的文档数量 + 1)) + 1 - 需要大型的语料库来得出IDF值.
- 用于提取关键词时, 罕见词语是重要词语的假设可能是错误的.
- 因为停用词的词频可能非常高, 导致TF-IDF也很高, 因此需要准备一份用于过滤的停用词表.
- 忽略了语序的意义.
提取文本中高频出现的词汇作为关键词(需要过滤掉停用词).
高频词不等于关键词, 因此通常会使用TF-IDF.
派生自PageRank的无监督关键词和句子提取.
用于关键词提取时, 需要词性标注, 仅保留名词和形容词.
参考实现: https://github.com/davidadamojr/TextRank
出自2010年的论文Automatic Keyword Extraction from Individual Documents.
计算效率和精确率高于TextRank, 召回率与TextRank相当.
步骤(用于英文文本):
- 1.根据标点符号将文本切割成句子.
- 2.根据停用词表拆分句子, 得到的短语即为候选关键词.
RAKE因此 可以提取出相当长的短语作为关键词, 这是该算法的特点之一.
对于停用词表容易收集的语言(例如英语)好用, 难以收集的语言(例如中文)则难以使用. - 3.为候选关键词中的每个单词计算统计特征:
- 词频 (Word Frequency):
该单词在所有候选短语中出现的总次数(对于短语中该单词重复出现的情况, 次数也相加). - 词度 (Word Degress):
出现该单词的候选短语的单词数的总和. - 词分数(Word Score):
词分数 = 词度 / 词频
- 5.计算候选短语的分数:
短语中单词的词分数的总和(对于短语中存在重复单词的情况, 分数也相加). - 6.取分数排名前1/3的候选短语作为结果.
参考实现: https://github.com/aneesha/RAKE
同类方法中最先进的, 使用的启发式更多.
建立的统计特征更多, 例如会考虑单词的大小写.
使用字符串相似度算法和词干提取, 从而能将相近的变体视作同一词汇.
使用字符串相似度算法和词干提取, 从而能将相近的变体视作同一词汇.
原理极致简单粗暴:
利用SBERT模型生成文档嵌入和词嵌入, 比较词嵌入和文档嵌入的余弦相似度.
将相似度排名靠前的词作为结果.
利用SBERT模型生成文档嵌入和词嵌入, 比较词嵌入和文档嵌入的余弦相似度.
将相似度排名靠前的词作为结果.
和BERTopic的作者为同一人.
官方推荐的英语模型为
多语言模型为
all-MiniLM-L6-v2,多语言模型为
paraphrase-multilingual-MiniLM-L12-v2.KeyBERT建立在这个假设之上:
与文档本身语义相似度最高的词就是文档的关键词.
与文档本身语义相似度最高的词就是文档的关键词.
KeyBERT的假设与SBERT模型的训练目的是有偏差的, 因为SBERT主要是在句子上训练的.
直接生成词嵌入的做法丢失了词的上下文信息, 进而导致余弦相似度计算出现问题.
直接生成词嵌入的做法丢失了词的上下文信息, 进而导致余弦相似度计算出现问题.
为了能够解决给词嵌入提供上下文, 建议采用滑动窗口的形式生成词嵌入(也就是N-gram),
因此产生的关键词往往是2~3个term的短语, 而非单一term.
因此产生的关键词往往是2~3个term的短语, 而非单一term.
BERT模型的上下文长度普遍为512个token, 现实中的文档很容易超出这个长度.
有少量模型支持更大的上下文支持, 但也意味着需要更多的内存/显存空间.
此外, 超长的文档总是存在, 不可能总是依赖模型具有更大的上下文支持.
有少量模型支持更大的上下文支持, 但也意味着需要更多的内存/显存空间.
此外, 超长的文档总是存在, 不可能总是依赖模型具有更大的上下文支持.
在实践中, 对于超出上下文长度的文本,
要么截断(假设前512个token代表了文档的主要内容),
要么分块嵌入后进行均值池化(mean pooling, 其实就是求平均).
要么截断(假设前512个token代表了文档的主要内容),
要么分块嵌入后进行均值池化(mean pooling, 其实就是求平均).
对于KeyBERT的应用场景(提取关键词), 还有一种折中方案:
分块嵌入, 然后直接在每个分块的嵌入上提取关键词.
汇总这些关键词, 统计关键词在整个文档上的词频, 取词频最高的关键词作为结果.
该方案的问题在于引入了启发式方法, 即认为词频最高的关键词更具有代表性.
这有可能因为在分块上提取出语义接近停用词的关键词, 导致该词意外成为最终结果.
分块嵌入, 然后直接在每个分块的嵌入上提取关键词.
汇总这些关键词, 统计关键词在整个文档上的词频, 取词频最高的关键词作为结果.
该方案的问题在于引入了启发式方法, 即认为词频最高的关键词更具有代表性.
这有可能因为在分块上提取出语义接近停用词的关键词, 导致该词意外成为最终结果.
- SBERT模型在训练时对均值优化进行过微调.
- 多维空间中的噪声实际上会在池化时因为方向不同而相互抵消(向原点靠近),
而信号则因为方向相同而在池化时被保留.
嵌入模型可能不适应目标领域, 这一问题在嵌入模型中挺常见.
微调嵌入模型需要大量语料作为训练集, 因此情况可能会变得相当棘手:
- 对于新出现的概念, 很可能没有足够的语料用来学习.
- 对于嵌入模型的分词器无法切成一个词的情况, 将需要更多数据量才能学习.
目前业界解决数据集不足问题的主要方法是通过LLM大量合成数据,
而这本身在一定程度上依赖于LLM具有相关能力.
而这本身在一定程度上依赖于LLM具有相关能力.
一项无监督文本挖掘任务:
将大量文档按照主题进行聚类, 并获得每个类的主题.
"主题"指的是由词(term)构成的元组.
将大量文档按照主题进行聚类, 并获得每个类的主题.
"主题"指的是由词(term)构成的元组.
主题建模的代表性算法.
类似于K-Means这样的聚类算法, LDA需要预先设定主题数量.
在处理短文本时表现不佳.
参考: https://en.wikipedia.org/wiki/Latent_Dirichlet_allocation
一种针对短文本设计的主题模型.
论文中提到, BTM在常规文本上的表现也能优于LDA.
论文中提到, BTM在常规文本上的表现也能优于LDA.
和KeyBERT作者为同一人.
使用HDBSCAN, 因此无需预先设定主题数量.
使用HDBSCAN, 因此无需预先设定主题数量.
步骤:
- 1.通过SBERT或其他嵌入模型生成文档嵌入.
- 2.用UMAP对嵌入进行降维.
这一步是因为高维向量受维数灾难影响, 聚类效果很差. - 3.通过HDBSCAN对降维后的嵌入进行聚类, 获得多个文档簇.
- 4.通过c-TF-IDF对每个文档簇进行主题词提取.
这一步骤是为了提供可解释性, 因为聚类获得的文档簇没有人类可读的标签.
BERTopic中的每一个组件都可以被替换成其他具有相同功能的组件.
例如, 如果计算资源足够多, 那么最后一个步骤也可以改成由LLM完成.
例如, 如果计算资源足够多, 那么最后一个步骤也可以改成由LLM完成.
参考: https://maartengr.github.io/BERTopic/algorithm/algorithm.html
该方法假设每篇文档仅包含一个主题.
理论上, 单一文档包含的主题越多, 效果就越差.
理论上, 单一文档包含的主题越多, 效果就越差.
c-TF-IDF是针对此任务需求而改进的TF-IDF算法.
c-TF-IDF = c-TF * IDF计算c-TF:
- 1.把一个文档簇中的文档拼接成一个大文档后, 计算出每个词的词频.
- 2.对每个词的词频执行L1标准化, 即词频除以"所有词的词频的绝对值的总和".
计算IDF:
log(1 + 每个文档簇的平均词数 / 该词在所有文档簇中的词频)动态主题任务允许你观察一个主题在不同时间下关键词的演变.
以"手机"为例:
- 在2000年代, "手机"的关键词可能是"诺基亚", "彩铃", "短信", "GPRS".
- 在2020年代, "手机"的关键词可能是"AI", "5G", "折叠屏", "高刷新率".
在BERTopic里进行动态主题建模的步骤:
- 1.对所有时间下的所有文档聚类,
利用c-TF-IDF找出概括性最强的主题, 例如"手机". - 2.把要探索的主题下的文档按照时间分桶.
- 3.利用c-TF-IDF, 对一个桶提取主题词.
参考: https://maartengr.github.io/BERTopic/getting_started/topicsovertime/topicsovertime.html
与KeyBERT面临的问题相同.
通过训练模型的嵌入矩阵, 同时达到极快的计算效率和主题建模质量.
需要预先设定主题数量.