算法学习笔记 Day 1:迁移学习与域自适应(DANN/CORAL)

算法学习笔记 Day 1:迁移学习与域自适应(DANN/CORAL)

目录

  1. 迁移学习简介
  2. 域自适应原理
  3. 代码实现
  4. PHM应用场景
  5. 注意事项与总结

1. 迁移学习简介

1.1 背景

在工业预测与健康管理(Prognostics and Health Management, PHM)领域,我们经常面临一个核心挑战:训练数据与测试数据分布不一致

具体来说:

场景 训练数据 测试数据 问题
跨工况 正常转速运行的轴承数据 不同转速下的轴承数据 工况变化导致分布偏移
跨设备 设备A的历史故障数据 设备B的新故障数据 设备个体差异
跨时段 历史生产数据 当前生产数据 环境/材料老化

传统机器学习方法假设训练和测试数据独立同分布(i.i.d.),这一假设在实际PHM场景中往往不成立,导致模型泛化能力急剧下降。

1.2 迁移学习的核心思想

迁移学习(Transfer Learning) 的核心思想是:利用源域(Source Domain)学到的知识,帮助目标域(Target Domain)的学习。

复制代码
源域:拥有丰富标签数据的领域(通常是容易获取数据的场景)
目标域:数据稀缺但我们真正关心的领域

形式化定义:

  • 源域 : D s = { ( x s i , y s i ) } i = 1 n s D_s = \{(x_s^i, y_s^i)\}_{i=1}^{n_s} Ds={(xsi,ysi)}i=1ns,包含大量标注数据
  • 目标域 : D t = { x t j } j = 1 n t D_t = \{x_t^j\}_{j=1}^{n_t} Dt={xtj}j=1nt,只有少量或没有标注数据
  • 任务 :在目标域上学习预测函数 f : x → y f: x \rightarrow y f:x→y

核心目标:最小化域间分布差异,使知识从源域有效迁移到目标域。

1.3 在PHM故障诊断中的重要性

  1. 数据稀缺问题:真实故障样本获取困难,迁移学习可以充分利用相似工况/设备的数据
  2. 标注成本高:工业数据标注需要专家知识,迁移学习减少对目标域标注的依赖
  3. 模型泛化:提高模型在不同工作条件下的鲁棒性
  4. 冷启动问题:新设备上线时没有足够历史数据,迁移学习提供启动能力

2. 域自适应原理

2.1 域差异度量

域自适应的关键在于如何量化"域间差异"。常用的度量方法有:

2.1.1 最大均值差异(MMD)

MMD(Maximum Mean Discrepancy)是度量两个分布差异的经典方法,通过比较样本在再生核希尔伯特空间(RKHS)中的均值差异:

MMD 2 ( D s , D t ) = ∥ 1 n s ∑ i = 1 n s ϕ ( x s i ) − 1 n t ∑ j = 1 n t ϕ ( x t j ) ∥ H 2 \text{MMD}^2(D_s, D_t) = \left\| \frac{1}{n_s}\sum_{i=1}^{n_s}\phi(x_s^i) - \frac{1}{n_t}\sum_{j=1}^{n_t}\phi(x_t^j) \right\|^2_{\mathcal{H}} MMD2(Ds,Dt)= ns1i=1∑nsϕ(xsi)−nt1j=1∑ntϕ(xtj) H2

其中 ϕ ( ⋅ ) \phi(\cdot) ϕ(⋅) 是核函数映射。常用的核函数为高斯核:
k ( x , y ) = exp ⁡ ( − ∥ x − y ∥ 2 2 σ 2 ) k(x, y) = \exp\left(-\frac{\|x - y\|^2}{2\sigma^2}\right) k(x,y)=exp(−2σ2∥x−y∥2)

PHM应用场景:将源域和目标域的特征都映射到RKHS,比较其均值嵌入向量的距离。

2.1.2 CORAL距离

CORAL(Correlation Alignment)是一种简单而有效的域差异度量方法,通过对齐源域和目标域的二阶统计量(协方差矩阵):

L CORAL = 1 4 d 2 ∥ C s − C t ∥ F 2 \mathcal{L}_{\text{CORAL}} = \frac{1}{4d^2}\|\mathbf{C}_s - \mathbf{C}_t\|_F^2 LCORAL=4d21∥Cs−Ct∥F2

其中:

  • C s \mathbf{C}_s Cs:源域特征的协方差矩阵
  • C t \mathbf{C}_t Ct:目标域特征的协方差矩阵
  • ∥ ⋅ ∥ F \|\cdot\|_F ∥⋅∥F:Frobenius范数

优点:计算简单,无需额外参数,在PHM领域效果稳健。

python 复制代码
def coral_loss(source_features, target_features):
    """
    计算CORAL损失:对齐源域和目标域特征的协方差矩阵
    """
    d = source_features.size(1)
    
    # 去均值化
    source_centered = source_features - source_features.mean(dim=0, keepdim=True)
    target_centered = target_features - target_features.mean(dim=0, keepdim=True)
    
    # 计算协方差矩阵
    source_cov = (source_centered.T @ source_centered) / (source_features.size(0) - 1)
    target_cov = (target_centered.T @ target_features) / (target_features.size(0) - 1)
    
    # CORAL损失
    loss = torch.norm(source_cov - target_cov, p='fro') ** 2 / (4 * d * d)
    return loss

2.2 DANN(域对抗神经网络)数学推导

DANN(Domain-Adversarial Neural Network)是迁移学习领域里程碑式的工作,其核心思想是:**训练一个特征提取器,使得提取的特征"欺骗"域判别器",从而达到域不变的效果。

2.2.1 网络架构

DANN由三部分组成:

复制代码
输入数据 → 特征提取器 G_f → 特征表示 F
                                 ↓
                           类别预测器 G_y → 类别标签 y
                           域判别器 G_d → 域标签 d
2.2.2 数学推导

特征提取器 : F = G f ( x ; θ f ) F = G_f(x; \theta_f) F=Gf(x;θf),将输入映射到特征空间

类别预测器 : y ^ = G y ( F ; θ y ) \hat{y} = G_y(F; \theta_y) y^=Gy(F;θy),预测样本类别

域判别器 : d ^ = G d ( F ; θ d ) \hat{d} = G_d(F; \theta_d) d^=Gd(F;θd),判断样本来自哪个域

损失函数设计

  1. 源域分类损失 (有监督):
    L y ( θ f , θ y ) = − E ( x s , y s ) ∼ D s ∑ k = 1 K 1 [ k = y s ] log ⁡ G y k ( G f ( x s ; θ f ) ; θ y ) \mathcal{L}y(\theta_f, \theta_y) = -\mathbb{E}{(x_s, y_s)\sim D_s} \sum_{k=1}^{K} \mathbb{1}_{[k=y_s]} \log G_y^k(G_f(x_s; \theta_f); \theta_y) Ly(θf,θy)=−E(xs,ys)∼Dsk=1∑K1[k=ys]logGyk(Gf(xs;θf);θy)

  2. 域判别损失
    L d ( θ f , θ d ) = − E x s ∼ D s log ⁡ G d ( G f ( x s ; θ f ) ; θ d ) − E x t ∼ D t log ⁡ ( 1 − G d ( G f ( x t ; θ f ) ; θ d ) ) \mathcal{L}d(\theta_f, \theta_d) = -\mathbb{E}{x_s\sim D_s} \log G_d(G_f(x_s; \theta_f); \theta_d) - \mathbb{E}_{x_t\sim D_t} \log(1 - G_d(G_f(x_t; \theta_f); \theta_d)) Ld(θf,θd)=−Exs∼DslogGd(Gf(xs;θf);θd)−Ext∼Dtlog(1−Gd(Gf(xt;θf);θd))

  3. 梯度反转层:这是DANN的核心创新!

在反向传播时,梯度反转层将域判别器的梯度取反:

R λ ( x ) = x R_\lambda(x) = x Rλ(x)=x
∂ R λ ∂ x = − λ I \frac{\partial R_\lambda}{\partial x} = -\lambda \mathbf{I} ∂x∂Rλ=−λI

这意味着特征提取器被训练去最大化域判别器的损失,从而学习到域不变的表示。

2.2.3 总体目标

min ⁡ θ f , θ y max ⁡ θ d L y ( θ f , θ y ) − λ L d ( θ f , θ d ) \min_{\theta_f, \theta_y} \max_{\theta_d} \mathcal{L}_y(\theta_f, \theta_y) - \lambda \mathcal{L}_d(\theta_f, \theta_d) θf,θyminθdmaxLy(θf,θy)−λLd(θf,θd)

  • 类别预测器:最小化分类误差
  • 特征提取器:最小化分类误差 + 最大化域判别误差(通过梯度反转)
  • 域判别器:最大化域判别准确率

2.3 损失函数设计

完整的DANN损失函数为:

python 复制代码
def dann_loss(source_features, target_features, source_labels, 
              feature_extractor, class_classifier, domain_classifier, 
              lambda_param=1.0):
    """
    计算DANN总损失
    
    Args:
        source_features: 源域特征
        target_features: 目标域特征
        source_labels: 源域标签
        lambda_param: 域对齐权重
    """
    # 1. 源域分类损失
    source_preds = class_classifier(source_features)
    cls_loss = F.cross_entropy(source_preds, source_labels)
    
    # 2. 域判别损失
    # 源域样本标签为0,目标域样本标签为1
    source_domain_labels = torch.zeros(source_features.size(0)).long().to(source_features.device)
    target_domain_labels = torch.ones(target_features.size(0)).long().to(target_features.device)
    
    combined_features = torch.cat([source_features, target_features], dim=0)
    combined_domain_labels = torch.cat([source_domain_labels, target_domain_labels], dim=0)
    
    domain_preds = domain_classifier(combined_features)
    domain_loss = F.cross_entropy(domain_preds, combined_domain_labels)
    
    # 3. 总损失
    total_loss = cls_loss - lambda_param * domain_loss
    
    return total_loss, cls_loss, domain_loss

3. 代码实现

3.1 整体网络结构

python 复制代码
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
import numpy as np
from sklearn.preprocessing import StandardScaler

class DANN(nn.Module):
    """
    域对抗神经网络 (Domain-Adversarial Neural Network)
    
    用于PHM领域的跨域故障诊断
    
    网络结构:
    输入 → 特征提取器 → 特征表示
                        ↓
                  类别预测器 → 故障类别
                  域判别器 → 域标签
    """
    
    def __init__(self, input_dim, num_classes, hidden_dims=[256, 128]):
        super(DANN, self).__init__()
        
        # ==================== 特征提取器 ====================
        # 将原始传感器数据映射到域不变的特征空间
        feature_layers = []
        prev_dim = input_dim
        
        for hidden_dim in hidden_dims:
            feature_layers.extend([
                nn.Linear(prev_dim, hidden_dim),
                nn.BatchNorm1d(hidden_dim),
                nn.ReLU(),
                nn.Dropout(0.3)
            ])
            prev_dim = hidden_dim
        
        self.feature_extractor = nn.Sequential(*feature_layers)
        self.feature_dim = prev_dim
        
        # ==================== 类别预测器 ====================
        # 预测故障类型(仅在源域有标签时训练)
        self.class_classifier = nn.Sequential(
            nn.Linear(self.feature_dim, 64),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(64, num_classes)
        )
        
        # ==================== 域判别器 ====================
        # 判断样本来自源域还是目标域
        self.domain_classifier = nn.Sequential(
            nn.Linear(self.feature_dim, 64),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(64, 2)  # 二分类:源域(0) 或 目标域(1)
        )
    
    def forward(self, x, alpha=1.0):
        """
        前向传播
        
        Args:
            x: 输入数据
            alpha: 梯度反转强度参数
        
        Returns:
            class_output: 类别预测
            domain_output: 域预测
            features: 提取的特征
        """
        # 提取特征
        features = self.feature_extractor(x)
        
        # 梯度反转层会在反向传播时反转梯度
        # 前向传播时不变
        features_grad = GradientReverseFunction.apply(features, alpha)
        
        # 类别预测和域预测
        class_output = self.class_classifier(features)
        domain_output = self.domain_classifier(features_grad)
        
        return class_output, domain_output, features
    
    def extract_features(self, x):
        """提取特征(用于推理)"""
        return self.feature_extractor(x)


class GradientReverseFunction(torch.autograd.Function):
    """
    梯度反转层实现
    
    前向传播:保持输入不变
    反向传播:将梯度乘以 -alpha 后输出
    
    这是DANN的核心组件,使得特征提取器学习域不变表示
    """
    
    @staticmethod
    def forward(ctx, x, alpha):
        ctx.alpha = alpha
        return x.view_as(x)
    
    @staticmethod
    def backward(ctx, grad_output):
        # 反向传播时反转梯度
        return grad_output.neg() * ctx.alpha, None


class GradientReverseLayer(nn.Module):
    """
    梯度反转层的封装
    """
    def __init__(self, alpha=1.0):
        super(GradientReverseLayer, self).__init__()
        self.alpha = alpha
    
    def forward(self, x):
        return GradientReverseFunction.apply(x, self.alpha)


def dann_loss(source_features, target_features, source_labels, 
              class_classifier, domain_classifier, lambda_param=1.0):
    """
    计算DANN损失函数
    
    损失组成:
    1. 源域分类损失:使特征提取器学习判别性特征
    2. 域判别损失(带负号):通过梯度反转,最大化域判别器的损失
    
    总目标:最小化分类损失,最大化域判别损失
    """
    # 源域分类损失
    source_preds = class_classifier(source_features)
    cls_loss = F.cross_entropy(source_preds, source_labels)
    
    # 构造域标签:源域为0,目标域为1
    source_domain_labels = torch.zeros(source_features.size(0)).long().to(source_features.device)
    target_domain_labels = torch.ones(target_features.size(0)).long().to(source_features.device)
    
    # 拼接源域和目标域特征
    combined_features = torch.cat([source_features, target_features], dim=0)
    combined_domain_labels = torch.cat([source_domain_labels, target_domain_labels], dim=0)
    
    # 域判别损失
    domain_preds = domain_classifier(combined_features)
    domain_loss = F.cross_entropy(domain_preds, combined_domain_labels)
    
    # 总损失:注意域损失前有负号(通过梯度反转实现)
    total_loss = cls_loss - lambda_param * domain_loss
    
    return total_loss, cls_loss, domain_loss

3.2 完整训练流程

python 复制代码
import matplotlib.pyplot as plt
from sklearn.metrics import classification_report, confusion_matrix
import seaborn as sns

class PHMDANNTrainer:
    """
    PHM领域DANN模型训练器
    
    支持:
    - 跨工况故障诊断
    - 跨设备迁移
    - 域自适应训练
    """
    
    def __init__(self, model, device='cuda' if torch.cuda.is_available() else 'cpu'):
        self.model = model.to(device)
        self.device = device
        self.history = {
            'cls_loss': [], 'domain_loss': [], 'total_loss': [],
            'source_acc': [], 'target_acc': []
        }
    
    def train_epoch(self, source_loader, target_loader, 
                   optimizer, lambda_param=1.0):
        """
        训练一个epoch
        
        Args:
            source_loader: 源域数据加载器(有标签)
            target_loader: 目标域数据加载器(无标签)
            optimizer: 优化器
            lambda_param: 域对齐参数
        
        Returns:
            metrics: 当前epoch的训练指标
        """
        self.model.train()
        
        total_cls_loss, total_domain_loss = 0, 0
        correct_source, total_source = 0, 0
        
        # 创建目标域迭代器
        target_iter = iter(target_loader)
        
        for source_data, source_labels in source_loader:
            # 获取目标域数据
            try:
                target_data, _ = next(target_iter)
            except StopIteration:
                target_iter = iter(target_loader)
                target_data, _ = next(target_iter)
            
            # 移到设备
            source_data = source_data.to(self.device)
            source_labels = source_labels.to(self.device)
            target_data = target_data.to(self.device)
            
            # 前向传播
            optimizer.zero_grad()
            
            # 提取特征
            source_features = self.model.feature_extractor(source_data)
            target_features = self.model.feature_extractor(target_data)
            
            # 计算损失
            total_loss, cls_loss, domain_loss = dann_loss(
                source_features, target_features, source_labels,
                self.model.class_classifier, 
                self.model.domain_classifier,
                lambda_param
            )
            
            # 反向传播
            total_loss.backward()
            optimizer.step()
            
            # 统计
            total_cls_loss += cls_loss.item()
            total_domain_loss += domain_loss.item()
            
            # 源域准确率
            source_preds = self.model.class_classifier(source_features)
            correct_source += (source_preds.argmax(1) == source_labels).sum().item()
            total_source += source_labels.size(0)
        
        # 计算平均值
        num_batches = len(source_loader)
        metrics = {
            'cls_loss': total_cls_loss / num_batches,
            'domain_loss': total_domain_loss / num_batches,
            'source_acc': correct_source / total_source
        }
        
        return metrics
    
    def evaluate(self, test_loader):
        """
        评估模型性能
        
        在目标域上进行测试
        """
        self.model.eval()
        
        all_preds, all_labels = [], []
        
        with torch.no_grad():
            for data, labels in test_loader:
                data, labels = data.to(self.device), labels.to(self.device)
                
                # 提取特征并预测
                features = self.model.extract_features(data)
                outputs = self.model.class_classifier(features)
                preds = outputs.argmax(1)
                
                all_preds.extend(preds.cpu().numpy())
                all_labels.extend(labels.cpu().numpy())
        
        # 计算指标
        accuracy = np.mean(np.array(all_preds) == np.array(all_labels))
        
        return {
            'accuracy': accuracy,
            'predictions': all_preds,
            'labels': all_labels
        }
    
    def plot_results(self):
        """可视化训练结果"""
        fig, axes = plt.subplots(1, 3, figsize=(15, 4))
        
        # 损失曲线
        axes[0].plot(self.history['cls_loss'], label='分类损失')
        axes[0].plot(self.history['domain_loss'], label='域损失')
        axes[0].set_xlabel('Epoch')
        axes[0].set_ylabel('Loss')
        axes[0].set_title('训练损失曲线')
        axes[0].legend()
        axes[0].grid(True)
        
        # 准确率曲线
        axes[1].plot(self.history['source_acc'], label='源域准确率')
        axes[1].plot(self.history['target_acc'], label='目标域准确率')
        axes[1].set_xlabel('Epoch')
        axes[1].set_ylabel('Accuracy')
        axes[1].set_title('准确率曲线')
        axes[1].legend()
        axes[1].grid(True)
        
        # 混淆矩阵
        if self.history.get('confusion_matrix') is not None:
            sns.heatmap(self.history['confusion_matrix'], annot=True, 
                        fmt='d', ax=axes[2], cmap='Blues')
            axes[2].set_title('混淆矩阵')
            axes[2].set_xlabel('预测')
            axes[2].set_ylabel('真实')
        
        plt.tight_layout()
        plt.savefig('./dann_training_results.png', dpi=150)
        plt.show()


def run_complete_experiment():
    """
    完整实验流程演示
    
    模拟场景:跨转速工况的轴承故障诊断
    - 源域:转速 1800 RPM 下的轴承振动数据
    - 目标域:转速 1500 RPM 下的轴承振动数据
    """
    print("=" * 60)
    print("DANN 跨域故障诊断实验")
    print("=" * 60)
    
    # ==================== 数据生成 ====================
    np.random.seed(42)
    torch.manual_seed(42)
    
    # 模拟参数
    n_samples = 500  # 每类样本数
    n_features = 64  # 特征维度(模拟传感器通道)
    n_classes = 4    # 故障类别数(正常 + 3种故障)
    n_epochs = 100
    
    # 生成源域数据(工况A:正常转速)
    print("\n[1] 生成模拟数据...")
    
    def generate_fault_data(n_samples, n_features, fault_type, noise_level=0.5):
        """生成模拟故障数据"""
        t = np.linspace(0, 10 * np.pi, n_features)
        
        if fault_type == 0:  # 正常
            freq = 1.0
        elif fault_type == 1:  # 内圈故障
            freq = 2.3  # 特定频率成分
        elif fault_type == 2:  # 外圈故障
            freq = 3.1
        else:  # 滚动体故障
            freq = 4.7
        
        X = []
        for _ in range(n_samples):
            signal = np.sin(freq * t) + noise_level * np.random.randn(n_features)
            X.append(signal)
        
        return np.array(X)
    
    # 生成源域数据(高噪声)
    source_data, source_labels = [], []
    for fault_type in range(n_classes):
        data = generate_fault_data(n_samples, n_features, fault_type, noise_level=0.8)
        source_data.append(data)
        source_labels.append(np.full(n_samples, fault_type))
    
    source_data = np.vstack(source_data)
    source_labels = np.concatenate(source_labels)
    
    # 生成目标域数据(低噪声,分布略有不同)
    target_data, target_labels = [], []
    for fault_type in range(n_classes):
        data = generate_fault_data(n_samples, n_features, fault_type, noise_level=0.3)
        target_data.append(data)
        target_labels.append(np.full(n_samples, fault_type))
    
    target_data = np.vstack(target_data)
    target_labels = np.concatenate(target_labels)
    
    # 数据标准化
    scaler = StandardScaler()
    source_data = scaler.fit_transform(source_data)
    target_data = scaler.transform(target_data)
    
    # 转换为张量
    source_data = torch.FloatTensor(source_data)
    source_labels = torch.LongTensor(source_labels)
    target_data = torch.FloatTensor(target_data)
    target_labels = torch.LongTensor(target_labels)
    
    # 数据加载器
    source_dataset = TensorDataset(source_data, source_labels)
    target_dataset = TensorDataset(target_data, target_labels)
    
    source_loader = DataLoader(source_dataset, batch_size=32, shuffle=True)
    target_loader = DataLoader(target_dataset, batch_size=32, shuffle=True)
    
    # 划分测试集
    test_size = 200
    test_target_dataset = TensorDataset(target_data[:test_size], target_labels[:test_size])
    test_loader = DataLoader(test_target_dataset, batch_size=32)
    
    print(f"   源域样本数: {len(source_data)}")
    print(f"   目标域样本数: {len(target_data)}")
    print(f"   特征维度: {n_features}")
    print(f"   故障类别数: {n_classes}")
    
    # ==================== 模型训练 ====================
    print("\n[2] 初始化DANN模型...")
    
    device = 'cuda' if torch.cuda.is_available() else 'cpu'
    model = DANN(input_dim=n_features, num_classes=n_classes, hidden_dims=[128, 64])
    trainer = PHMDANNTrainer(model, device)
    
    optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4)
    scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.5)
    
    print(f"   设备: {device}")
    print(f"   模型参数量: {sum(p.numel() for p in model.parameters()):,}")
    
    # ==================== 训练循环 ====================
    print("\n[3] 开始训练...")
    print("-" * 60)
    
    for epoch in range(n_epochs):
        # 动态调整lambda参数(从0渐增到1)
        lambda_param = min(1.0, epoch / 30)
        
        # 训练一个epoch
        metrics = trainer.train_epoch(source_loader, target_loader, optimizer, lambda_param)
        
        # 记录历史
        trainer.history['cls_loss'].append(metrics['cls_loss'])
        trainer.history['domain_loss'].append(metrics['domain_loss'])
        trainer.history['source_acc'].append(metrics['source_acc'])
        
        # 每20个epoch评估一次
        if (epoch + 1) % 20 == 0:
            eval_results = trainer.evaluate(test_loader)
            trainer.history['target_acc'].append(eval_results['accuracy'])
            
            print(f"Epoch {epoch+1:3d} | "
                  f"分类损失: {metrics['cls_loss']:.4f} | "
                  f"域损失: {metrics['domain_loss']:.4f} | "
                  f"源域准确率: {metrics['source_acc']:.4f} | "
                  f"目标域准确率: {eval_results['accuracy']:.4f}")
    
    # ==================== 最终评估 ====================
    print("\n[4] 最终评估结果:")
    print("-" * 60)
    
    final_results = trainer.evaluate(test_loader)
    print(f"   目标域测试准确率: {final_results['accuracy']:.4f}")
    
    # 绘制结果
    trainer.plot_results()
    
    # ==================== 对比实验 ====================
    print("\n[5] 对比实验:baseline(无域自适应):")
    print("-" * 60)
    
    # 训练一个没有域判别器的baseline模型
    class BaselineModel(nn.Module):
        def __init__(self, input_dim, num_classes):
            super().__init__()
            self.features = nn.Sequential(
                nn.Linear(input_dim, 128),
                nn.ReLU(),
                nn.Linear(128, 64),
                nn.ReLU(),
                nn.Linear(64, num_classes)
            )
        
        def forward(self, x):
            return self.features(x)
    
    baseline = BaselineModel(n_features, n_classes).to(device)
    baseline_optimizer = optim.Adam(baseline.parameters(), lr=0.001)
    
    # 仅使用源域数据训练
    for epoch in range(n_epochs):
        for data, labels in source_loader:
            data, labels = data.to(device), labels.to(device)
            baseline_optimizer.zero_grad()
            outputs = baseline(data)
            loss = F.cross_entropy(outputs, labels)
            loss.backward()
            baseline_optimizer.zero_grad()
    
    # 在目标域上评估baseline
    baseline.eval()
    correct, total = 0, 0
    with torch.no_grad():
        for data, labels in test_loader:
            data, labels = data.to(device), labels.to(device)
            outputs = baseline(data)
            preds = outputs.argmax(1)
            correct += (preds == labels).sum().item()
            total += labels.size(0)
    
    baseline_acc = correct / total
    print(f"   Baseline目标域准确率: {baseline_acc:.4f}")
    print(f"   DANN提升: +{(final_results['accuracy'] - baseline_acc)*100:.2f}%")
    
    print("\n" + "=" * 60)
    print("实验完成!")
    print("=" * 60)
    
    return model, trainer, final_results


# 运行实验
if __name__ == "__main__":
    model, trainer, results = run_complete_experiment()

3.3 使用CORAL的简化实现

如果觉得DANN过于复杂,可以考虑更简单的CORAL方法:

python 复制代码
class CORALModel(nn.Module):
    """
    CORAL (Correlation Alignment) 模型
    
    原理:对齐源域和目标域特征的协方差矩阵
    
    优点:
    - 无需对抗训练,训练稳定
    - 实现简单,计算高效
    - 只需一个损失项
    """
    
    def __init__(self, input_dim, num_classes, hidden_dims=[128, 64]):
        super().__init__()
        
        # 特征提取器
        self.feature_extractor = nn.Sequential(
            nn.Linear(input_dim, hidden_dims[0]),
            nn.BatchNorm1d(hidden_dims[0]),
            nn.ReLU(),
            nn.Dropout(0.3),
            nn.Linear(hidden_dims[0], hidden_dims[1]),
            nn.BatchNorm1d(hidden_dims[1]),
            nn.ReLU(),
            nn.Dropout(0.3)
        )
        
        # 分类器
        self.classifier = nn.Linear(hidden_dims[1], num_classes)
    
    def coral_loss(self, source_features, target_features):
        """
        CORAL损失:对齐协方差矩阵
        
        数学公式:
        L_coral = ||C_s - C_t||_F^2 / (4 * d^2)
        
        其中 C_s 和 C_t 分别是源域和目标域的协方差矩阵
        """
        d = source_features.size(1)
        
        # 去均值
        source_c = source_features - source_features.mean(dim=0, keepdim=True)
        target_c = target_features - target_features.mean(dim=0, keepdim=True)
        
        # 计算协方差矩阵
        source_cov = (source_c.T @ source_c) / (source_features.size(0) - 1)
        target_cov = (target_c.T @ target_c) / (target_features.size(0) - 1)
        
        # CORAL损失
        loss = torch.norm(source_cov - target_cov, p='fro') ** 2
        loss = loss / (4 * d * d)
        
        return loss
    
    def forward(self, x):
        features = self.feature_extractor(x)
        logits = self.classifier(features)
        return logits, features


def train_coral(source_loader, target_loader, model, optimizer, alpha=1.0):
    """
    训练CORAL模型
    
    总损失 = 分类损失 + alpha * CORAL损失
    """
    model.train()
    
    total_cls_loss, total_coral_loss = 0, 0
    
    target_iter = iter(target_loader)
    
    for source_data, source_labels in source_loader:
        try:
            target_data, _ = next(target_iter)
        except StopIteration:
            target_iter = iter(target_loader)
            target_data, _ = next(target_iter)
        
        source_data, source_labels = source_data.cuda(), source_labels.cuda()
        target_data = target_data.cuda()
        
        optimizer.zero_grad()
        
        # 前向传播
        source_logits, source_features = model(source_data)
        _, target_features = model(target_data)
        
        # 分类损失
        cls_loss = F.cross_entropy(source_logits, source_labels)
        
        # CORAL损失
        coral_loss = model.coral_loss(source_features, target_features)
        
        # 总损失
        loss = cls_loss + alpha * coral_loss
        
        loss.backward()
        optimizer.step()
        
        total_cls_loss += cls_loss.item()
        total_coral_loss += coral_loss.item()
    
    return total_cls_loss / len(source_loader), total_coral_loss / len(source_loader)

4. PHM应用场景

4.1 跨工况故障诊断

问题描述:同一设备在不同转速、负载、温度等工况下运行,数据分布存在显著差异。

解决方案:使用DANN/CORAL提取工况不变的特征表示。

复制代码
场景示例:
┌─────────────────────────────────────────────────────────┐
│  轴承跨转速迁移                                          │
│                                                         │
│  源域:1800 RPM轴承振动数据(含故障标签)                 │
│       ↓                                                 │
│  特征提取器学习:工况无关的故障特征                        │
│       ↓                                                 │
│  目标域:1200 RPM轴承振动数据(无标签)                   │
│       ↓                                                 │
│  输出:1200 RPM下的故障诊断结果                          │
└─────────────────────────────────────────────────────────┘

实际数据特征

  • 不同转速下,故障特征频率会发生缩放
  • 负载变化影响振动信号的幅值分布
  • 温度影响导致传感器漂移

4.2 跨设备迁移

问题描述:新设备上线时没有足够的历史故障数据,需要从相似设备迁移知识。

典型案例

迁移场景 源域 目标域 挑战
同型号不同机体 机组A数据 机组B数据 制造公差
同型号不同时段 历史数据 当前数据 磨损老化
不同型号同原理 大型风机 小型风机 尺度差异

处理策略

  1. 提取高层次的物理特征(如共振频率、包络谱特征)
  2. 使用领域对抗学习减小设备特异性
  3. 结合物理先验知识约束特征空间

4.3 实际案例分析

案例:风力发电机齿轮箱故障诊断

背景

  • 齿轮箱故障会导致重大停机事故
  • 不同风场的风速条件差异大
  • 故障样本极其稀缺

方法

复制代码
1. 数据收集
   - 风场A:丰富的故障数据(源域)
   - 风场B:正常数据为主(目标域)

2. 网络设计
   - 特征提取器:1D-CNN处理振动信号
   - 域判别器:判断数据来源
   - 类别分类器:识别故障类型

3. 训练策略
   - 第一阶段:源域分类训练
   - 第二阶段:域对抗微调
   - 第三阶段:目标域伪标签自训练

4. 结果
   - 源域准确率:95.2%
   - 目标域准确率:89.7%(无迁移:62.3%)
   - 提升:+27.4%
案例:数控机床刀具磨损监测

背景

  • 刀具状态直接影响加工质量
  • 不同工件材料、不同切削参数下监测难度大

迁移学习方法

  1. 工况感知特征提取:提取与切削参数无关的特征
  2. MMD正则化:最小化域间分布差异
  3. 渐进式迁移:从相似工况逐步扩展

5. 注意事项与总结

5.1 负迁移问题

什么是负迁移?

当源域和目标域差异过大时,迁移学习可能损害目标域的性能,而不是提升。

复制代码
正常迁移:
源域知识 → 正向迁移 → 目标域性能提升

负迁移:
源域知识 → 误导性迁移 → 目标域性能下降

如何避免负迁移?

  1. 域相似性预检

    • 在迁移前计算域间相似度(MMD、CORAL距离)
    • 相似度低于阈值时不进行迁移
  2. 渐进式迁移

    python 复制代码
    # 从与目标域最相似的源域开始
    source_domains = sort_by_similarity(target_domain)
    for source in source_domains:
        if similarity(source, target) > threshold:
            perform_transfer(source, target)
  3. 多源域融合

    • 避免单一源域带来的偏差
    • 多个源域的知识融合更稳健

5.2 参数选择技巧

λ参数(域对齐权重)
λ值 效果 适用场景
0.0-0.3 弱域对齐,侧重分类 域差异较小
0.5-1.0 中等域对齐 一般场景
1.0-2.0 强域对齐 域差异较大

建议:从λ=1.0开始,根据目标域准确率调整

学习率
python 复制代码
# 推荐设置
optimizer = optim.Adam([
    {'params': model.feature_extractor.parameters(), 'lr': 0.001},
    {'params': model.class_classifier.parameters(), 'lr': 0.01},  # 分类器学习率更高
    {'params': model.domain_classifier.parameters(), 'lr': 0.01}
], weight_decay=1e-4)
网络容量
源域数据量 建议网络深度 建议dropout
>5000 深网络 0.5
1000-5000 中等深度 0.3-0.4
<1000 浅网络 0.1-0.2

5.3 与其他方法对比

方法 原理 优点 缺点 适用场景
DANN 对抗学习 效果最好,理论上保证 训练不稳定,调参难 域差异大
CORAL 协方差对齐 简单稳定,计算快 只能对齐二阶统计量 域差异中等
MMD 核方法 理论基础好 计算量大 小规模数据
伪标签 自训练 无需域假设 错误累积 有少量目标域数据
微调 有监督学习 简单 需要目标域标签 标签可获取时

5.4 最佳实践建议

复制代码
PHM迁移学习实战 checklist:
□ 1. 数据探索:可视化源域和目标域分布
□ 2. 域相似性评估:计算MMD/CORAL距离
□ 3. 选择基线:简单方法(CORAL)先试
□ 4. 网络设计:特征提取器足够深/宽
□ 5. 训练策略:渐进式λ调整
□ 6. 监控指标:同时监控源域和目标域准确率
□ 7. 早停机制:目标域准确率下降时停止
□ 8. 结果验证:多种评估指标

5.5 总结

核心要点

  1. 迁移学习解决PHM中的数据分布不一致问题
  2. DANN通过对抗训练学习域不变表示
  3. CORAL通过协方差对齐简化域自适应
  4. 负迁移是需要警惕的重要问题
相关推荐
skywalk81632 小时前
https://www.voscreen.com/ 是一个非常好的学习英语的网站,请判断和总结它是怎样实现的?如果想复刻一个该网站,需要怎么做?
学习
后季暖2 小时前
agent学习笔记4
笔记·学习
橘颂TA2 小时前
【笔试】算法的暴力美学——牛客 NC242:单词搜索,思路:dfs 算法
算法·深度优先
凯尔萨厮2 小时前
Spring学习笔记(基于注解)
笔记·学习·spring
某风吾起2 小时前
通过mmwave studio配置TI毫米波雷达IWR1843的StaticConfig
嵌入式硬件·学习
EnglishJun2 小时前
ARM嵌入式学习(二十二)-- 操作系统的中断处理以及ioctl
学习
南無忘码至尊2 小时前
Unity学习90天-第3天-认识触屏输入(手游基础)并完成手机点击屏幕,物体向点击位置移动
学习·unity·c#·游戏引擎·游戏开发
沐苏瑶2 小时前
Java据结构深度解析:AVL 树与红黑树
数据结构·算法
feifeigo1232 小时前
MATLAB中对转子建立有限元模型并进行动力学计算
算法