微信扫码
添加专属顾问
我要投稿
掌握spaCy中文分句的核心技术与微调方法,从基础规则到深度学习模型,全面提升文本处理效率。 核心内容: 1. spaCy三大中文分句方案对比与适用场景分析 2. 从数据准备到模型训练的完整微调流程详解 3. 分句技术在RAG等复杂场景中的扩展应用
本次文章配套代码地址:https://github.com/li-xiu-qi/XiaokeAILabs/tree/main/datas/spacy_finetune
后续可能会微调一个专门用来分块的模型,感兴趣的同学欢迎联系我或者给项目一个star哦,仓库地址:https://github.com/li-xiu-qi/spacy_chuking
在实际 NLP 项目中,中文分句往往是文本处理的第一步,但 spaCy 默认的分句效果在面对特殊标点、缩写、省略号、对话等复杂情况时,常常难以满足高精度需求。很多同学会问:spaCy 到底有哪些分句方案?如何选择最适合自己的分句方式?能不能既高效又智能地实现分句,甚至扩展到更复杂的文本分块?
本教程将系统梳理 spaCy 的分句原理与工程实现,带你从最基础的规则分句(Sentencizer)、到可训练的 senter 组件、再到依存句法驱动的 DependencyParser,逐步深入每种方案的优缺点和适用场景。你不仅能学会如何微调 spaCy 分句模型,还能理解不同模型结构(CNN/Transformer)的本质区别,掌握如何根据实际需求灵活选择 pipeline 组件。更重要的是,本文还将带你思考如何将分句思想扩展到 RAG 等智能分块场景,助力你的文本处理系统更上一层楼。
句子边界检测(Sentence Boundary Detection, SBD)是自然语言处理(NLP)中的基础模块,其准确性直接影响分词、词性标注、依存句法、命名实体识别等下游任务。SBD的难点在于标点符号的歧义性(如缩写、小数点、省略号等),这使得“遇到句号就分割”远远不够,必须结合上下文、词性、大小写等多维特征。现代NLP通常将SBD视为一个分类问题,针对每个潜在边界点,利用统计学习或神经网络模型进行判别。
不同语言和专业领域(如医学、法律、金融)对SBD有不同挑战。spaCy支持多语言和可定制微调,能适应多样化场景。
在实际应用中,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个词(窗口大小)。
当 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 组件。
微调 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": [true, false, false, false, true, false, false, false, false]}
{"text": "小明喜欢看书,也喜欢运动。你呢?", "tokens": ["小明", "喜欢", "看书", ",", "也", "喜欢", "运动", "。", "你", "呢", "?"], "sent_starts": [true, false, false, false, false, false, false, false, true, false, false]}
这里的数据格式是 spaCy 句子分割器(senter)训练所需的 jsonl 格式,每一行为一个 JSON 对象,包含两个字段:
举例说明:
{"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
,表示是第二个句子的开头;false
,表示不是句子起始。这样 spaCy 就能根据 sent_starts
信息,学习如何在中文文本中正确分句。
如果你需要将上述 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 文件中的分句效果:
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
文档对象。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 配置文件 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 可省略)。下面是一个 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 预训练模型、分词方式,可在此基础上修改。
bert-base-chinese
是通用的中文 Transformer 预训练模型,适合大多数场景。hfl/chinese-roberta-wwm-ext
是哈工大开源的中文 RoBERTa-wwm 扩展版,在许多中文任务上表现优于 BERT,推荐在资源允许时优先尝试。hfl/chinese-macbert-base
、uer/chinese-roberta-base
等,也可根据实际需求和硬件资源选择。learn_rate
(学习率):控制模型参数更新的步幅。Transformer 微调推荐较小的学习率(如 2e-5 ~ 5e-5),过大易导致训练不稳定,过小则收敛慢。dropout
(随机丢弃率):防止过拟合,常用值为 0.1~0.3。增大 dropout 可提升泛化能力,但过大可能影响模型表达能力。patience
(早停容忍步数):连续多少步验证集无提升则提前停止训练。patience 设大可获得更充分训练,设小则可加快实验迭代。batch_size
、max_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_names
:nlp.pipe_names
属性返回当前 pipeline 中所有组件的名称列表,比如 ['transformer', 'senter']
,便于了解模型结构。token
对象:doc
中的每个元素都是一个 token(词或符号),可以通过 token.text
获取文本,通过 token.is_sent_start
判断该 token 是否为句子起始。sents
:doc.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 分数等)体现。
如果你需要对分句模型进行评测,推荐的做法如下:
准备评测集:
推理与对比:
分句常用评估指标:
公式如下:
其中:
你可能会觉得这些评估指标的名字(如“精确率”“召回率”)有点“怪”,因为分句任务和传统的分类、检索任务不太一样。其实这里的“精确率”“召回率”是借用信息检索领域的通用定义:
虽然分句不是传统意义上的“检索”或“分类”,但本质上也是“判断每个 token 是否为句子开头”的二分类问题,所以这些指标依然适用。spaCy 评测时也是用这些通用指标来衡量分句效果。
评测代码示例(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_p
、sents_r
、sents_f
分别表示分句的精确率、召回率和 F1 分数,1.0 表示分句完全正确。token_acc
、ents_f
等为 None,说明本次只评测了分句,不涉及分词、实体等任务。sents_f
、sents_p
、sents_r
越接近 1,说明分句模型效果越好。📏 本文以流程演示为主,实际评测可直接真实的评测数据进行。
在 RAG(Retrieval-Augmented Generation)系统中,文本分块(chunking)是比分句更关键的预处理环节。高质量的分块能显著提升检索与生成效果。我们能否将 spaCy 的分句能力扩展为“智能分块”呢?以下是我们的思考与实践建议:
准备高质量分块数据
数据格式示例
分块策略的多样性
实际 RAG 应用中,分块点的判断不应仅依赖标点或简单规则。比如:
因此,分块点的判断策略应根据具体场景灵活设计,既可用规则,也可用模型学习,甚至两者结合。
这种方法本质上是将 spaCy 分句思想扩展到更高层次的“分块点判断”,能为 RAG 应用带来更智能、更贴合业务需求的分块能力。但是模型的输入序列长度也是有限的,似乎还是需要进行预先分块,而不是无论多长都能直接输入模型进行处理,这个问题我觉得值得我们进一步去思考如何处理。
CNN(如 HashEmbedCNN):
Transformer:
实践建议:
如果你的分句任务主要依赖于局部标点和短距离上下文,CNN结构(如HashEmbedCNN)即可满足需求,且效率高。 如果文本结构复杂、长距离依赖明显,或希望获得更强的泛化能力,建议优先尝试Transformer结构。
通过本文,你已经掌握了 spaCy 中文分句的多种实现路径,从规则分句到可训练模型,从底层原理到工程实践,每一步都配有详实的代码和应用建议。你不仅能独立完成分句模型的训练、推理和评估,还能根据实际业务需求,灵活选择最合适的 pipeline 组件和底层结构,实现高效、智能的文本分句与分块。
未来,随着大模型和 RAG 技术的不断发展,分句与分块的边界将更加模糊,如何结合业务场景、数据特点和下游任务,设计出最优的文本预处理方案,将成为提升检索与生成效果的关键。希望本教程能成为你 NLP 实践路上的坚实基石,助你高效解决实际问题。
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费场景POC验证,效果验证后签署服务协议。零风险落地应用大模型,已交付160+中大型企业
2025-04-30
2025-04-19
2025-04-16
2025-04-16
2025-04-20
2025-05-26
2025-04-20
2025-04-19
2025-04-06
2025-05-07