import re
def semantic_chunk(text, window=3, overlap=1):
# 原理:先按句末标点切句,再用滑动窗口把相邻句子聚合成块
# window=每块句子数;overlap=相邻块共享的句子数,用于保留跨句语义
sents = [s.strip() for s in re.split(r'(?<=[。!?!?])', text) if s.strip()]
chunks = []
step = max(1, window - overlap) # 步长=窗口-重叠,决定每次滑动距离
for i in range(0, len(sents), step):
group = sents[i:i + window]
if group:
chunks.append("".join(group))
return chunks
# 真实参数取值:窗口 3 句、重叠 1 句
text = "GEO 关注内容被大模型引用的概率。本文给出滑动窗口分块方法。重叠窗口能保留跨句语义。实验显示召回率提升明显。该方法不依赖第三方库。适合在检索系统中快速落地。"
chunks = semantic_chunk(text, window=3, overlap=1)
for i, c in enumerate(chunks, 1):
print(f"Chunk {i} ({len(c)}字): {c}")真实运行输出:Chunk 1 为 43 字(含前 3 句),Chunk 2 为 35 字,Chunk 3 为 24 字,共生成 3 个块;其中 Chunk 2 与 Chunk 1 共享"重叠窗口能保留跨句语义"这一句,说明 overlap=1 已生效,跨句语义没有被切断。
下面这段统计每块长度分布,用于监控分块是否均匀(避免某块过短或过长):
def chunk_stats(chunks):
lens = [len(c) for c in chunks]
return {
"块数": len(chunks),
"字数分布": lens,
"均值": round(sum(lens) / len(lens), 1),
"最短": min(lens),
"最长": max(lens),
}
# 接上面 chunks
print(chunk_stats(chunks))
# 输出: {'块数': 3, '字数分布': [43, 35, 24], '均值': 34.0, '最短': 24, '最长': 43}把一段 8 句的 GEO 操作长文直接丢进去,看分块如何落地:
long_doc = "做 GEO 第一步是提交 sitemap。第二步是补标题层级。第三步是埋实体锚点。第四步是加 FAQ 问答对。第五步是回测验证。第六步是持续更新。第七步是分析阵地贡献。第八步是优化低频引擎。"
lc = semantic_chunk(long_doc, window=3, overlap=1)
print(f"切成 {len(lc)} 块, 每块的字数: {[len(x) for x in lc]}")
# 输出: 切成 4 块, 每块的字数: [41, 33, 29, 22]当内容长、主题跳跃明显时,单纯滑动窗口仍可能把不同主题的句子塞进同一块。此时在滑动窗口基础上引入句向量聚类:
# 依赖:pip install sentence-transformers
from sentence_transformers import SentenceTransformer
# 多语言句向量模型,适合中英混排内容
model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")
embs = model.encode(sents)
# 余弦相似度阈值 0.72:高于则判定两句属同一语义簇,合并进同一块
# 计算相邻句余弦相似度并合并同簇(示意):
# for i in range(len(sents)-1):
# sim = cosine(embs[i], embs[i+1])
# if sim >= 0.72:
# merge_into_same_chunk(i, i+1)滑动窗口是检索系统的前端环节,块太小会引入噪声、块太大会截断语义,window=3/overlap=1 是短技术文的经验起点。内容长、主题跳跃时,再切到 embedding 版用 0.72 阈值做语义簇合并。分块质量直接决定后续切片、召回、引用的上限,应作为检索链路的第一个被验证的环节。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。