AI监考是一个天然存在张力的系统——它需要尽可能多地采集和分析考生的行为数据来识别作弊,同时又必须严格遵守个人信息保护法对数据采集、存储、使用的限制。本文记录我们在企学宝AI监考系统上,如何用技术手段在"防作弊效果"和"隐私保护"之间找到可落地的平衡点。
一、问题背景:一个不可调和的矛盾?
企业线上考试场景下,AI监考几乎是刚需。一场3000人的全员合规考试,如果没有监考,作弊率可能高达30%——翻手机、切屏搜答案、找人代考,手段层出不穷。但AI监考需要做什么?它需要:
- 调用摄像头实时采集人脸画面
- 检测视线方向、头部姿态、是否有多人出现
- 分析面部特征确认考生身份
- 全程录制视频或截取关键帧
- 对异常行为进行标记和回溯
这些操作每一项都涉及敏感个人信息。人脸属于生物特征信息,在《个人信息保护法》(以下简称"个保法")中被列为敏感个人信息,处理前需要取得个人的单独同意,并且要有特定的目的和充分的必要性。
这就构成了一个根本性的张力:防作弊要求尽可能多的数据采集和分析,隐私保护要求尽可能少的数据采集和处理。 如果完全偏向任何一端,系统要么形同虚设,要么根本无法上线。
我们的目标是找到那个平衡点——在满足合规要求的前提下,让AI监考的效果尽可能接近"无限制采集"的方案。
二、合规框架:先搞清楚法律底线
在动手设计技术方案之前,必须先搞清楚法律要求。这不是法务部门的事——技术团队必须理解合规边界,因为很多合规要求直接决定了技术架构的设计。
2.1 核心法规梳理
企业培训AI监考涉及的主要法规:
| 法规 | 关键条款 | 对技术架构的影响 |
|---|---|---|
| 《个人信息保护法》 | 敏感个人信息需单独同意(第29条) | 必须在考前弹窗获取单独授权,不能捆绑在用户协议里 |
| 《个人信息保护法》 | 最小必要原则(第6条) | 不能无差别录制全程视频,需要按需采集 |
| 《个人信息保护法》 | 存储期限最小化(第19条) | 视频/图片不能永久存储,需要自动过期删除 |
| 《个人信息保护法》 | 自动化决策透明度(第24条) | AI判定作弊后,需要可解释的判定依据 |
| 《人脸识别技术应用安全管理规定》 | 人脸信息不得用于非必要目的 | 人脸数据只能用于身份核验和监考,不能用于其他分析 |
| GDPR(如服务海外客户) | Data Protection by Design(第25条) | 隐私保护必须内嵌在系统架构中,不是事后补丁 |
2.2 合规红线与技术约束
把法规翻译成技术语言,核心约束如下:
采集层:
- 摄像头调用必须有明确的考前授权,不能"静默开启"
- 采集频率要最小化——不需要每秒30帧,但需要足够检测异常
- 不能采集与监考无关的信息(比如背景中的其他人脸)
处理层:
- 人脸特征数据不能明文存储,必须加密或脱敏
- AI模型的推理过程需要可解释——不能只输出"疑似作弊",要给出判定依据
- 处理过程不能把数据传到境外(数据本地化要求)
存储层:
- 视频/图片有明确的保留期限(我们设定为考试结束后30天)
- 到期后必须自动删除,不能依赖人工清理
- 存储必须加密,访问必须有审计日志
使用层:
- 监考数据只能用于考试作弊判定,不能用于员工绩效评估
- 不能把人脸数据用于考勤、情绪分析等其他目的
- 数据主体(考生)有权要求查阅和删除自己的数据
三、整体架构:Privacy by Design
基于上述合规要求,我们设计了如下架构。核心理念是数据最小化——在每个环节都只保留"刚好够用"的数据。
┌──────────────────────────────────────────────────────────────────┐
│ 考生端(浏览器/客户端) │
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌────────────────────┐ │
│ │ 摄像头采集 │───>│ 本地预处理 │───>│ 特征提取(本地) │ │
│ │ (授权后开启) │ │ (降帧/裁剪) │ │ (人脸检测+特征向量) │ │
│ └─────────────┘ └──────────────┘ └─────────┬──────────┘ │
│ │ │
│ 只上传特征向量+异常帧 │
│ │ │
└────────────────────────────────────────────────────┼──────────────┘
│
┌──────────▼──────────┐
│ 边缘推理节点 │
│ (可选:企业私有部署) │
│ │
│ ┌───────────────┐ │
│ │ 姿态检测模型 │ │
│ │ 视线估计模型 │ │
│ │ 多人检测模型 │ │
│ └───────────────┘ │
│ │
│ 推理结果:结构化数据 │
│ (不保留原始视频) │
└──────────┬──────────┘
│
┌──────────▼──────────┐
│ 云端监考服务 │
│ │
│ ┌───────────────┐ │
│ │ 异常行为聚合 │ │
│ │ 作弊判定引擎 │ │
│ │ 可解释报告生成 │ │
│ └───────────────┘ │
│ │
│ ┌───────────────┐ │
│ │ 数据生命周期 │ │
│ │ 管理器 │ │
│ │ (自动过期删除) │ │
│ └───────────────┘ │
└─────────────────────┘
这个架构有三个关键设计决策,分别对应三个隐私保护的核心原则。
决策一:边缘优先——原始视频不出设备
这是整个架构最重要的决策。传统的AI监考方案是把摄像头画面实时上传到云端,在云端做所有推理。这意味着原始视频数据在网络上传输、在云端存储,任何一个环节泄露都是重大隐私事故。
我们的方案是:尽可能在考生端本地完成预处理和特征提取,只上传结构化的检测结果和少量异常帧,不上传原始视频流。
具体来说,考生端(浏览器端通过WebAssembly + TensorFlow.js,或桌面客户端通过本地推理引擎)完成以下处理:
# 考生端本地处理伪代码(实际为JS/WASM实现)
class LocalPreprocessor:
"""本地预处理:降帧、裁剪、特征提取"""
def __init__(self, config: ExamConfig):
self.frame_interval = config.frame_interval # 采集间隔(秒)
self.face_detector = load_model("face_detection_lite") # 轻量人脸检测
self.face_embedder = load_model("face_embedding") # 人脸特征提取
self.upload_queue = []
def process_frame(self, frame) -> dict | None:
"""处理单帧:只提取特征,丢弃原始画面"""
# 1. 人脸检测
faces = self.face_detector.detect(frame)
if len(faces) == 0:
return {
"event": "no_face", "timestamp": now()}
elif len(faces) > 1:
return {
"event": "multiple_faces", "timestamp": now(),
"count": len(faces)}
face = faces[0]
# 2. 姿态估计(本地轻量模型)
pose = estimate_pose(frame, face) # 返回 pitch/yaw/roll 角度
# 3. 视线估计
gaze = estimate_gaze(frame, face) # 返回视线方向向量
# 4. 人脸特征向量(128维,不可逆)
embedding = self.face_embedder.extract(face.crop)
# 5. 丢弃原始帧,只返回结构化数据
return {
"timestamp": now(),
"face_count": len(faces),
"pose": {
"pitch": pose.pitch, "yaw": pose.yaw, "roll": pose.roll},
"gaze_direction": gaze.to_vector(),
"face_embedding": embedding, # 128维浮点向量,无法还原为人脸图像
"is_looking_away": gaze.deviation > GAZE_THRESHOLD,
"frame_quality": face.quality_score
}
这里有一个关键技术点:人脸特征向量(face embedding)是不可逆的。 128维的浮点向量无法还原为原始人脸图像,这在法律上意味着它不属于"人脸图像",合规压力小很多。我们用它来做身份核验(考前录入的人脸特征 vs 考试中持续比对),而不是存储人脸照片。
决策二:按需上传——不是所有数据都需要传到云端
本地处理后的数据,也不是全部上传。我们设计了一个分级上传策略:
class UploadPolicy:
"""分级上传策略:只上传必要数据"""
# 正常帧:只上传结构化数据(姿态、视线等),不上传任何图像
NORMAL_INTERVAL = 30 # 每30秒上传一次结构化数据
# 异常帧:上传异常帧的缩略图(模糊处理背景)
ANOMALY_THRESHOLD = {
"gaze_away_duration": 5, # 视线偏离超过5秒
"multiple_faces": 1, # 出现多人
"no_face_duration": 10, # 人脸消失超过10秒
"pose_extreme": 45, # 头部偏转超过45度
}
def should_upload_frame(self, local_result: dict) -> tuple[bool, str]:
"""判断是否需要上传异常帧图像"""
# 检查是否触发异常
anomaly_type = self._check_anomaly(local_result)
if anomaly_type is None:
return False, ""
# 异常帧上传前做隐私处理
if anomaly_type == "multiple_faces":
# 多人场景:模糊非考生人脸
return True, "blur_others"
elif anomaly_type == "gaze_away":
# 视线偏离:上传低分辨率缩略图
return True, "thumbnail"
else:
return True, "original"
def _check_anomaly(self, result: dict) -> str | None:
"""检查是否触发异常阈值"""
# ... 具体判断逻辑
pass
实际效果是:一场2小时的考试,传统方案需要上传约2GB的视频数据,我们的方案只上传约5-15MB的结构化数据 + 少量异常帧(通常5-20张缩略图)。数据量减少了99%以上。
决策三:自动过期——数据不能无限留存
即使采集了数据,如果及时删除,隐私风险也是可控的。我们设计了一个严格的数据生命周期管理:
from datetime import datetime, timedelta
from enum import Enum
class DataCategory(Enum):
STRUCTURED_DATA = "structured" # 结构化检测数据
ANOMALY_FRAMES = "anomaly_frames" # 异常帧截图
IDENTITY_EMBEDDING = "embedding" # 身份特征向量
EXAM_REPORT = "report" # 监考报告
# 各类数据的保留期限
RETENTION_POLICY = {
DataCategory.STRUCTURED_DATA: timedelta(days=30), # 30天后删除
DataCategory.ANOMALY_FRAMES: timedelta(days=30), # 30天后删除
DataCategory.IDENTITY_EMBEDDING: timedelta(days=90), # 身份数据保留稍长(用于跨考试身份核验)
DataCategory.EXAM_REPORT: timedelta(days=365), # 报告保留1年(审计需要)
}
class DataLifecycleManager:
"""数据生命周期管理器"""
def __init__(self, storage_client, db_client):
self.storage = storage_client
self.db = db_client
def schedule_deletion(self, exam_id: str, data_category: DataCategory):
"""为考试数据设置自动删除计划"""
retention = RETENTION_POLICY[data_category]
delete_at = datetime.utcnow() + retention
# 写入删除任务队列(由定时任务执行)
self.db.insert_deletion_task(
exam_id=exam_id,
category=data_category.value,
delete_at=delete_at,
created_at=datetime.utcnow()
)
def execute_deletion(self):
"""执行到期数据的删除(定时任务,每小时运行)"""
pending_tasks = self.db.get_pending_deletions(
before=datetime.utcnow()
)
for task in pending_tasks:
if task.category == DataCategory.STRUCTURED_DATA.value:
self.db.delete_structured_data(task.exam_id)
elif task.category == DataCategory.ANOMALY_FRAMES.value:
self.storage.delete_prefix(f"exams/{task.exam_id}/frames/")
elif task.category == DataCategory.IDENTITY_EMBEDDING.value:
self.db.delete_embeddings(task.exam_id)
# 标记任务完成
self.db.mark_deletion_done(task.id)
# 写入审计日志
self.audit_log.record_deletion(task)
def handle_data_subject_request(self, employee_id: str,
action: str) -> dict:
"""处理数据主体的查阅/删除请求(个保法第45/47条)"""
if action == "access":
# 考生有权查阅自己的监考数据
return {
"structured_data": self.db.get_my_exam_data(employee_id),
"anomaly_frames": self.storage.list_my_frames(employee_id),
"retention_status": self._get_retention_info(employee_id)
}
elif action == "delete":
# 考生有权要求删除(但需保留审计所需的最小记录)
self._soft_delete_personal_data(employee_id)
# 保留不可恢复的审计摘要(不含个人信息)
self._retain_audit_summary(employee_id)
return {
"status": "completed", "deleted_at": datetime.utcnow()}
四、AI模型设计:可解释性优先
个保法第24条要求自动化决策应当透明,结果应当公平公正。翻译到AI监考场景:当系统判定一个考生"疑似作弊"时,必须能给出清晰的判定依据,而不是一个黑箱的"异常分数"。
4.1 异常检测模型:多信号融合
我们不训练一个端到端的"作弊检测模型"(这种模型很难解释),而是把作弊检测拆解为多个独立的、可解释的子检测器,每个检测器只负责一个维度的异常:
class AnomalyDetector:
"""多信号融合的异常检测器,每个信号可独立解释"""
def __init__(self):
self.gaze_detector = GazeAnomalyDetector()
self.pose_detector = PoseAnomalyDetector()
self.identity_detector = IdentityDetector()
self.environment_detector = EnvironmentDetector()
self.audio_detector = AudioDetector() # 可选
def analyze(self, frame_data: dict, history: list[dict]) -> AnomalyReport:
"""分析单帧数据,返回可解释的异常报告"""
signals = []
# 1. 视线异常检测
gaze_signal = self.gaze_detector.detect(
current=frame_data["gaze_direction"],
history=[f["gaze_direction"] for f in history[-10:]]
)
if gaze_signal.is_anomaly:
signals.append(Signal(
type="gaze_anomaly",
severity=gaze_signal.severity,
explanation=f"视线持续偏离屏幕{gaze_signal.duration:.1f}秒,"
f"方向为{gaze_signal.direction}",
evidence={
"gaze_vector": frame_data["gaze_direction"],
"duration": gaze_signal.duration}
))
# 2. 姿态异常检测
pose_signal = self.pose_detector.detect(
current=frame_data["pose"],
history=[f["pose"] for f in history[-10:]]
)
if pose_signal.is_anomaly:
signals.append(Signal(
type="pose_anomaly",
severity=pose_signal.severity,
explanation=f"头部偏转角度{pose_signal.angle:.0f}°,"
f"超过阈值{POSE_THRESHOLD}°",
evidence={
"pose_angles": frame_data["pose"]}
))
# 3. 身份一致性检测(确认是否本人)
identity_signal = self.identity_detector.detect(
current_embedding=frame_data["face_embedding"],
reference_embedding=frame_data["reference_embedding"]
)
if identity_signal.is_anomaly:
signals.append(Signal(
type="identity_mismatch",
severity="high",
explanation=f"人脸特征与考前录入不匹配,"
f"相似度{identity_signal.similarity:.2f}",
evidence={
"similarity": identity_signal.similarity}
))
# 4. 环境异常检测(多人、换背景等)
env_signal = self.environment_detector.detect(frame_data)
if env_signal.is_anomaly:
signals.append(Signal(
type="environment_anomaly",
severity=env_signal.severity,
explanation=env_signal.description,
evidence=env_signal.evidence
))
return AnomalyReport(
timestamp=frame_data["timestamp"],
signals=signals,
overall_risk=self._aggregate_risk(signals)
)
def _aggregate_risk(self, signals: list[Signal]) -> RiskLevel:
"""聚合多信号为整体风险等级"""
if not signals:
return RiskLevel.NORMAL
high_count = sum(1 for s in signals if s.severity == "high")
medium_count = sum(1 for s in signals if s.severity == "medium")
if high_count >= 1:
return RiskLevel.HIGH
elif medium_count >= 2:
return RiskLevel.MEDIUM
elif medium_count >= 1:
return RiskLevel.LOW
return RiskLevel.NORMAL
这样设计的好处是:当系统判定"疑似作弊"时,监考报告里不是一句"AI判定异常",而是具体的"视线持续偏离屏幕8.3秒,方向为右下方"——考生和考试管理员都能看到具体的判定依据,可以申诉和复核。
4.2 减少误判:上下文感知的阈值调节
AI监考最大的痛点是误判。员工在考试时转头想问题、家里有猫经过、光线变化导致人脸检测失败——这些都可能触发异常。如果误判率太高,系统就失去了信任。
我们的方案是引入上下文感知的动态阈值:
class ContextAwareThreshold:
"""上下文感知的动态阈值调节"""
def __init__(self):
# 基础阈值
self.base_gaze_threshold = 5.0 # 视线偏离5秒
self.base_pose_threshold = 30.0 # 头部偏转30度
def adjust_threshold(self, exam_context: ExamContext,
employee_history: EmployeeHistory) -> ThresholdConfig:
"""根据上下文动态调整阈值"""
gaze_threshold = self.base_gaze_threshold
pose_threshold = self.base_pose_threshold
# 1. 考试难度自适应:难题允许更多思考时间(视线偏离更久)
if exam_context.current_question_difficulty > 0.7:
gaze_threshold *= 1.5 # 放宽50%
# 2. 历史行为基线:有些员工习惯性转头思考
if employee_history.has_habit_of_looking_away:
gaze_threshold *= employee_history.habit_factor # 可能放宽到2-3倍
# 3. 考试时长:长时间考试后半段放宽阈值(疲劳导致姿态变化)
elapsed_ratio = exam_context.elapsed_time / exam_context.total_time
if elapsed_ratio > 0.7:
pose_threshold *= 1.3
# 4. 环境因素:多人检测的阈值根据场景调整
# 开放式办公环境允许背景有人(但不允许看屏幕)
if exam_context.environment_type == "open_office":
# 不调整多人检测阈值,但调整"他人看屏幕"的检测灵敏度
pass
return ThresholdConfig(
gaze_threshold=gaze_threshold,
pose_threshold=pose_threshold
)
动态阈值的效果很明显:在固定阈值方案下,误判率约18%(人工复核确认);引入上下文感知后,误判率降到6%左右,同时真正作弊的检出率只下降了不到2%。
五、数据加密与访问控制
5.1 端到端加密
数据从考生端到云端的传输采用端到端加密,服务端无法解密传输中的原始数据:
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
import os
class ProctoringEncryption:
"""监考数据端到端加密"""
def __init__(self):
# 每个考试有独立的密钥对
self.exam_key_pair = None
def generate_exam_keys(self, exam_id: str) -> tuple:
"""为每次考试生成独立的加密密钥"""
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
public_key = private_key.public_key()
# 私钥只在解密时使用,存储在HSM或KMS中
# 公钥分发给考生端用于加密上传数据
return private_key, public_key
def encrypt_upload(self, data: bytes, public_key) -> bytes:
"""考生端加密上传数据"""
# 生成对称密钥(AES-256-GCM)
symmetric_key = os.urandom(32)
iv = os.urandom(12)
cipher = Cipher(algorithms.AES(symmetric_key), modes.GCM(iv))
encryptor = cipher.encryptor()
ciphertext = encryptor.update(data) + encryptor.finalize()
# 用公钥加密对称密钥
encrypted_key = public_key.encrypt(
symmetric_key,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
# 返回:加密的对称密钥 + IV + 密文 + GCM tag
return encrypted_key + iv + ciphertext + encryptor.tag
def decrypt_upload(self, encrypted_data: bytes, private_key) -> bytes:
"""服务端解密(仅在需要人工复核时)"""
key_size = 256 # RSA-2048加密后的密文长度
encrypted_key = encrypted_data[:key_size]
iv = encrypted_data[key_size:key_size+12]
tag = encrypted_data[-16:]
ciphertext = encrypted_data[key_size+12:-16]
# 用私钥解密对称密钥
symmetric_key = private_key.decrypt(
encrypted_key,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
# 用对称密钥解密数据
cipher = Cipher(algorithms.AES(symmetric_key), modes.GCM(iv, tag))
decryptor = cipher.decryptor()
return decryptor.update(ciphertext) + decryptor.finalize()
5.2 分级访问控制
不是所有人都能看到所有监考数据。我们设计了严格的分级访问控制:
class AccessControl:
"""监考数据分级访问控制"""
# 角色与权限矩阵
ROLE_PERMISSIONS = {
"examinee": {
# 考生:只能看自己的监考报告摘要,不能看原始数据
"view_own_report_summary": True,
"view_own_anomaly_frames": False, # 异常帧需要申请
"view_raw_video": False,
"request_data_deletion": True,
},
"invigilator": {
# 监考员:可以看所负责考场的异常帧和报告
"view_assigned_anomaly_frames": True,
"view_assigned_reports": True,
"view_raw_video": False, # 没有原始视频权限
"mark_false_positive": True, # 标记误判
},
"exam_admin": {
# 考试管理员:可以看完整报告,但原始视频需要审批
"view_all_reports": True,
"view_anomaly_frames": True,
"request_raw_video": True, # 需要审批流程
"export_statistics": True, # 只能导出统计数据,不能导出个人数据
},
"system_admin": {
# 系统管理员:只能看系统运行状态,不能看考试数据
"view_system_metrics": True,
"view_exam_data": False,
}
}
def check_access(self, user_role: str, action: str,
context: dict) -> bool:
"""检查访问权限"""
permissions = self.ROLE_PERMISSIONS.get(user_role, {
})
return permissions.get(action, False)
def audit_access(self, user_id: str, action: str,
target_exam_id: str, result: bool):
"""记录访问审计日志"""
self.audit_logger.log(
user_id=user_id,
action=action,
target_exam_id=target_exam_id,
result="granted" if result else "denied",
timestamp=datetime.utcnow(),
ip_address=get_client_ip()
)
六、考前授权流程:合规的"第一道门"
技术方案再好,如果授权流程不合规,整个系统都有法律风险。个保法要求处理敏感个人信息需要取得"单独同意",不能捆绑在用户协议里。
6.1 授权流程设计
考试开始前
│
▼
┌──────────────────────────────────────────┐
│ 第一步:告知 │
│ │
│ 弹窗展示: │
│ - 本次考试启用AI监考 │
│ - 将采集哪些数据(摄像头画面、姿态数据) │
│ - 数据用于什么目的(仅限本次考试作弊判定)│
│ - 数据保留多长时间(30天) │
│ - 谁可以查看这些数据(监考员+管理员) │
│ - 你有哪些权利(查阅、删除、申诉) │
│ │
│ [查看完整的隐私政策] │
└──────────────────┬───────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ 第二步:单独授权(非捆绑) │
│ │
│ □ 我同意启用摄像头进行身份核验 │
│ □ 我同意启用AI行为分析进行考试监考 │
│ □ 我同意在考试结束后保留异常帧30天 │
│ │
│ 注意:三项授权独立勾选,不捆绑 │
│ 不授权不影响考试资格(但可能改为人工监考) │
└──────────────────┬───────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ 第三步:选择监考方式 │
│ │
│ 如果考生拒绝AI监考: │
│ - 提供替代方案:预约线下考场 │
│ - 或:人工远程监考(真人视频监考) │
│ - 不能因为拒绝AI监考而取消考试资格 │
└──────────────────────────────────────────┘
前端实现要点:
// 考前授权弹窗组件(React)
function ProctoringConsent({
examId, onConsentComplete }) {
const [consents, setConsents] = useState({
camera: false, // 摄像头身份核验
aiAnalysis: false, // AI行为分析
dataRetention: false // 数据保留
});
const [showPolicy, setShowPolicy] = useState(false);
// 三项必须全部同意才能启用AI监考
const allConsented = Object.values(consents).every(v => v);
const handleConfirm = async () => {
// 记录授权日志(含时间戳、版本号,用于审计)
await api.recordConsent({
examId,
consents,
policyVersion: "v2.3", // 隐私政策版本号
timestamp: new Date().toISOString(),
// 授权时的设备指纹(用于证明"是本人授权的")
deviceFingerprint: getDeviceFingerprint()
});
if (allConsented) {
// 启动AI监考
await startAIProctoring(examId);
} else {
// 进入替代方案
await requestAlternativeProctoring(examId);
}
onConsentComplete(allConsented);
};
return (
<Modal title="考试监考授权" closable={
false}>
<div className="consent-info">
<p>本次考试将启用AI监考系统,具体说明如下:</p>
<InfoItem icon="📹">
采集数据:通过摄像头采集面部画面,用于身份核验和行为分析
</InfoItem>
<InfoItem icon="🎯">
使用目的:仅用于检测考试作弊行为,不用于其他任何目的
</InfoItem>
<InfoItem icon="⏰">
保留期限:考试数据保留30天后自动删除
</InfoItem>
<InfoItem icon="👁">
查看权限:仅监考员和考试管理员可查看异常记录
</InfoItem>
<InfoItem icon="🔒">
您的权利:您可以随时查阅、要求删除您的监考数据
</InfoItem>
</div>
<a onClick={
() => setShowPolicy(true)}>查看完整隐私政策</a>
<div className="consent-checkboxes">
<Checkbox
checked={
consents.camera}
onChange={
v => setConsents({
...consents, camera: v})}
>
我同意启用摄像头进行身份核验
</Checkbox>
<Checkbox
checked={
consents.aiAnalysis}
onChange={
v => setConsents({
...consents, aiAnalysis: v})}
>
我同意启用AI行为分析进行考试监考
</Checkbox>
<Checkbox
checked={
consents.dataRetention}
onChange={
v => setConsents({
...consents, dataRetention: v})}
>
我同意在考试结束后保留异常帧30天
</Checkbox>
</div>
<div className="consent-actions">
<Button onClick={
handleConfirm} disabled={
!allConsented}>
同意并进入考试
</Button>
<Button onClick={
() => requestAlternativeProctoring(examId)}>
拒绝AI监考,选择人工监考
</Button>
</div>
</Modal>
);
}
注意几个关键设计:三项授权独立勾选不捆绑,拒绝AI监考不影响考试资格(提供替代方案),授权记录包含时间戳和政策版本号(用于审计追溯)。
七、私有化部署场景的特殊考量
企学宝有大量私有化部署客户(金融、国企等),这些场景下的隐私合规有一些特殊考量。
7.1 数据不出域
私有化部署的核心诉求是"数据不出企业边界"。AI监考的推理和存储全部在企业内网完成:
┌─────────────────────────────────────────┐
│ 企业内网 │
│ │
│ ┌──────────┐ ┌──────────────────┐ │
│ │ 考生终端 │───>│ 边缘推理节点 │ │
│ │ (摄像头) │ │ (GPU服务器) │ │
│ └──────────┘ └────────┬─────────┘ │
│ │ │
│ ┌────────▼─────────┐ │
│ │ 监考服务(内网) │ │
│ │ + 数据存储(内网) │ │
│ └──────────────────┘ │
│ │
│ ✗ 不连接外网 │
│ ✗ 不调用云端AI API │
│ ✗ 数据不离开企业网络 │
└─────────────────────────────────────────┘
边缘推理节点部署在企业内部的GPU服务器上,运行轻量化的检测模型。模型通过离线方式更新(定期从总部获取模型更新包,通过U盘或内网传输)。
7.2 模型轻量化
边缘部署对模型大小有要求。我们的模型经过以下优化:
| 模型 | 原始大小 | 优化后大小 | 优化手段 |
|---|---|---|---|
| 人脸检测 | 120MB | 4.2MB | MobileNetV3 backbone + 剪枝 + INT8量化 |
| 姿态估计 | 85MB | 3.8MB | 关键点从17个减少到9个 + 量化 |
| 视线估计 | 45MB | 2.1MB | 自定义轻量网络 + 知识蒸馏 |
| 多人检测 | 200MB | 6.5MB | YOLO-NAS + 量化 + 只检测"人"类别 |
总模型大小约16.6MB,可以在CPU上运行(不需要GPU),推理延迟约30ms/帧,满足实时检测需求。
八、效果数据与踩坑实录
8.1 上线效果
在某金融客户(约2万员工,月度合规考试)上线6个月后的数据:
| 指标 | 上线前(人工监考) | 上线后(AI监考) |
|---|---|---|
| 作弊检出率 | 约12%(人工抽检) | 约28%(AI全量检测) |
| 误判率 | 约5%(人工判断不一致) | 约6%(AI判定+人工复核) |
| 监考人力成本 | 每场考试需5-8名监考员 | 每场考试需1名监考员复核AI结果 |
| 考生投诉率 | - | 上线首月12件,第6月降至3件 |
| 隐私合规审计 | 未通过(无数据生命周期管理) | 通过(含数据自动删除、授权留痕) |
8.2 踩过的坑
坑1:浏览器兼容性问题导致摄像头权限获取失败。
Chrome、Firefox、Safari对摄像头权限的处理方式不同。Safari在macOS上要求用户在系统级别授权,如果用户之前拒绝过,网页端无法再次请求。解决方案是在授权弹窗里加一个"如何开启摄像头权限"的分步引导,并且提供"桌面客户端"作为备选方案(桌面客户端可以引导用户去系统设置开启)。
坑2:光线不足导致人脸检测失败率过高。
晚上或光线暗的环境下,人脸检测失败率从2%飙升到25%,大量"no_face"异常触发。解决方案是:本地预处理阶段加一个图像增强(直方图均衡化),同时把"no_face"的异常判定从"立即触发"改为"持续10秒以上才触发",给考生调整光线的时间。
坑3:员工对"被监控"的心理抵触。
上线初期,员工投诉集中在"感觉被监视"而不是真正的隐私问题。解决方案是:在考试界面增加一个"监考状态指示器",实时显示当前AI在检测什么("正在检测视线方向""正在核验身份"),让考生知道AI在做什么,减少"黑箱感"。同时,在授权弹窗里强调"AI只看行为模式,不记录你在做什么"。
坑4:海外客户的GDPR合规差异。
服务海外客户时,GDPR的要求比国内个保法更严格。比如GDPR要求数据跨境传输必须有合法依据(SCCs或 adequacy decision),我们的方案是为海外客户单独部署一套海外节点(数据不出境),同时修改数据保留期限(GDPR下更短)。这部分工作量不小,但通过模块化的架构设计(把存储和推理做成可插拔模块),切换成本可控。
坑5:身份特征向量的"不可逆"不等于"无风险"。
虽然128维特征向量理论上无法还原为人脸图像,但有学术研究表明,在特定条件下(已知模型结构+大量训练数据),有可能从特征向量反推出近似人脸。我们的应对措施是:特征向量加密存储,且只在身份核验时临时解密;同时定期评估密码学安全性的进展,必要时切换到更安全的特征表示方案。
九、总结:平衡的核心原则
回顾整个设计过程,我们在"防作弊"和"隐私保护"之间找到平衡,核心依赖四个原则:
第一,数据最小化。 不是"能采的都采",而是"只采必需的"。本地预处理 + 结构化数据上传,把原始视频留在设备端,数据量减少99%。
第二,目的限制。 采集的数据只用于考试作弊判定,不能用于其他任何目的。这在技术架构上体现为:数据访问控制严格限制用途,审计日志确保可追溯。
第三,存储最小化。 数据有明确的保留期限,到期自动删除,不依赖人工操作。即使数据被泄露,影响范围也随时间自动缩小。
第四,透明可解释。 AI的每一个判定都有具体的、可理解的依据。考生能看到为什么被标记,管理员能看到判定的完整证据链,审计方能看到系统的合规状态。
这四个原则不是互相妥协的产物,而是通过技术手段同时实现的。边缘计算解决了"数据最小化"和"防作弊效果"的矛盾——推理在本地完成,效果不打折,但原始数据不出设备。可解释AI解决了"自动化决策"和"透明性"的矛盾——不用黑箱模型,而是多个可解释子检测器的融合。数据生命周期管理解决了"审计需求"和"存储最小化"的矛盾——保留期内满足审计,到期后自动清除。
技术不是隐私的对立面。好的隐私合规设计,本身就是好的系统设计。
作者注:本文涉及的法规解读仅供参考,不构成法律意见。具体合规方案请咨询专业法律顾问。文中数据为示意数据,不代表线上真实指标。