资讯详情 兰卡斯特汉语语料库LCMC:设计原理与Python处理实战指南
📅 2026/10/13 23:32:24
简介兰卡斯特汉语语料库LCMC是一份百万词级现代汉语书面语通用平衡语料库面向语言学研究者、汉语教师及自然语言处理爱好者为其提供免费开放的语料数据支撑。该语料库在构建时参考FLOB/FROWN的设计思路并作为英汉时体对比研究项目的成果之一适合用于语体分布考察、词频统计、英汉平行对比及汉语教学研究等场景。包内共71个文件核心为39个htm格式的语料分卷文本、31个xml结构标注文件及1个doc格式的语料库使用说明文档文件组织清晰可方便检索与按需提取压缩包整体仅6.34MB体积小巧易于获取。目前已有3466人学习/下载受到相关领域学习者关注。通过这套语料资源用户可快速获得近百万词的现代汉语平衡语料样本用于学术研究中的语料检索、标注分析与语言特征统计是开展汉语语料库相关课题的实用基础数据。1. 兰卡斯特汉语语料库LCMC语料库先搞清楚它到底是什么想做汉语语域差异研究第一反应往往是去翻现成的通用语料库但真要对比“新闻、小说、学术论文”之间的词汇与词性分布时手里缺一个结构透明的成品。兰卡斯特汉语语料库LCMC就是为此设计的它有约 100 万词次的现代汉语书面语采样于 1990 年前后的正式出版物并且全部经过分词和词性标注。学习自然语言处理的人不需要再费劲洗菜一样清理原始文本可以直接拿它做频率统计、搭配检索、语域对比甚至试跑一个有监督的词性标注器。但很多朋友拿到 LCMC 后第一个反应是“这文件格式好怪”这其实是它最优秀的起点。LCMC 直接模仿了英语 Brown 语料库的抽样框架每一段文本来自什么文体、抽取多少词都被严格规定过而不是随手攒的网页文本。对从事语料库语言学、计算语言学入门、社会语言学语体分析的人来说它是一份可以重复验证的小而完备的基准语料。本文就从语料库设计、读取逻辑、统计模式一直到你实际使用时容易翻车的五个细节按动手路线展开。2. LCMC 的语料设计为什么说它是“平衡”语料库2.1 模仿 Brown 的抽样框架15 类文体不是随便分的LCMC 的可贵之处不在于语料总量多而在于抽样设计有明确依据。它沿用 Brown 语料库的抽样思路把现代汉语书面语按使用领域拆成 15 个类别每个类别对应一组代码。常见类别如下表所示代码文体范围典型篇章A新闻报道报纸要闻、本地新闻B社论与立场文章报纸评论、编者按C新闻评论与专栏专栏作家文章D宗教类布道稿、宗教刊物E技术与操作说明技能手册、使用指南F通俗社科科普文章、社会分析G传记与散文人物传、杂文随笔H公文与报告政府公报、会议报告J学术论文学报、研究论文K一般小说现代长中篇L侦探小说推理故事M科幻小说幻想题材N武侠小说武打叙事P爱情小说言情叙事R幽默小品笑话、讽刺短文这个分类让你在做语域对比时不用自己再发明一套“新闻 vs 文学”的粗糙标签直接用现成的代码就能聚合。类别之间彼此独立但又在性别、地域、主题上做了基础平衡所以它叫平衡语料库。每条语料按段落和句子切分尽量保证一个类内文本长度接近方便统计时不用反复做长度归一化。2.2 分词与词性标注数据长什么样LCMC 在发布时就已经完成了基础加工。常见发行版本会把一篇文本拆成一行一行的短句每一行中词语和词性标注连在一起不同来源的格式略有差异。最典型的是“词语/标注”用斜杠分隔例如他/r 站/v 在/p 门口/n 的/u 位置/n 上/f这里的r是代词v是动词p是介词n是名词u是助词f是方位词。标点符号一般也保留但会作为独立 item。另有些版本把当作换行标记整篇文本压缩成一个长行。你拿到手后第一件事不是急着统计而是先看一套文件里的一致格式否则后面代码会一跑就崩。还需要注意LCMC 使用的标注集并不等于国内常见的“北大现代汉语语料库标注集”它更像标注粒度固定、标记数量较少的折中方案名词不再细分“普通名词/专有名词”时会占用多个标志位动词也只区分及物与不及物等少数属性。实际使用时你只需要认全名词、动词、形容词、助词、代词这些大类就能完成大部分统计工作太细的属性反而容易带来噪音。2.3 和现代通用语料库的差异为什么 100 万词仍然值得用很多人会质疑100 万词是不是太小了对比动辄几十亿词的现代汉语语料LCMC 确实不算大但它解决的是“结构已知、类别可控”的问题。现代互联网语料看似规模大却有难以避免的领域倾斜新闻和百科占了绝大多数口语与文言混杂而且清洗过程常常丢失原创文本信息。LCMC 从设计之初就把文体的比例和抽样来源写进框架每一类文本的质量都被人工检查过这让它在统计意义上更可信。另一个不可替代点是时间截面。LCMC 采样于 1990 年前后正好处于改革开放后、互联网普及前的书面语阶段。如果你想研究“语言演变”对比九十年代书面语和现代网络语料之间的词汇差异LCMC 是不可多得的对照组。它可以与同时代的现代汉语语料库互补但不适合直接用来训练当前的大语言模型因为数据量和领域新鲜度都不够。把它当作规格明确的评测集、验证集比当作训练集更明智。3. 把 LCMC 用起来下载、检索和最小处理流程3.1 先确认你拿到的版本形态LCMC 在流传中产生了不少“民间版本”最常见的是压缩包里有按文体代码命名的多个文本文件比如lcmc-a.txt、lcmc-b.txt也有的版本把所有文件平铺在一层目录里文件名包含A01这样的编号。还有一些整合版直接做成一个大的 utf-8 纯文本文件每个句子一行。拿到之后我建议先建一个干净的目录把原始文件单独存放避免后续脚本修改源文件导致不可逆损坏。打开文件前先用文本编辑器或命令行预览前几行确认编码。旧版流转的 LCMC 可能来自 GBK 或 GB2312 环境如果你的操作系统默认是 UTF-8直接读取就会乱码。为了一劳永逸可以把原始文件统一转成 UTF-8 格式再保存为副本后续所有 Python 代码都用 UTF-8 处理。3.2 用 Python 批量读取并生成词性分布下面这个脚本是我处理 LCMC 时最常用的一段适合从多文件语料库中提取每个词性标签的全局频次。它会遍历当前目录下的所有lcmc-*.txt文件并逐行拆分。import os import re from collections import Counter def process_lcmc_file(path): pos_counter Counter() word_counter Counter() # 以 utf-8 读取忽略解码错误行 with open(path, r, encodingutf-8, errorsignore) as f: for line in f: line line.strip() if not line: continue # 假设每个词与标注用斜杠分隔 items re.split(r[\s/], line) # items 可能是 [词, 标注] 的平铺序列 for item in items: if not item: continue if / in item: w, p item.rsplit(/, 1) word_counter[w] 1 pos_counter[p] 1 else: # 某些版本没有斜杠单独处理做词频 word_counter[item] 1 return word_counter, pos_counter if __name__ __main__: total_word Counter() total_pos Counter() folder ./lcmc_files for filename in os.listdir(folder): if not filename.startswith(lcmc-): continue if not filename.endswith(.txt): continue wc, pc process_lcmc_file(os.path.join(folder, filename)) total_word.update(wc) total_pos.update(pc) print(total_pos.most_common(30))这段代码的核心是正则切分和Counter累加。正则re.split(r[\s/], line)会把空格、制表符和斜杠统一作为分隔符这样能同时兼容“词 标注”和“词/标注”两种常见格式。但需要注意rsplit(/, 1)只拆最后一个斜杠避免词中含有斜杠时出错。中文粒子中偶尔会有英文标点但分词后一般不会有界符混入。如果某个文本里出现连续两个斜杠或标注属性带前缀后缀你需要根据实际数据微调正则。一个快速验证方法是统计total_pos中是否出现无法识别的标记比如nx、qq等这类标记一般来自版本差异可以在后续统一映射到大类。3.3 检索特定结构用正则匹配“形容词名词”LCMC 的另一类常规应用是搭配检索比如找出语料中所有“形容词名词”序列。由于文本已经分词和标注不需要再跑分词模型直接用行级正则匹配即可。import os import re def find_adj_noun(folder): pattern re.compile(r(\w)/a\s(\w)/n) results [] for filename in os.listdir(folder): if not filename.endswith(.txt): continue with open(os.path.join(folder, filename), r, encodingutf-8, errorsignore) as f: for line in f: # 去除行内其他标点 clean_line line.strip() for m in pattern.finditer(clean_line): adj, noun m.group(1), m.group(2) results.append((adj, noun, filename)) return results if __name__ __main__: folder ./lcmc_files pairs find_adj_noun(folder) for adj, noun, fname in pairs[:20]: print(adj, noun, fname)注意这里我用\w匹配中文词在 Python 的re中\w默认只能匹配英文、数字和下划线不能匹配中文。如果你直接跑上面的代码可能会完美跳过所有中文。正确写法应该用[\u4e00-\u9fff]或者开启re.UNICODE标志。在 Python 3 中re模块默认 Unicode 匹配\w对中文并不生效所以更稳妥的是自定义字符集。带标注的正则容易受空格干扰如果某一版本里标注和词之间夹着多个空格模式就要改成([\u4e00-\u9fff])\s*/\s*a\s([\u4e00-\u9fff])\s*/\s*n。这种参数差异正是你在处理不同发行版本时必须先做“格式侦查”的原因。4. LCMC 能做什么语域对比和词性分布分析的落地场景4.1 同一词在不同文体中的频率差异语域对比是 LCMC 最擅长的事。比如“进行”这个词在学术论文和新闻评论里很常见但武侠小说里可能几乎不出现。你可以按文体代码拆开统计计算归一化频率# 统计特定词在不同文体中的每十万词频次 from collections import defaultdict import os, re word 进行 freq defaultdict(int) total defaultdict(int) for fname in os.listdir(./lcmc_files): if not fname.endswith(.txt): continue style fname.split(-)[1][0] # 从文件名提取文体代码 with open(os.path.join(./lcmc_files, fname), r, encodingutf-8, errorsignore) as f: text f.read() wc text.count(word) freq[style] wc # 粗略统计总词数 total[style] len(text.split()) for style, count in freq.items(): rate count / (total[style] / 100000) print(style, count, round(rate, 2))这段代码里style fname.split(-)[1][0]对文件名lcmc-j01.txt有效它会取到j。但如果你的文件不是这种命名就需要换成其他解析方式。这种统计的价值不是数字本身而是拿不同文体横向比较后能看出词汇的语域偏好这正是语料库语言学的经典分析路径。4.2 作为词性标注评测基准LCMC 的标注经过了多轮人工校对比市场上许多中文标注语料稳定。因此做词性标注实验时可以把 LCMC 切出一个小测试集用你自己的标注器去预测再和原始标注比较计算准确率。规模不大跑起来很快方便反复调参。评测时要注意LCMC 的标注集和官方常用标注集存在映射关系。你必须先建立映射表例如把 LCMC 的v映射成你模型输出的动词把a映射成形容词否则两端标签名都对不上准确率会莫名其妙地低。这个映射表务必单独保存成 JSON 文件以后每次实验复用同一份保证评测可比。4.3 与另一个语料库做平行对比的小思路如果你手上还有一个其他来源的语料想把它们放到同一个框架下比较建议不要直接混合后去统计。更好的做法是先分别计算两个语料的词性分布向量再用卡方或对数似然比找出差异显著的词。LCMC 的平衡结构能作为基线另一个语料作为目标组。这样能避免语料总量差异造成的假象。比如你想比较“网络评论 vs LCMC 新闻类”可以先随机从网络上抽取 10 万词再用 LCMC 的 A 类作为新闻代表。对比时把所有词频归一化到每十万词再计算 log-likelihood 值筛出 D 值最大的前 50 个词作为“特征词”。这个方法在语域分析里相当成熟难点不在统计上而在人为判断哪些差异词有解释价值。5. 用 LCMC 的 5 个常见坑从标注集到文本编码5.1 文件层编码不一致导致读出来乱码现象打开文件后看到t之类的符号Python 读取时报UnicodeDecodeError。原因网上流传的文件一部分是 GBK 编码一部分是 UTF-8 编码没有统一规范。你的代码用 UTF-8 去读 GBK 文本中文部分全部变成乱码甚至中断。解决不要手动猜测每个文件编码。用chardet或langdetect快速检测再统一转码。我已经习惯在脚本里加上一层errorsignore兜底但最好还是先转成 UTF-8 的副本之后所有脚本都用同一编码。5.2 标注集认不准现象统计出的词性标签里出现奇怪标记比如qr、nz、vg和资料里列出的词性代码表对不上。原因不同版本的 LCMC 可能改写过标注集或者在原版基础上补充标注。LCMC 基础标注集比较简洁但民间追加版本的制作者会引入自己的扩展标记。解决不要编代码表直接在语料里抽出所有不认识的标签建立一个扩展映射表。宁可把未知的标签全部归入OTHER类也不要在统计时强行拆开否则结果不稳定。5.3 简繁体混用导致检索漏词现象检索“定义”的时候统计结果远低于预期因为部分文件中写的是“定義”。原因1990 年代的出版物本身就存在简繁体混排尤其港台来源的文本没有全部转成简体网络转版时也常常只转一部分。解决先做一次整体繁体转简体但转换后不要直接覆盖原始文件。我通常会对每个文本输出两个副本原始版和“标准化简体版”检索用简体版文本内容研究用原始版两不误。5.4 语料规模小统计结果容易波动现象在小说类文本里统计某个低频词发现频率在不同章节之间的差异达到数倍甚至同义词排序不稳定。原因每个文体类别的文本量并不大低频词可能只出现一次频率受单篇主题影响非常大统计误差远大于高频词。解决做频率对比时设置最低出现次数阈值比如只统计至少出现 5 次的词并对频数做平滑处理。更重要的是不要因为一次统计差异就下结论对抽样区间做几次折半重测稳定下来再解释。5.5 民间“改写版”标注错误被当成基准现象你在验证标注器时发现测试集上准确率始终上不了 90%检查之后才知道有一批文件的标注明显错位比如动词标成名词。原因某些转贴者对文件乱改没有保留原始校对结果甚至把原始文件里多音字全部替换了。解决把 LCMC 当作基准之前先随机抽样 5 到 10 个文件人工标注一小段比较你拿到的版本和官方样例描述是否一致。发现异常就换一个版本来源或者只选用已知可信的那几个文体子集。6. 进阶用 Python 做一个 LCMC 语域对比小工具最后分享一个我平时最常用的进阶技巧做一个极简的语域对比工具让“学术类 vs 武侠小说类”这种跨域比较可以一键出结果。核心思路是先按文体聚合词性频次再把频次转化为每十万词分布最后输出差异最显著的词。from collections import defaultdict import math import os def load_style(folder, style_code): words [] for fname in os.listdir(folder): if not fname.endswith(.txt): continue if style_code not in fname: continue with open(os.path.join(folder, fname), r, encodingutf-8, errorsignore) as f: text f.read() # 简单分词假设横杠分隔 words.extend([w for w in text.replace(/, ).split()]) return words def ll_score(freq_a, total_a, freq_b, total_b): # 将对数似然比计算简化E 为期望值 a, c freq_a, freq_b b, d total_a - a, total_b - c e1 (a b) * (a c) / (total_a total_b) e2 (a d) * (b d) / (total_a total_b) return 2 * (a * math.log((a / e1) if a else 1) c * math.log((c / e2) if c else 1)) academic load_style(./lcmc_files, j) novel load_style(./lcmc_files, k) word_freq_a defaultdict(int) word_freq_b defaultdict(int) for w in academic: word_freq_a[w] 1 for w in novel: word_freq_b[w] 1 total_a len(academic) total_b len(novel) diff [] for w in set(word_freq_a) | set(word_freq_b): fa word_freq_a.get(w, 0) fb word_freq_b.get(w, 0) if fa fb 5: # 低频过滤 continue diff.append((ll_score(fa, total_a, fb, total_b), w, fa, fb)) diff.sort(reverseTrue) for score, w, fa, fb in diff[:30]: print(round(score, 2), w, fa, fb)这个工具没有任何外部依赖核心就是对数似然比排序。对语文研究来说这种量化结果的价值在于给你一个明确的线索最终解释权仍然在语言规律那里。我的习惯是跑完数据后总会挑两个差异显眼的词回原文里看上下文确认不是被代词或者文本噪声误导。语料库工具最怕的就是统计响了却没读过原始片段。用 LCMC 这类小语料库最大的收益是你能完全掌控数据生命周期从格式、编码到统计口径都查得到相较于黑盒式的大型云端语料踩坑成本低得出结论也更踏实。希望这篇笔记能帮你少走几趟弯路快速把语料库变成可知、可算、可解释的产出。本文还有配套的精品资源点击获取