在文本挖掘和信息检索领域,-Shingle(通常也被称为 -gram)是一种将连续的文本切分成固定长度碎片的技术。它是海量文本去重(如 MinHash + LSH 架构)中极其关键的数据预处理阶段。
简单来说,它的核心任务是:把一篇文章(一维的字符串)转化成一个集合(Set),并且在这个集合中锁死文本的局部语序。
一、 核心概念:滑动窗口(Sliding Window)
-Shingle 的工作原理就像一把长度为 的滑动尺子。尺子从文本的开头开始,每次框住 个单位的内容作为一个 Shingle,然后向右平移一个单位,重复这个过程,直到文本结束。
根据具体需求,这里的“单位”可以是字符(Character),也可以是单词(Word):
- 基于字符的 -Shingle:通常用于拼写检查、DNA 序列分析或中文字符处理。
- 基于单词的 -Shingle:通常用于英文等有天然空格分隔的文本去重与防抄袭。
直观案例演练
我们以短语 abcde 为例,来看看在不同的 值下,基于字符切分出来的 -Shingle 集合是什么样的:
-
当 时(尺子长度为 1):每次只框一个字母。
-
集合结果:
{ "a", "b", "c", "d", "e" } -
当 时(尺子长度为 2):第一次框
ab,向右滑一格框bc,再滑一格框cd…… -
集合结果:
{ "ab", "bc", "cd", "de" } -
当 时(尺子长度为 3):
-
集合结果:
{ "abc", "bcd", "cde" }
二、 核心痛点:为什么工业界去重一定要用 -Shingle?
在做文本相似度计算时,如果我们只把文章简单地切成“单个词的集合”,会遇到严重的“洗牌式抄袭”漏洞。也就是说,作弊者只需要把原文章的段落顺序、句子顺序故意打乱调换,就能完美绕过传统的字词查重。
1. 经典漏洞:传统单字/单词去重
假设现在有两句话,句子 B 连人话都算不上,只是把句子 A 的单词顺序完全打乱了:
- 句子 A:
I love eating apples - 句子 B:
apples eating love I
如果按照传统的“单字/单词”切分并转化为集合(Set),由于集合具有无序性和唯一性,它们切分后的结果是:
- 集合 A:
{ "I", "love", "eating", "apples" }(共 4 个元素) - 集合 B:
{ "apples", "eating", "love", "I" }调整顺序后等同于{ "I", "love", "eating", "apples" }
此时我们计算两者的 Jaccard 相似度:
漏洞出现了:这两句话在人类看来语序完全反了,意思也彻底变了,但算法却极其自信地判定它们“100% 绝对完全相同”。
2. -Shingle 的“破局”原理
如果我们使用 的词级 Shingle(每次取连续的两个词组成的“词组”),让这把滑动尺锁死前后词的邻居关系:
对句子 A (I love eating apples) 进行滑动切分:
- 第一组(前两个词):
"I love" - 第二组(往后滑一个词):
"love eating" - 第三组(再往后滑一个词):
"eating apples"
- 集合 A (2-Shingle) =
{ "I love", "love eating", "eating apples" }(共 3 个元素)
对句子 B (apples eating love I) 进行滑动切分:
- 第一组:
"apples eating" - 第二组:
"eating love" - 第三组:
"love I"
- 集合 B (2-Shingle) =
{ "apples eating", "eating love", "love I" }(共 3 个元素)
3. 防篡改效果对比
现在我们重新对 之后的两个集合计算 Jaccard 相似度: 由于锁死了局部语序,这两句话切出来的词组没有一个能对得上!
- 交集 =
{ }(0 个) - 并集 =
{ "I love", "love eating", "eating apples", "apples eating", "eating love", "love I" }(6 个)
💡 核心总结
只按单词切:只管“有没有这个词”,不管“词在哪里”,所以调换顺序无法识别(相似度 100%)。
按 -Shingle 切:不仅管“有没有这个词”,还管“它的上下游是谁”。只要你敢打乱顺序,连续的词组就会断裂,相似度就会瞬间暴跌,从而精准揪出那些故意洗牌改写的抄袭手段。
三、 工业界如何选择 的大小?
的取值是一个精妙的平衡,通常取决于文本的平均长度以及字符集的大小。
| 取值情况 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 太小(如 1 或 2) | 集合元素少,计算和存储开销极小。 | 区分度极低。像 "the", "and", "的", "是" 这种高频词/字符会大量重复,导致几乎所有文章的相似度都很高。 | 垃圾邮件分类的粗筛、极其简短的搜索关键词提示。 |
| 太大(如 20 以上) | 区分度极高,几乎具备唯一性。 | 容错率极低。哪怕抄袭者只改动了一个错别字,就会导致连续的 20 个 Shingle 全部碎掉(无法匹配),从而漏掉抄袭。 | 完全相同的文件/代码块硬匹配。 |
🎯 黄金经验法则(Engineering Rule of Thumb)
在实际工程中,选择 的标准是:让任意一个随机 Shingle 出现的概率足够低。
- 短文本(如短信、推特、商品评论):工业界一般选择 基于字符的 。
- 长文本(如新闻、论文、博客文章):工业界一般选择 基于单词/词组的 。
四、 落地工程优化:Shingle 空间爆炸了怎么办?
如果一篇文章有 1 万个词,用 变换后,就会产生约 1 万个长度为 9 的字符串(例如 "海量文本去重与相似度")。直接存储这些大字符串,内存会直接挤爆,后续的 MinHash 矩阵也无法高效计算。
为了解决这个瓶颈,工业界采用了标准的 Tokens -Shingle Hashing 流程:
在切出 -Shingle 字符串后,立即用一个高效、均匀分布的哈希函数(如 MurmurHash3 或 64位 CRC)将其转化为一个 固定长度的整数(4字节或8字节)。
原始文本: "I love eating"
↓ (2-Shingle 切分)
字符串集合: { "I love", "love eating" }
↓ (MurmurHash3 数字化)
整数集合: { 284719384, 918273645 }
通过这一步降维,原本冗长的文本块就变成了一串紧凑、高密度的数字集合。接下来,系统就能完美对接 MinHash 签名算法,开启百亿级数据流的亚线性快速检索了。