支持私有化部署
AI知识库

53AI知识库

学习大模型的前沿技术与行业应用场景


spaCy中文分句模型微调秘籍,从数据准备到模型评测,一学就会!

发布日期:2025-07-01 07:30:34 浏览次数: 1518
作者:筱可AI研习社

微信搜一搜,关注“筱可AI研习社”

推荐语

掌握spaCy中文分句的核心技术与微调方法,从基础规则到深度学习模型,全面提升文本处理效率。

核心内容:
1. spaCy三大中文分句方案对比与适用场景分析
2. 从数据准备到模型训练的完整微调流程详解
3. 分句技术在RAG等复杂场景中的扩展应用

杨芳贤
53AI创始人/腾讯云(TVP)最具价值专家

spaCy中文分句模型微调秘籍,从数据准备到模型评测,一学就会

🎯 文章目标

  • 认识 spaCy 中文分句的三大方案(Sentencizer、senter、DependencyParser)及其适用场景
  • 理解分句背后的原理、工程实现与模型结构(CNN/Transformer)
  • 学会从数据准备、格式转换、模型训练到推理与评估的完整流程
  • 掌握如何根据实际需求选择合适的 pipeline 组件和底层结构
  • 拓展分句思想到 RAG 等复杂场景,实现高质量文本分块与智能预处理

本次文章配套代码地址:https://github.com/li-xiu-qi/XiaokeAILabs/tree/main/datas/spacy_finetune

后续可能会微调一个专门用来分块的模型,感兴趣的同学欢迎联系我或者给项目一个star哦,仓库地址:https://github.com/li-xiu-qi/spacy_chuking

📋 目录


  • 🎯 文章目标
  • 📋 目录
  • 🚁 前言
  • 🧠 spaCy句子分割理论与工程基础补充
    • 1. 句子边界检测(SBD)在NLP中的基础性作用
    • 2. spaCy三大句子分割策略对比
  • 🗺️ 整体流程概览
  • 🪄 微调难度说明
  • 🛠️ 环境准备
  • 📑 数据准备与格式说明
  • 🔄 jsonl 转 spaCy 格式脚本
    • 🖨️ 读取 .spacy 文件并打印每个 doc 的句子分割结果
  • ⚙️ 配置文件初始化
  • 🏋️‍♂️ 训练模型
    • 基于默认的模型进行训练
    • 🤖 使用 Transformer 训练
  • 🧪 模型推理与效果展示
  • 📈 微调效果与评估
  • 🧩 RAG 分块扩展思考:从分句到智能分块
    • 分块的核心思想
    • 实现路径
    • 📚 不同模型结构的上下文感知能力说明
  • 🏁 总结与展望
  • 📚 往期精选


🚁 前言

在实际 NLP 项目中,中文分句往往是文本处理的第一步,但 spaCy 默认的分句效果在面对特殊标点、缩写、省略号、对话等复杂情况时,常常难以满足高精度需求。很多同学会问:spaCy 到底有哪些分句方案?如何选择最适合自己的分句方式?能不能既高效又智能地实现分句,甚至扩展到更复杂的文本分块?

本教程将系统梳理 spaCy 的分句原理与工程实现,带你从最基础的规则分句(Sentencizer)、到可训练的 senter 组件、再到依存句法驱动的 DependencyParser,逐步深入每种方案的优缺点和适用场景。你不仅能学会如何微调 spaCy 分句模型,还能理解不同模型结构(CNN/Transformer)的本质区别,掌握如何根据实际需求灵活选择 pipeline 组件。更重要的是,本文还将带你思考如何将分句思想扩展到 RAG 等智能分块场景,助力你的文本处理系统更上一层楼。

🧠 spaCy句子分割理论与工程基础补充

1. 句子边界检测(SBD)在NLP中的基础性作用

句子边界检测(Sentence Boundary Detection, SBD)是自然语言处理(NLP)中的基础模块,其准确性直接影响分词、词性标注、依存句法、命名实体识别等下游任务。SBD的难点在于标点符号的歧义性(如缩写、小数点、省略号等),这使得“遇到句号就分割”远远不够,必须结合上下文、词性、大小写等多维特征。现代NLP通常将SBD视为一个分类问题,针对每个潜在边界点,利用统计学习或神经网络模型进行判别。

SBD主流方法简述

  • 早期:基于规则(正则表达式、标点列表)
  • 统计学习:HMM、最大熵、CRF等
  • 深度学习:RNN、Transformer等
  • spaCy的senter和parser组件正是吸收了这些主流思想的产物

多语言与领域适应性

不同语言和专业领域(如医学、法律、金融)对SBD有不同挑战。spaCy支持多语言和可定制微调,能适应多样化场景。

2. spaCy三大句子分割策略对比

组件
可训练
速度
准确率
依存信息
适用场景
DependencyParser
需要依存句法的复杂NLP任务
senter
只需分句,需高精度
Sentencizer
极快
低-中
规则明确、格式规范的文本
  • DependencyParser:基于依存句法,最强大但最慢,适合需要句法树的场景。
  • senter:专用分句器,速度快、精度高,适合只需分句的任务。
  • Sentencizer:基于规则,极快但不智能,适合格式规范文本。

在实际应用中,Sentencizer 是 spaCy 提供的基于规则的分句器,它无需任何训练和标注数据,只依赖简单的标点规则(如句号、问号、感叹号等)进行分句,非常适合规则明确、对分句精度要求不高的场景。你只需在 pipeline 里加上 sentencizer 组件即可直接使用。例如:

import spacy
nlp = spacy.blank("zh")
nlp.add_pipe("sentencizer")
doc = nlp("今天天气很好。我想去公园。你呢?")
for sent in doc.sents:
    print(sent.text)

Sentencizer 开箱即用,无需训练,适合快速分句需求。

选择建议:

  • 需要依存句法分析时用DependencyParser
  • 只需高精度分句时用senter
  • 追求极致速度且文本结构简单时用Sentencizer
  • 本文教程聚焦于senter分句器的训练与微调,未涉及DependencyParser的训练。原因在于:
    • senter组件专为句子边界检测设计,训练和部署都更为高效,适合大多数只需分句的NLP场景。
    • DependencyParser不仅进行分句,还会进行完整的句法依存分析,适用于需要深入理解句法结构的复杂任务(如关系抽取、深层语义分析等),其训练和推理资源消耗更大。
    • 对于一般的文本分句需求,senter已能满足高精度、快速分割的要求,无需引入更复杂的依存分析。

在 spaCy 的 senter 组件中,默认采用的是 CNN(如 HashEmbedCNN)结构实现。你也可以通过修改 config 文件,切换为 transformer 或其他结构,但默认就是 CNN。CNN 结构需配置 window_size,决定每个 token 能看到的上下文范围;而 transformer 结构无需配置窗口,能自动建模全局上下文(只受最大 token 长度限制)。如果你的分句任务主要依赖于局部标点和短距离上下文,CNN结构即可满足需求且效率高;如果文本结构复杂、长距离依赖明显,或希望获得更强的泛化能力,建议优先尝试Transformer结构。

此外,spaCy 的 senter 或 transformer+senter 组件在推理时,是将整段文本(如一整个句子或段落)分词后,作为一个整体输入模型,而不是每次只输入几个单词。spaCy 会自动处理超长文本的切分,最大 token 数由 transformer 模型决定(如 512)。这意味着 spaCy senter/transformer 是“整段文本分词后一起输入模型”,不是每次只输入3或5个词(窗口大小)。

parser 组件的分句原理

当 pipeline 里有 parser(依存句法分析器)时,spaCy 会先预测每个 token 的依存关系,构建依存树,再结合规则自动判断句子边界(如遇到 ROOT 节点、特定标点等)。这种方式能处理复杂句法结构,比单纯靠标点分句更智能。例如:“他说:你去吧,然后回来。”,parser 能识别“你去吧”是独立分句,即使没有明显句号。

spaCy 默认的句法依存分析器(parser)最早是基于 CNN+特征嵌入(HashEmbedCNN)和动态过渡系统(transition-based parser)实现的。但在新版 spaCy(v3 及以后),parser 也可以用 transformer 作为特征提取器(tok2vec),即“transformer+transition-based parser”结构。你可以在 config 文件里选择 tok2vec 用 CNN 还是 transformer。

总结来说,spaCy 的依存分析器既可以用 CNN,也可以用 transformer,取决于你的配置。

如果你用 transformer+senter 进行句子分割,通常不需要再依赖句法依存分析(parser)来分句。transformer+senter 已经能利用全局上下文和强大的特征表达能力,通常分句效果比传统 CNN 更好,适合复杂文本。senter 组件专门为句子边界识别而设计,训练目标就是分句,不依赖依存结构。只有在你还需要做依存句法分析(比如后续要用依存树做信息抽取、关系抽取等任务)时,才需要加 parser 组件。

🗺️ 整体流程概览

  1. 🛠️ 环境准备
  2. 🖊️ 数据准备与标注
  3. 🔄 数据格式转换
  4. ⚙️ 配置文件初始化
  5. 🏋️‍♂️ 模型训练(标准/Transformer)
  6. 🧪 模型推理与效果展示

🪄 微调难度说明

微调 spaCy 的句子分割器相对简单,主要流程包括数据准备、配置文件初始化、模型训练和评估。spaCy 已经为我们封装了大部分底层实现细节。我们只需准备好特定格式的数据(如 jsonl 或 spacy 格式),并通过简单的配置文件指定训练参数,无需手动编写复杂的深度学习训练代码。整个微调流程高度自动化,极大降低了上手门槛,下面从环境配置开始,注意不要使用过高的python版本,否则会导致一些库的兼容问题。

🛠️ 环境准备

建议使用 Python 3.10 及以下版本。推荐新建虚拟环境,并安装 spaCy 及相关依赖:

python -m venv .venv
source .venv/bin/activate
pip install -r tests/requirements.txt

📑 数据准备与格式说明

训练数据需为 spaCy 支持的 jsonl 或 spacy 格式。每条数据应包含文本及句子边界标注。

具体可以参考下面的示例:

{"text""今天天气很好。我想去公园。""tokens": ["今天天气""很""好""。""我""想""去""公园""。"], "sent_starts": [truefalsefalsefalsetruefalsefalsefalsefalse]}
{"text""小明喜欢看书,也喜欢运动。你呢?""tokens": ["小明""喜欢""看书"",""也""喜欢""运动""。""你""呢""?"], "sent_starts": [truefalsefalsefalsefalsefalsefalsefalsetruefalsefalse]}

这里的数据格式是 spaCy 句子分割器(senter)训练所需的 jsonl 格式,每一行为一个 JSON 对象,包含两个字段:

  • text:原始文本字符串。
  • sent_starts:一个与文本中每个 token 对应的布尔值列表,标记每个 token 是否为句子的起始(true 表示该 token 是一个新句子的开头,false 表示不是)。

举例说明:

{"text": "今天天气很好。我想去公园。", "tokens": ["今天天气", "很", "好", "。", "我", "想", "去", "公园", "。"], "sent_starts": [true, false, false, false, true, false, false, false, false]}

字段解释:

  • text:原始文本字符串,即需要分句的完整句子。
  • tokens:对原始文本进行分词后的结果,是一个字符串列表,每个元素为一个 token(词或符号)。
  • sent_starts:与 tokens 等长的布尔值列表,标记每个 token 是否为句子的起始。true 表示该 token 是新句子的开头,false 表示不是。

例如,上面例子中:

  • "今天天气" 对应 true,表示是第一个句子的开头;
  • "我" 对应 true,表示是第二个句子的开头;
  • 其余 token 对应 false,表示不是句子起始。

这样 spaCy 就能根据 sent_starts 信息,学习如何在中文文本中正确分句。


🔄 jsonl 转 spaCy 格式脚本

如果你需要将上述 jsonl 格式的数据转换为 spaCy 所需的 .spacy 格式,可以参考如下脚本:

import spacy
from spacy.tokens import Doc, DocBin
import json

nlp = spacy.blank("zh")
doc_bin = DocBin()

with open("senter_train.jsonl""r", encoding="utf-8"as f:
    for line in f:
        example = json.loads(line)
        tokens = example["tokens"]
        sent_starts = example["sent_starts"]
        if len(tokens) != len(sent_starts):
            raise ValueError(f"tokens 数与 sent_starts 不一致: {tokens}\nsent_starts: {sent_starts}")
        doc = Doc(nlp.vocab, words=tokens)
        for i, token in enumerate(doc):
            token.is_sent_start = sent_starts[i]
        doc_bin.add(doc)

doc_bin.to_disk("senter_train.spacy")
print("已生成 senter_train.spacy ")

脚本会读取 jsonl 文件,生成 spaCy 训练所需的二进制数据文件(.spacy)。其作用是:将标注了句子起始信息的 jsonl 格式数据,转换为 spaCy 训练所需的二进制格式(.spacy 文件),用于训练 spaCy 的句子分割器(senter)。

Doc 是 spaCy 的核心数据结构之一,表示经过分词和注解的一段文本。本质上是一个“文档对象”,包含分词后的 token 列表及各种语言学特征(如词性、句法、实体等)。

在本脚本中,Doc 对象的创建方式如下:

doc = Doc(nlp.vocab, words=tokens)

nlp.vocab:当前语言环境的词汇表(vocab),支持 spaCy 的各种词汇特性。words=tokens:分词后的 token 列表(字符串列表),每个元素对应文本中的一个 token。

这样创建的 Doc 对象包含了自定义的分词结果,后续可为每个 token 设置句子起始标记(is_sent_start),用于训练 spaCy 的句子分割模型。

DocBin 是 spaCy 提供的高效二进制序列化工具,用于批量存储和管理多个 Doc 对象。它可将大量 Doc 文档对象打包成一个二进制文件(如 .spacy),便于模型训练和分发。

在本脚本中,DocBin 对象的创建方式如下:

doc_bin = DocBin()

每处理好一个 Doc,通过 doc_bin.add(doc) 加入容器。最后用 doc_bin.to_disk("senter_train.spacy") 一次性保存为 spaCy 训练所需的二进制格式文件。

🖨️ 读取 .spacy 文件并打印每个 doc 的句子分割结果

你可以用下面的代码快速验证 .spacy 文件中的分句效果:

import spacy
from spacy.tokens import DocBin

# 读取 .spacy 文件并打印每个 doc 的句子分割结果
nlp = spacy.blank("zh")
doc_bin = DocBin().from_disk("senter_train.spacy")
docs = list(doc_bin.get_docs(nlp.vocab))

for i, doc in enumerate(docs):
    print(f"Doc {i+1}:")
    for sent in doc.sents:
        print(f"  句子: {sent.text}")
    print("-"*20)

输出:

Doc 1:
  句子: 今天天气 很 好 。
  句子: 我 想 去 公园 。
--------------------
Doc 2:
  句子: 小明 喜欢 看书 , 也 喜欢 运动 。
  句子: 你 呢 ?
--------------------

这样可以直接看到每个 doc 的分句效果,方便检查数据标注和格式是否正确。

在上面代码中,get_docs(nlp.vocab) 的作用是:

  • doc_bin.get_docs(nlp.vocab) 会根据你传入的 nlp.vocab(即 spaCy 的词汇表对象),把二进制的 .spacy 文件内容还原为一个个 Doc 文档对象。
  • 由于 spaCy 的 DocBin 序列化时只保存了 token 信息等内容,反序列化时需要传入当前的 Vocab(词汇表)来正确构建每个 Doc
  • 这样你就可以像平时一样遍历、分析这些 Doc,比如用 doc.sents 获取分句结果。

简而言之,get_docs(nlp.vocab) 就是“用当前的词汇表,把二进制数据还原成 spaCy 的文档对象列表”,是 spaCy 官方推荐的标准用法。

⚙️ 配置文件初始化

spaCy 训练依赖 config 文件。推荐用命令自动生成:

python -m spacy init config config.cfg --pipeline senter --lang zh
  • python -m spacy init config:初始化 spaCy 配置文件。
  • config.cfg:输出的配置文件名。
  • --pipeline senter:只训练句子分割(senter)组件。
  • --lang zh:指定语言为中文。

📝 config 文件定义了训练流程、模型结构、超参数,更多可选的参数请看官方文档等。

运行后会输出一个内容大概是这样的config.cfg文件:

[paths]
train = null
dev = null
vectors = null
init_tok2vec = null

[system]
gpu_allocator = null
seed = 0

[nlp]
lang = "zh"
pipeline = ["senter"]
batch_size = 1000
disabled = []
before_creation = null
after_creation = null
after_pipeline_creation = null
vectors = {"@vectors":"spacy.Vectors.v1"}

[nlp.tokenizer]
@tokenizers = "spacy.zh.ChineseTokenizer"
segmenter = "jieba"

[components]

[components.senter]
factory = "senter"
overwrite = false
scorer = {"@scorers":"spacy.senter_scorer.v1"}

[components.senter.model]
@architectures = "spacy.Tagger.v2"
nO = null
normalize = false

[components.senter.model.tok2vec]
@architectures = "spacy.HashEmbedCNN.v2"
pretrained_vectors = null
width = 12
depth = 1
embed_size = 2000
window_size = 1
maxout_pieces = 2
subword_features = true

[corpora]

[corpora.dev]
@readers = "spacy.Corpus.v1"
path = ${paths.dev}
max_length = 0
gold_preproc = false
limit = 0
augmenter = null

[corpora.train]
@readers = "spacy.Corpus.v1"
path = ${paths.train}
max_length = 0
gold_preproc = false
limit = 0
augmenter = null

[training]
dev_corpus = "corpora.dev"
train_corpus = "corpora.train"
seed = ${system.seed}
gpu_allocator = ${system.gpu_allocator}
dropout = 0.1
accumulate_gradient = 1
patience = 1600
max_epochs = 0
max_steps = 20000
eval_frequency = 200
frozen_components = []
annotating_components = []
before_to_disk = null
before_update = null

[training.batcher]
@batchers = "spacy.batch_by_words.v1"
discard_oversize = false
tolerance = 0.2
get_length = null

[training.batcher.size]
@schedules = "compounding.v1"
start = 100
stop = 1000
compound = 1.001
t = 0.0

[training.logger]
@loggers = "spacy.ConsoleLogger.v1"
progress_bar = false

[training.optimizer]
@optimizers = "Adam.v1"
beta1 = 0.9
beta2 = 0.999
L2_is_weight_decay = true
L2 = 0.01
grad_clip = 1.0
use_averages = false
eps = 0.00000001
learn_rate = 0.001

[training.score_weights]
sents_f = 1.0
sents_p = 0.0
sents_r = 0.0

[pretraining]

[initialize]
vectors = ${paths.vectors}
init_tok2vec = ${paths.init_tok2vec}
vocab_data = null
lookups = null
before_init = null
after_init = null

[initialize.components]

[initialize.tokenizer]
pkuseg_model = null
pkuseg_user_dict = "default"

🏋️‍♂️ 训练模型

基于默认的模型进行训练

训练集和验证集都用同一份数据,因为我们这里是示例:

python -m spacy train config.cfg \
  --paths.train data/senter_train.spacy \
  --paths.dev data/senter_train.spacy \
  --output output_model
  • python -m spacy train config.cfg:用 config 文件启动训练。
  • --paths.train:训练集路径。
  • --paths.dev:验证集路径。
  • --output:输出模型目录。

训练过程会输出日志,最终模型在 output_model/model-best

🤖 使用 Transformer 训练

如果你希望用 transformer 结构进行句子分割微调,可以使用如下命令(需准备好 transformer 配置文件 config_trf.cfg)。

使用 GPU 训练:

python -m spacy train config_trf.cfg \
  --gpu-id 0 \
  --paths.train data/senter_train.spacy \
  --paths.dev data/senter_train.spacy \
  --output output_model_trf

使用 CPU 训练:

python -m spacy train config_trf.cfg \
  --paths.train data/senter_train.spacy \
  --paths.dev data/senter_train.spacy \
  --output output_model_trf
  • config_trf.cfg:基于 transformer 的 spaCy 配置文件。
  • --gpu-id 0:指定使用第 0 块 GPU 训练(如无 GPU 可省略)。
  • 其余参数同上。

📝 Transformer 配置文件示例

下面是一个 spaCy Transformer 配置文件(config_trf.cfg)示例,适用于中文句子分割任务,并附有详细注释:

# [paths] 部分用于定义关键文件的路径,在训练时通过命令行传入。
[paths]
train = null
dev = null
vectors = null
init_tok2vec = null

# [system] 部分用于配置训练的硬件和可复现性。
[system]
# gpu_allocator: 设置使用的GPU内存分配器。
# "pytorch" 是推荐值。设为 null 或 none 则使用CPU。
gpu_allocator = pytorch
# seed: 随机种子,用于确保每次训练结果的可复现性。
seed = 0

# [nlp] 部分是流水线(pipeline)的核心配置。
[nlp]
lang = "zh"
# pipeline: 定义组件的处理顺序。这里,文本会先经过 transformer 组件,
# 然后其输出会作为 senter 组件的输入。顺序至关重要。
pipeline = ["transformer", "senter"]
# batch_size: 一次处理的文本数量。可根据内存大小调整。
batch_size = 1000
disabled = []
before_creation = null
after_creation = null
after_pipeline_creation = null

[nlp.tokenizer]
# @tokenizers: 指定中文分词器。
@tokenizers = "spacy.zh.ChineseTokenizer"
segmenter = "jieba"

# ===================================================================
#                          组件 (Components) 配置
# ===================================================================
[components]

# [components.transformer] 定义了 Transformer 组件本身。
[components.transformer]
factory = "transformer"

[components.transformer.model]
@architectures = "spacy-transformers.TransformerModel.v3"
# name: 指定要使用的 Hugging Face Hub 上的预训练模型。
# 例如 "bert-base-chinese" 或 "hfl/chinese-roberta-wwm-ext"。
# spaCy 会在首次运行时自动下载。
name = "bert-base-chinese"
tokenizer_config = {"use_fast": true}

# [components.senter] 定义了句子切分器 (Sentence Recognizer)。
[components.senter]
factory = "senter"
overwrite = false
scorer = {"@scorers":"spacy.senter_scorer.v1"}

[components.senter.model]
@architectures = "spacy.Tagger.v2"
nO = null
normalize = false

[components.senter.model.tok2vec]
# @architectures: 指定 tok2vec (token-to-vector) 的架构。
# "TransformerListener" 是一个特殊组件,它本身不产生向量,
# 而是“监听”并直接使用上游 "transformer" 组件生成的、带有上下文信息的向量。
@architectures = "spacy-transformers.TransformerListener.v1"
grad_factor = 1.0
# pooling: 定义如何将一个词的多个子词(word-piece)的向量聚合成一个单一向量。
# "reduce_mean.v1" 表示使用平均值策略。
pooling = {"@layers":"reduce_mean.v1"}

# ===================================================================
#                         语料库 (Corpora) 配置
# ===================================================================

[corpora]

[corpora.dev]
@readers = "spacy.Corpus.v1"
path = ${paths.dev}
max_length = 0
gold_preproc = false
limit = 0
augmenter = null

[corpora.train]
@readers = "spacy.Corpus.v1"
path = ${paths.train}
max_length = 0
gold_preproc = false
limit = 0
augmenter = null

# ===================================================================
#                          训练 (Training) 配置
# ===================================================================
[training]
dev_corpus = "corpora.dev"
train_corpus = "corpora.train"
seed = ${system.seed}
gpu_allocator = ${system.gpu_allocator}
# dropout: 在训练期间随机丢弃一部分神经元,是防止过拟合的常用技巧。
dropout = 0.1
# accumulate_gradient: 梯度累积。如果显存不足,可设为 >1 的整数(如2, 4)。
# 这会用更多训练时间来换取更低的显存消耗。
accumulate_gradient = 1
# patience: 早停机制。如果在连续 1600 步内模型性能都没有提升,训练将自动停止。
patience = 1600
max_epochs = 0
# max_steps: 最大训练步数。若设为 0,则由 max_epochs 控制训练长度。
max_steps = 20000
# eval_frequency: 每训练多少步,就在开发集上评估一次性能。
eval_frequency = 200
frozen_components = []
annotating_components = []
before_to_disk = null
before_update = null

[training.batcher]
# @batchers: 定义如何将数据打包成批次 (batch)。
# "spacy.batch_by_words.v1" 会尽量让每个批次包含差不多数量的词。
@batchers = "spacy.batch_by_words.v1"
discard_oversize = false
tolerance = 0.2
get_length = null

[training.batcher.size]
# @schedules: 定义批次大小的变化策略。
# "compounding.v1" 表示批次大小从 start 值平滑增长到 stop 值。
@schedules = "compounding.v1"
start = 100
stop = 1000
compound = 1.001
t = 0.0

[training.logger]
@loggers = "spacy.ConsoleLogger.v1"
progress_bar = true

[training.optimizer]
@optimizers = "Adam.v1"
beta1 = 0.9
beta2 = 0.999
L2_is_weight_decay = true
L2 = 0.01
grad_clip = 1.0
use_averages = false
eps = 0.00000001
# learn_rate: 学习率。对于 Transformer 微调,一个较小的值(如 2e-5)是很好的起点。
learn_rate = 2e-5

[training.score_weights]
# sents_f: 评估时使用的主要分数,这里是句子切分的 F1-score。
sents_f = 1.0
sents_p = 0.0
sents_r = 0.0

[pretraining]

# [initialize] 部分用于在训练开始前初始化组件。
[initialize]
vectors = null
init_tok2vec = null
vocab_data = null
lookups = null
before_init = null
after_init = null

[initialize.components]

如需自定义 transformer 预训练模型、分词方式,可在此基础上修改。

🤔 Transformer 预训练模型选择建议

  • bert-base-chinese 是通用的中文 Transformer 预训练模型,适合大多数场景。
  • hfl/chinese-roberta-wwm-ext 是哈工大开源的中文 RoBERTa-wwm 扩展版,在许多中文任务上表现优于 BERT,推荐在资源允许时优先尝试。
  • 其他可选模型如 hfl/chinese-macbert-baseuer/chinese-roberta-base 等,也可根据实际需求和硬件资源选择。
  • 注意:更大的模型(如 RoBERTa-wwm-ext)通常带来更好的效果,但显存和训练时间消耗也更高。

🛠️ 关键超参数调优指南

  • learn_rate(学习率):控制模型参数更新的步幅。Transformer 微调推荐较小的学习率(如 2e-5 ~ 5e-5),过大易导致训练不稳定,过小则收敛慢。
  • dropout(随机丢弃率):防止过拟合,常用值为 0.1~0.3。增大 dropout 可提升泛化能力,但过大可能影响模型表达能力。
  • patience(早停容忍步数):连续多少步验证集无提升则提前停止训练。patience 设大可获得更充分训练,设小则可加快实验迭代。
  • 其他如 batch_sizemax_steps 也可根据显存和数据量适当调整。

合理选择模型和调优超参数,有助于在硬件资源允许的前提下获得最佳分句效果。

⚡ 使用 transformer 结构通常能获得更好的效果,但对显存和硬件要求更高。 根据我的本地环境测试,GPU为A6000,代码如果使用cpu训练cnn版本的模型,大概1分钟内可以完成训练,而训练transformer版本的模型需要使用GPU训练大概几分钟,如果是CPU可能需要十几分钟。不过本次文章的超参数是我乱设置的,实际训练的时候建议调整一下。

🧪 模型推理与效果展示

训练完成后,可以直接加载模型进行分句推理。下面分别演示如何加载标准模型和 transformer 结构训练的模型,并输出分句结果:

import spacy

# 加载标准模型
test_text = "今天天气很好。我想去公园。"
nlp = spacy.load("output_model/model-best")
doc = nlp(test_text)  # 直接用pipeline处理原始文本
print("标准模型分句结果(spaCy自带分词+完整pipeline):")
print("Pipeline 组件:", nlp.pipe_names)
for token in doc:
    print(token.text, token.is_sent_start)
print("\n分句结果:")
for sent in doc.sents:
    print(sent.text)

# 加载 transformer 结构训练的模型
nlp_trf = spacy.load("output_model_trf/model-best")
doc_trf = nlp_trf(test_text)  # 直接用pipeline处理原始文本
print("\nTransformer 模型分句结果(spaCy自带分词+完整pipeline):")
print("Pipeline 组件:", nlp_trf.pipe_names)
for token in doc_trf:
    print(token.text, token.is_sent_start)
print("\n分句结果:")
for sent in doc_trf.sents:
    print(sent.text)

输出:

标准模型分句结果(spaCy自带分词+完整pipeline):
Pipeline 组件: ['senter']
今天天气 True
很 False
好 False
。 False
我 True
想 False
去 False
公园 False
。 False

分句结果:
今天天气很好。
我想去公园。

Transformer 模型分句结果(spaCy自带分词+完整pipeline):
Pipeline 组件: ['transformer''senter']
今天天气 True
很 False
好 False
。 False
我 True
想 False
去 False
公园 False
。 False

分句结果:
今天天气很好。
我想去公园。

上述代码会分别输出标准模型和 transformer 微调模型的分句效果,便于直观对比模型分割效果。

📝 代码说明

  • nlp 对象:spaCy 的语言处理管道对象,包含分词、句子分割等组件。通过 spacy.load() 加载训练好的模型后,nlp 就可以直接对原始文本进行处理。
  • doc 对象:spaCy 的 Doc 类型,是对文本的结构化表示,包含分词、句子、标注等信息。doc 可以像列表一样遍历每个 token,也可以通过 doc.sents 获取分句结果。
  • pipe_namesnlp.pipe_names 属性返回当前 pipeline 中所有组件的名称列表,比如 ['transformer', 'senter'],便于了解模型结构。
  • token 对象:doc 中的每个元素都是一个 token(词或符号),可以通过 token.text 获取文本,通过 token.is_sent_start 判断该 token 是否为句子起始。
  • sentsdoc.sents 是 spaCy Doc 对象的一个生成器,表示分句后的句子序列。遍历 doc.sents 可以依次获得每个句子的文本(sent.text),用于查看模型的分句效果。

下面通过一个实际代码示例,帮助理解 nlp、doc、pipe_names、token、sents 的用法:

import spacy

# 加载模型
nlp = spacy.load("output_model/model-best")

# nlp 对象:可以直接处理原始文本
text = "小明喜欢看书,也喜欢运动。你呢?"
doc = nlp(text)

# pipe_names:查看 pipeline 组件
print("Pipeline 组件:", nlp.pipe_names)  # 例如 ['transformer', 'senter']

# doc 对象:结构化文本,支持遍历 token
print("所有 token:")
for token in doc:
    print(f"{token.text}\t是否句子起始: {token.is_sent_start}")

# token 对象:每个 token 的属性
first_token = doc[0]
print(f"第一个 token: {first_token.text}, 是否句子起始: {first_token.is_sent_start}")

# sents:遍历分句结果
print("\n分句结果:")
for sent in doc.sents:
    print(sent.text)

输出如下:

Pipeline 组件: ['senter']
所有 token:
小明    是否句子起始: True
喜欢    是否句子起始: False
看书    是否句子起始: False
,      是否句子起始: False
也      是否句子起始: False
喜欢    是否句子起始: False
运动    是否句子起始: False
。      是否句子起始: False
你      是否句子起始: True
呢      是否句子起始: False
?      是否句子起始: False
第一个 token: 小明, 是否句子起始: True

分句结果:
小明喜欢看书,也喜欢运动。
你呢?

如果换成transformer模型,那么输出结果会变成这样:

Pipeline 组件: ['transformer''senter']
所有 token:
小明 是否句子起始: True
喜欢 是否句子起始: False
看书 是否句子起始: False
, 是否句子起始: False
也 是否句子起始: False
喜欢 是否句子起始: False
运动 是否句子起始: False
。 是否句子起始: False
你 是否句子起始: True
呢 是否句子起始: False
? 是否句子起始: False
第一个 token: 小明, 是否句子起始: True

分句结果:
小明喜欢看书,也喜欢运动。
你呢?

通过这个例子可以直观理解 spaCy 推理流程中各对象的作用和输出。

📈 微调效果与评估

微调后,模型能更准确地识别句子边界,减少误分割和漏分割。例如,能更好地处理缩写、省略号、特殊标点、特殊表达等情况。实际效果可通过在自有数据集上的评估指标(如准确率、召回率、F1 分数等)体现。

如果你需要对分句模型进行评测,推荐的做法如下:

  1. 准备评测集

  • 评测集应与训练集分开,包含真实场景下的文本及人工标注的句子边界。
  • 格式与训练数据一致,便于直接加载。
  • 推理与对比

    • 用训练好的模型对评测集文本进行分句预测。
    • 将模型输出的分句结果与人工标注的标准答案进行对比。
  • 分句常用评估指标

    公式如下:

    其中:

    你可能会觉得这些评估指标的名字(如“精确率”“召回率”)有点“怪”,因为分句任务和传统的分类、检索任务不太一样。其实这里的“精确率”“召回率”是借用信息检索领域的通用定义:

    虽然分句不是传统意义上的“检索”或“分类”,但本质上也是“判断每个 token 是否为句子开头”的二分类问题,所以这些指标依然适用。spaCy 评测时也是用这些通用指标来衡量分句效果。

    • 精确率(Precision):模型预测为“句子起始点”的位置中,有多少是真正的句子起始。
    • 召回率(Recall):所有真实的句子起始点中,有多少被模型找出来了。
    • F1 分数:综合衡量模型“找得准”和“找得全”的能力。
    • :真正例数(正确预测为句子边界的数量)
    • :真负例数(正确预测为非句子边界的数量)
    • :假正例数(错误预测为句子边界的数量)
    • :假负例数(漏掉的句子边界数量)
    • 精确率(Precision):被模型判为句子边界的结果中,实际为边界的比例。
    • 召回率(Recall):所有真实句子边界中,被模型正确识别的比例。
    • F1 分数(F1 Score):精确率和召回率的调和平均,更全面反映模型性能。
    • 准确率(Accuracy):整体预测正确的比例(可选,分句任务更常用 F1/精确率/召回率)。
  • 评测代码示例(spaCy Scorer 版)

    你可以直接用 spaCy 自带的 Scorer 工具对训练数据进行分句评测,代码如下:

    import spacy
    from spacy.tokens import DocBin
    from spacy.scorer import Scorer
    from spacy.training import Example

    # 加载训练好的 spaCy 分句模型
    nlp = spacy.load("output_model/model-best")
    # 加载标注好的分句数据(.spacy 格式)
    doc_bin = DocBin().from_disk("data/senter_train.spacy")
    docs = list(doc_bin.get_docs(nlp.vocab))

    scorer = Scorer()  # 初始化评测器
    examples = []  # 存放 Example 对象
    for doc in docs:
        pred_doc = nlp(doc.text)  # 用模型对原文重新分句
        example = Example(pred_doc, doc)  # 构造评测用的 Example 对象
        examples.append(example)
    # 评测所有 Example,返回各项指标
    results = scorer.score(examples)
    print("分句评测结果:")
    print(results)

    输出示例:

    分句评测结果:
    {'token_acc': None, 'token_p': None, 'token_r': None, 'token_f': None, 'sents_p': 1.0, 'sents_r': 1.0, 'sents_f': 1.0, ...}

    结果解读:

    需要注意的是,spaCy Scorer 并不会输出“准确率(accuracy)”这一指标。这是因为分句任务中,大多数 token 都不是句子起始点(即负例远多于正例),如果用准确率,模型即使全部预测为“不是句子起始”,准确率也会很高,但模型其实没有学到分句能力。因此,准确率不能真实反映分句模型的实际效果。spaCy 的 Scorer 工具更关注查准率(精确率)、查全率(召回率)和 F1 分数,这些指标能更科学地衡量分句模型的实际能力。

    • sents_psents_rsents_f 分别表示分句的精确率、召回率和 F1 分数,1.0 表示分句完全正确。
    • 其它如 token_accents_f 等为 None,说明本次只评测了分句,不涉及分词、实体等任务。
    • 只要 sents_fsents_psents_r 越接近 1,说明分句模型效果越好。

    📏 本文以流程演示为主,实际评测可直接真实的评测数据进行。

    🧩 RAG 分块扩展思考:从分句到智能分块

    在 RAG(Retrieval-Augmented Generation)系统中,文本分块(chunking)是比分句更关键的预处理环节。高质量的分块能显著提升检索与生成效果。我们能否将 spaCy 的分句能力扩展为“智能分块”呢?以下是我们的思考与实践建议:

    分块的核心思想

    • spaCy 的分句本质是判断某个符号(如标点)是否适合作为分句点。
    • RAG 分块可以直接扩展为“判断某个符号/位置是否适合作为分块点”,本质上是同一类序列决策问题,只是目标不同。
    • 可以训练模型直接预测哪些位置适合分块,而不是先分句再合并。
    • 这样可以灵活适配各种场景,并结合上下文、结构等多种信号。

    实现路径

    1. 准备高质量分块数据

    • 收集与目标 RAG 场景相关的文档,人工标注最佳分块点。 -以 spaCy 分句类似的方式,为每个潜在分块点(如标点、换行等)打上“是否为分块点”的标签。
  • 数据格式示例

    • 原文:
      机器学习是人工智能的一个分支。它致力于研究计算机如何模拟或实现人类的学习行为。近年来,深度学习作为其一个重要领域,取得了突破性进展,尤其是在计算机视觉和自然语言处理方面。这得益于算力的提升和海量数据的出现。
    • 分块点标注:
      [True, False, True, False, False, ...]  # 仅示意,True 表示当前位置可分块
  • 分块策略的多样性

  • 实际 RAG 应用中,分块点的判断不应仅依赖标点或简单规则。比如:

    • 角色对话:同一角色连续发言,即使有多个句号,也不应分块,避免切断语义完整的表达。
    • 诗歌/歌词/古文:分块点应结合结构、韵律或语义,而非单纯标点。
    • 技术文档/代码:分块点可结合标题、缩进、格式等多种信号。

    因此,分块点的判断策略应根据具体场景灵活设计,既可用规则,也可用模型学习,甚至两者结合。

    这种方法本质上是将 spaCy 分句思想扩展到更高层次的“分块点判断”,能为 RAG 应用带来更智能、更贴合业务需求的分块能力。但是模型的输入序列长度也是有限的,似乎还是需要进行预先分块,而不是无论多长都能直接输入模型进行处理,这个问题我觉得值得我们进一步去思考如何处理。

    📚 不同模型结构的上下文感知能力说明

    • CNN(如 HashEmbedCNN)

      • 需要配置窗口大小(window_size),如 window_size=1 表示每个 token 只看自己和左右各1个 token。
      • 窗口大小直接影响模型能看到的上下文范围,窗口越大,模型能利用的上下文信息越多,但计算量也随之增加。
      • 适合局部上下文特征为主的任务,训练和推理速度较快。
    • Transformer

      • 不需要配置窗口,Transformer 通过自注意力机制自动建模全局上下文(只受最大 token 长度限制)。
      • 每个 token 都能“看到”输入序列中的所有 token,能捕捉长距离依赖和复杂语义关系。
      • 适合需要全局信息、长文本建模的场景,但对显存和计算资源要求更高。

    实践建议:

    • 如果你的分句任务主要依赖于局部标点和短距离上下文,CNN结构(如HashEmbedCNN)即可满足需求,且效率高。
    • 如果文本结构复杂、长距离依赖明显,或希望获得更强的泛化能力,建议优先尝试Transformer结构。

    🏁 总结与展望

    通过本文,你已经掌握了 spaCy 中文分句的多种实现路径,从规则分句到可训练模型,从底层原理到工程实践,每一步都配有详实的代码和应用建议。你不仅能独立完成分句模型的训练、推理和评估,还能根据实际业务需求,灵活选择最合适的 pipeline 组件和底层结构,实现高效、智能的文本分句与分块。

    未来,随着大模型和 RAG 技术的不断发展,分句与分块的边界将更加模糊,如何结合业务场景、数据特点和下游任务,设计出最优的文本预处理方案,将成为提升检索与生成效果的关键。希望本教程能成为你 NLP 实践路上的坚实基石,助你高效解决实际问题。

53AI,企业落地大模型首选服务商

产品:场景落地咨询+大模型应用平台+行业解决方案

承诺:免费场景POC验证,效果验证后签署服务协议。零风险落地应用大模型,已交付160+中大型企业

联系我们

售前咨询
186 6662 7370
预约演示
185 8882 0121

微信扫码

添加专属顾问

回到顶部

加载中...

扫码咨询