AI 数据合规检查:GDPR 和个保法的字段级自动扫描方案 AI 数据合规检查GDPR 和个保法的字段级自动扫描方案数据合规这件事很多团队的处理方式是法务出一份清单工程师手动打标签。问题是数据仓库有 200 张表、5000 个字段手动标注的覆盖率可能只有 30%而且标注后字段改名了、新增了标签又不跟上了。等到审计时才发现漏标了 50 个敏感字段罚款可能比整改成本高 10 倍。AI 数据合规检查的思路是用 NLP 模型自动识别字段含义判断是否符合 GDPR 和个保法的合规要求。它不是替代法务判断而是替代工程师手动扫描 5000 个字段这个劳力密集环节。一、合规扫描的三个层次数据合规检查不是有没有身份证号这么简单。法规要求覆盖三个层次层次1直接标识字段姓名、身份证号、手机号、邮箱——这些字段一眼就能看出是个人信息。但数据仓库里的命名可能不是这么直白cust_name、id_card_no、phone_num、email_addr。缩写、英文、业务自定义命名让直接标识也变得不那么直接。层次2间接标识字段 quasi-identifier年龄、性别、城市、职业——单独看都不是个人信息但组合起来可以唯一识别一个人。GDPR 把这种组合可识别也定义为个人信息。比如35岁女性北京律师可能只有一个人符合这四个字段合在一起就是个人信息。层次3推断敏感字段消费偏好、健康数据、政治倾向——这些字段本身不包含标识信息但法规认为它们属于敏感个人信息个保法第28条。你的数据仓库里可能有purchase_category、health_checkup_result、political_survey_answer这类字段它们需要更严格的处理比如必须明示同意才能收集。graph TB A[数据仓库5000字段] -- B[层次1扫描br/直接标识字段] A -- C[层次2扫描br/间接标识组合] A -- D[层次3扫描br/推断敏感字段] B -- E[GDPR Art.4br/个人信息] C -- E D -- F[个保法§28br/敏感个人信息] E -- G[合规建议:br/脱敏/加密/最小化] F -- H[合规建议:br/明示同意单独存储] style B fill:#e1f5fe style C fill:#fff3e0 style D fill:#fce4ec三个层次的扫描逻辑不同层次1靠字段名和样例数据匹配层次2靠字段组合的 k-匿名性评估层次3靠语义推断。AI 在这三个层次都能发挥作用但发挥的方式不一样。二、层次1字段名样例数据的自动识别字段名识别是合规扫描的基础。我们用 NLP 模型做字段名的语义分类from sentence_transformers import SentenceTransformer import numpy as np # 加载中文语义模型——为什么用 text2vec 而不是规则匹配 # 因为规则匹配只能覆盖已知命名比如 id_card_no # 但数据仓库里可能有 50 种不同的身份证号命名方式 # 语义模型能理解id_number、身份证号码、cert_id都是同一个含义 # 规则列表永远追不上业务的命名创意 model SentenceTransformer(shibing624/text2vec-large-chinese) # 定义合规类别的参考向量——每个类别用多个典型字段名编码 # 为什么用多个而不是单个 # 因为单个参考词的向量可能不够代表性 # 多个参考词的均值向量更稳定覆盖更多表达方式 PII_categories { 直接标识-姓名: [姓名, 顾客名称, 客户名, customer_name, cust_name, user_name], 直接标识-身份证: [身份证号, 证件号, id_card, cert_no, identity_number, id_number], 直接标识-手机号: [手机号, 联系电话, phone, mobile, phone_number, contact_number], 直接标识-邮箱: [电子邮箱, email, email_address, mail, email_addr], 间接标识-年龄: [年龄, age, birth_year, 年龄段, age_group], 间接标识-地址: [住址, 地址, address, home_address, 居住地, residence], 敏感-健康: [体检结果, health_status, 疾病记录, medical_record, 健康数据], 敏感-偏好: [消费偏好, purchase_category, 购物习惯, brand_preference], 非敏感-业务: [订单号, order_id, 交易金额, amount, 注册日期, signup_date], } # 计算每个类别的参考向量类别内所有参考词的均值 category_vectors {} for cat, refs in PII_categories.items(): vecs model.encode(refs) category_vectors[cat] np.mean(vecs, axis0) # 均值向量更稳定 def classify_field(field_name, field_sampleNone, threshold0.75): 分类单个字段——threshold0.75 是什么意思 0.75 表示语义相似度≥75%才归入该类别 为什么不设更高因为字段名可能用缩写或英文和参考词的相似度会被稀释 比如 cust_name 和 客户名 的相似度可能在 0.78 左右 0.85 的阈值会漏掉这类缩写命名 为什么不设更低因为 0.7 以下会误分类 比如 amount 和 address 相似度可能在 0.65但含义完全不同 field_vec model.encode([field_name])[0] # 计算与每个类别的相似度 scores {} for cat, cat_vec in category_vectors.items(): # 内积向量已归一化后等同于余弦相似度 sim np.dot(field_vec, cat_vec) / ( np.linalg.norm(field_vec) * np.linalg.norm(cat_vec) ) scores[cat] sim # 取最高相似度的类别 best_cat max(scores, keyscores.get) best_score scores[best_cat] if best_score threshold: return best_cat, best_score # 如果字段名匹配不到检查样例数据 # 为什么样例数据是补充而不是主要判断依据 # 因为样例数据可能被脱敏过比如身份证号显示为 110***********1234 # 脱敏后的样例无法用于识别字段含义 # 但未被脱敏的样例数据是很好的辅助证据 if field_sample: sample_vec model.encode([f{field_name}: {field_sample}])[0] sample_scores {} for cat, cat_vec in category_vectors.items(): sim np.dot(sample_vec, cat_vec) / ( np.linalg.norm(sample_vec) * np.linalg.norm(cat_vec) ) sample_scores[cat] sim sample_best max(sample_scores, keysample_scores.get) sample_best_score sample_scores[sample_best] if sample_best_score threshold: return sample_best, sample_best_score return 未分类, best_score三、层次2间接标识组合的 k-匿名性评估间接标识字段单独看可能没问题但组合起来可能能唯一识别一个人。k-匿名性评估就是检查任意 k 个间接标识字段的组合是否能让至少 k 个人匹配——如果只有 1 个人匹配这个组合就泄露了个人信息。import pandas as pd def evaluate_k_anonymity(df, quasi_id_columns, k5): 评估 k-匿名性——k5 是什么意思 k5 表示任何间接标识组合至少对应 5 个人 为什么 k5 而不是 k3 因为 GDPR 推荐的最低标准是 k5 k3 在小样本下可能不够安全3个人里可能只有1个女性组合仍然可识别 k5 提供更大的安全缓冲 # 按间接标识列组合分组计算每组的人数 # 为什么用 groupby size 而不是 value_counts # 因为 groupby 可以处理多列组合value_counts 只能处理单列 group_sizes df.groupby(quasi_id_columns).size() # 统计有多少组合的 k 值低于阈值 # 低于阈值意味着这个组合能唯一识别少于 k 个人 unsafe_combos group_sizes[group_sizes k] # 计算不安全比例——为什么关注比例而不是绝对数 # 因为数据量不同时绝对数没有可比性 # 比如 100 万条数据里有 500 个不安全组合0.05% # 和 1 万条数据里有 500 个不安全组合5%风险级别完全不同 unsafe_ratio len(unsafe_combos) / len(group_sizes) return { quasi_ids: quasi_id_columns, total_combos: len(group_sizes), unsafe_combos: len(unsafe_combos), unsafe_ratio: unsafe_ratio, min_k: group_sizes.min(), # 最小的 k 值——有人只靠这些字段就能被唯一识别 mean_k: group_sizes.mean(), # 平均 k 值——整体匿名性水平 } # 实际使用扫描所有可能的间接标识组合 # 为什么不只检查全部间接标识列的组合 # 因为攻击者可能只掌握部分信息比如只知道年龄和城市 # 需要检查每个子组合的 k 值不只是完整组合 quasi_ids [age_group, gender, city, occupation] # 检查所有 2 列组合——为什么先检查 2 列 # 因为 2 列组合是最常见的攻击场景攻击者通常只知道 2-3 个属性 # 如果 2 列组合就低于 k5说明数据匿名性严重不足 from itertools import combinations results [] for r in range(2, len(quasi_ids) 1): for combo in combinations(quasi_ids, r): result evaluate_k_anonymity(df, list(combo), k5) results.append(result) # 输出风险最高的组合——unsafe_ratio 最高意味着最容易泄露个人信息 worst max(results, keylambda x: x[unsafe_ratio]) print(f风险最高的组合: {worst[quasi_ids]}, 不安全比例: {worst[unsafe_ratio]:.2%})如果发现age_group city的组合 k 值只有 2每对年龄-城市组合平均只有 2 个人合规建议是对这两个字段做泛化处理年龄从具体值泛化到年龄段城市泛化到省份。四、层次3推断敏感字段的语义推断有些字段名不含敏感关键词但数据内容涉及敏感信息。比如tag_list字段存的是运动达人、母婴关注、保健品购买——这些标签组合可能推断出健康状况和育儿状态。def infer_sensitive_from_content(df, field_name, sensitive_keywords): 从字段内容推断敏感类别——为什么用关键词而不是模型 因为敏感推断需要精确匹配法规定义的敏感类别 模型推断可能有幻觉把普通购物标签误判为健康敏感 关键词匹配更可控误判率更低 生产环境可以先用关键词做初筛再用模型做深度判断 # 统计字段内容中包含敏感关键词的比例 # 为什么关注比例而不是存在与否 # 因为一个字段可能有 90% 的普通数据和 10% 的敏感数据 # 如果只判断是否存在所有多值字段都会被标为敏感 # 比例判断更精确只有超过阈值比如 5%才标为敏感 content df[field_name].astype(str) hit_count 0 for kw in sensitive_keywords: hit_count content.str.contains(kw, caseFalse).sum() hit_ratio hit_count / len(df) # 阈值 5%——为什么这么低 # 因为个保法对敏感数据的定义是一旦泄露就可能造成歧视或侵害 # 5% 的人受影响已经构成风险不能忽视 if hit_ratio 0.05: return True, hit_ratio return False, hit_ratio # 个保法定义的敏感数据类别关键词 sensitive_keywords { 健康: [疾病, 体检, 处方, 手术, 慢性病, 药品, 健康数据], 生物识别: [指纹, 人脸, 虹膜, 声纹, 基因], 金融: [收入, 负债, 征信, 贷款, 信用评分], 未成年: [儿童, 学生, 未成年, 中小学], 政治: [选举, 投票, 政治倾向, 党派], } # 扫描所有文本类字段——为什么只扫文本类 # 因为数字类字段amount、count通常不含语义信息 # 敏感推断主要适用于标签、描述、备注等文本内容 text_fields [col for col in df.columns if df[col].dtype object] for field in text_fields: for category, keywords in sensitive_keywords.items(): is_sensitive, ratio infer_sensitive_from_content(df, field, keywords) if is_sensitive: print(f⚠ {field} 可能包含{category}敏感数据命中比例: {ratio:.1%})五、合规建议的自动生成扫描完成后AI 可以根据分类结果自动生成合规建议def generate_compliance_report(scan_results): 自动生成合规建议报告——为什么用规则模板而不是 LLM 因为合规建议必须精确匹配法规条文不能有歧义 LLM 生成的建议可能含糊比如建议适当处理 规则模板直接映射到法规条文每条建议都有法律依据 report [] for result in scan_results: field result[field] category result[category] severity result[severity] if category.startswith(直接标识): # GDPR Art.4 个保法 §4个人信息必须最小化收集 report.append(f- {field}: 直接标识字段建议脱敏处理哈希/掩码仅保留必要字段) if severity 高: report.append(f → 法规依据: GDPR Art.5(1)(c) 数据最小化原则) elif category.startswith(间接标识): # k-匿名性不足的间接标识字段需要泛化 report.append(f- {field}: 间接标识字段建议泛化处理如城市→省份、年龄→年龄段) report.append(f → 法规依据: GDPR Art.25 数据保护设计) elif category.startswith(敏感): # 个保法 §28敏感个人信息需要明示同意单独存储 report.append(f- {field}: 敏感个人信息需要明示同意加密存储访问审计) report.append(f → 法规依据: 个保法第28条GDPR Art.9) return \n.join(report)五、总结AI 数据合规检查分三个层次直接标识字段靠语义模型匹配字段名和样例数据间接标识组合靠 k-匿名性评估检测信息泄露风险推断敏感字段靠关键词语义推断识别隐藏的敏感数据。落地时注意三点阈值设定要结合业务场景——字段名匹配阈值 0.75、k-匿名性 k5、敏感内容命中比例 5%这些都是起点值根据你的数据量和业务特点可能需要调整扫描不是一次性的——数据仓库持续演进新增字段和改名都会让旧扫描结果失效。建议每月自动扫描一次增量字段重点检查AI 扫描的结果需要法务复核——AI 识别的类别和风险等级是技术判断合规建议的法律适用性需要法务团队确认。AI 替代的是扫描劳力不是合规判断合规扫描的终极目标是让 5000 个字段的合规标注从手工考古变成自动巡航覆盖率从 30% 提升到 95%漏标风险从审计时才发现变成扫描时就预警。