ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于互信息与左右熵的新词发现:Atlas开源工具实战解析

基于互信息与左右熵的新词发现:Atlas开源工具实战解析 1. 从一个跑不动的分词任务说起我最初接触Atlas是因为在做一个大规模行业语料清洗和新词发现的需求。传统做法是先把语料跑一遍分词再用词频统计筛出候选但问题很快暴露出来通用分词器对垂直领域的专业术语、新造词、组合词几乎无能为力而人工维护词典的成本又高得离谱——今天收录的词明天可能就过气了而且各个业务线的语料风格差异巨大一套词表根本没法通吃。这个场景下我需要的不是一个“词表”而是一套能从语料里自动把新的、稳定的、有语义边界的语言单元挖出来的方案。当时调研了一圈业界常用做法无非是N-gram频次统计加规则过滤或者用互信息和左右熵这类统计量做候选筛选。Atlas就是后一种思路的工程化实现它不依赖预置词典不要求标注语料只需要一堆原始文本就能通过统计手段自动发现新词。这套设计非常契合我的需求——尤其适合垂直领域术语挖掘、舆情关键词提取、搜索词补全、还有文本数据的探索性分析这类场景。需要说明的是Atlas并不是一个Web服务也不是一个带界面的工具它是一套开源的C命令行程序核心能力是“从大规模语料中提取新词”主要面向有基本工程能力的开发者、算法工程师和数据从业者。如果你手上正好有一批文本想知道里面有哪些反复出现的规律性片段而且不想手动翻数据那这篇文章应该能帮你省下不少时间。2. Atlas的核心思路为什么是“互信息左右熵”这套组合2.1 两个统计量的直觉理解Atlas的底层原理并不复杂它同时使用了两个统计量来衡量一个候选片段是否是“词”互信息PMI和左右熵Entropy。互信息衡量的是片段内部字与字之间的绑定强度。打个比方如果“人”和“工”经常一起出现而它们各自单独出现的频率也都很高那它们凑在一起到底是巧合还是真形成了一个固定组合互信息就是回答这个问题的它比较“一起出现的概率”和“各自单独出现的期望概率”。如果一起出现的概率远大于随机拼凑的期望互信息值就高说明这两个字确实有很强的内部黏合性。左右熵衡量的是片段外部的自由程度。一个真正的词应该能出现在各种不同的上下文里。比如“人工智能”后面可以接“发展”“技术”“领域”“研究”前面也可以有不同的修饰词这说明它的左右边界很自由是一个稳定的独立单元。反之如果某个片段只固定出现在同一个句子里比如只在“根据气象部门预测”中出现那它的左右熵就会很低大概率只是一个固定搭配的碎片不是真正的新词。这两个指标一个管“内部黏合”一个管“外部自由”组合起来就能比较好地判断一个N-gram片段到底是不是一个独立的语言单元。这也是Atlas区别于单纯词频统计的关键它不只看一个片段出现多少次更看它出现的方式是否符合“词”的特征。2.2 这套组合为什么适合大规模语料我个人的体会是互信息和左右熵这套组合特别适合“数据量大、没有标注、领域未知”的语料场景。原因有三点。第一它是无监督的。不需要任何人工标注不需要词典甚至不需要你知道语料里大概有哪些领域直接扔文本进去就行。这意味着它可以快速迁移到新的业务线不用每个项目都重新标注一遍数据。第二它的计算模式是高度可并行的。Atlas底层用C实现数据处理流程设计成分块处理在多核机器上能直接用满CPU资源。我实测在32核的服务器上处理几GB的语料也就几十分钟跑完这在大规模文本处理场景下是完全能接受的。第三它的输出是可控的。通过调节频次下限、互信息阈值、熵阈值等参数可以控制新词挖掘的“保守程度”——想要高精度就调高阈值想要高召回就调低阈值。这种灵活性在实际工程里非常重要因为不同业务对新词的精度和召回要求完全不一样。3. 环境准备与编译从源码到可执行文件3.1 依赖只有两项Atlas的依赖非常轻官方文档里写明只需要g支持C11和CMake2.8以上版本。我在Ubuntu 16.04和CentOS 7上都编译过表现都很稳定。如果你用的是macOS或者Windows的WSL环境理论上也没问题但官方主要在Linux上做的测试所以强烈建议在Linux环境下使用能少踩很多坑。3.2 编译步骤实录整个编译过程就三步简单到没什么可说的git clone https://github.com/Tencent/atlas.git cd atlas ./build.shbuild.sh脚本会自动创建build目录、调用cmake生成Makefile、然后执行make编译。编译完成后可执行文件在build/atlas。我第一次编译的时候遇到一个小问题服务器上g版本比较老只有4.8结果编译到一半报了一堆C11语法相关的错误。后来把g升级到5.4以上就顺利过了。所以如果你也遇到编译失败优先检查编译器版本不要急着改代码。3.3 确认安装成功编译完成后先跑一下帮助命令确认安装正常./build/atlas --help如果能看到train、segment等子命令的帮助信息说明编译成功。Atlas有两个核心子命令train用于训练新词模型也就是新词挖掘segment用于基于训练好的词典做分词。这个分词能力算是附赠的——你可以先用train从语料里挖出新词再把新词补充进词典然后用segment做后续的文本切分。4. train命令参数详解每个参数背后的逻辑4.1 参数清单与推荐值Atlas的train命令参数不算多但每个都很关键。我按自己的使用习惯整理了一张表标了推荐值和使用场景方便你直接参考。参数含义推荐值说明train_file输入语料文件路径必填每行一句/一段UTF-8编码model_dir输出模型目录必填训练结果输出到该目录min_freq候选词最小频次5~10低于该频次的N-gram直接丢弃控制候选集规模max_word_len候选词最大长度5~6超过该长度的片段不考察min_pmi互信息最低阈值20~30控制内部黏合强度要求min_entropy左右熵最低阈值1.0~2.0控制上下文自由度要求max_freq候选词最大频次1000000过滤掉“的”“了”等超高频泛化词train_mode训练模式11表示新词发现2表示模型增量训练thread_num并行线程数CPU核数多核加速建议拉满dic_path基础词典路径可选传入基础词典用于过滤已有词4.2 这些参数到底怎么影响结果很多人第一次跑Atlas喜欢把所有参数都用默认值然后看结果觉得“这挖的什么玩意儿”。其实参数不调好结果确实会很糙。我逐一讲下我踩过的坑。min_freq这个参数决定了候选词的“准入门槛”。如果设得太低比如1~2候选集会爆炸性膨胀大量随机拼凑的二字组合会混进来导致后续排序和过滤的负担很重。设得太高又会漏掉一些低频但很有价值的术语——尤其是长尾领域、小样本场景下的专业词。我一般先设5跑一遍看看结果再根据输出质量和数量做调整。min_pmi是个相对值不同语料下合适的阈值差异很大。新闻类语料词汇丰富、搭配多样PMI普遍偏低阈值太高什么都挖不出来垂直领域语料比如医学、法律术语重复度高PMI普遍偏高阈值可以适当提高来过滤噪声。我建议第一次跑的时候先设一个较低的阈值比如10看输出词表里噪声的比例再逐步上调。这个“先粗后细”的调参思路比一上来就赌一个高阈值要高效得多。min_entropy阈值我平时设在1.0~1.5之间。左右熵低于1.0的片段基本就是固定搭配比如“根据”“通过”“为了”这类。这些词虽然也算“词”但通常不是你想挖的新词。如果你特别想过滤掉这类虚词可以把min_entropy提到2.0左右但要注意有些专有名词的上下文也比较固定比如人名、地名熵值天然偏低阈值太高会把它们一起过滤掉。max_word_len默认值是5但我建议在中文语料上设到6或者7。四字成语、五字术语、六字机构名都很常见只设到5会漏掉不少有意义的组合。当然长度设得越长候选集规模越大计算时间也会增加需要平衡一下。4.3 一个完整的train命令示例假设我们有一份新闻语料文件news_corpus.txt每行一条新闻UTF-8编码。我的常用命令是这样的./build/atlas train \ --train_filenews_corpus.txt \ --model_dir./news_model \ --min_freq5 \ --max_word_len6 \ --min_pmi15 \ --min_entropy1.2 \ --thread_num16 \ --train_mode1跑完后模型目录里会生成词表文件和模型文件。词表文件里每一行是一个候选词及其统计信息包含词本身、频次、左右熵、互信息等字段。5. 数据预处理这一步做不好后面全是白费5.1 语料清洗是真正的第一步Atlas本身不做语料清洗你喂给它什么它就分析什么。如果语料里满是HTML标签、URL、特殊符号、乱码字符这些噪声会直接影响N-gram的统计结果——想象一下“nbsp”这种字符串如果频繁出现在语料里它也会被当成一个候选词挖出来。所以数据预处理的质量直接决定了新词挖掘的下限。我的清洗流程一般是先用正则去掉HTML标签和URL再统一标点符号为全角或半角然后过滤掉包含异常字符的行最后做一次简单的去重。需要注意的是不要过度清洗——比如把所有的数字都删掉可能会导致一些包含数字的术语比如“3D打印”“5G通信”完全无法被发现。5.2 是否要做分词预处理Atlas官方推荐的是“不预先分词”直接使用原始文本作为train_file输入。它的设计逻辑是新词挖掘就是为了发现分词器不认识的新词如果先用分词器切一遍新词已经被切碎了互信息和左右熵的计算就失去了意义。所以直接用原始文本不要做任何分词处理。但有一个例外如果语料本身是拼接出来的比如把标题和正文拼在一起你需要确保拼接处有明确的边界标记比如空格或换行。否则标题的最后一个字和正文的第一个字跨边界组成一个虚假的“词”会干扰统计结果。5.3 语料规模的底线Atlas对语料规模有一个比较尴尬的底线要求——太少了跑不出效果。如果你的语料只有几百KB挖掘出来的“新词”大概率都是噪声统计显著性完全不够。我个人的经验是至少准备50MB以上的干净文本大约几万篇新闻结果才会有参考价值。如果语料本身很小那更加建议降低min_freq和min_pmi用“低门槛人工审核”的方式来补救。6. 实操过程从原始语料到最终词表的完整流程6.1 流程概览我给自己定了一套固定的操作流程每次跑Atlas都照这个来基本不会出大问题。整理成步骤来看语料采集与清洗确认文本编码为UTF-8去除明显噪声按行分割。初步训练用比较宽松的参数跑一遍train拿到基础词表。结果粗筛用词频和简单规则快速过滤明显噪声比如纯数字、纯符号、单字词。参数迭代根据粗筛结果调整阈值重新训练。人工抽检随机抽取200~500个新词人工判断准确率。词表固化把通过审核的词补充进业务词典用于后续分词或检索。6.2 关键步骤的实际操作训练完成后我习惯先用命令行快速看一眼词表规模和头部词wc -l news_model/word_dict.txt head -50 news_model/word_dict.txt词表文件每一行的格式大致是“词\t频次\t左熵\t右熵\t互信息”。头部词通常是最频繁出现的一批词你可以快速判断这些词是不是符合预期——如果全是“我们”“可以”“什么”这类通用词说明min_freq设太低或者需要调整min_entropy如果已经出现了不少领域术语说明参数方向基本正确。接下来我会用一个简单的Python脚本做自动过滤把包含数字字母以外的杂字符、或者明显是HTML实体的词删掉再按频次排序导出成可读性更高的词表import codecs with codecs.open(news_model/word_dict.txt, r, utf-8) as fin, \ codecs.open(filtered_words.txt, w, utf-8) as fout: for line in fin: parts line.strip().split(\t) if len(parts) 5: continue word, freq, left_entropy, right_entropy, pmi parts[0], int(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) # 过滤纯符号、纯数字 if not any(\u4e00 ch \u9fff for ch in word): continue if len(word) 2: continue # 按业务需求进一步过滤 fout.write(f{word}\t{freq}\t{pmi}\t{left_entropy}\t{right_entropy}\n)这个脚本很简单但它帮我挡掉了大量明显没用的候选让后续人工抽检的负担小了很多。6.3 效果评估的标准很多人在“新词挖掘”这件事上最困惑的是我怎么知道挖掘结果好不好我的评估标准很简单——抽500个词人工标注是否为“有意义的新词”然后算准确率。这里的“有意义”根据业务定义不同而有差异做搜索词补全可能更看重词语的完整性和搜索意图做舆情分析可能更看重领域术语的覆盖率做分词词典扩充则要求词本身有明确的语义边界。如果准确率低于50%说明参数太宽松需要调高min_pmi或min_entropy如果在80%以上说明参数基本合适可以进一步微调如果准确率很高但召回明显不足遗漏了大量明显的新词则需要考虑降低min_freq或适当扩大max_word_len。7. 常见问题与排查技巧实录7.1 编译报错Atlas最常见的编译错误就是C11标准不支持。如果你用的是老旧的g 4.8编译时会报出一堆“xx is not a member of std”之类的错误。解决方案是升级g或者安装高版本的build-essential工具链。另外如果系统里同时存在多个gcc版本可能需要手动指定编译器版本./build.sh \ CC/usr/bin/gcc-5 \ CXX/usr/bin/g-57.2 训练结果几乎全是噪声如果你发现挖出来的“新词”全是“的了我们在有”这类词大概率是min_freq设置得太低。频次小于5的N-gram组合统计上根本不具备显著性尤其在小语料上更是如此。先把min_freq调到10以上再把min_pmi调到20以上结果通常会有质的改善。7.3 一个领域词都挖不出来和上面相反如果语料里明显有很多专业术语但一个都没挖出来那多半是阈值设太高了。先检查min_pmi垂直领域语料中术语的PMI一般很高但有些简写和组合词PMI并不高可以先把min_pmi降到5~10看输出结果再做判断。另外检查一下max_word_len是不是太短很多术语是4个字以上的。7.4 性能瓶颈Atlas默认是单线程的吗不是但它也不会自动跑满所有核。你需要显式设置thread_num参数。我习惯设为CPU核数跑起来之后CPU利用率基本可以拉满。如果你有32核thread_num32几GB的语料大概几十分钟跑完。这里有个小技巧如果语料实在太大、内存吃紧可以先按行随机采样一部分语料做参数探索确定好阈值后再全量跑能节省不少时间。7.5 常见问题速查表现象可能原因解决方案编译失败C11报错g版本过低升级到g 5.4以上全是通用虚词min_freq太低或min_entropy太低提高min_freq到10min_entropy到1.5什么都挖不出来阈值过高或语料太小降低min_pmi/min_entropy增加语料量训练太慢单线程跑显式设置thread_num为CPU核数结果里有交叉重复片段没有用基础词典过滤传入dic_path把已知词过滤掉含大量英文/数字串语料清洗不足预处理阶段做文本规范化8. 模型的增量训练与分词语料产出8.1 增量训练的使用场景Atlas还支持增量训练模式train_mode2这个功能一开始我没太在意后来在业务迭代时发现它非常有用。场景是这样的第一次用一季度的语料训练出一个新词模型到了季度末积累了新的语料如果从零开始重新训练每次都要全量跑一遍耗时耗资源。用增量训练模式可以基于已有的模型只对新增语料做学习把新出现的高频词补充进模型里。增量训练的命令和train类似关键是train_mode设为2并指定已有的model_dir作为初始模型./build/atlas train \ --train_filenew_corpus.txt \ --model_dir./existing_model \ --train_mode2 \ --min_freq5 \ --min_pmi15 \ --min_entropy1.2 \ --thread_num16增量训练模式下模型会保留原有词表同时把新增语料中挖掘出的新词合并进来。这个机制在实际业务中非常实用尤其是语料按周、按月持续产出的场景不需要每次都全量重跑。8.2 用segment子命令做分词扩展训练好的词表除了用于新词盘点还可以直接用于分词。Atlas的segment子命令加载训练好的模型对新的文本进行分词——它的分词结果会优先使用训练阶段学到的词因此对垂直领域语料的效果往往比通用分词器更好。./build/atlas segment \ --model_dir./news_model \ --inputtest.txt \ --outputseg_result.txt这个功能很适合做前置处理先用少量语料训练一个领域词表然后把这个模型当作一个定制分词器对全量语料进行切分。比起通用分词器自定义词典的方案Atlas的方案更省心——因为它不需要你手动维护一个庞大的领域词典模型自己会从语料中不断学习。9. 我的经验总结与调参实战建议这套工具我用了很长一段时间踩过的坑不少但沉淀下来的方法论也越来越清晰。最核心的一条经验是先粗后细两轮训练定参数。第一轮用宽松参数min_freq3min_pmi5min_entropy0.8跑一遍目标是看全貌——知道语料里大概有哪些潜在的词候选集大一点没关系毕竟只是摸底。第二轮根据第一轮的输出质量收紧参数比如min_freq8min_pmi20min_entropy1.5目标是跑出真正高质量的新词表。这个方法比一上来就猜最优参数要高效得多因为不同语料的统计特征差异太大你根本没法凭空判断哪个阈值是“最优”的。另外一个经验是不要过度依赖单一指标。互信息和左右熵各自只能反映“词”的一个侧面组合使用才能互相弥补。如果你发现某些很明显的新词没被挖出来可能就是因为它的左熵或右熵偏低比如经常跟在同一个词后面这时候可以适当放宽min_entropy然后用频次排序来做二次筛选。反过来如果噪声多优先提PMI而不是提熵值——提熵值容易误伤真实新词提PMI则比较精准。最后关于中文编码务必确保输入文件是UTF-8编码否则会出现乱码甚至程序崩溃。如果你手上的语料是GBK编码的先用iconv转一下iconv -f GBK -t UTF-8 source.txt target.txt这一步虽然简单但真的会卡住很多人——我自己就在这上面浪费过不少时间。如果你准备在自己的业务里试一下Atlas我的建议是先从一份50MB左右的干净语料开始把上面这些参数跑一遍感受一下每个参数对结果的影响。数据量不大迭代成本低你能比较快地建立起对这套工具的直觉。等摸清楚它的脾气之后再上大语料、做增量训练、接入生产流程都会顺很多。
返回列表