实验4.4 模型卡安全审计
对模拟的模型卡进行安全审计,练习系统性评估开源模型的安全风险
🧪 实验 4.4:模型卡安全审计
🎯 学习目标
完成本实验后,你将能够:
- ✅ 理解模型卡(Model Card)的作用和安全审计价值
- ✅ 编写自动化审计脚本检查基本信息、训练数据、风险披露
- ✅ 评估模型格式安全性和许可证合规性
- ✅ 生成综合审计报告并进行模型安全排名
📚 前置知识
- 完成实验 4.1 ~ 4.3
- 了解开源模型发布的基本流程(如 Hugging Face)
- 相关理论:模块四:供应链安全
🖥️ 实验环境
- 平台:任意 Python 环境(🖥️ 无需 GPU)
- 模型:不需要 AI 模型,纯 Python 编程练习
- Python:≥ 3.10
📝 填空说明
本实验共 5 个填空,难度:⭐⭐☆☆☆
⏱️ 预计用时
约 25 分钟
📑 目录
1. 第一部分:准备模拟模型卡数据(约 3 分钟)
2. 第二部分:模型卡基本信息审计(约 4 分钟)
3. 第三部分:训练数据透明度审计(约 5 分钟)
4. 第四部分:安全风险审计(约 5 分钟)
5. 第五部分:模型格式与许可证安全检查(约 4 分钟)
6. 第六部分:生成综合审计报告(约 4 分钟)
📤 提交说明
完成所有填空后,请将本 Notebook 文件(.ipynb)导出并提交至课程平台。评分标准:
- 5 个填空正确完成(每个 15 分,共 75 分)
- 思考题回答质量(15 分)
- 代码运行结果(10 分)
⚠️ 安全提醒:本实验仅用于教育目的。
第一部分:准备模拟模型卡数据
本实验不需要 GPU 和 AI 模型,是一个纯 Python 编程练习。
我们将模拟 3 个来自不同来源的开源模型卡(Model Card),然后编写审计脚本来检查它们的安全相关信息。模型卡是模型发布者提供的"说明书",包含模型用途、训练数据、风险提示等关键信息。
# ====== 环境依赖安装 ======
%pip install ipython-autotime -q
%load_ext autotime# ====== 定义模拟模型卡(1/3):SafeChat-7B ======
# 一个信息完善、安全措施到位的模型卡
model_cards = {}
model_cards["model_A"] = {
"model_name": "SafeChat-7B",
"organization": "TrustAI-Lab",
"version": "2.1.0",
"model_type": "对话大语言模型",
"parameters": "7B",
"license": "Apache-2.0",
"model_format": "safetensors",
"intended_use": "通用中文对话、客服系统、教育辅助",
"out_of_scope_use": "不适用于医疗诊断、法律建议、自动驾驶决策等高风险场景",
"training_data": {
"sources": ["中文维基百科", "公开新闻语料", "开源对话数据集"],
"size": "50GB 文本",
"preprocessing": "去除PII信息、过滤低质量文本、去重",
"cutoff_date": "2024-06"
},
"bias_and_limitations": {
"known_biases": ["可能对某些地区方言理解不足", "训练数据以简体中文为主"],
"limitations": ["不适合处理专业领域问题", "可能产生幻觉内容"],
"mitigation": "使用RLHF进行安全对齐,部署了内容过滤器"
},
"evaluation": {
"benchmarks": ["C-Eval: 62.5", "MMLU: 58.3", "SafetyBench: 85.2"],
"red_teaming": "通过第三方红队测试,报告已发布",
"safety_score": 85
},
"security": {
"vulnerability_disclosure": "security@trustai-lab.com",
"last_security_audit": "2024-05",
"known_vulnerabilities": []
},
"contact": "team@trustai-lab.com",
"last_updated": "2024-07-15"
}
print("✅ 模型卡 A 已加载:SafeChat-7B(信息完善)")📝 对比观察:下面的 QuickLLM-3B 信息极度缺失,使用了不安全的 pickle 格式。这种模型卡在实际平台上非常常见,是供应链安全的高风险来源。
# ====== 定义模拟模型卡(2/3):QuickLLM-3B ======
# 一个信息严重缺失、存在安全风险的模型卡
model_cards["model_B"] = {
"model_name": "QuickLLM-3B",
"organization": "anonymous_user_42",
"version": "1.0",
"model_type": "文本生成模型",
"parameters": "3B",
"license": "unknown",
"model_format": "pickle",
"intended_use": "通用文本生成",
"training_data": {
"sources": ["互联网数据"],
"size": "未知",
},
"evaluation": {
"benchmarks": ["据说效果很好"],
},
"last_updated": "2024-01-10"
}
print("✅ 模型卡 B 已加载:QuickLLM-3B(信息缺失)")📝 中间案例:MedAssist-13B 是一个医疗领域模型,信息较为完整但安全披露不够全面。注意医疗类模型的审计要求更加严格。
# ====== 定义模拟模型卡(3/3):MedAssist-13B ======
# 一个医疗领域模型,信息较完整但安全披露不足
model_cards["model_C"] = {
"model_name": "MedAssist-13B",
"organization": "HealthTech-Inc",
"version": "3.0.2",
"model_type": "医疗对话模型",
"parameters": "13B",
"license": "CC-BY-NC-4.0",
"model_format": "safetensors",
"intended_use": "医疗问答辅助、健康咨询参考",
"training_data": {
"sources": ["医学教材", "公开医疗问答", "临床指南摘要"],
"size": "20GB",
"preprocessing": "经医学专家审核"
},
"bias_and_limitations": {
"known_biases": ["以中国医疗体系为主", "不涵盖罕见病"],
"limitations": ["不能替代专业医生诊断", "可能存在过时的医学信息"]
},
"evaluation": {
"benchmarks": ["MedQA-CN: 71.2", "CMB: 68.5"],
"safety_score": 72
},
"security": {
"vulnerability_disclosure": "sec@healthtech.com"
},
"last_updated": "2024-04-20"
}
print("✅ 模型卡 C 已加载:MedAssist-13B(信息中等)")
# ====== 打印汇总 ======
print("\n" + "=" * 60)
print("📋 模拟模型卡数据已准备")
print("=" * 60)
for key, card in model_cards.items():
print(f" {key}: {card['model_name']} ({card['organization']})")
print(f" 类型: {card['model_type']}, 参数: {card['parameters']}, 格式: {card['model_format']}")
print(f"\n✓ 共 {len(model_cards)} 个模型卡,准备开始审计!")# ✅ 检查点 1:验证模型卡数据
assert len(model_cards) == 3, f"❌ 应有 3 个模型卡,实际有 {len(model_cards)} 个"
assert model_cards["model_A"]["model_format"] == "safetensors", "❌ model_A 应使用安全的 safetensors 格式"
assert model_cards["model_B"]["model_format"] == "pickle", "❌ model_B 应使用不安全的 pickle 格式"
assert model_cards["model_B"]["license"] == "unknown", "❌ model_B 许可证应为 unknown"
print("✅ 检查点 1 通过:全部 3 个模型卡数据加载完成!")第二部分:模型卡基本信息审计
审计的第一步是检查模型卡的基本信息是否完整。一个负责任的模型发布者应该提供充分的元数据。
# ========== 填空 1:基本信息完整性检查 ==========
#
# 🎯 任务:编写函数检查模型卡的基本字段是否存在且有效
#
# 💡 提示:
# - 必填字段:model_name, organization, version, license, model_format,
# intended_use, training_data, last_updated
# - 使用 dict.get(key) 检查字段是否存在
# - 返回缺失字段列表和完整性评分
#
# 请将 ___________ 替换为正确的代码
def check_basic_info(card):
"""
检查模型卡基本信息的完整性
参数:
card (dict): 模型卡数据
返回:
dict: 包含缺失字段列表和完整性评分
"""
required_fields = [
"model_name", "organization", "version", "license",
"model_format", "intended_use", "training_data",
"bias_and_limitations", "evaluation", "security",
"contact", "last_updated"
]
missing = ___________
# 期望:一个列表,包含 card 中缺失的必填字段
# 提示:[field for field in required_fields if field not in card]
total = len(required_fields)
present = total - len(missing)
score = present / total * 100
return {
"missing_fields": missing,
"present_count": present,
"total_count": total,
"completeness_score": score
}
# 对3个模型执行检查
print("=" * 60)
print("🔍 审计项1:基本信息完整性")
print("=" * 60)
for key, card in model_cards.items():
result = check_basic_info(card)
grade = "A" if result["completeness_score"] >= 90 else ("B" if result["completeness_score"] >= 70 else ("C" if result["completeness_score"] >= 50 else "D"))
color = "🟢" if grade in ["A", "B"] else ("🟡" if grade == "C" else "🔴")
print(f"\n {color} {card['model_name']} ({key})")
print(f" 完整性:{result['present_count']}/{result['total_count']} ({result['completeness_score']:.0f}%) — 等级 {grade}")
if result["missing_fields"]:
print(f" 缺失字段:{', '.join(result['missing_fields'])}")
else:
print(f" ✓ 所有必填字段完整")第三部分:训练数据透明度审计
训练数据是模型安全的基础。一个可信的模型应该清楚说明训练数据的来源、规模和预处理方式。
# ========== 填空 2:训练数据透明度检查 ==========
#
# 🎯 任务:审计模型卡中训练数据信息的透明度
#
# 💡 提示:
# - 检查 training_data 字段是否存在以下子字段:
# sources(数据来源)、size(数据规模)、preprocessing(预处理方式)
# - 评估数据来源的具体程度("互联网数据" vs 具体数据集名称)
# - 检查是否说明了数据截止日期(cutoff_date)
#
# 请将 ___________ 替换为正确的代码
def check_training_data(card):
"""
审计训练数据的透明度
参数:
card (dict): 模型卡数据
返回:
dict: 透明度评估结果
"""
issues = []
score = 100 # 满分开始,有问题就扣分
training_data = card.get("training_data", {})
if not training_data:
return {"issues": ["未提供任何训练数据信息"], "score": 0}
# 检查数据来源
sources = training_data.get("sources", [])
if not sources:
issues.append("未说明训练数据来源")
score -= 30
else:
# 检查来源描述是否过于模糊
vague_keywords = ["互联网", "网络", "未知", "各种"]
for source in sources:
if any(kw in source for kw in vague_keywords):
issues.append(f"数据来源描述模糊:'{source}'")
score -= 10
# 检查数据规模
size = ___________
# 期望:从 training_data 字典中获取 "size" 字段,如果不存在则返回 None
# 提示:training_data.get("size", None) 或 training_data.get("size")
if not size:
issues.append("未说明训练数据规模")
score -= 15
elif size == "未知":
issues.append("训练数据规模标记为'未知'")
score -= 10
# 检查预处理方式
if "preprocessing" not in training_data:
issues.append("未说明数据预处理方式(是否去除了PII等敏感信息?)")
score -= 20
# 检查数据截止日期
if "cutoff_date" not in training_data:
issues.append("未说明训练数据截止日期")
score -= 10
return {"issues": issues, "score": max(score, 0)}
# 执行检查
print("=" * 60)
print("🔍 审计项2:训练数据透明度")
print("=" * 60)
for key, card in model_cards.items():
result = check_training_data(card)
grade = "A" if result["score"] >= 80 else ("B" if result["score"] >= 60 else ("C" if result["score"] >= 40 else "D"))
color = "🟢" if grade in ["A", "B"] else ("🟡" if grade == "C" else "🔴")
print(f"\n {color} {card['model_name']}")
print(f" 透明度评分:{result['score']}/100 — 等级 {grade}")
if result["issues"]:
for issue in result["issues"]:
print(f" ⚠️ {issue}")
else:
print(f" ✓ 训练数据信息完整透明")🤔 思考一下
1. QuickLLM-3B 的训练数据信息有什么问题? "互联网数据"这种描述为什么不够?
2. 为什么训练数据的预处理方式很重要? 如果不去除 PII 信息会怎样?
3. 数据截止日期为什么也是安全相关信息? 想想过时的信息可能带来什么问题。
🤔 思考一下:如果你是一名安全审计员,在 Hugging Face 上看到一个没有模型卡的模型,但它的下载量很高,你会如何评估它的风险?除了模型卡,还有哪些信号可以帮助判断模型的可信度?
第四部分:安全风险审计
检查模型是否充分披露了已知的偏见、限制和安全风险。对于高风险应用领域(如医疗)的模型,这一项尤为重要。
# ========== 填空 3:偏见与风险披露检查 ==========
#
# 🎯 任务:审计模型卡中的偏见、限制和安全信息披露
#
# 💡 提示:
# - 检查 bias_and_limitations 字段是否存在
# - 检查是否列出了已知偏见(known_biases)
# - 检查是否说明了局限性(limitations)
# - 检查是否提供了缓解措施(mitigation)
# - 对于特定领域模型(如医疗),检查是否有使用范围说明(out_of_scope_use)
#
# 请将 ___________ 替换为正确的代码
def check_risk_disclosure(card):
"""
审计安全风险披露的充分性
参数:
card (dict): 模型卡数据
返回:
dict: 风险披露评估结果
"""
issues = []
score = 100
# 检查偏见与限制
bal = card.get("bias_and_limitations", {})
if not bal:
issues.append("未提供任何偏见与限制信息")
score -= 40
else:
if not bal.get("known_biases"):
issues.append("未列出已知偏见")
score -= 15
if not bal.get("limitations"):
issues.append("未说明模型局限性")
score -= 15
if not bal.get("mitigation"):
issues.append("未提供偏见缓解措施")
score -= 10
# 检查使用范围
out_of_scope = ___________
# 期望:从 card 中获取 "out_of_scope_use" 字段
# 提示:card.get("out_of_scope_use")
if not out_of_scope:
issues.append("未说明不适用的使用场景(out_of_scope_use)")
score -= 15
# 检查安全联系方式
security = card.get("security", {})
if not security:
issues.append("未提供安全漏洞报告渠道")
score -= 15
elif not security.get("vulnerability_disclosure"):
issues.append("未提供安全漏洞报告联系方式")
score -= 10
# 特殊检查:高风险领域
high_risk_keywords = ["医疗", "医学", "金融", "法律", "自动驾驶"]
model_type = card.get("model_type", "") + " " + card.get("intended_use", "")
is_high_risk = any(kw in model_type for kw in high_risk_keywords)
if is_high_risk:
if not out_of_scope:
issues.append("⚠️ 高风险领域模型必须明确使用范围限制!")
score -= 20
if not security:
issues.append("⚠️ 高风险领域模型必须提供安全联系方式!")
score -= 10
return {"issues": issues, "score": max(score, 0), "is_high_risk": is_high_risk}
# 执行检查
print("=" * 60)
print("🔍 审计项3:安全风险披露")
print("=" * 60)
for key, card in model_cards.items():
result = check_risk_disclosure(card)
grade = "A" if result["score"] >= 80 else ("B" if result["score"] >= 60 else ("C" if result["score"] >= 40 else "D"))
color = "🟢" if grade in ["A", "B"] else ("🟡" if grade == "C" else "🔴")
risk_tag = " [高风险领域]" if result["is_high_risk"] else ""
print(f"\n {color} {card['model_name']}{risk_tag}")
print(f" 风险披露评分:{result['score']}/100 — 等级 {grade}")
if result["issues"]:
for issue in result["issues"]:
print(f" ⚠️ {issue}")
else:
print(f" ✓ 风险披露充分")第五部分:模型格式与许可证安全检查
模块四第4章提到,模型文件格式和许可证也是供应链安全的重要环节。pickle 格式存在任意代码执行风险,而不明确的许可证可能带来合规问题。
# ========== 填空 4:格式与许可证安全检查 ==========
#
# 🎯 任务:检查模型的文件格式安全性和许可证合规性
#
# 💡 提示:
# - safetensors 格式:安全(不能嵌入代码),推荐
# - pickle/pt/bin 格式:有风险(可嵌入恶意代码)
# - 许可证检查:是否明确、是否为已知的开源许可证
# - 已知安全许可证:Apache-2.0, MIT, CC-BY-4.0, CC-BY-NC-4.0 等
#
# 请将 ___________ 替换为正确的代码
def check_format_and_license(card):
"""
检查模型格式安全性和许可证合规性
参数:
card (dict): 模型卡数据
返回:
dict: 格式和许可证检查结果
"""
issues = []
score = 100
# 检查模型格式
model_format = card.get("model_format", "unknown")
safe_formats = ["safetensors"]
risky_formats = ["pickle", "pkl", "pt", "bin"]
if model_format in safe_formats:
format_status = "安全"
elif model_format in risky_formats:
format_status = "有风险"
issues.append(f"模型使用 {model_format} 格式,存在任意代码执行风险!建议使用 safetensors")
score -= 30
else:
format_status = "未知"
issues.append(f"模型格式 '{model_format}' 未知,无法评估安全性")
score -= 15
# 检查许可证
license_name = card.get("license", "")
known_licenses = ___________
# 期望:一个包含常见开源许可证名称的列表
# 提示:["Apache-2.0", "MIT", "CC-BY-4.0", "CC-BY-NC-4.0", "GPL-3.0", "BSD-3-Clause"]
if not license_name or license_name == "unknown":
issues.append("许可证未知或未指定!使用此模型可能存在法律风险")
score -= 25
elif license_name not in known_licenses:
issues.append(f"许可证 '{license_name}' 不在已知许可证列表中,请手动确认")
score -= 10
return {
"issues": issues,
"score": max(score, 0),
"format": model_format,
"format_status": format_status,
"license": license_name
}
# 执行检查
print("=" * 60)
print("🔍 审计项4:格式与许可证")
print("=" * 60)
for key, card in model_cards.items():
result = check_format_and_license(card)
grade = "A" if result["score"] >= 80 else ("B" if result["score"] >= 60 else ("C" if result["score"] >= 40 else "D"))
color = "🟢" if grade in ["A", "B"] else ("🟡" if grade == "C" else "🔴")
fmt_icon = "🛡️" if result["format_status"] == "安全" else ("⚠️" if result["format_status"] == "有风险" else "❓")
print(f"\n {color} {card['model_name']}")
print(f" 格式:{result['format']}({fmt_icon} {result['format_status']})")
print(f" 许可证:{result['license'] or '未指定'}")
print(f" 安全评分:{result['score']}/100 — 等级 {grade}")
if result["issues"]:
for issue in result["issues"]:
print(f" ⚠️ {issue}")🤔 思考一下
1. 为什么 pickle 格式有安全风险? 回忆模块四第4章的内容——pickle 可以在反序列化时执行任意 Python 代码。
2. safetensors 是怎么解决这个问题的? 它只存储张量数据,不允许嵌入代码。
3. 如果一个模型没有明确的许可证,会有什么问题? 想想商业使用和法律合规的角度。
🤔 思考一下:为什么 pickle 格式被认为是不安全的?如果你需要加载一个只提供 pickle 格式的模型,有哪些安全防护措施可以采取?
第六部分:生成综合审计报告
将前面所有审计项整合,生成一份完整的安全审计报告,为每个模型给出综合评级和使用建议。
# ========== 填空 5:综合审计报告生成器 ==========
#
# 🎯 任务:整合所有审计结果,计算综合评分并生成报告
#
# 💡 提示:
# - 综合评分 = 各项评分的加权平均
# - 建议权重:基本信息 20%,训练数据 30%,风险披露 30%,格式许可 20%
# - 根据综合评分给出使用建议(推荐/谨慎/不推荐)
#
# 请将 ___________ 替换为正确的代码
def generate_audit_report(card, model_key):
"""
生成综合安全审计报告
参数:
card (dict): 模型卡数据
model_key (str): 模型标识
返回:
dict: 完整审计报告
"""
# 执行各项审计
basic = check_basic_info(card)
training = check_training_data(card)
risk = check_risk_disclosure(card)
fmt = check_format_and_license(card)
# 计算综合评分(加权平均)
overall_score = ___________
# 期望:加权平均分数
# 提示:basic["completeness_score"] * 0.2 + training["score"] * 0.3 + risk["score"] * 0.3 + fmt["score"] * 0.2
# 确定综合评级
if overall_score >= 80:
grade = "A"
recommendation = "✅ 推荐使用 — 信息透明,安全措施充分"
elif overall_score >= 60:
grade = "B"
recommendation = "🟡 谨慎使用 — 部分信息缺失,建议补充后再使用"
elif overall_score >= 40:
grade = "C"
recommendation = "🟠 风险较高 — 关键信息不足,不建议用于生产环境"
else:
grade = "D"
recommendation = "🔴 不推荐 — 严重信息缺失,存在重大安全风险"
# 收集所有问题
all_issues = []
all_issues.extend([f"[基本信息] 缺失: {f}" for f in basic["missing_fields"]])
all_issues.extend([f"[训练数据] {i}" for i in training["issues"]])
all_issues.extend([f"[风险披露] {i}" for i in risk["issues"]])
all_issues.extend([f"[格式许可] {i}" for i in fmt["issues"]])
return {
"model_name": card["model_name"],
"model_key": model_key,
"scores": {
"基本信息": basic["completeness_score"],
"训练数据": training["score"],
"风险披露": risk["score"],
"格式许可": fmt["score"],
},
"overall_score": overall_score,
"grade": grade,
"recommendation": recommendation,
"issues": all_issues,
"is_high_risk": risk.get("is_high_risk", False)
}
# 生成所有模型的审计报告
print("=" * 60)
print("📋 模型安全审计综合报告")
print("=" * 60)
reports = []
for key, card in model_cards.items():
report = generate_audit_report(card, key)
reports.append(report)
for report in reports:
hr_tag = " 🏥 [高风险领域]" if report["is_high_risk"] else ""
print(f"\n{'━' * 60}")
print(f"📦 {report['model_name']}{hr_tag}")
print(f"{'━' * 60}")
# 各项评分
print(" 各项评分:")
for item, score in report["scores"].items():
bar_len = int(score / 10)
bar = "█" * bar_len + "░" * (10 - bar_len)
print(f" {item}:{bar} {score:.0f}/100")
print(f"\n 综合评分:{report['overall_score']:.1f}/100 — 等级 {report['grade']}")
print(f" 使用建议:{report['recommendation']}")
if report["issues"]:
print(f"\n 发现的问题(共{len(report['issues'])}项):")
for issue in report["issues"][:5]: # 最多显示5个
print(f" ⚠️ {issue}")
if len(report["issues"]) > 5:
print(f" ... 还有 {len(report['issues']) - 5} 项")
# 模型排名
print(f"\n{'━' * 60}")
print("📊 安全等级排名")
print(f"{'━' * 60}")
for i, report in enumerate(sorted(reports, key=lambda x: x["overall_score"], reverse=True), 1):
print(f" {i}. {report['model_name']} — {report['overall_score']:.1f}分 (等级 {report['grade']})")📋 实验小结
核心收获
1. 概念:模型卡是评估开源模型安全性的重要依据,包含训练数据、风险披露、许可证等关键信息
2. 技能:能编写自动化审计脚本,从基本信息、训练数据、风险披露、格式许可四个维度评估模型
3. 思考:不同质量的模型卡反映了发布者的安全意识,信息不透明的模型应谨慎使用
关键代码回顾
``
python
检查必填字段
missing = [f for f in required_fields if f not in card]加权综合评分
overall = basic 0.2 + training 0.3 + risk 0.3 + format 0.2安全格式判断
safe = model_format in ["safetensors"] # safetensors 不能嵌入代码
risky = model_format in ["pickle", "pt"] # pickle 可执行任意代码
``审计要点速查
| 审计项 | 关键检查点 | 为什么重要 |
|--------|-----------|-----------|
| 基本信息 | 字段完整性 | 信息不全的模型来源可疑 |
| 训练数据 | 来源、规模、预处理 | 数据质量决定模型安全 |
| 风险披露 | 偏见、限制、缓解措施 | 负责任的发布者会主动披露 |
| 格式许可 | safetensors vs pickle | 避免供应链攻击和法律风险 |
参考答案
点击展开参考答案
填空 1:基本信息完整性检查
``python`
missing = [field for field in required_fields if field not in card]
填空 2:训练数据透明度检查
`python`
size = training_data.get("size", None)
填空 3:风险披露检查
`python`
out_of_scope = card.get("out_of_scope_use")
填空 4:格式与许可证安全检查
`python`
known_licenses = ["Apache-2.0", "MIT", "CC-BY-4.0", "CC-BY-NC-4.0", "GPL-3.0", "BSD-3-Clause"]
填空 5:综合审计报告
`python``
overall_score = basic["completeness_score"] 0.2 + training["score"] 0.3 + risk["score"] 0.3 + fmt["score"] 0.2
⚠️ 实验结束提醒
本实验不需要 GPU,但如果你之前的实验还有 Cloud Studio 实例在运行,请记得停止运行中的实例以节省资源额度。
操作步骤:Cloud Studio 控制台 → 运行中的实例 → 停止