做“句子级”chunk(分块)

示例代码:

def chunk_by_sentences(input_text: str, tokenizer: callable):
    """
    Split the input text into sentences using the tokenizer
    :param input_text: The text snippet to split into sentences
    :param tokenizer: The tokenizer to use
    :return: A tuple containing the list of text chunks and their corresponding token spans
    """
    inputs = tokenizer(input_text, return_tensors='pt', return_offsets_mapping=True)
    punctuation_mark_id = tokenizer.convert_tokens_to_ids('.')
    sep_id = tokenizer.convert_tokens_to_ids('[SEP]')

    token_offsets = inputs['offset_mapping'][0]
    token_ids = inputs['input_ids'][0]
    
    chunk_positions = [
        (i, int(start + 1))
        for i, (token_id, (start, end)) in enumerate(zip(token_ids, token_offsets))
        if token_id == punctuation_mark_id
        and (
            token_offsets[i + 1][0] - token_offsets[i][1] > 0
            or token_ids[i + 1] == sep_id
        )
    ]
    chunks = [
        input_text[x[1] : y[1]]
        for x, y in zip([(1, 0)] + chunk_positions[:-1], chunk_positions)
    ]
    span_annotations = [
        (x[0], y[0]) for (x, y) in zip([(1, 0)] + chunk_positions[:-1], chunk_positions)
    ]
    return chunks, span_annotations

首先我们要明白为什么这段代码要这么处理句子背后的设计动机实际应用场景是什么。


🎯 总目标:做“句子级”chunk(分块)

这段函数 chunk_by_sentences 的核心目标是:

把长文本切分成一句一句的小段(chunk),每一段尽量是一个完整的句子,而不是强行按字数截断。


❓为什么要按“句号”来切分?

我们处理长文本(比如新闻、文档)时,经常要:

  • 分块送进 embedding 模型(BERT、LLaMA)
  • 做检索(RAG)、摘要、问答
  • 限制最大 token 数量(模型处理能力有限)

如果直接按照长度硬切(比如100个字一截)会出现:

  • 句子断裂:句子被切一半,语义丢失
  • 对齐错乱:无法用 chunk 做回溯,原始句子找不到了
  • embedding 不准确:被截断的句子语义被破坏,向量不好用

所以更好的做法是:

✅ “遇到句号再切”,让每个 chunk 是一整句,语义完整!


📘 中文例子:为什么必须精确切句?

示例文本:

今天北京下雪。气温零下五度。适合打雪仗。

我们希望分成:

  1. 今天北京下雪。
  2. 气温零下五度。
  3. 适合打雪仗。

如果我们只是按固定长度切,比如每15个字符一段:

  • 第一个chunk可能是:今天北京下雪。气温零(句子被切开)
  • 第二个chunk:下五度。适合打雪仗。(不连贯)

这对以下任务都不好:

任务为什么需要整句切
Embedding每个句子要独立编码成向量,不能被切坏
RAG 检索检索时要找出“哪句话最相关”
可视化高亮前端要高亮整句,而不是一半句
错误定位如果模型输出有误,需要精确知道是哪个句子错了

🔍 那为什么还要用 tokenizer 的 offset_mapping 来切?

你可能会想:

直接用 str.split("。") 不就行了吗?为啥这么复杂?

这是因为:

❗️直接字符串切割的问题:

  • 丢失原始 token 的起止位置(比如第几个 token)
  • 无法和 BERT 模型的 token 位置对应(embedding 用不上)
  • 遇到标点、空格、英语等语言混合情况,会切不准

所以这段代码精细处理每个 token 的 offset,确保:

✅ chunk 是按完整句子切的
✅ chunk 的 token 索引位置也能对应(用在 embedding 或模型中)
✅ 多语言、标点复杂的情况都能处理好


✅ 总结:这段代码解决的问题

你要做的任务遇到的痛点这段代码怎么帮你解决
拆分长文本成句子字符切不准、token对不上用 tokenizer 精准对齐 token 和字符
BERT/Embedding模型只能吃 512 个 token,需要提前分块按句号切分,每句都是一小段
多语言混合(中英)标点不同、空格不同,直接切会错offset_mapping 处理 token 对应位置
检索问答 / RAG需要句子粒度的上下文每个 chunk 都是语义完整的句子
回溯文本原文需要定位哪个句子被用了提供了 span_annotations 标记每句对应的 token 起止范围
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值