深度学习之用CelebA_Spoof数据集搭建一个活体检测-一些模型训练中的改动带来的改善

实验背景

在前面的深度学习之用CelebA_Spoof数据集搭建一个活体检测-模型搭建和训练,我们基于CelebA_Spoof数据集构建了一个用SqueezeNe框架进行训练的活体2D模型,采用了蒸馏法进行了一些简单的工作。在前面提供的训练参数中,主要用了以下几个参数:

lr=0.001, step_size=10, gamma=0.1, alpha=0.5, T=3.0, epochs=300, log_interval=5, batch_size=128, save_interval=10, eval_interval=5

但是,效果并不是很好,得到的评估指标为:

{'Accuracy': 0.8841447074586869, 'Precision': 0.9865851252712566, 'Recall': 0.8468018456588917, 'F1': 0.9113647235700131, 'FPR': 0.027303754266211604, 'ROC_AUC': 0.9661331014932475, 'EER': 0.076691427424212, 'PR_AUC': 0.9862052738456543, 'AP': 0.9862056134292535}

对于一个好的活体检测模型来说,各项指标都不是很好。对于这个指标,就需要进一步进行全面的分析了,如:预处理、训练的各个参数的玄学调整,模型结构深度,蒸馏中的权重比等等之类。在各种折腾后都得不到比较好的改变,于是想在特征上进行改进,如果人工再加点特征试试,会是怎样?这突发奇想就想到了:傅里叶变换。为什么用它,因为非活体的照片很多都是翻拍的,那么因为相机或者屏幕的闪烁,可能会出现一些条纹或者频域上的特征,这些就有可能很好的区分这两类图片。为了提升模型对伪造攻击的识别能力,我们尝试在训练过程中加入傅里叶变换作为辅助特征。

方法对比

基线模型训练时候的训练过程(无傅里叶变换)

直接是普通的蒸馏训练过程,正常的损失计算。

# 传统RGB图像预处理
def _compute_loss(self, student_out, teacher_out, targets):
        current_T = max(1.0, self.args.T * (0.95 ** (self.current_epoch/10)))
        """计算蒸馏损失"""
        # KL散度损失
        kl_loss = nn.KLDivLoss(reduction='batchmean')(
            torch.log_softmax(student_out/self.args.T, dim=1),
            torch.softmax(teacher_out/self.args.T, dim=1)
        ) * (current_T ** 2)
        
        # 交叉熵损失
        ce_loss = self.criterion(student_out, targets)
        
        total_loss = self.args.alpha * kl_loss + (1 - self.args.alpha) * ce_loss
        return total_loss

    def train_epoch(self, train_loader, epoch):
        try:

            """完整训练逻辑"""
            self.student.train()
            self.current_epoch = epoch
            total_loss = 0.0
            correct = 0
            total = 0

            
            for batch_idx, (data, target) in enumerate(train_loader):
                data, target = data.to(self.device), target.to(self.device)
                if self.gpu == 0 and batch_idx % 200 == 0:
                    current_lr = self.optimizer.param_groups[0]['lr']
                    print(f'当前学习率: {current_lr:.6f}')  # 添加这行打印学习率
                self.optimizer.zero_grad()
                
                # 前向传播
                student_out = self.student(data)
                with torch.no_grad():
                    teacher_out = self.teacher(data)        
                # 计算损失
                loss = self._compute_loss(student_out, teacher_out, target)
                
                # 反向传播
                loss.backward()
                self.optimizer.step()
                
                # 统计指标
                total_loss += loss.item()
                _, predicted = student_out.max(1)
                total += target.size(0)
                correct += predicted.eq(target).sum().item()
                
                # 主进程打印日志
                if self.gpu == 0 and batch_idx % 200 == 0:
                    avg_loss = total_loss / (batch_idx + 1)
                    acc = 100. * correct / total
                    print(f'Epoch {epoch} Batch {batch_idx}/{len(train_loader)} '
                        f'Loss: {avg_loss:.4f} | Acc: {acc:.2f}%')
            self.scheduler.step()
            return {
                'loss':total_loss / len(train_loader),
                'accuracy': 100.0 * correct / total
                }
        except Exception as e:
            if "NCCL" in str(e):
                print(f"NCCL错误发生,尝试恢复训练...")
                torch.distributed.destroy_process_group()
                torch.distributed.init_process_group(backend='nccl')
                return {'loss': 0, 'accuracy': 0}
            else:
                print(f"训练过程中发生错误: {str(e)}")
                raise e

改进模型(加入傅里叶变换)

加入的傅里叶变换该怎么加呢,我们只在训练过程中加入,那么得到的特征中具有较好区分性就行,所以不需要将输入图像数据都进行傅里叶变换,这样也防止在后续的推理过程中都需要进行傅里叶变换,增加无畏的动作和减少更多的特征内卷。
训练过程中,正常的输入图像数据,正常的教师学生模型的特征求取,但是同时采用傅里叶变换对图像数据进行预处理,用于后续的损失函数加入。
训练参数为:

lr=0.001, step_size=10, gamma=0.9, alpha=0.5, T=3.0, epochs=300, log_interval=5, batch_size=128, save_interval=10, eval_interval=5
    def _fourier_transform(self, x):
        x = torch.fft.fftshift(torch.fft.fft2(x, dim=(-2, -1)))  # 中心化频谱
        x = torch.abs(x)
        # 动态调整滤波区域
        h, w = x.shape[-2:]
        crow, ccol = h//2, w//2
        mask = torch.ones_like(x)
        mask[..., crow-10:crow+10, ccol-10:ccol+10] = 0.3  # 部分保留中心低频
        return torch.log(1 + 10*x*mask)  # 增强高频特征
def _compute_loss(self, student_out, teacher_out, targets):
        current_T = max(1.0, self.args.T * (0.95 ** (self.current_epoch/10)))
        """计算蒸馏损失"""
        # KL散度损失
        kl_loss = nn.KLDivLoss(reduction='batchmean')(
            torch.log_softmax(student_out/self.args.T, dim=1),
            torch.softmax(teacher_out/self.args.T, dim=1)
        ) * (current_T ** 2)
        
        # 交叉熵损失
        ce_loss = self.criterion(student_out, targets)
        
        total_loss = self.args.alpha * kl_loss + (1 - self.args.alpha) * ce_loss
        #if self.gpu == 0:  # 仅主进程打印
        #    print(f"原始损失 - KL: {kl_loss.item():.4f} | CE: {ce_loss.item():.4f}")
        # 添加频域分支损失
        if self.use_freq:
            base_weight = self.freq_weight  # 基础权重
            dynamic_weight = min(0.25, 0.15 + self.current_epoch*0.001)
            freq_loss = self.criterion(self.freq_pred, targets) * base_weight * dynamic_weight
            total_loss += freq_loss
            #if self.gpu == 0:
            #    print(f"频域分支损失: {freq_loss.item():.4f} (权重: {self.freq_weight})")
        
        return total_loss

    def train_epoch(self, train_loader, epoch):
        try:

            """完整训练逻辑"""
            self.student.train()
            self.current_epoch = epoch
            total_loss = 0.0
            correct = 0
            total = 0

            
            for batch_idx, (data, target) in enumerate(train_loader):
                data, target = data.to(self.device), target.to(self.device)
                if self.gpu == 0 and batch_idx % 200 == 0:
                    current_lr = self.optimizer.param_groups[0]['lr']
                    print(f'当前学习率: {current_lr:.6f}')  # 添加这行打印学习率
                self.optimizer.zero_grad()
                
                # 前向传播
                student_out = self.student(data)
                with torch.no_grad():
                    teacher_out = self.teacher(data)
                if self.use_freq:
                    # 频域处理
                    with torch.no_grad():
                        freq_data = self._fourier_transform(data)
                        self.freq_pred = self.freq_branch(freq_data).squeeze()          
                # 计算损失
                loss = self._compute_loss(student_out, teacher_out, target)
                
                # 反向传播
                loss.backward()
                self.optimizer.step()
                
                # 统计指标
                total_loss += loss.item()
                _, predicted = student_out.max(1)
                total += target.size(0)
                correct += predicted.eq(target).sum().item()
                
                # 主进程打印日志
                if self.gpu == 0 and batch_idx % 200 == 0:
                    avg_loss = total_loss / (batch_idx + 1)
                    acc = 100. * correct / total
                    print(f'Epoch {epoch} Batch {batch_idx}/{len(train_loader)} '
                        f'Loss: {avg_loss:.4f} | Acc: {acc:.2f}%')
            self.scheduler.step()
            return {
                'loss':total_loss / len(train_loader),
                'accuracy': 100.0 * correct / total
                }
        except Exception as e:
            if "NCCL" in str(e):
                print(f"NCCL错误发生,尝试恢复训练...")
                torch.distributed.destroy_process_group()
                torch.distributed.init_process_group(backend='nccl')
                return {'loss': 0, 'accuracy': 0}
            else:
                print(f"训练过程中发生错误: {str(e)}")
                raise e

性能指标对比

指标基线模型傅里叶增强模型提升幅度
Accuracy88.41%93.40%+4.99%
Precision98.66%98.52%-0.14%
Recall84.68%91.86%+7.18%
F1 Score91.14%95.08%+3.94%
ROC AUC96.61%97.83%+1.22%
EER7.67%5.84%-1.83%

关键发现

  1. 召回率显著提升:傅里叶变换帮助模型更好地捕捉伪造痕迹,使召回率提高了7.18%

  2. 等错误率降低:EER从7.67%降至5.84%,表明系统整体性能更均衡

  3. 特征互补性:虽然单独看频域特征效果有限,但与空间特征结合产生了协同效应

实现建议

在本次实验,我是保留了中心低频,增强了高频特征,当然也可以不这么干,毕竟有些低频的信息也有用,需要多次验证采取最好的。代码中添加的过滤如下:

        # 动态调整滤波区域
        h, w = x.shape[-2:]
        crow, ccol = h//2, w//2
        mask = torch.ones_like(x)
        mask[..., crow-10:crow+10, ccol-10:ccol+10] = 0.3  # 部分保留中心低频
        return torch.log(1 + 10*x*mask)  # 增强高频特征

结论

傅里叶变换的引入使模型在保持高精确度的同时,显著提升了召回能力。这只是在调整模型过程中的一点小改善,当然还有其他更好的方法,SqueezeNe的模型结构还是浅,如果没有更多的限制,可以加深加大,这样效果会更好。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值