← blog 专题系列 · 2026-04-06

Small-Token

梳理 RAG 系统中短 Token 的识别与过滤:从 jieba 分词原理到停用词表、IDF 筛选、信息熵与标点比例等方法,再到 Query 过滤、降权、改写与短 Token 命中判定策略。

12 min read

什么是短token(what)

RAG中的短token有两种含义

  1. 短文档块(chunk),在文档分块的时候因为文档太长,导致被分块嵌入而留下的尾巴
  2. 短Token,例如 “的”,“是”,这些无实际意义但却会产生语义近似的短词

深入理解中文分词原理

在讨论短Token识别方法之前,我们需要了解中文分词的基础原理。中文文本没有天然的分词边界,分词质量直接影响后续的短Token识别效果。

jieba分词原理

jieba是目前最流行的中文分词器,采用三层算法叠加策略:

第一层:前缀词典匹配(机械分词)

jieba构建基于Trie树的前缀词典,采用最大正向匹配算法(MM)进行分词。

class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end = False
        self.word = None

第二层:HMM新词发现

对于未登录词,jieba引入隐马尔可夫模型(HMM)进行新词发现,采用B/M/E/S状态标注:

  • B (Begin): 词开始
  • M (Middle): 词中间
  • E (End): 词结束
  • S (Single): 单字词

第三层:Viterbi算法解码

动态规划求解最优状态序列:

δ_t(i) = max[P(状态i | t时刻) × max[δ_{t-1}(j) × A_{ji}]]

主流中文分词器对比

分词器算法特点速度准确率适用场景
jieba前缀词典+HMM快较高通用场景
pkusegCNN+HMM中高高精度需求
LTP神经网络中高复杂NLP任务
HanLP多种算法融合中高企业级应用
thulacCRF+前缀词典快较高平衡速度与精度

分词算法分类

机械分词法:正向最大匹配(MM)、反向最大匹配(RMM)、双向最大匹配

N-gram语言模型:通过统计词序列共现概率解决分词歧义

序列标注方法:HMM、MEMM、CRF使用BEMS标签体系进行分词

深度学习方法:BiLSTM-CRF、BERT-CRF提供最高精度

jieba实战使用

import jieba

# 精确模式
words = jieba.lcut("我正在学习自然语言处理", cut_all=False)
# ['我', '正在', '学习', '自然语言', '处理']

# 全模式
words_all = jieba.lcut("我正在学习自然语言处理", cut_all=True)

# 关键词抽取
import jieba.analyse
keywords = jieba.analyse.extract_tags(text, topK=10)

如何处理短token

● RAG中短Token的处理策略

一、短Token的识别

定义标准:

类型特征示例
停用词长度≤2,无语义信息的、是、在、和
高频通用词文档频率>阈值我们、可以、这个
模糊词单字、无义词啊、嗯、什么
标点/数字纯符号或数字123、@#$
识别方法:

方法1:预设停用词表

STOP_WORDS = {'的', '是', '在', '和', '了', '我', '你', '它', '这', '那'}

方法2:IDF筛选(信息量视角)

# IDF = log(N / df)  →  IDF过低 = 区分度低 = 信息量少

def is_short_token(token, idf_threshold=2.0):
    """IDF低于阈值视为短token"""
    return get_idf(token) < idf_threshold

方法3:混合策略

def is_short_token_v2(token, min_len=2, idf_threshold=2.0):
    return len(token) <= min_len or get_idf(token) < idf_threshold

方法4:多维度组合过滤(综合实现)

def comprehensive_filter(text, min_chars=10, min_tokens=3, 
                        max_stopword_ratio=0.6, min_entropy=2.0, max_punct_ratio=0.3):
    if len(text) < min_chars or len(text.split()) < min_tokens:
        return False
    if not filter_by_stopword_ratio(text, max_stopword_ratio):
        return False
    if not filter_by_entropy(text, min_entropy):
        return False
    if not filter_by_punctuation_ratio(text, max_punct_ratio):
        return False
    return True

def filter_by_stopword_ratio(text, max_ratio=0.6):
    import jieba
    words = list(jieba.cut(text))
    stopwords = {'的', '了', '是', '在', '有', '和', '就', '不', '人', '都'}
    stopword_count = sum(1 for w in words if w in stopwords)
    ratio = stopword_count / len(words) if words else 1.0
    return ratio < max_ratio

def filter_by_entropy(text, min_entropy=2.0):
    import math
    from collections import Counter
    if not text:
        return False
    char_freq = Counter(text)
    length = len(text)
    entropy = 0.0
    for count in char_freq.values():
        p = count / length
        if p > 0:
            entropy -= p * math.log2(p)
    return entropy >= min_entropy

def filter_by_punctuation_ratio(text, max_punct_ratio=0.3):
    import re
    punct_count = len(re.findall(r'[,。!?、;:""''()【】《》\-—…]', text))
    total_chars = len(text)
    if total_chars == 0:
        return False
    return punct_count / total_chars < max_punct_ratio

二、过滤策略(Query层面)

1. 直接过滤

# 检索前从query中移除短token

def filter_query(query: str) -> str:
    tokens = tokenize(query)
    filtered = [t for t in tokens if not is_short_token(t)]
    return " ".join(filtered)

# 例: "Java是什么" → "Java"
# 例: "如何在Python中使用多线程" → "Python使用多线程"

优点:简单直接,检索效率高
缺点:可能丢失部分语义(如"不Java")

2. 保留但降权

  def weighted_query(tokens):
      """
      短token给予极低权重
      长token(实体、术语)给予高权重
      """
      weights = []
      for t in tokens:
          if is_short_token(t):
              weights.append(0.1)   # 短token权重降为0.1
          else:
              weights.append(1.0)   # 正常token权重1.0
      return weights

  # 向量检索时:query_vector = Σ(token_vector * weight)

三、Query改写策略

1. 同义词/概念扩展

  # 短token "大" 可能需要扩展
  "大" → "大公司" / "大型" / "规模大"

  # 使用词向量找相似长词
  def expand_short_token(token):
      similar = find_similar_tokens(token, topk=3)
      # 过滤掉仍然是短token的
      return [t for t in similar if len(t) > 2]

  2. 伪相关反馈 (Pseudo Relevance Feedback)

  def query_expansion(query, top_k=5):
      """
      1. 用原query检索,获取Top-K结果
      2. 从结果中提取高频实词
      3. 将实词加入原query
      """
      initial_results = vector_search(query, top_k=top_k)
      expanded_terms = extract_keywords_from_results(initial_results)
      # 只保留非短token的关键词
      expanded_terms = [t for t in expanded_terms if not is_short_token(t)]
      return query + " " + " ".join(expanded_terms)

四、短Token命中判定(结果质量评估)

这是用于评估检索结果可信度的机制,不改变检索本身。

graph TD
    S1["Step 1: 从Query中提取短token集合 S = {s1, s2, ...}"]
    S2["Step 2: 对每个检索结果R,计算短token命中率\nhit_ratio(R) = |S ∩ content(R)| / |S|"]
    S3["Step 3: 置信度校准\nif hit_ratio > 0.8: confidence *= 0.5 (降低该结果可信度)"]
    S4["Step 4: 综合排序\nfinal_score(R) = similarity_score(R) * confidence(R)"]
    S1 --> S2 --> S3 --> S4
  代码示例

  def calculate_short_token_hit_ratio(query: str, doc_content: str) -> float:
      """
      计算短token命中率
      命中率过高说明文档"假相关"
      """
      query_tokens = set(tokenize(query))
      doc_tokens = set(tokenize(doc_content))

      short_tokens = {t for t in query_tokens if is_short_token(t)}
      if not short_tokens:
          return 0.0

      # 命中的短token
      hit_tokens = short_tokens & doc_tokens
      return len(hit_tokens) / len(short_tokens)

  def adjust_confidence(sim_score: float, hit_ratio: float) -> float:
      """
      短token命中过高 → 降权
      """
      if hit_ratio > 0.8:
          return sim_score * 0.3   # 严重降权
      elif hit_ratio > 0.5:
          return sim_score * 0.7   # 轻度降权
      return sim_score

判定场景示例

Query短Token集合Doc A 命中Doc B 命中判定
“什么是AI”{什么,是}100%60%Doc B更可信
“Java多线程实现”{}--无短token干扰

五、实战方案总结

graph TD
    Q["Query: Java多线程是什么"]
    S1["1. 短Token识别 → {什么,是}"]
    S2["2. 过滤/降权 → query = Java多线程\nweight = {Java:1, 多线程:1, 什么:0.1, 是:0.1}"]
    S3["3. 向量检索 → Top-10 结果"]
    S4["4. 命中判定 (置信度校准) → 计算每个结果的短token命中率 → 命中过高则降权"]
    FIN["最终结果"]
    Q --> S1 --> S2 --> S3 --> S4 --> FIN

关键原则

阶段原则
识别长度 + IDF双重判定,不依赖单一标准
Query改写短token扩展为相关概念,而非直接删除语义
命中判定用于置信度校准,不是直接过滤结果
降权幅度渐进式(0.3/0.7/1.0),避免过度惩罚

追加内容 (2026-04-07)

RAG系统中短Token过滤的最佳实践

在构建RAG(检索增强生成)系统时,短Token过滤是一个容易被忽视但至关重要的环节。本文将深入探讨短Token的识别方法、过滤策略以及实际应用中的注意事项。

什么是短Token

短Token是指语义信息量过低的文本块,主要表现为以下几类:

  • 极短文本:如”是的”、“好的”、“OK”等无实质内容的回复
  • 单独标点或数字:如”…”、“123”、”——“等
  • 无意义短语:重复的模板文本、问候语等
  • 空内容块:只有标签或占位符而没有实际语义

一般来说,低于3-5个Token或10-20个字符的chunk被认为是短Token。这一阈值并非固定值,需根据具体业务场景和分词器特性进行调整。

为什么需要过滤短Token

过滤短Token能为RAG系统带来多方面的提升:

降低噪声干扰:短Token容易产生虚假匹配,例如用户查询”的”可能会匹配到大量包含该字的文档,但这些匹配并无实际检索价值。

节省系统资源:减少无意义的向量存储和计算开销。在大规模向量数据库中,这一优化能显著降低成本。

提升检索质量:避免无意义的chunk占据检索Top-K位置,确保返回结果的相关性和实用性。

改善用户体验:返回更有实质内容的上下文,减少LLM处理无效信息的负担。

常用识别方法

长度阈值法

最直接的方法,根据字符数进行过滤:

def filter_by_length(text: str, min_chars: int = 10) -> bool:
    """判断文本是否低于长度阈值"""
    char_count = len(text.strip())
    return char_count >= min_chars

停用词比例法

检查停用词在文本中的占比,过高的停用词比例通常意味着内容空洞:

import jieba

STOPWORDS = {'的', '了', '是', '在', '我', '有', '和', '就', '不', '人'}

def stopword_ratio(text: str) -> float:
    """计算停用词比例"""
    words = list(jieba.cut(text))
    if not words:
        return 1.0
    stopword_count = sum(1 for w in words if w in STOPWORDS)
    return stopword_count / len(words)

N-gram重复检测

通过检测重复模式识别模板化内容:

from collections import Counter

def has_repetitive_pattern(text: str, n: int = 3, threshold: float = 0.6) -> bool:
    """检测是否存在重复的N-gram模式"""
    words = text.split()
    if len(words) < n:
        return True
    ngrams = [tuple(words[i:i+n]) for i in range(len(words)-n+1)]
    counter = Counter(ngrams)
    if not counter:
        return True
    most_common_ratio = counter.most_common(1)[0][1] / len(ngrams)
    return most_common_ratio > threshold

信息熵/复杂度法

利用信息熵检测文本复杂度,低熵值通常表示内容简单重复:

import math
from collections import Counter

def text_entropy(text: str) -> float:
    """计算文本的信息熵"""
    if not text:
        return 0.0
    counter = Counter(text)
    length = len(text)
    entropy = 0.0
    for count in counter.values():
        p = count / length
        entropy -= p * math.log2(p)
    return entropy

标点/数字比例法

检测标点符号和数字的占比,过高时可能为无效内容:

import re

def punctuation_ratio(text: str) -> float:
    """计算标点符号占比"""
    punctuation = set(',。!?、:;""''()【】《》—…·')
    if not text:
        return 0.0
    punc_count = sum(1 for c in text if c in punctuation)
    return punc_count / len(text)

IDF筛选法:信息量视角

IDF(逆文档频率)是衡量词区分度的重要指标:

IDF = log(N / df)

其中N为文档总数,df为包含该词的文档数。IDF值越低,表示该词的区分度越低,信息量越少。

高频停用词:如”的”、“了”、“是”等几乎出现在所有文档中,df接近N,IDF接近0,应过滤。

专业术语:出现在较少文档中,df较小,IDF较高,应保留。

小语料库下IDF的问题

然而,在小语料库场景下,IDF筛选存在显著局限:

问题示例:假设N=4个文档,“的”字只出现在1个文档中,则:

IDF(“的”) = log(4/1) = log(4) ≈ 1.386

这个值并不低,“的”反而不会被过滤掉。

根本原因:小语料库下,文档频率无法准确反映词的实际重要性。统计样本不足导致概率估计偏差。

解决方案:IDF筛选应与停用词表组合使用,形成双重保障:

def should_filter(token: str, idf_threshold: float = 0.5) -> bool:
    """综合判断是否应过滤"""
    # 命中停用词表 -> 直接过滤
    if token in STOPWORDS:
        return True
    # IDF过低且非专业术语 -> 过滤
    if get_idf(token) < idf_threshold and not is_domain_term(token):
        return True
    return False

完整的过滤流程

实际应用中,短Token过滤应嵌入整个文档处理流水线:

graph LR
    DOC["文档"] --> CHUNK["分块"] --> TOK["分词"] --> REC["短Token识别"] --> FIL["过滤规则"] --> VEC["向量化存储"]

具体实现时需注意以下几点:

  1. 阈值需调优:根据业务场景和分词器特性,选择合适的字符数/Token数阈值。

  2. 多策略组合:长度阈值、停用词比例、N-gram重复检测等方法组合使用,互相补充。

  3. 评估指标:过滤后检索 precision/recall 的变化是检验过滤策略有效性的最终标准。

  4. 可配置性:提供配置选项,让用户根据实际需求调整过滤强度。

总结

短Token过滤是RAG系统预处理阶段的重要环节,直接影响检索质量和系统效率。通过合理组合长度阈值法、停用词比例法、IDF筛选等多种策略,可以有效去除噪声内容,提升检索结果的相关性。在实际应用中,阈值选择和策略组合需要根据具体业务场景进行调优和验证。

Sources

No external sources for this entry.

Related