0%

数据脱敏(PII)

一句话定位:把语料里的个人身份信息(PII)识别并替换掉,避免模型记忆并泄露隐私。技术不难,难在识别覆盖率与误杀率的平衡

1. 正则 + NER 双路识别

两条路径互补,缺一不可:

  • 正则(规则)路:针对格式固定的实体,准确率高、速度快:
    • 邮箱、手机号、身份证号、银行卡号、IP 地址;
    • 密钥/凭证(API Key、AccessKey、私钥块)——这类泄露风险最高,且格式特征明显(前缀 + 定长随机串 + 熵值高)。
  • NER(模型)路:针对无固定格式的实体,靠上下文判断:
    • 人名、地名、机构名、住址。
    • 正则无能为力(人名没有模式),必须靠命名实体识别模型。

工程实现常用 Microsoft Presidio 这类框架:内置识别器(正则 + checksum 校验 + NER)+ 可插拔的匿名化策略。注意带校验位的实体(身份证、银行卡)应加校验位验证,可大幅降低正则误报。

2. 替换而非删除(保持文本结构)

关键原则:用占位符替换,不直接删除

  • 删除会破坏句子结构与语法完整性,产生断裂的病句,反而污染训练语料;
  • 替换为类型化占位符(如 <EMAIL><PHONE>[PERSON])或同类型伪造值(Faker 生成的假邮箱/假人名),能保持句法结构与 token 分布,让模型学到”这里是一个邮箱”的模式而非具体值。
  • 保持长度/类型一致性也有利于下游 tokenization 的稳定性。

3. 误杀率的权衡

这是本环节的核心 Tradeoff:

  • 过度脱敏(误杀高):把正常词误判为人名/地名(中文人名与普通词高度重叠,如”小明””建国”),或把代码里的变量名、文档里的示例邮箱一并替换 → 语料被大量占位符污染,语义受损、可读性下降;
  • 脱敏不足(漏检):真实 PII 残留 → 模型可能在生成时逐字复现训练数据中的隐私信息,构成合规风险(GDPR 等)。

实践取向:

  • 按风险分级:高危实体(密钥、身份证、银行卡)宁可误杀,从严;低危且易误判的(人名、地名)适度放宽或结合上下文置信度阈值;
  • 抽样人工评估:对脱敏前后样本做双向抽查,同时量化漏检率与误杀率,而不是只看替换总量。

参考:Microsoft Presidio 官方文档;论文《Scrubbing Sensitive PII from Large Datasets》

数据配比与混合策略

一句话定位:清洗完成后,各领域数据”按什么比例喂、喂几遍”直接决定模型的能力画像。这是从”数据工程”跨向”训练效果”的关键决策环节。

1. 各域采样权重

预训练语料由多个域构成(网页 / 书籍 / 代码 / 多语言 / 论文等),各域数据量差异极大(网页占绝对多数),若按自然比例采样,模型会被网页数据主导。

因此需要人为设定采样权重(reweighting):

  • 上采样高质量但量小的域(书籍、百科、论文)——提高知识密度与长文本连贯性;
  • 下采样量大但质量参差的域(网页);
  • 代码比例影响推理与结构化能力;多语言比例影响跨语言能力,且与目标语言的能力存在竞争(挤占效应)。

The Pile 的做法即为典型:由 22 个子数据集构成,每个域显式指定采样权重,使最终 token 分布与自然分布显著不同。

2. 重复轮次(Epoch)设计

同一域的数据可被重复训练多轮,但重复不是免费的

  • 高质量小域适度重复(如 2~4 epoch)能提升收益;
  • 重复过多会导致记忆化(memorization)与过拟合,边际收益快速衰减,甚至损害泛化;
  • 因此”有效 token 数”应折算重复轮次来计算,而非简单相加。

这与 2.4 去重是一体两面:去重是消除非预期的重复,epoch 设计是引入受控的重复。

3. Data Mixing Laws:用小规模实验预测配比

核心思路:不必用全量算力试错配比——

  1. 小规模(小模型 / 少量 token)上跑多组不同混合比例的实验;
  2. 拟合出”混合比例 → 最终 validation loss”的函数关系(可解析的规律形式);
  3. 用该函数外推预测大规模训练下的最优混合比例,再投入真正的大规模训练。

价值:把”配比”从依赖直觉的玄学,变成可低成本量化预测的工程问题。这与 1.2 中”提前抽样发现问题而非事后救火”的方法论完全同源——先用小成本实验建立可外推的基线,再投产

参考:论文《The Pile》配比章节;论文《Data Mixing Laws: Optimizing Data Mixtures by Predicting Language Modeling Performance》

预训练数据采集与来源

一句话定位:预训练数据链路的第一环,决定了语料的规模上限与”脏度”下限——采什么源、怎么从原始网页里把正文捞干净,直接决定后续所有清洗环节的工作量。

1. 多源异构

预训练语料通常由多类来源混合而成:

  • 网页:以 Common Crawl 为主,规模最大(PB 级)、覆盖最广,但噪声最重(模板、广告、导航、低质内容)。
  • 文档:书籍、论文(arXiv)、百科等,质量高、长文本连贯性好,是提升知识密度的关键。
  • 代码:GitHub 等代码语料,对提升模型推理与结构化能力有显著作用。
  • 其余:问答社区、多语言语料等。

异构性体现在格式、编码、质量、语言分布都不一致,因此采集之后必须统一到标准化的中间表示,才能进入后续流水线。

2. WARC 格式与正文提取的难点

Common Crawl 以 WARC(Web ARChive)格式分发,一条记录包含 HTTP 请求/响应头与原始 HTML 负载(另有 WAT 元数据、WET 纯文本衍生格式)。

从 WARC 中提取正文的核心难点:

  • 去模板(Boilerplate Removal):页面里导航栏、页脚、侧边栏、广告、版权声明等跨页面重复的模板内容,占比常常超过正文;若不去除,会在语料中形成海量近重复片段,既浪费算力也损害模型质量。
  • 去导航:菜单、面包屑、”相关阅读”链接列表等短文本块,语义价值低但数量极大。
  • 附带难点:HTML 结构不规范、字符编码混乱、正文与评论区难以区分。

工程上常用基于 DOM 结构与文本密度的启发式抽取(如 trafilatura、jusText 类思路),并与后续的规则过滤(2.2)配合,形成”抽取 + 过滤”两道防线。

3. 工程视角

该环节是解析型 CPU 密集任务:单条记录处理不重,但记录数极大(十亿量级),瓶颈在解析吞吐与 I/O,靠高并行度 + 大批量顺序读取 + 列式/压缩中间格式(对应 1.6、1.7)来提速。

参考:Common Crawl 官方文档;论文《The Pile: An 800GB Dataset of Diverse Text for Language Modeling》

Tokenization:SentencePiece 原理

一句话定位:SentencePiece 把 tokenization 做成”端到端、语言无关”的组件——不依赖预分词、直接吃原始文本,并提供 Unigram 语言模型式的概率化切分,是中日韩等无空格语言的标准选择。

1. 无需预分词,直接在原始文本上训练

  • 传统 BPE 流程假设文本已按空格预分词,这带来两个问题:依赖语言特定的分词器;对无空格语言(中文、日文、韩文、泰文)根本不适用。
  • SentencePiece 把空格本身编码为普通字符(用 表示),于是整个句子就是一个字符流,切分完全由算法学习决定。
  • 直接收益:
    • 语言无关:同一套流程适配任意语言,天然适配中日韩;
    • 完全可逆(lossless):解码时把 还原为空格即可精确还原原文,不丢失空白信息;
    • 训练与推理端一致,无需外挂分词器。

2. Unigram 语言模型式的概率化切分

除支持 BPE 外,SentencePiece 提供 Unigram LM 算法:

  • 假设每个子词独立出现,各有概率 p(x);一个切分方案的概率为其所有子词概率之积;
  • 训练时从一个较大的候选子词集出发,用 EM 算法迭代估计各子词概率,并逐步剪枝掉对总体似然贡献最小的子词,直到词表达到目标大小(注意方向与 BPE 相反:BPE 自底向上合并,Unigram 自顶向下裁剪);
  • 编码时用 Viterbi 算法概率最大的切分路径,即全局最优切分;还支持按概率采样多种切分(subword regularization),可作为数据增强提升鲁棒性。

3. 对比 BPE:概率最优 vs 频率贪心

这是面试的关键落点,一句话概括:

Unigram 是概率最优切分,BPE 是频率贪心合并。

维度 BPE Unigram (SentencePiece)
建词表方向 自底向上,贪心合并高频对 自顶向下,按似然剪枝
切分依据 固定的合并规则顺序(贪心、确定性) 全局概率最大路径(Viterbi)
多切分支持 单一切分 可按概率采样多切分(正则化)
预分词 通常需要 不需要

BPE 的贪心可能得到局部最优切分;Unigram 在给定词表下求全局最优,理论上更合理,但训练成本更高。

参考:论文《SentencePiece: A simple and language independent subword tokenizer and detokenizer for Neural Text Processing》(Kudo & Richardson, 2018);SentencePiece 官方 GitHub 文档

Tokenization:BPE 原理

一句话定位:BPE(Byte Pair Encoding)是子词切分的奠基算法——用”频率贪心合并”在字符与词之间找折中,同时解决 OOV 与词表爆炸两个老问题。

1. 算法原理:频率贪心合并

训练过程:

  1. 从字符起始:初始词表就是语料中出现的全部基础字符(字节级 BPE 则以 256 个字节为基),每个词被拆成字符序列;
  2. 统计相邻符号对频率:扫描语料,统计所有相邻 symbol pair 的出现次数;
  3. 合并频率最高的一对:把出现频率最高的相邻对合并为一个新 symbol,加入词表,并记录该合并规则;
  4. 重复 2~3,直到词表大小达到预设目标(如 32k、50k)。

推理(编码)时,按训练阶段记录的合并规则顺序对输入文本逐步合并,得到子词序列。

本质:这是一个频率驱动的贪心过程——高频组合被合并成整体(常见词最终成为单个 token),低频组合保持为更小的片段。

2. 解决的两个问题

  • OOV(未登录词):传统词级词表遇到没见过的词只能映射为 <UNK>,信息完全丢失。BPE 的词表包含字符/字节级单元,任何词都能被拆成已知子词的组合,因此不存在真正的 OOV(字节级 BPE 可覆盖任意 Unicode 输入)。
  • 词表爆炸:若用完整词表,自然语言的词形变化(时态、复数、派生、复合词)会让词表膨胀到百万级,嵌入矩阵与 softmax 层开销不可接受。BPE 用固定预算(几万)的子词表覆盖开放词汇,把词表大小控制住。

核心权衡:词表越大 → 序列越短(推理更快)但嵌入层越大;词表越小 → 嵌入层小但同样文本被切成更多 token(序列变长、计算变多)。

3. 与 SentencePiece 的关系

BPE 通常需要预分词(先按空格切词再做子词合并),这对中日韩等无空格语言不友好;SentencePiece 直接在原始文本上训练并支持 Unigram 概率化切分,是对该局限的改进(见 2.8)。

参考:论文《Neural Machine Translation of Rare Words with Subword Units》(Sennrich et al., 2016)

MinHash / LSH 模糊去重

一句话定位:面试高频必问。在十亿级文档里找出”内容近似重复”的文档簇——两两比对是 O(N²) 不可行,MinHash 把相似度压成签名、LSH 把候选集缩小到桶内,使近似去重在大规模下可行。去重能显著提升语言模型效果(减少记忆化、提高有效 token 占比)。

1. 完整流水线

1 Shingling(分片)

把文档切成长度为 k 的连续片段集合(k-shingle / k-gram,可按词或字符)。文档由此变成一个集合,文档相似度 = 两集合的 Jaccard 相似度 J(A,B) = |A∩B| / |A∪B|

k 的选择:k 太小则不同文档偶然共享大量 shingle(假阳性高);k 太大则对微小改写过于敏感(召回下降)。

2 MinHash 签名

  • 对集合施加一个哈希置换 h,取集合中所有元素哈希值的最小值作为一个签名分量。
  • 关键性质:P(minhash(A) == minhash(B)) = J(A,B)——两个集合最小哈希相等的概率恰好等于 Jaccard 相似度。
  • 用 n 个独立哈希函数得到长度为 n 的签名向量,则”签名相同分量的比例”就是 Jaccard 的无偏估计。作用是把任意大的集合压成定长 n 的签名,把集合比较变成定长向量比较。

3 分带(Banding)分桶

把长度 n 的签名切成 b 个 band,每个 band 含 r 行(n = b × r)。对每个 band 做哈希分桶:只要有任一 band 完全相同,两文档即成为候选对

碰撞概率(相似度为 s 的两文档成为候选的概率):

P(候选) = 1 - (1 - s^r)^b

这是一条 S 形曲线,其陡峭的转折点(近似阈值)约在 s ≈ (1/b)^(1/r)

4 桶内两两比对

只对落入同桶的候选对计算真实(或签名估计的)相似度,超过阈值判为重复。候选集远小于全量对,这是复杂度从 O(N²) 降下来的根本原因。

5 连通图求重复簇

把”判定为重复”的文档对看作图的边,用并查集 / 连通分量求出重复簇,每簇保留一篇代表文档(或按质量分挑最优),其余丢弃。

2. 工程要点

2.1 签名长度 n 与 band 数 b 决定精确率/召回率权衡

P = 1 - (1 - s^r)^b 可知:

  • b 增大(r 减小) → 曲线阈值左移,更”宽松”:召回高、假阳性多、候选对暴涨(后续比对更贵);
  • r 增大(b 减小) → 阈值右移,更”严格”:精确率高、漏掉中等相似的重复;
  • n 增大 → 相似度估计方差更小(更准),但哈希计算与存储成本线性上升。

调参思路:先定业务上的相似度阈值 s₀(如 0.8),再选 b、r 使 S 曲线的陡升区间对齐 s₀。

2.2 Hash 计算是 CPU 热点

每篇文档要算 n 个(常见 128~256)MinHash,是纯 CPU 密集的大头。优化:用快速哈希(MurmurHash/xxHash)、一次哈希多次置换的技巧(如 (a*h + b) mod p)、向量化与批处理、提高并行度。

2.3 桶内比对易数据倾斜

分桶后桶大小严重不均:模板化页面、超高频近重复内容会让某些桶极度膨胀,桶内两两比对是 O(m²),单个大桶就能拖死整个作业——这正是 1.2 数据倾斜的典型现场。

治理手段可直接复用 1.2:

  • 对超大桶加盐拆分或做二次分桶;
  • 桶大小上限,超限的桶采样或单独处理(隔离热键);
  • 提前对桶大小分布做抽样探测,而非等作业跑挂(对应”提前发现而非事后救火”)。

参考:《Mining of Massive Datasets》第 3 章(Finding Similar Items);论文《Deduplicating Training Data Makes Language Models Better》(Lee et al., 2022)

I/O 优化手段

一句话定位:数据处理链路的吞吐上限往往卡在 I/O(磁盘/网络/解析),而非计算。优化的主线是”少读、少传、少解析、提前准备”。

1. 投影下推 / 谓词下推(少读、少传)

  • 投影下推(Projection Pushdown):只读取查询/计算需要的列。列存下同一列连续存储,可直接跳过无关列块,IO 量随列数下降。
  • 谓词下推(Predicate Pushdown):把 filter 条件下推到扫描层,在读取时即过滤。如 Parquet 用 Row Group / Page 的 min-max/null 统计做谓词剪枝,跳过不满足条件的数据块。
  • 收益来源:减少从存储到计算引擎的数据量(IO + 网络 + 反序列化)。

2. 压缩算法选型(带宽 vs CPU)

算法 速度 压缩比 适用
Snappy 中(~2-3x) CPU 是瓶颈、热数据/中间数据
Zstd 可调,较快 高(~3-5x+) 带宽/存储是瓶颈、冷数据
  • 权衡:压缩省带宽与存储,但解压耗 CPU。CPU 紧张选 Snappy,IO/存储紧张选 Zstd

3. 小文件合并(降元数据与调度开销)

  • 大量小文件 → NameNode/文件句柄元数据压力大、每文件一个 task 导致调度开销高、随机寻址成本高。
  • 手段:写入时合并大文件、定期 Compaction(HDFS 小文件合并、ORC/Parquet 合并)、使用面向大文件的格式。合并后 task 数下降,吞吐提升明显。

4. 异步预取与 MMap(掩盖延迟)

  • 异步预取(Async Prefetch):后台线程提前读取后续数据,与计算重叠,掩盖 IO 延迟(类比 CPU 硬件预取)。
  • 内存映射(MMap):文件映射到进程地址空间,由 OS 页缓存管理,免系统调用拷贝,适合大文件顺序/随机读;但大文件 MMap 可能挤占页缓存,需权衡。

参考:Apache Parquet / ORC 官方文档(Pushdown、压缩);Linux mmap 手册

数据管道全链路设计范式

一句话定位:数据管道不是”一串脚本”,而是一套”分层解耦 + 算子可复用 + 可重跑 + 资源隔离”的工程系统。面试要把”接入→清洗→转换→输出”讲成可治理的范式。

1. 分层解耦:接入 → 清洗 → 转换 → 输出

  • 每层职责单一,通过标准数据契约(schema / 格式)衔接;
  • 单层可独立替换、独立扩缩(如换 source 不影响 transform);
  • 故障定位时按层切分,避免”一处坏全线瘫”。

2. 算子抽象与可复用性

  • 把处理单元抽象为统一接口的算子(map / filter / join / aggregate;Flink 算子、Spark 算子、Ray Actor);
  • 沉淀为可复用算子库 + 编排模板,业务逻辑靠组合而非重写;
  • 对应”平台化意识”:一次优化沉淀为通用能力。

3. 幂等与重试设计

  • 幂等:同一输入多次执行结果一致(唯一键 / 去重表 / upsert / 幂等写入),使重试安全;
  • 重试 + 断点续跑:失败任务可重跑(至少一次 → 配合幂等达成”有效一次”),靠位点/offset/checkpoint 续跑,避免全量重算;
  • 对应 1.1.4 Flink 的 Exactly-Once 思想。

4. CPU 密集与 GPU 密集的资源池分离

  • 解析/清洗/特征(CPU 密集)与推理/打标(GPU 密集)分属不同资源池(如 CPU 集群 vs GPU 集群);
  • 用 Ray / 调度器做异构编排(如 head 在 CPU 集群、worker 在 GPU 集群),避免互相抢占;
  • 对应 1.1.5 Ray 的异构资源调度优势。

5. 可观测与背压

  • 监控各环节吞吐/延迟/积压;
  • 背压(Backpressure):下游过载时反向限速,防止雪崩(Flink 原生背压机制)。

参考:Google《Data Pipelines》相关实践;Flink / Spark Structured Streaming 背压机制文档

数据格式对比:JSONL / Parquet / Arrow / ORC

一句话定位:选数据格式本质是在”解析成本 / 存储扫描 / 跨语言传输”之间取舍。面试常考”为什么把 JSONL 换成 Parquet/Arrow 吞吐能翻倍”。

1. 四种格式速览

格式 形态 解析成本 存储/扫描 跨语言 生态
JSONL 文本,每行一对象 高(逐行 JSON decode,CPU 瓶颈) 无压缩/列裁剪 通用 日志、交换
Parquet 列式 + 压缩 低(二进制列读) 友好(列裁剪+谓词下推+高压缩) Hadoop 事实标准
Arrow 内存列式 极低(零拷贝) 内存计算友好 极佳(C++/Python/Java 共享) 内存计算/传输
ORC 列式 + 压缩 友好 Hive 生态强绑定
  • JSONL:可读但需逐行解析,无 schema/压缩弱,适合采集交换,查询慢。
  • Parquet:列式 + 嵌套支持 + 高压缩,分析型存储首选。
  • Arrow:内存中的列式格式,跨语言零拷贝共享(Spark↔Pandas、Ray Plasma),避免序列化。
  • ORC:与 Parquet 类似,但和 Hive/Impala 集成更深。

2. 面试话术:JSONL → Parquet/Arrow 吞吐提升的三点

  1. 消除文本解析:免去逐行 JSON decode(最贵的 CPU 环节);
  2. 只读需要的列(列裁剪):不必把整行反序列化;
  3. 零拷贝 / 二进制列式:Arrow 在内存层免序列化,Parquet 在存储层免文本解析。

参考:Apache Parquet / Arrow / ORC 官方文档;Google Dremel 论文(Parquet 思想来源)

Flink 流批一体与状态管理

一句话定位:Flink 的杀手锏是”一套引擎跑流和批”(流批一体),以及可扩缩、可容错的状态管理(State + Checkpoint);二者共同支撑 Exactly-Once。面试重点在 State Backend 选型、Barrier 对齐、以及 Exactly-Once 是怎么串起来的。

1. 流批一体(Unified Engine)

  • Flink 认为批是流的特例(有界流):同一套 DataStream API / Table-SQL API,通过 ExecutionMode.BATCH 即可跑有界数据,无需维护两套代码。
  • 统一 Runtime:调度、容错、shuffle、状态后端对批流一致;降低了”流一套、批一套”的维护成本,也保证了语义一致。
  • 面试话术:把”流批一体”讲成”统一 API + 统一 Runtime + 有界即无界特例”,比单纯说”支持批处理”更有深度。

2. 状态管理:State Backend 选型

Backend 存储位置 优势 适用
HashMapStateBackend JVM 堆 读写最快、无需序列化 小状态、低延迟
EmbeddedRocksDBStateBackend 本地磁盘 + 堆外 支持超大状态、增量 Checkpoint 大状态、长窗口、高吞吐
  • 选型看状态规模延迟预算:状态放得下堆就用 HashMap(快),放不下或要做增量 checkpoint 就用 RocksDB(慢但可扩展)。
  • 状态类型:Keyed State(按 key 分区,随 key 路由)/ Operator State(算子级,如 source offset)。

3. Checkpoint 的 Barrier 对齐机制

  • Barrier 由 Source 按固定间隔注入数据流,随数据向下游流动,标记”属于第 n 个 checkpoint 的数据边界”。
  • 对齐(Alignment):算子收到某个输入通道的 barrier-n 后,该通道后续数据被缓存阻塞,直到所有输入通道都收到 barrier-n,才做状态快照(snapshot)并向下游广播 barrier-n。
  • 对齐的意义:保证 Exactly-Once——barrier 之后的数据绝不会混入本次 checkpoint 的状态。
  • 非对齐 Checkpoint(Unaligned):把正在传输中的缓冲也纳入快照,避免反压下 barrier 被”堵”在通道里导致 checkpoint 超时,降低尾延迟,但快照更大。

4. Exactly-Once 如何达成

端到端 Exactly-Once = 可重放 Source + Checkpoint 状态持久化 + 事务/幂等 Sink

  1. Source 记录位点(Kafka offset / 文件偏移),失败可从 checkpoint 重放;
  2. 基于 Chandy-Lamport 分布式快照算法,barrier 对齐保证各算子状态对应同一逻辑时间点;
  3. Sink 用两阶段提交(2PC,如 Flink-Kafka 事务)幂等写入,使”状态提交”与”外部输出”原子化。

参考:Apache Flink 官方文档 State Backends / Checkpointing;论文《Lightweight Asynchronous Snapshots for Distributed Dataflows》(Chandy-Lamport)