做波斯语NLP研究的人,最头疼的不是算法,而是数据。市面上英文、中文的新闻语料一抓一大把,但波斯语这种低资源语言,能找到的高质量公开数据集相当有限。我最近在整理文本分析实验素材时拿到了一份很有意思的数据集——33万+条伊朗地区波斯语新闻文本,时间跨度覆盖2016到2022年,每条数据都包含标题、摘要和正文,按社会、政治、经济、国际等领域做了分类标记。这篇文章就围绕这个数据集聊聊它的构成逻辑、适合做什么任务、落地NLP时有哪些需要处理的细节,以及我在实际跑实验时踩过的坑。
1. 波斯语数据集的稀缺性与其破局价值
1.1 为什么波斯语NLP领域特别缺数据
波斯语属于印欧语系,虽然使用人口接近一亿,但互联网上的数字化语料储备和英中这类大语种完全不在一个量级。更麻烦的是,波斯语本身的形态变化复杂,格标记、附着语素、复合动词非常普遍,加上口语和书面语的差异、阿拉伯语借词的大量混入,导致它不像英文那样“分词+词性标注”就能直接喂给模型。
想做波斯语NLP的人往往卡在第一步:找不到一份既能用于监督学习、又能在无监督任务里充当底料的数据集。新闻语料恰好是很好的选择——它接近标准书面语、文本结构清晰、话题分布广、自带时间戳。33万+条这个规模,对中小型实验来说算是一笔可观的资源:可以用来训练词向量,可以用来微调多语言预训练模型,也可以直接在分类和情感分析任务上做训练集。
从我的经验来看,波斯语的开源语料大多是散装爬出来的网页文本,混乱、重复、噪声大。这份数据集的吸引力在于它已经做过了基础整理,标题、摘要、正文分层存放,而且每条记录都有主题分类和时间字段,这在后续做过滤和抽样时省掉了很多力气。
1.2 跨文化传播研究为什么需要这种结构语料
语言数据不只是给自然语言处理用的,在传播学和计算社会科学领域,新闻语料也扮演重要角色。做跨文化传播分析的人,经常需要对比不同语言媒体在报道议题上的框架差异、关键词选择、话语结构。这件事的前提就是有一定体量、覆盖较长时段、带领域标签的平行结构语料。
传统的内容分析往往依靠人工抽样编码,样本量到几百上千条就很费人力了。配合33万条这种量级的语料,就有条件做“人工编码小样本 + 模型预测全量”的混合路线:先在抽样样本上建立议题框架的标注规则,然后让分类模型去打标,最后对整个语料库做历时趋势和结构分析。2016到2022年的时间跨度也很重要,这七年里能观察到的议题兴衰、情绪曲线、关键词演变是横截面数据给不了的。
另外,标题、摘要、正文三层拆分的设计对研究来说很友好:标题适合做短文本分类和速览分析,摘要在检索和文本摘要任务里是天然标注,正文则用来做深层的语义挖掘。三种粒度的文本放在一起,基本上把NLP里能玩的文本级别都覆盖了。
2. 数据集构成与结构设计:从字段到分类
2.1 核心字段:标题、摘要、正文的三层叙事逻辑
我拿到数据之后第一件事是把字段结构摸清楚。典型的一条记录大致是下面这个结构:
| 字段 | 类型 | 说明 |
|---|---|---|
| news_id | 字符串 | 全局唯一编号 |
| source | 字符串 | 来源媒体/站点标识 |
| category | 字符串 | 领域分类标签 |
| publish_time | 日期 | 发布时间,格式类似2019-03-12 |
| title | 字符串 | 新闻标题 |
| summary | 字符串 | 摘要或导语段落 |
| content | 字符串 | 完整新闻正文 |
把文本拆成三个粒度是这套数据最实用的设计之一。短文本实验用title,短小精悍,模型跑起来快;需要一点上下文就用title+summary;真正做语义分析、主题建模和长文本分类的时候再把content加进来。这个设计初看平平无奇,实际用起来非常顺手,因为很多公开数据集只给一段笼统的body,做评估的时候到底用什么粒度还得自己切,容易产生不一致。
还要注意source字段。虽然这份数据对来源做了匿名化处理,但保留来源标识对分析还是有用的——比如你想研究不同类型媒体在议题选择上的差异,或者想在验证阶段把同一来源的数据划到同一侧避免评估偏差,这个字段能帮上忙。
2.2 时间跨度与领域分类的设计逻辑
数据集覆盖2016至2022年,大致横跨七年。做时间序列性质的分析(比如热点话题演化、情感波动、关键词热度变化)至少需要三五年跨度才有说服力,几年以内的数据在时间维度上太薄,拟合不出趋势。
领域分类方面,常见的标记包括社会、政治、经济、国际,还有一些细颗粒度的子类。这里有个需要知道的点:新闻网站本身的分类标签通常是编辑给的,标准不一定稳定,有的站把体育和文化混在一起,有的站把科技归到社会。所以拿到分类标签后别直接当金标准用,先做一个标签一致性检查——抽样看分类准确率,重点排查边缘样本,比如它们到底是按主题分类还是按发稿栏目分类。
我在实际使用中习惯把它重映射成四到六个粗粒度大类,最多八类,再细分就很容易出现样本不均衡,有些小类甚至只有几千条,训练分类器时欠拟合得很明显。
2.3 规模把控与质量筛选的个人做法
33万+条听起来不少,但做深度分析之前还是得做一轮质量筛选。我通常按这几步走:
- 去除完全重复的标题,或者正文相似度超过阈值的记录(用SimHash或者MinHash做一次近似去重,比逐条算精确相似度快很多)。
- 过滤掉长度异常的记录。正文少于500个字符的内容往往只是图片集或转发链接,信息量不足;超长也没什么必要,一般截断到合理范围即可。
- 检查category字段的空值和错值,没有明确分类的记录我会单独标记为unknown,后续按需求决定是丢弃还是归到其他类。
- 对publish_time做一致性检查,避免出现明显乱序数据(比如晚于采集时间、年份严重偏离范围的记录)。
做一遍清洗之后,剩余的有效数据一般在30万条上下,具体取决于阈值怎么设。对绝大多数实验来说,30万条足够支撑BERT-base级别模型的微调,也足够把传统的TF-IDF、LDA这类方法跑出稳定结果。
3. 基于这套数据跑NLP的核心流程
3.1 第一步:文本清洗与标准化
波斯语文本的清洗比中文英文复杂一些,主要集中在字符标准化上。我在项目里维护了一个标准的预处理流水线,核心是处理下面几个问题:
- 统一阿拉伯语借词中的字符变体。波斯语虽然用阿拉伯字母体系,但一些字符的写法跟阿拉伯语不一样,比如波斯语的“ک”(U+06A9)和“ي”(U+06CC),经常会被网页错误地写成阿拉伯语的“ك”(U+0643)和“ى”(U+0649),需要替换成波斯语形式。
- 处理零宽非连接符。波斯语里的ZWNJ(U+200C)看起来像空格但在字符层面是有意义的存在,比如“میروم”中间的连字符不能简单去掉。但很多爬虫抓来的文本会在不该出现的地方多出ZWNJ,还经常因为编码转换变成不可见字符。我一般会保留某些合法形态,但对连续重复的ZWNJ做收缩。
- 移除HTML残留、URL、控制字符和异常标点,同时把emoji和特殊符号替换成占位符或者直接移除,具体取决于下游任务是否需要保留表情信息。
下面是我常用的一个标准化函数,算是操作中最基础的一块:
import re def normalize_persian_text(text): # 统一波斯语字符变体 text = text.replace('ك', 'ک').replace('ي', 'ی') text = text.replace('ى', 'ی').replace('ؤ', 'و').replace('ئ', 'ی') # 移除多余控制字符与HTML残留 text = re.sub(r'<[^>]+>', ' ', text) text = re.sub(r'[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]', '', text) # 将各种空白统一为空格 text = re.sub(r'\s+', ' ', text) # 修复ZWNJ出现位置异常的问题 text = re.sub(r'\u200c+', '\u200c', text) text = re.sub(r' \u200c', ' ', text) text = re.sub(r'\u200c ', ' ', text) # 移除首尾空白 text = text.strip() return text标准化对后续所有任务的提升是隐性的,不做的话,分词、向量化、聚类都会因为同一个词的不同字符形态而各干各的。
3.2 第二步:分词与词形还原
波斯语分词是个明确的难点。英语按空格和标点拆基本够用,波斯语不行——很多词内组合由空格或ZWNJ连接,但语义上是一个整体。比如复合动词“کار کردن”(干活),两个词之间有空隙,句法上必须看作整体;而问题在于,名义上是一体的词,有时候也带空格。没有统一标准。
常用的波斯语NLP工具里,Hazm和Parsivar是绕不开的名字。Hazm集成度高,包含了词性标注、词干还原、依存句法分析这些模块,尤其适合做标准处理流;Parsivar的优点是分词规则更细,在句法边界处理上表现更好。我做综合实验的时候一般先用Hazm跑一遍预处理,遇到分词结果明显不对的情况再用Parsivar替换单个步骤。
用Hazm做分词和词形还原的示例:
from hazm import Normalizer, WordTokenizer, Lemmatizer normalizer = Normalizer() tokenizer = WordTokenizer() lemmatizer = Lemmatizer() text = "او در این زمینه سالها تجربه دارد" text = normalizer.normalize(text) tokens = tokenizer.tokenize(text) lemmas = [lemmatizer.lemmatize(token) for token in tokens]这里有个经验是,lemmatizer处理复合动词时经常给出不可预期的结果,建议先跑一批样本人工看一遍,如果发现词形还原对下游任务帮助不大,可以直接关掉。文本分类和主题建模对词形还原并不敏感,反倒是在信息检索和精确匹配场景里更有用。
3.3 第三步:数据集划分与任务评估策略
做监督任务时,数据集怎么划分是个容易被忽略但影响很大的问题。如果新闻数据在时间上是连续采集的,随机打乱划分会带来时间泄漏——同一报道事件的内容既出现在训练集又出现在测试集,模型其实在“背答案”。
我建议这样处理:
- 按时间划分。比如用前五年的数据做训练,后两年的数据做验证和测试。这样做评估的是模型对未来新闻的泛化能力,更贴近真实应用场景。
- 如果任务和具体事件强相关,尽量对相近时间的文本做分组处理,避免同一天或同一事件的多条报道被拆散到两个集合里。
- 分类任务里如果类目不均衡,可以在训练时做加权采样,或者用StratifiedSplit保证每个集合的类别比例一致。
文本分类、情感分析、命名实体识别是这个数据集上最容易上手的标定任务。33万条记录按8:1:1划分,大约是26万训练、3.5万验证、3.5万测试,这个规模足够让模型学到稳定的模式。
3.4 第四步:跨语言研究接口的搭建
如果目标是做跨文化传播分析,光有波斯语数据还不够,通常需要跟英文或其他语言的数据做对照。许多研究者会在这一层做翻译对齐,把这份语料的子集翻译成英文,或者用多语言预训练模型实现跨语言句子嵌入。
多语言嵌入是成本相对低的方案,直接用多语言BERT系列模型把两边的句子映射到同一个语义空间,再做相似度计算和聚类。优点是省翻译费且适合大规模数据,缺点是语义对齐精度有限,特别是波斯语这种形态丰富的语言,多语言模型的表示容易偏保守。
如果你对对齐精度要求高,就得好好选择翻译策略。我自己的经验是,先把标题翻译成目标语言,拿去做初步筛选,再对筛选后的子集做正文翻译。标题翻译成本低,筛掉的文本就不需要翻译正文了。
4. 典型应用场景与挖掘方法
4.1 主题演化与热点趋势追踪
这种带时间字段的大规模新闻语料,做主题演化是最能出成果的方向之一。传统做法是LDA加滑窗主题强度计算,近几年的新版本是BERTopic这一类方法。
用LDA时,我的建议是先做一轮合理的预处理,包括去掉太常见和太罕见的词,控制特征维度,然后对整个语料先训练一个初步模型确定主题数量,再按年份切片统计主题比例变化。这个过程比想象中琐碎,因为主题数量不是拍脑袋定的,一般要跑一个主题一致性实验。
用BERTopic的操作也类似,但它更依赖嵌入模型,需要把每篇文档转成向量再做聚类。33万条文本全部过嵌入模型虽然耗时,但在单卡GPU上跑一个晚上还是能接受的。BERTopic里有一个核心参数是min_topic_size,太大会漏掉细粒度话题,太小又会生成一堆噪声主题。我常用的策略是先跑到默认值,观察主题数量,再逐步调参。
4.2 情感分析与社会情绪趋势测量
情感分析在波斯语里没有中文英文那么成熟,公开的标注集规模普遍偏小。这个数据集本身没有情感标签,需要自己构建训练集。我用的是“小样本人工标注+半监督扩展”的方法:先随机抽取一小部分文本人工标注情感极性(正面、负面、中性),再把标注过的数据作为种子训练一个弱分类器,然后用它在更大样本上预测,挑出置信度高的样本加进训练集,迭代几轮。
这里面有个需要警惕的坑:新闻正文的情感表达比较隐晦,模型在标题上表现还行,一到正文就崩。我的对策是把标题和正文结合作为训练输入,把标题作为语义聚焦器,正文作为上下文补充。结合之后,分类准确率比只用正文高出不少。
情感分析结果还可以配合时间维度做成情绪曲线,比如逐年或逐月统计正负情感比,观察整体情绪是上行还是下行。这类趋势数据对传播学研究或商业舆情监测都有一定参考价值。
4.3 媒体话语与跨文化传播框架分析
跨文化传播分析不能只停留在感情色彩层面,更常见的是“框架分析”。框架分析的基本思路是看媒体在报道同一个议题时,是如何定义问题、归因责任、给出解决方案的。每个维度对应一组语言线索,然后可以通过关键词共现、句法模式、词频对比来量化。
考虑到33万条数据不可能全部人工编码,我采用的工作流是:
- 选定某个研究议题,筛出相关报道子集。
- 从子集中随机抽取数百条,按框架维度人工编码。
- 用编码结果训练一个文本分类模型,自动给剩下的文本打上框架标签。
- 在不同时间段和不同来源媒体之间统计框架分布的差异。
这个流程里最花时间的是人工编码阶段,因为框架类别本身有模糊地带,比如“问题定义”和“归因”在文本里经常纠缠在一起。我建议编码规范里给每个类别配上正反例,减少标注不一致。
4.4 命名实体识别与传播网络挖掘
如果想把数据用得更深入一步,可以试命名实体识别。波斯语NER的难点不只在标注数据稀缺,还在于外来音译实体非常多,比如阿拉伯语人名、英语机构名的波斯语音译形式,这些词的拼写变体多得离谱。
实践中可以先用预训练的多语言NER模型打一轮“伪标签”,然后人工修正部分样本,把修正后的数据作为训练集去微调模型。这种方法虽然粗放,但在低资源语言上屡试不爽。
有了实体信息,就能做简单的传播网络分析:提取每篇新闻里出现的人物、机构、地点,建立共现关系网络;配合时间维度,可以看到某个议题涉及的实体集合如何变化,谁一直是高频主角,谁在特定时间昙花一现。这种分析做起来不复杂,但如果能结合领域背景去解释,就是一篇很好的研究报告。
5. 常见问题与排查技巧
5.1 RTL显示、编码和ZWNJ的隐性坑
波斯语是右到左文字,这在数据处理阶段不太碍事,放到展示层就麻烦。比如你在Jupyter里打印几万条文本,看起来可能是乱序的,因为终端没有正确处理bidi(双向文本)算法。解决方法是输出到带RTL支持的编辑器或使用HTML渲染,而不是直接硬调打印。
编码问题最常见的是文本来自不同采集管道时混入Windows-1256和UTF-8,外部工具导出后中文环境打开直接变乱码。规范做法是一律转成UTF-8,并在入库前做一次编码探测。
ZWNJ这个字符很特殊,肉眼看不见,却在分词、搜索、去重里反复捣乱。很多波斯语分词算法对ZWNJ的处理逻辑都不一样,有的把它看成空格,有的保留原样。我的建议是,在跑分词之前先用统一的标准化规则处理ZWNJ,并且全程记录规则版本,否则换了环境结果很容易对不上。
5.2 分类标签噪声与弱监督
新闻网站的领域标签不等于严谨的训练标签,比如某条体育新闻可能因为涉及经济议题被标到经济类,某条文化报道可能被塞进社会类。标签错位会导致模型学到的“规则”混乱,而且人工抽样看起来准确率很高,实际一上线效果就掉。
对付标签噪声的办法是弱监督清洗:用一个在干净子集上训练的模型去预测全量数据,然后找出预测结果与原始标签不一致的样本,集中人工复核。通常主要标签类别之间的混淆经过人工复核后修正率很高,修正之后再重新训练,精度会有明显提升。
5.3 预训练模型适配与显存控制
微调多语言预训练模型是这个数据集上跑SOTA任务最现实的路径,但显存是很大的制约。我之前在单卡上用BERT-base做分类训练,batch size稍大就显存溢出。后来切成了梯度累积的方式,batch size设为16,梯度累积4步,等效batch size为64,损失函数日志看起来平滑了很多。
另一个细节是处理长文本。新闻正文动不动就上千词,直接塞给BERT-base是不现实的。一般做法是只取前512个token,或者对正文做摘要抽取后再输入模型。标题+摘要的组合往往比截断正文更有效。
5.4 时间漂移的处理
语义变化是语言数据里普遍存在的现象,2016年的报道用词和2022年差别很大。如果在全量数据上训练模型,再用它预测最近的数据,效果可能比预期差一截。解决这个问题的通用思路是给近期样本更高的采样权重,或者按时间做分段模型。如果任务对时间敏感,建议保留验证集的时间戳信息,方便追踪模型在不同年份子集上的表现变化。
5.5 常见问题速查表
| 问题 | 现象 | 排查方向 |
|---|---|---|
| 文本显示乱序 | 打印时字序颠倒 | 检查RTL渲染,使用bidi算法处理显示 |
| 词向量效果差 | 相似词匹配不准 | 检查字符标准化是否完整,是否混入变体符号 |
| 分类结果异常 | 预测标签集中在少数类别 | 检查类别权重、标签噪声、训练集是否均衡 |
| 主题模型噪声大 | 生成的Topic语义不清晰 | 调整停用词、降低min_topic_size或增大特征过滤 |
| 模型显存溢出 | 训练过程中OOM | 调小batch size,使用梯度累积,截断长文本 |
| 跨语言对齐偏离 | 双语检索效果差 | 评估不同多语言模型的表示质量,优先选择对齐能力强的版本 |
| 预测近期数据效果差 | 时间越近准确率越低 | 增加近期样本权重,或使用时间感知的模型结构 |
6. 复盘:做这套数据处理时的几点经验
如果让我重新做一遍,最想调整的部分是数据清洗流程的模块化。一开始我为了省事把标准化、去重、分词写在一个脚本里,后来要改清洗规则、重跑实验的时候,整条链路都被迫重新跑一遍,非常浪费时间。正确的做法是把每步存成中间格式,清洗完存一份,分词完存一份,这样每一步都只依赖上一步的产物,想换某个环节不会导致全链路重跑。
另外一个体会是,语料数据的价值不在于“拥有”,而在于“怎么拆和怎么用”。同样是30万条新闻,你可以拿来训练分类模型,也可以拆出子集做历时分析,还可以搭配跨语言嵌入做传播对比。应用的深度取决于你愿意花多少时间去研究这堆文本的结构和语义,而不是单纯拿过来跑个模型发篇文章就完事。
最后一个小建议:由于波斯语数据集的特殊性,建议自己建立一个小型的高质量标注池,几百条就行,用来做快速评估和模型诊断。每次改了预处理规则或者换新模型,先拿这个标注池跑一遍,能迅速判断改动是让效果变好还是变差。做低资源语言的NLP项目,细节决定成败,很多看起来奇怪的问题到最后都是某个字符或者某条清洗规则在背后捣鬼。