简介:兰卡斯特汉语语料库(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', encoding='utf-8', errors='ignore') 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', encoding='utf-8', errors='ignore') 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', encoding='utf-8', errors='ignore') 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快速检测,再统一转码。我已经习惯在脚本里加上一层errors='ignore'兜底,但最好还是先转成 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', encoding='utf-8', errors='ignore') 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(reverse=True) for score, w, fa, fb in diff[:30]: print(round(score, 2), w, fa, fb)这个工具没有任何外部依赖,核心就是对数似然比排序。对语文研究来说,这种量化结果的价值在于给你一个明确的线索,最终解释权仍然在语言规律那里。我的习惯是跑完数据后,总会挑两个差异显眼的词回原文里看上下文,确认不是被代词或者文本噪声误导。语料库工具最怕的就是,统计响了,却没读过原始片段。
用 LCMC 这类小语料库,最大的收益是你能完全掌控数据生命周期:从格式、编码到统计口径都查得到,相较于黑盒式的大型云端语料,踩坑成本低,得出结论也更踏实。希望这篇笔记能帮你少走几趟弯路,快速把语料库变成可知、可算、可解释的产出。
本文还有配套的精品资源,点击获取