TensorFlow孪生3D CNN深度伪造检测模型始终预测同一类别问题
孪生3D CNN深度伪造检测模型训练停滞问题
问题背景与模型设计
我正在训练一个用于深度伪造检测的孪生3D CNN模型,模型接收成对的3D图像(人脸3D图像与背景块3D图像):
- 3D图像由多帧堆叠而成,维度为
(height, width, depth, channels=3),已归一化至0.0-1.0 - 采用自定义数据生成器提供批次数据,格式为
([input1, input2], output),其中input形状为(batch_size, img_height, img_width, img_depth, 3),output为二分类标签(0代表真实,1代表伪造)
核心假设与目标
真实视频中人脸与背景的噪声模式一致,伪造视频中人脸被篡改而背景未改动,噪声模式存在差异。目标是让孪生网络提取噪声特征,基于曼哈顿距离计算0-1之间的相似度得分(真实视频得分接近0,伪造视频接近1)。
基础3D CNN模型架构
def create_3D_CNN(input_shape): model = Sequential() # 1 model.add(Conv3D(8, kernel_size=(3, 3, 3), padding='same', input_shape=input_shape)) model.add(BatchNormalization()) model.add(Activation('relu')) model.add(MaxPool3D((2, 2, 1), strides=(2, 2, 1), padding='same')) # 2 model.add(Conv3D(16, kernel_size=(3, 3, 3), padding='same')) model.add(BatchNormalization()) model.add(Activation('relu')) model.add(MaxPool3D((2, 2, 2), strides=(2, 2, 2), padding='same')) # 3 model.add(Conv3D(32, kernel_size=(3, 3, 3), padding='same')) model.add(BatchNormalization()) model.add(Activation('relu')) model.add(MaxPool3D((2, 2, 2), strides=(2, 2, 2), padding='same')) # 4 model.add(Conv3D(64, kernel_size=(3, 3, 3), padding='same')) model.add(BatchNormalization()) model.add(Activation('relu')) model.add(MaxPool3D((2, 2, 2), strides=(2, 2, 2), padding='same')) # 5 model.add(Conv3D(128, kernel_size=(3, 3, 3), padding='same')) model.add(BatchNormalization()) model.add(Activation('relu')) model.add(MaxPool3D((2, 2, 2), strides=(2, 2, 2), padding='same')) # final model.add(Flatten()) model.add(Dropout(0.5)) model.add(Dense(1024)) model.add(BatchNormalization()) model.add(Activation('relu')) return model
孪生网络构建代码
该模型提取1024维特征,孪生网络共享权重处理两个输入,得到特征向量a和b,输出层通过公式predicted_output = 1 - exp(-sum|a_i - b_i|)计算得分:
def manhatten_distance(vects): x, y = vects return 1 - K.exp(-K.sum(K.abs(x - y), axis=1, keepdims=True)) def man_dist_output_shape(shapes): shape1, shape2 = shapes return (shape1[0], 1) input_shape = train_gen[0][0][0][0].shape print('input_shape =', input_shape) #-------------- defining layers using functional api ---------------------- # inputs input_face = Input(shape=input_shape) input_back = Input(shape=input_shape) # feature extraction (shared weights) CNN_3D = create_3D_CNN(input_shape) features_face = CNN_3D(input_face) features_back = CNN_3D(input_back) # distance between the 2 feature vectors distance = Lambda(manhatten_distance, output_shape=man_dist_output_shape)([features_face, features_back]) #---------------- creating final model --------------------------- model = Model(inputs=[input_face, input_back], outputs=distance) model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) model.summary()
问题现象
训练时准确率始终接近50%,二元交叉熵损失不低于7.8,预测时始终输出同一值。
解决尝试
将曼哈顿距离层替换为拼接层,新增含sigmoid激活的单神经元输出层后,模型开始正常学习。
疑问
为何曼哈顿距离层会阻碍模型学习?
问题原因分析
梯度传播的稳定性缺陷
原公式1 - exp(-sum|a_i - b_i|)的梯度特性存在严重问题:
- 当特征向量的L1距离(
sum|a-b|)较大时,exp(-sum)趋近于0,此时输出对特征的梯度exp(-sum) * sign(a_i - b_i)也趋近于0,直接导致特征提取CNN层的权重几乎得不到更新(梯度消失); - 若训练初期模型未学到有效特征,
sum|a-b|要么随机波动过大触发梯度消失,要么一直处于极小值区间,模型无法通过梯度信号引导特征往区分正负样本的方向优化,最终陷入准确率卡在50%的死循环。
缺乏可学习的特征适配能力
原方案用固定公式直接转换L1距离到0-1区间,相当于强制要求孪生网络提取的1024维特征必须天然适配这个转换逻辑,但实际中:
- 很多特征维度可能是冗余的,或者与噪声模式无关;
- 固定公式无法对特征维度进行加权,无法突出对区分真实/伪造样本更关键的特征。
而拼接特征后加sigmoid全连接层的方式,新增的可学习权重可以自动对特征维度进行加权组合,把特征映射到更适合二分类的空间,相当于给了模型“自定义区分规则”的能力,而非被固定公式限制死。
输出与损失函数的匹配度问题
二元交叉熵损失要求输出是符合概率分布的预测值,但原公式的输出只是一个类似相似度的数值,并非严格的概率:
- 训练初期,若模型输出全集中在0或1附近(比如特征随机导致
sum|a-b|要么极大要么极小),交叉熵损失会急剧增大(比如输出全接近1时,真实样本(标签0)的损失为-log(1-1),数值极大); - 同时伴随梯度消失,模型无法通过反向传播调整参数。
而sigmoid层本身就是为输出概率设计的,能更好地匹配交叉熵损失的优化目标,让损失值的变化更合理,梯度传播更稳定。
特征区分度的利用效率低下
原模型对所有特征维度的L1距离平等求和,无法过滤冗余特征的干扰,导致有效信号被稀释。而拼接后的全连接层可以通过权重分配,自动放大有效特征的贡献、抑制无关特征的影响,从而更高效地利用特征空间的区分度,让模型更快学到真实/伪造样本的差异。
内容的提问来源于stack exchange,提问作者Abdulwahab Almestekawy
相关产品推荐
相关产品推荐

