☰
粤语NLP实战:pycantonese分词、词性标注与粤拼转换指南
2026/10/14 0:15:09 网站建设 项目流程

简介:pycantonese是一套面向粤语语言学与自然语言处理场景的Python工具库,适合从事粤语文本挖掘、语音数据研究或计算语言学的开发者使用。它围绕粤语特性提供Jyutping罗马音转换、分词、停用词过滤和词性标注等核心能力,可帮助解决粤语拼音转换困难、词边界模糊、语法结构特殊等常见问题。资源包共294个文件,压缩包大小约15.46MB,以py源码、rst/txt/html文档、js脚本及woff/woff2字体资源为主,既包含库本体完整代码,也附有可供参考的构建文档与说明材料。值得一提的是,压缩包内还收录了FC系列粤语对话语料(.cha)样本,便于读者结合真实语料快速上手实验。配合NLTK、spaCy等框架,pycantonese可进一步支撑情感分析、语义理解乃至机器翻译等高级任务,无论用于学术研究还是粤语产品开发,都是值得收藏的实用工具包。已有689人浏览学习。

1. 「唔該」不是「唔」加「該」:粤语 NLP 为什么绕不开 pycantonese

「唔該」在粤语使用者的日常对话里是「谢谢」的意思,但在通用中文分词器眼里,它经常被切成「唔 / 該」两个无意义碎片。粤语书面语与普通话文本在词汇、虚词、句式代号上差异极大,通用 NLP 工具链面对「食咗」「我哋」「埋單」这种口语词时准确率会断崖式下跌。pycantonese 正是为这个场景而生的 Python 库:它把粤语语言学的语料知识、读音规则和 NLP 工程能力接在一起,提供分词、词性标注、粤拼转换、谐音和字词频数据等能力。这篇文章按「装环境 → 分词标注 → 粤拼 → 踩坑 → 进阶」的顺序走一遍可复现路径,适合做粤语语料清洗、字幕对齐、语言学习产品的人,也适合想理解「一个语言分支的 NLP 落地到底难在哪」的开发者。

2. 用 pycantonese 搭建粤语文本处理环境:安装、数据与首次加载

先明确一件事:pycantonese 不是一个大而全的深度学习框架,它是一个把「粤语语言学资产」工程化的工具库。理解这一点能帮你省下不少瞎调参数的时间。它的主要价值在数据与规则层,比如内置口语语料、读音对应表、字词频统计,以及基于这些数据训练或推导出来的分词与标注能力。接下来的三节,我会从安装开始,一步步走到第一次调用分词函数。

2.1 pip 安装与依赖检查:最小环境长什么样

常见做法是用虚拟环境隔离项目依赖,避免把系统 Python 环境搞乱。安装命令很简单:

python -m venv .venv source .venv/bin/activate # Windows 下执行 .venv\Scripts\activate pip install pycantonese

安装完成后,先验证包是否能正常导入,再检查版本与依赖状态:

pip list | grep -i cantonese python -c "import pycantonese; print(pycantonese.__version__)"

这里有两个参数值得说明。第一,Python 版本建议用 3.10 或更新的稳定版,过旧的解释器在字符串处理、类型标注上会暴露一系列兼容问题,尤其是后面涉及粤拼字符规范化时,旧版本的标准库行为差异很容易变成隐形坑。第二,如果 pip 安装时出现依赖下载超时,不要盲目加--no-cache-dir,先确认网络源或换镜像源,再重试安装。

提示:pycantonese 的公开接口在不同版本里可能有微调,装完先跑一次dir(pycantonese),把当前版本暴露的函数名过目一遍,再进下一步。这个习惯能避免照着旧教程写代码时遇到AttributeError。

2.2 内置语料与读音数据:首次加载时发生了什么

pycantonese 之所以比通用分词器更适合粤语,核心在于它内置了面向粤语口语的语料资产。我第一次用的时候以为它只是一组分词函数,后来才发现,每当调用涉及分词、读音、词频的接口时,库里会加载对应的语料数据与读音对照表。这个过程不是初始化时一次性发生的,而是在首次调用时按需载入,因此冷启动会有一次可感知的延迟。

理解这个机制对性能调优很重要。如果你是在 Web 服务里使用它,最好在服务启动阶段主动触发一次数据加载,而不是让第一个请求去承担冷启动耗时。可以通过查看模块级弹出的公开入口来确认当前版本的数据加载方式:

import pycantonese as pc # 列出当前版本暴露的全部公开入口,确认数据模块与函数名 entry_names = [n for n in dir(pc) if not n.startswith("_")] print(entry_names)

这段代码的逻辑很简单:dir(pc)返回模块的全部属性名,过滤掉下划线开头的内部属性,剩下的就是你可以直接调用的接口或数据对象。运行后你会看到类似segment、pos_tag、语料相关对象等一系列名字。这一步相当于先摸清工具台上有哪些扳手,再动手拆机器。

参数上唯一要注意的是「按需加载」与「常驻内存」的取舍。如果程序反复调用分词接口,数据一旦加载就会留在进程内存里,后续调用会明显变快;但如果你的程序是短生命周期的一次性脚本,那就没必要做启动预热,直接调用即可。

2.3 第一次调用分词:先看输出再看报错

跑通一个最小的分词脚本,是确认环境与接口是否匹配的最快方式。我通常用一句包含典型粤语口语词的文本做冒烟测试,比如「尋日」(昨天)、「我哋」(我们)、「餐廳」、「食飯」、「埋單」(结账)、「銀包」(钱包):

import pycantonese as pc text = "尋日我哋成班人去咗餐廳食飯,埋單嗰陣先知唔記得帶銀包。" tokens = pc.segment(text) print(tokens)

如果当前版本把分词接口放在了子模块里,直接调用pc.segment可能报AttributeError,这时不要慌,回到 2.2 节列出的入口清单里找到对应函数名即可。分词输出的通常是一个列表,每个元素是一个切分后的词项,比如「成班人」「食飯」「埋單」会被尽量保留为整体,这正是粤语词典与统计模型在起作用。

第一次运行如果报错,多半不是 pycantonese 自身的问题,而是输入文本的编码。命令行直接粘贴中文一般没有问题,但如果从文件读取,请务必使用open(..., encoding="utf-8"),这一点在 Windows 环境下尤其重要,否则你会看到各种「粤拼乱码」和字符乱码,后文第 5 章会专门展开。

3. 粤语分词与词性标注:把「食咗飯」切对不是玄学

分词和词性标注是粤语 NLP 的基本盘。通用中文分词器在普通话新闻语料上表现不错,但一旦碰上粤语口语,切分结果就会变得很难看。这章的思路是:先看通用工具怎么翻车,再用 pycantonese 的正确姿势做分词与标注,最后讲清楚分词前的预处理参数应该怎么控制。

3.1 通用中文分词器在粤语口语上的翻车现场

「我尋日食咗飯,而家好飽。」这句话如果用通用分词器处理,经常会被切成「我 / 尋 / 日 / 食 / 咗 / 飯 / 而家 / 好 / 飽」。「尋日」「咗」「而家」这些词在普通话词典里根本不存在,通用分词器只能把它们按单字拆开。更麻烦的是语气词和虚词,比如「啦」「㗎」「咋」「啫」,它们没有实在意义但在粤语里承担重要的语气功能,通用工具会直接把它们当作标点或噪声丢弃。

对比一下两类工具的目标差异:通用分词器追求的是在混合文本上的平均表现,词表以普通话常用词为主;pycantonese 针对的是粤语口语与书面语,词表里收录了大量粤语特有词汇和语料统计结果。这不是谁比谁强的简单比拼,而是语域匹配的问题。拿粤语文本去用普通话词典,就像拿着普通话菜单到粤菜馆点菜,不是菜不好,是工具不匹配。

所以我的建议是:如果你的数据里夹杂普通话和粤语,先用语言识别或关键字集合判断文本语域,再分流处理。纯粤语或粤语为主的语料,直接用 pycantonese 处理;普通话语料多的,才考虑混用通用工具。判断的方法很简单,统计「嘅、咗、哋、唔、係」这些高频粤语字的出现密度,大于阈值就走粤语管道。

3.2 分词结果与词性标注:一个段落两种输出

词性标注是分词的下一级操作。pycantonese 的做法是先切词,再对每个词项给出词性标签。下面这段代码把分词和标注放在一起跑:

import pycantonese as pc text = "佢尋日唔舒服,所以請咗一日假。" tokens = pc.segment(text) annotated = pc.pos_tag(tokens) for token, pos in zip(tokens, annotated): print(f"{token}\t{pos}")

这段代码的逻辑是:segment先返回词项列表,pos_tag接收词项列表后返回等长的词性标签列表,zip把词项与标签一一配对打印。这里有一个容易误解的点:pos_tag的输入不是原始句子,而是已经切好的词项列表。如果你直接把字符串塞进去,接口会因类型不匹配报错。

关于词性标签,要注意它使用的标注集与普通话工具并不完全相同,具体标签缩写需要以当前版本输出的实际值为准。我的建议是先用一小段文本打印所有出现的标签,统计成一个标签表,再决定是否要映射到你业务系统内部的词性体系。这一步看起来多余,但在做后续特征工程时能省很多时间,尤其是当你需要把词性特征喂给下游模型时,标签集不一致会导致灾难性的特征引用错误。

3.3 分词参数怎么调:词典、缓存与最小词长

很多从通用分词器转过来的用户会习惯性地找「自定义词典」「最小词长」之类的配置项。pycantonese 的分词逻辑更多依赖于内置语料与统计模型,自定义词典并不像通用工具那样即插即用。我一般做的事情是控制「输入侧」而不是「词表侧」。

最有效的输入侧控制是预处理。粤语文本常带有全角标点、零宽字符、特殊空格等噪声,这些字符会严重影响切分。下面是我常用的规范化函数:

import re import unicodedata import pycantonese as pc def normalize_for_cantonese(text: str) -> str: # 全角空格统一为半角空格 text = text.replace("\u3000", " ") # 将全角标点统一为半角表单,方便后续处理 text = unicodedata.normalize("NFKC", text) # 在主要标点两侧补空格,避免标点粘在词项上 text = re.sub(r"([,。!?、;:])", r" \1 ", text) return text.strip() raw_text = "而家三點半,我哋去食糖水好唔好?" for token in pc.segment(normalize_for_cantonese(raw_text)): print(token)

这段代码包含三个关键处理点:NFKC归一化会把全角逗号、括号、数字统一成半角,减少字符变体带来的干扰;正则替换规则在标点前后补空格,是为了防止标点与相邻词项粘在一起,造成切分边界错误;最后strip()去掉首尾空白。

参数上真正值得调节的不是分词器内部,而是输入文本的清洗强度。如果语料是从网页抓取的,保留 HTML 实体转义符;如果是字幕文件,还夹杂时间轴标记。我强烈建议在进入分词之前,把跟正文无关的标记全部剥离,否则分词器会把「wolf-720p」「00:01:23,400」这样的噪声也当成处理对象,输出结果会显得非常碎。

4. 粤拼转换与语言学前置数据:从汉字到读音

这一章进入标题里「语言学」的部分。粤拼(Jyutping)是粤语的罗马化注音方案,pycantonese 把汉字到粤拼的转换做成了可直接调用的能力。此外,同音字查询和字词频数据也是语言学资产在工程里的典型应用。理解了这些,你才能把 pycantonese 从「分词工具」升级成「粤语文本分析工具箱」。

4.1 用 pycantonese 把整句文本转成粤拼串

把汉字转成粤拼,最常见的场景包括语音合成前端、字幕注音、错别字校对。逐字转换的代码很直观:

import pycantonese as pc text = "唔該幫我落單" for ch in text: jp = pc.jyutping(ch) print(ch, jp)

这里pc.jyutping(ch)接收单个汉字,返回对应的粤拼字符串。粤拼的特色是自带声调数字,比如「唔」是m4,「該」是goi1,数字 1 到 6 分别代表六个声调。这个数字设计对工程非常友好,你可以直接通过去除末尾数字来获得不带声调的读音,也可以把数字当作声调特征输入模型。

实际使用中要注意,多音字会按照语料统计下的最常见读音返回,而不是根据上下文动态选择读音。例如「行」在「銀行」和「行路」里读音不同,如果你需要按词定音,最好先分词再逐词查询,或者拿到所有候选读音后自行根据上下文选择。这一点是粤拼转换最大的边界,不要指望它像语音合成前端那样做完整的多音字消歧。

提示:如果你需要的是「词级」粤拼而不是「字级」粤拼,做法是先分词再对每个词项取第一个字的读音,或者按词项拼接字符读音。实际工程项目里,词级读音对下游任务更有意义。

4.2 谐音与同音字组:语言学功能在工程里的真实用途

pycantonese 的数据里包含读音到汉字的映射关系,利用它可以实现「给定一个字的读音,找到所有同音字」的功能。这个能力在错别字纠错、昵称生成、歌词近似匹配里非常实用。

不需要专门找接口,用字符到粤拼的转换就能自己构建一个同音字查询:

import pycantonese as pc chars = "唔毋吾梧" target = pc.jyutping("唔") same_sound = [ch for ch in chars if pc.jyutping(ch) == target] print(target, same_sound)

这段代码的逻辑很简单:对候选字集合里的每个字转粤拼,与目标字的粤拼做精确匹配,收集所有同音字。要注意的是,粤拼字符串包含声调数字,所以「唔」(m4) 和「毋」如果是同音,它们的粤拼完全相同才能匹配上。如果你想把声调不同的字也视为近似音,就先把末尾的数字去掉再比较。

这个功能在实际产品里可以做成「读音容错映射表」。比如用户输入歌词时写成了「唔該」但正确文本是「毋該」,虽然字形差异大,读音却高度一致,通过读音匹配就能召回。不过要提醒一句,粤语存在文白异读现象,同一个字在读书音和白话音里可能读音不同,pycantonese 返回的通常是常见层级的读音,遇到生僻语境时要人工复核。

4.3 词频表与字频数据:做文本分析的第二个入口

「语言学」在工程里的另一层落地是统计资产。pycantonese 内置的语料可以用来统计字频、词频,这对教学大纲设计、字幕重点词筛选、语料质量评估都有直接帮助。即使你不直接使用它内置的统计接口,也可以把语料导出成纯文本后用标准库做计数:

from collections import Counter def top_chars(corpus_texts: list[str], n: int = 30) -> list[tuple[str, int]]: counter = Counter() for text in corpus_texts: # 跳过空白字符,统计每个汉字出现次数 counter.update(ch for ch in text if not ch.isspace()) return counter.most_common(n)

逻辑是把语料列表逐句展开成字符流,用Counter累加频率,最后取前 n 个高频字。如果你需要的是词频,则先对每句分词,再对词项列表做同样的统计。

这里有一个容易忽略的细节:字频和词频的用途完全不同。字频适合做「常用字表」和输入法词库排序,词频适合做「教学词汇表」和「文本主题判断」。做之前先想清楚下游要什么,不要拿词频数据硬套字频分析场景。语料本身的语域也会直接影响统计结果,口语会话语料统计出来的高频词,和新闻语料的高频词差很远。

5. 粤语 NLP 的坑位清单:编码、标注集与语料差异

用 pycantonese 做过一轮真实项目后,你会发现坑往往不在库的功能缺失,而在数据与环境的细节上。这一章整理 4 个我必须反复提醒的踩坑点,每条按「现象 → 原因 → 解决」展开。

5.1 Python 字符串编码导致粤拼乱码

现象:在 Windows 终端里运行粤拼转换脚本,输出的m4 goi1变成乱码,或者csv文件里出现大量锟斤拷。

原因:终端默认编码与 Python 标准输出的 UTF-8 不一致;写入文件时没有显式指定编码,导致系统按默认编码(Windows 下常为 GBK)写入粤拼字符串。

解决:运行脚本前设置环境变量set PYTHONIOENCODING=utf-8;读写文件时显式指定编码。写入文件时建议用utf-8-sig,因为某些 Windows 工具(比如 Excel)打开无 BOM 的 UTF-8 文件会乱码:

with open("jyutping_out.csv", "w", encoding="utf-8-sig") as f: f.write("字,粵拼\n") for ch in text: f.write(f"{ch},{pc.jyutping(ch)}\n")

顺带提醒,从网页复制文本时很容易带入零宽空格和零宽连字符,这些不可见字符会混进文本里,看似正常的字符串在分词和转换时产生诡异结果。遇到输出正常但匹配异常的情况,优先检查字符串编码与不可见字符。

5.2 词性标注集与普通话标注集不一致的乌龙

现象:拿着普通话词性标注规范的文档去解释 pycantonese 的输出,看到某个标签后开始猜含义,结果语法特征映射全错。

原因:pycantonese 的词性标注基于粤语语料自身的标注体系,与普通话工具使用的标注集来源不同,缩写也不通用。

解决:不要猜,直接跑一段覆盖各类词性的文本,把输出标签全部打印出来,建立一份「当前版本标签实际含义」的本地注释。如果业务系统内部有自己定义的词性体系,写一个映射函数,把 pycantonese 的标签映射成内部标签,而不要把词性字符串直接当特征丢给下游模型。标签集不一致引发的问题在特征工程阶段很难排查,因为错误隐藏在字符串匹配里。

5.3 口语文本与书面语文本在分词上的明显偏差

现象:用 pycantonese 处理新闻稿或正式公告,分词结果显得碎,不少应该成词的搭配被切开了。

原因:内置语料的语域偏向粤语口语会话,词频统计和词典结构都更贴合「傾偈」(聊天)场景,对正式书面语的覆盖不足。

解决:先用关键词集合判断输入文本的语域。如果文本偏书面语,就要做好心理预期:切分质量可能不如口语场景。更稳妥的方案是,在书面粤语场景下用通用中文分词器作为基线,再用 pycantonese 的分词结果与基线做对比,在差异处人工抽查,确定最终以哪个结果为准。实际项目里我会把这个判断逻辑封装成一个函数,根据「嘅、咗、哋」等口语词密度自动选择处理管道。

5.4 标点与全角字符预处理不到位导致切分边界错乱

现象:「食咗飯。」被切出「飯。」这样的词项,或「而家三點半」里的全角逗号把整句切割成奇怪片段。

原因:没有对全角标点做归一化,标点直接粘在相邻词项上,分词器只能把「标点 + 词」当成一个整体。

解决:在进入分词之前,把全角标点统一为半角并在标点两侧补空格。我在 3.3 节给的normalize_for_cantonese函数可以复用。如果语料里有特殊符号,比如「/」「@」「#」,按业务需求决定是否在预处理时替换成空格。有一个原则可以记一下:宁可让切分结果多出几个孤立标点,也不要让标点污染真正的词项边界。孤立标点在后续过滤阶段很容易处理,污染后的词项才是真正的麻烦。

6. 进阶技巧:用粤拼做发音相似度匹配

最后一个技巧,是我在字幕校对与用户输入纠错项目里反复用到的:把文本转成粤拼,再做读音层面的相似度匹配。这个方法特别适合处理「白字」(同音错别字)问题,因为很多粤语口语词的写法不唯一,用户输入时经常写成另一个同音字。

做法分成三步。第一步把字符转成去掉声调后的粤拼,第二步用字符串相似度算法做比较,第三步设定阈值判断是否算发音相近:

import re import unicodedata from difflib import SequenceMatcher import pycantonese as pc def norm_jyutping(ch: str) -> str: s = pc.jyutping(ch) # 去掉末尾声调数字,只保留声母韵母信息 return re.sub(r"[0-9]+$", "", unicodedata.normalize("NFC", s)) def sound_similar(ch_a: str, ch_b: str) -> float: if ch_a == ch_b: return 1.0 pa, pb = norm_jyutping(ch_a), norm_jyutping(ch_b) if not pa or not pb: return 0.0 return SequenceMatcher(None, pa, pb).ratio() for pair in [("唔", "吾"), ("咪", "米"), ("係", "系")]: print(pair, round(sound_similar(*pair), 2))

这段代码里,norm_jyutping负责把「唔」的m4转成m,把「該」的goi1转成goi;SequenceMatcher计算两个拼写串的相似度。我自己在项目里常用的参考阈值是 0.7:大于 0.7 视为发音接近,进入候选替换;0.5 到 0.7 之间需要人工复核;低于 0.5 直接放弃。这个阈值需要根据你的具体字符集微调,比如带声调比较时,阈值通常要提高到 0.8 以上。

要注意的是,粤拼的声母韵母结构比普通话拼音更复杂,韵母里有长元音和短元音对,比如aa和a,相似度计算时会被视为不同字符。这是符合预期的,因为粤语里这两个音确实有辨义作用。如果你做的是宽松匹配,可以在比较前把aa归一化成a,但代价是召回率上升的同时误判也变多。

我自己现在处理粤语文本的习惯是:先把语域判断、字符规范化和分词封装成一个流水线函数,再在流水线之后接业务逻辑,而不是每次临时写一遍预处理。这样既能保证不同脚本之间的结果一致,也方便在语料版本或接口变化时只改一处。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询