主题079:迁移学习与域适应(Transfer Learning and Domain Adaptation)

目录

  1. 引言与背景
  2. 迁移学习基础理论
  3. 域适应核心方法
  4. 预训练模型与微调策略
  5. 实例一:基于预训练模型的疲劳寿命预测
  6. 实例二:域适应在跨材料疲劳预测中的应用
  7. 高级迁移学习技术
  8. 工程应用案例
  9. 常见问题与解决方案
  10. 总结与展望

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

一、引言与背景

1.1 为什么需要迁移学习?

在结构耐久性仿真和疲劳寿命预测领域,数据获取往往面临巨大挑战:

数据稀缺的现实困境:

  • 疲劳试验周期长:一个完整的S-N曲线测试可能需要数周甚至数月
  • 试验成本高昂:单组疲劳试验成本可达数万元
  • 新材料数据积累慢:新型合金、复合材料的疲劳数据严重不足
  • 极端工况数据难以获取:高温、腐蚀、辐射等环境下的试验数据稀缺

传统方法的局限性:

  • 物理模型需要大量参数标定
  • 纯数据驱动模型在小样本条件下容易过拟合
  • 不同材料、不同工况的模型难以复用

1.2 迁移学习的工程价值

迁移学习(Transfer Learning)提供了一种革命性的解决方案:

核心思想: 利用源域(Source Domain)的丰富知识,辅助目标域(Target Domain)的学习任务。

在疲劳预测中的典型应用场景:

源域 目标域 迁移内容
钢材疲劳数据 铝合金疲劳预测 应力-寿命关系模式
室温试验数据 高温工况预测 温度影响规律
标准试样数据 实际构件预测 几何效应建模
实验室数据 现场监测数据 载荷谱特征

1.3 域适应的必要性

当源域和目标域的数据分布存在差异时,直接迁移往往效果不佳。域适应(Domain Adaptation)技术通过:

  1. 特征对齐:消除域间分布差异
  2. 实例重加权:强调与目标域相似的源域样本
  3. 子空间映射:寻找域不变的特征表示

1.4 本主题学习目标

完成本主题学习后,您将能够:

  • 理解迁移学习的核心概念和数学原理
  • 掌握预训练模型的微调策略
  • 实现多种域适应算法
  • 将迁移学习应用于疲劳寿命预测工程问题
  • 评估迁移学习效果和诊断迁移失败原因

二、迁移学习基础理论

2.1 形式化定义

定义(迁移学习): 给定源域 D S = { X S , P ( X S ) } D_S = \{X_S, P(X_S)\} DS={XS,P(XS)} 和学习任务 T S T_S TS,目标域 D T = { X T , P ( X T ) } D_T = \{X_T, P(X_T)\} DT={XT,P(XT)} 和学习任务 T T T_T TT,迁移学习旨在利用 D S D_S DS T S T_S TS 的知识,提升目标域上的学习性能,其中 D S ≠ D T D_S \neq D_T DS=DT T S ≠ T T T_S \neq T_T TS=TT

关键要素:

  • 域(Domain) D = { X , P ( X ) } D = \{X, P(X)\} D={X,P(X)},包含特征空间和边缘概率分布
  • 任务(Task) T = { Y , P ( Y ∣ X ) } T = \{Y, P(Y|X)\} T={Y,P(YX)},包含标签空间和条件概率分布
  • 迁移知识:模型参数、特征表示、实例权重等

2.2 迁移学习分类

根据源域和目标域的差异类型,迁移学习可分为:

2.2.1 基于实例的迁移(Instance-based Transfer)

核心思想: 对源域样本进行重加权,使与目标域相似的样本获得更高权重。

数学表达:
min ⁡ θ ∑ i = 1 n S w i ⋅ L ( f ( x i S ; θ ) , y i S ) + λ R ( θ ) \min_{\theta} \sum_{i=1}^{n_S} w_i \cdot L(f(x_i^S; \theta), y_i^S) + \lambda R(\theta) θmini=1nSwiL(f(xiS;θ),yiS)+λR(θ)

其中 w i w_i wi 是第 i i i 个源域样本的权重。

适用场景:

  • 源域和目标域特征空间相同
  • 分布差异主要体现在样本分布上
  • 目标域有少量标注数据
2.2.2 基于特征的迁移(Feature-based Transfer)

核心思想: 学习域不变的特征表示,使源域和目标域在特征空间中分布一致。

数学表达:
min ⁡ ϕ L t a s k ( ϕ ( X S ) , Y S ) + λ ⋅ D ( P ( ϕ ( X S ) ) , P ( ϕ ( X T ) ) ) \min_{\phi} L_{task}(\phi(X_S), Y_S) + \lambda \cdot D(P(\phi(X_S)), P(\phi(X_T))) ϕminLtask(ϕ(XS),YS)+λD(P(ϕ(XS)),P(ϕ(XT)))

其中 ϕ \phi ϕ 是特征变换函数, D ( ⋅ , ⋅ ) D(\cdot, \cdot) D(,) 是分布距离度量。

常用方法:

  • 主成分分析(PCA)对齐
  • 最大均值差异(MMD)最小化
  • 对抗性域适应
2.2.3 基于参数的迁移(Parameter-based Transfer)

核心思想: 共享模型参数或先验分布,利用源域训练好的模型作为目标域的初始化。

数学表达:
θ T = θ S + Δ θ \theta_T = \theta_S + \Delta\theta θT=θS+Δθ

其中 θ S \theta_S θS 是源域模型参数, Δ θ \Delta\theta Δθ 是目标域的微调量。

典型应用:

  • 神经网络微调(Fine-tuning)
  • 贝叶斯迁移学习
  • 多任务学习
2.2.4 基于关系的迁移(Relational-based Transfer)

核心思想: 迁移源域中的关系知识,如相似性关系、因果关系等。

适用场景:

  • 社交网络分析
  • 知识图谱推理
  • 推荐系统

2.3 域间差异度量

量化源域和目标域的差异是迁移学习的关键:

2.3.1 最大均值差异(Maximum Mean Discrepancy, MMD)

M M D ( X S , X T ) = ∥ 1 n S ∑ i = 1 n S ϕ ( x i S ) − 1 n T ∑ j = 1 n T ϕ ( x j T ) ∥ H MMD(X_S, X_T) = \left\| \frac{1}{n_S}\sum_{i=1}^{n_S}\phi(x_i^S) - \frac{1}{n_T}\sum_{j=1}^{n_T}\phi(x_j^T) \right\|_H MMD(XS,XT)= nS1i=1nSϕ(xiS)nT1j=1nTϕ(xjT) H

其中 ϕ \phi ϕ 是核函数映射, H H H 是再生核希尔伯特空间。

性质:

  • 当且仅当 P ( X S ) = P ( X T ) P(X_S) = P(X_T) P(XS)=P(XT) 时,MMD = 0
  • 计算效率高,适用于大规模数据
  • 对核函数选择敏感
2.3.2 对抗性差异(Adversarial Divergence)

通过训练域判别器来度量域间差异:

min ⁡ G max ⁡ D V ( D , G ) = E x ∼ P S [ log ⁡ D ( x ) ] + E x ∼ P T [ log ⁡ ( 1 − D ( G ( x ) ) ) ] \min_G \max_D V(D, G) = \mathbb{E}_{x\sim P_S}[\log D(x)] + \mathbb{E}_{x\sim P_T}[\log(1-D(G(x)))] GminDmaxV(D,G)=ExPS[logD(x)]+ExPT[log(1D(G(x)))]

其中 G G G 是特征生成器, D D D 是域判别器。

2.3.3 Wasserstein距离

W ( P S , P T ) = inf ⁡ γ ∈ Γ ( P S , P T ) E ( x , y ) ∼ γ [ ∥ x − y ∥ ] W(P_S, P_T) = \inf_{\gamma \in \Gamma(P_S, P_T)} \mathbb{E}_{(x,y)\sim\gamma}[\|x-y\|] W(PS,PT)=γΓ(PS,PT)infE(x,y)γ[xy]

其中 Γ ( P S , P T ) \Gamma(P_S, P_T) Γ(PS,PT) 是所有联合分布的集合。

优势:

  • 即使分布支撑集不重叠也能提供有意义的梯度
  • 训练更稳定

2.4 迁移学习理论保证

2.4.1 泛化误差界

迁移学习的泛化误差可以分解为:

ϵ T ( h ) ≤ ϵ S ( h ) + d ( D S , D T ) + λ ∗ \epsilon_T(h) \leq \epsilon_S(h) + d(D_S, D_T) + \lambda^* ϵT(h)ϵS(h)+d(DS,DT)+λ

其中:

  • ϵ T ( h ) \epsilon_T(h) ϵT(h):目标域误差
  • ϵ S ( h ) \epsilon_S(h) ϵS(h):源域误差
  • d ( D S , D T ) d(D_S, D_T) d(DS,DT):域间差异
  • λ ∗ \lambda^* λ:理想联合假设的误差

启示:

  1. 源域性能越好,迁移效果越好
  2. 域间差异越小,迁移越容易
  3. 存在域无关的理想假设时,迁移可行
2.4.2 负迁移(Negative Transfer)

当源域知识对目标域学习产生负面影响时,发生负迁移。

负迁移的成因:

  • 源域和目标域任务相关性低
  • 域间差异过大
  • 迁移策略不当

避免策略:

  • 域相似性评估
  • 选择性迁移
  • 自适应迁移权重

三、域适应核心方法

3.1 特征对齐方法

3.1.1 标准化对齐

最简单的特征对齐方法是对源域和目标域分别进行标准化:

x ′ = x − μ σ x' = \frac{x - \mu}{\sigma} x=σxμ

实现步骤:

  1. 分别计算源域和目标域的均值和标准差
  2. 对各自域的数据进行标准化
  3. 在标准化后的空间中进行模型训练和迁移

优缺点:

  • 优点:简单高效,无需额外计算
  • 缺点:仅对齐一阶和二阶统计量,对复杂分布差异效果有限
3.1.2 子空间对齐(Subspace Alignment, SA)

核心思想: 将源域和目标域映射到各自的低维子空间,然后学习子空间之间的对齐变换。

算法步骤:

  1. PCA降维:
    X S P C A = P C A ( X S , k ) , X T P C A = P C A ( X T , k ) X_S^{PCA} = PCA(X_S, k), \quad X_T^{PCA} = PCA(X_T, k) XSPCA=PCA(XS,k),XTPCA=PCA(XT,k)

  2. 计算对齐矩阵:
    M = U T U S T M = U_T U_S^T M=UTUST
    其中 U S U_S US U T U_T UT 分别是源域和目标域的主成分。

  3. 源域对齐:
    X S a l i g n e d = X S P C A ⋅ M X_S^{aligned} = X_S^{PCA} \cdot M XSaligned=XSPCAM

数学原理:
子空间对齐最小化以下目标函数:
min ⁡ M ∥ U S M − U T ∥ F 2 \min_M \|U_S M - U_T\|_F^2 MminUSMUTF2

其闭式解为:
M ∗ = U S T U T M^* = U_S^T U_T M=USTUT

3.1.3 相关对齐(Correlation Alignment, CORAL)

CORAL方法对齐源域和目标域的二阶统计量(协方差矩阵):

min ⁡ A ∥ C S − A C T A T ∥ F 2 \min_{A} \|C_S - A C_T A^T\|_F^2 AminCSACTATF2

其中 C S C_S CS C T C_T CT 分别是源域和目标域的协方差矩阵。

闭式解:
A ∗ = C S 1 / 2 C T − 1 / 2 A^* = C_S^{1/2} C_T^{-1/2} A=CS1/2CT1/2

特点:

  • 保持特征维度不变
  • 计算效率高
  • 适用于深度特征对齐

3.2 实例重加权方法

3.2.1 核均值匹配(Kernel Mean Matching, KMM)

KMM通过最小化源域和目标域在核空间中的均值差异来确定样本权重:

min ⁡ w ∥ ∑ i = 1 n S w i ϕ ( x i S ) − 1 n T ∑ j = 1 n T ϕ ( x j T ) ∥ 2 \min_{w} \left\| \sum_{i=1}^{n_S} w_i \phi(x_i^S) - \frac{1}{n_T}\sum_{j=1}^{n_T} \phi(x_j^T) \right\|^2 wmin i=1nSwiϕ(xiS)nT1j=1nTϕ(xjT) 2

约束条件:
w i ≥ 0 , ∣ ∑ i = 1 n S w i − n S ∣ ≤ n S ϵ w_i \geq 0, \quad \left|\sum_{i=1}^{n_S} w_i - n_S\right| \leq n_S \epsilon wi0, i=1nSwinS nSϵ

3.2.2 重要性采样

根据密度比估计样本重要性权重:

w ( x ) = P T ( x ) P S ( x ) w(x) = \frac{P_T(x)}{P_S(x)} w(x)=PS(x)PT(x)

估计方法:

  • 概率密度估计(核密度估计、高斯混合模型)
  • 对数密度比估计
  • 分类器方法(训练二分类器区分源域和目标域样本)

3.3 深度域适应

3.3.1 对抗性域适应(Adversarial Domain Adaptation)

网络结构:

  • 特征提取器 G f G_f Gf:提取域不变特征
  • 标签预测器 G y G_y Gy:预测样本标签
  • 域判别器 G d G_d Gd:区分源域和目标域

损失函数:
min ⁡ G f , G y max ⁡ G d L = L y − λ L d \min_{G_f, G_y} \max_{G_d} L = L_y - \lambda L_d Gf,GyminGdmaxL=LyλLd

其中:

  • L y L_y Ly:标签预测损失(源域有标签)
  • L d L_d Ld:域判别损失
  • λ \lambda λ:权衡参数

训练策略:
采用梯度反转层(Gradient Reversal Layer, GRL)实现对抗训练:
R λ ( x ) = x , d R λ d x = − λ I R_\lambda(x) = x, \quad \frac{dR_\lambda}{dx} = -\lambda I Rλ(x)=x,dxdRλ=λI

3.3.2 域分离网络(Domain Separation Networks, DSN)

DSN将特征分解为:

  • 私有特征:域特有信息
  • 共享特征:域不变信息

损失函数:
L = L t a s k + λ L s i m i l a r i t y + γ L d i f f e r e n c e L = L_{task} + \lambda L_{similarity} + \gamma L_{difference} L=Ltask+λLsimilarity+γLdifference

其中:

  • L t a s k L_{task} Ltask:任务损失
  • L s i m i l a r i t y L_{similarity} Lsimilarity:共享特征相似性损失
  • L d i f f e r e n c e L_{difference} Ldifference:私有特征差异性损失

四、预训练模型与微调策略

4.1 神经网络迁移学习框架

4.1.1 特征提取器(Feature Extractor)

方法: 冻结预训练网络的所有层,仅使用其作为特征提取器,在其后添加新的分类/回归层。

适用场景:

  • 目标域数据量极小
  • 源域和目标任务高度相关
  • 计算资源有限
4.1.2 微调(Fine-tuning)

方法: 使用预训练权重初始化,然后在目标域上继续训练整个网络或部分层。

微调策略:

策略 描述 适用场景
全网络微调 训练所有层 目标域数据充足
分层微调 从顶层开始逐层解冻 中等数据量
分类头微调 只训练最后一层 数据量极小
渐进式微调 逐步降低学习率 防止灾难性遗忘

4.2 微调关键技术

4.2.1 学习率设置

原则: 预训练层使用较小学习率,新层使用较大学习率。

学习率衰减策略:
η t = η 0 ⋅ γ t / e p o c h s \eta_t = \eta_0 \cdot \gamma^{t/epochs} ηt=η0γt/epochs

或余弦退火:
η t = η m i n + 1 2 ( η m a x − η m i n ) ( 1 + cos ⁡ ( t T π ) ) \eta_t = \eta_{min} + \frac{1}{2}(\eta_{max} - \eta_{min})(1 + \cos(\frac{t}{T}\pi)) ηt=ηmin+21(ηmaxηmin)(1+cos(Ttπ))

4.2.2 层冻结策略

渐进式解冻(Progressive Unfreezing):

  1. 首先只训练新添加的分类层
  2. 然后逐层解冻预训练网络的层
  3. 每层使用不同的学习率

实现代码逻辑:

# 阶段1:只训练分类层
for param in base_model.parameters():
    param.requires_grad = False
classifier.requires_grad = True

# 阶段2:解冻顶层
for param in base_model.layer4.parameters():
    param.requires_grad = True

# 阶段3:解冻更多层
for param in base_model.layer3.parameters():
    param.requires_grad = True
4.2.3 正则化技术

防止过拟合:

  • Dropout:随机失活神经元
  • 权重衰减(L2正则化) L = L t a s k + λ ∥ θ ∥ 2 L = L_{task} + \lambda \|\theta\|^2 L=Ltask+λθ2
  • 早停(Early Stopping):验证集性能不再提升时停止训练

防止灾难性遗忘:

  • 弹性权重巩固(EWC):保护对源域任务重要的权重
  • 知识蒸馏(Knowledge Distillation):保持源域模型的输出行为

4.3 微调效果评估

4.3.1 评估指标

回归任务:

  • 均方误差(MSE)
  • 平均绝对误差(MAE)
  • 决定系数( R 2 R^2 R2
  • 平均绝对百分比误差(MAPE)

分类任务:

  • 准确率(Accuracy)
  • 精确率(Precision)
  • 召回率(Recall)
  • F1分数
  • AUC-ROC
4.3.2 对比实验设计

基线方法:

  1. 源模型直接应用:不微调,直接测试
  2. 从头训练:随机初始化,在目标域上训练
  3. 迁移学习:预训练模型 + 微调

评估维度:

  • 收敛速度
  • 最终性能
  • 数据效率(不同数据量下的性能)
  • 稳定性(多次运行的方差)

五、实例一:基于预训练模型的疲劳寿命预测

5.1 问题描述

工程背景:
某航空企业已积累了大量钢材的疲劳试验数据,现需要预测新型铝合金材料的疲劳寿命。由于铝合金试验数据有限(仅50组),直接建模效果不佳。

迁移学习方案:
利用钢材数据训练源域模型,迁移到铝合金疲劳寿命预测任务。

5.2 数据生成与预处理

5.2.1 Basquin方程

疲劳寿命与应力幅值的关系由Basquin方程描述:

N f = A ⋅ ( Δ σ ) − b N_f = A \cdot (\Delta\sigma)^{-b} Nf=A(Δσ)b

其中:

  • N f N_f Nf:疲劳寿命(循环次数)
  • Δ σ \Delta\sigma Δσ:应力幅值(MPa)
  • A A A:疲劳强度系数
  • b b b:疲劳强度指数

不同材料的参数:

  • 钢材: A = 1.5 × 10 12 A = 1.5 \times 10^{12} A=1.5×1012 b = 5.0 b = 5.0 b=5.0
  • 铝合金: A = 5.0 × 10 10 A = 5.0 \times 10^{10} A=5.0×1010 b = 4.0 b = 4.0 b=4.0
5.2.2 数据生成代码
def generate_fatigue_data_steel(n_samples=500, noise_level=0.1, seed=42):
    """
    生成钢材的疲劳寿命数据(源域)
    
    使用Basquin方程: Nf = A * (Δσ)^(-b)
    """
    np.random.seed(seed)
    
    # 钢材的Basquin参数
    A_steel = 1.5e12
    b_steel = 5.0
    
    # 应力幅值范围 (MPa)
    delta_sigma = np.linspace(100, 600, n_samples)
    
    # 根据Basquin方程计算疲劳寿命
    Nf_steel = A_steel * (delta_sigma ** (-b_steel))
    
    # 添加对数正态噪声
    log_Nf = np.log10(Nf_steel)
    log_noise = np.random.normal(0, noise_level, n_samples)
    Nf_noisy = 10 ** (log_Nf + log_noise)
    
    # 添加其他特征
    stress_ratio = np.random.uniform(-1, 0.5, n_samples)
    temperature = np.random.uniform(20, 100, n_samples)
    roughness = np.random.uniform(0.5, 3.0, n_samples)
    
    X_steel = np.column_stack([delta_sigma, stress_ratio, temperature, roughness])
    y_steel = np.log10(Nf_noisy)
    
    return X_steel, y_steel

def generate_fatigue_data_aluminum(n_samples=50, noise_level=0.15, seed=123):
    """
    生成铝合金的疲劳寿命数据(目标域)
    """
    np.random.seed(seed)
    
    # 铝合金的Basquin参数
    A_al = 5.0e10
    b_al = 4.0
    
    delta_sigma = np.linspace(80, 400, n_samples)
    Nf_al = A_al * (delta_sigma ** (-b_al))
    
    log_Nf = np.log10(Nf_al)
    log_noise = np.random.normal(0, noise_level, n_samples)
    Nf_noisy = 10 ** (log_Nf + log_noise)
    
    stress_ratio = np.random.uniform(-1, 0.5, n_samples)
    temperature = np.random.uniform(20, 150, n_samples)
    roughness = np.random.uniform(0.3, 2.5, n_samples)
    
    X_al = np.column_stack([delta_sigma, stress_ratio, temperature, roughness])
    y_al = np.log10(Nf_noisy)
    
    return X_al, y_al

5.3 源域模型训练

5.3.1 神经网络架构

采用多层感知机(MLP)作为基础模型:

  • 输入层:4个神经元(应力幅值、应力比、温度、表面粗糙度)
  • 隐藏层1:64个神经元,ReLU激活
  • 隐藏层2:32个神经元,ReLU激活
  • 隐藏层3:16个神经元,ReLU激活
  • 输出层:1个神经元(对数疲劳寿命)
5.3.2 训练代码
def train_source_model(X_source, y_source):
    """在源域上训练模型"""
    print("训练源域模型...")
    
    # 标准化特征
    scaler = StandardScaler()
    X_scaled = scaler.fit_transform(X_source)
    
    # 训练神经网络
    model = MLPRegressor(
        hidden_layer_sizes=(64, 32, 16),
        activation='relu',
        solver='adam',
        alpha=0.001,
        learning_rate_init=0.001,
        max_iter=1000,
        random_state=42,
        early_stopping=True,
        validation_fraction=0.1,
        n_iter_no_change=20
    )
    
    model.fit(X_scaled, y_source)
    print(f"源模型训练完成,迭代次数: {model.n_iter_}")
    
    return model, scaler

5.4 迁移学习实现

5.4.1 简单微调
def fine_tune_model(source_model, source_scaler, X_target, y_target, 
                   learning_rate=0.0001, max_iter=500):
    """
    微调预训练模型
    
    使用源模型的权重初始化,在目标域上继续训练
    """
    print("微调模型...")
    
    # 标准化目标域数据
    X_target_scaled = source_scaler.transform(X_target)
    
    # 创建新模型
    target_model = MLPRegressor(
        hidden_layer_sizes=(64, 32, 16),
        activation='relu',
        solver='adam',
        alpha=0.001,
        learning_rate_init=learning_rate,
        max_iter=max_iter,
        random_state=42,
        warm_start=False
    )
    
    # 先拟合初始化
    target_model.fit(X_target_scaled, y_target)
    
    # 复制源模型权重
    target_model.coefs_ = [c.copy() for c in source_model.coefs_]
    target_model.intercepts_ = [i.copy() for i in source_model.intercepts_]
    
    # 使用warm_start继续训练
    target_model.warm_start = True
    target_model.fit(X_target_scaled, y_target)
    
    return target_model
5.4.2 渐进式微调
def progressive_fine_tune(source_model, source_scaler, X_target, y_target):
    """
    渐进式微调策略
    
    使用学习率递减策略模拟渐进式微调
    """
    print("\n=== 渐进式微调 ===")
    
    X_target_scaled = source_scaler.transform(X_target)
    
    # 阶段1:高学习率
    print("阶段1:快速适应...")
    model_stage1 = MLPRegressor(
        hidden_layer_sizes=(64, 32, 16),
        activation='relu',
        solver='adam',
        learning_rate_init=0.001,
        max_iter=200,
        random_state=42,
        warm_start=False
    )
    model_stage1.fit(X_target_scaled, y_target)
    model_stage1.coefs_ = [c.copy() for c in source_model.coefs_]
    model_stage1.intercepts_ = [i.copy() for i in source_model.intercepts_]
    model_stage1.warm_start = True
    model_stage1.fit(X_target_scaled, y_target)
    
    # 阶段2:中等学习率
    print("阶段2:精细调整...")
    model_stage2 = MLPRegressor(
        hidden_layer_sizes=(64, 32, 16),
        activation='relu',
        solver='adam',
        learning_rate_init=0.0003,
        max_iter=200,
        random_state=42,
        warm_start=False
    )
    model_stage2.fit(X_target_scaled, y_target)
    model_stage2.coefs_ = [c.copy() for c in model_stage1.coefs_]
    model_stage2.intercepts_ = [i.copy() for i in model_stage1.intercepts_]
    model_stage2.warm_start = True
    model_stage2.fit(X_target_scaled, y_target)
    
    # 阶段3:低学习率微调
    print("阶段3:最终优化...")
    model_stage3 = MLPRegressor(
        hidden_layer_sizes=(64, 32, 16),
        activation='relu',
        solver='adam',
        learning_rate_init=0.0001,
        max_iter=200,
        random_state=42,
        warm_start=False
    )
    model_stage3.fit(X_target_scaled, y_target)
    model_stage3.coefs_ = [c.copy() for c in model_stage2.coefs_]
    model_stage3.intercepts_ = [i.copy() for i in model_stage2.intercepts_]
    model_stage3.warm_start = True
    model_stage3.fit(X_target_scaled, y_target)
    
    return model_stage3

5.5 实验结果分析

5.5.1 性能对比
方法 MAE (log) RMSE (log) MAPE
源模型(未微调) 0.8622 0.9005 83.86% -0.3604
从头训练 0.1870 0.2448 37.71% 0.8995
迁移学习-简单微调 0.8478 0.8873 83.28% -0.3206
迁移学习-渐进式 0.2253 0.2694 61.59% 0.8783
5.5.2 结果解读

关键发现:

  1. 源模型直接应用效果差:MAPE高达83.86%,说明钢材和铝合金的疲劳特性差异显著,直接迁移不可行。

  2. 从头训练表现最好:在小样本条件下(30个训练样本),从头训练反而获得了最佳性能(MAPE 37.71%)。这表明:

    • 神经网络具有较强的拟合能力
    • 对于简单映射关系,小样本也可能足够
    • 源域和目标域的差异可能过大
  3. 渐进式微调优于简单微调:渐进式微调通过分阶段调整学习率,获得了比简单微调更好的效果。

  4. 负迁移现象:简单微调的性能甚至略差于源模型直接应用,说明不当的迁移策略可能导致负迁移。

5.5.3 可视化结果

生成的可视化图表包括:

  1. 源域与目标域数据分布:展示钢材和铝合金的S-N曲线差异
  2. 模型预测对比:不同方法的预测曲线与真实值对比
  3. 性能指标对比:MAPE和R²的柱状图对比
  4. 训练损失曲线:展示各模型的收敛过程
  5. 预测误差分析:残差图分析预测误差分布

六、实例二:域适应在跨材料疲劳预测中的应用

6.1 问题描述

多域迁移场景:
在实际工程中,可能需要同时处理多个目标域的疲劳预测问题:

  • 目标域1:铝合金,室温(材料不同)
  • 目标域2:钢材,高温(工况不同)
  • 目标域3:铝合金,高温(材料和工况都不同)

挑战:
不同域之间的数据分布差异程度不同,需要采用不同的迁移策略。

6.2 域适应方法实现

6.2.1 域适应类设计
class DomainAdaptation:
    """
    域适应类
    
    实现多种域适应策略:
    1. 特征对齐(Feature Alignment)
    2. 实例重加权(Instance Reweighting)
    3. 子空间对齐(Subspace Alignment)
    """
    
    def __init__(self, method='feature_alignment'):
        self.method = method
        self.scaler = StandardScaler()
        self.transform_matrix = None
    
    def feature_alignment(self, X_source, X_target):
        """
        特征对齐:通过标准化使源域和目标域特征分布对齐
        """
        X_source_aligned = self.scaler.fit_transform(X_source)
        X_target_aligned = self.scaler.transform(X_target)
        return X_source_aligned, X_target_aligned
    
    def subspace_alignment(self, X_source, X_target, n_components=2):
        """
        子空间对齐:将源域和目标域映射到同一子空间
        """
        # 对源域和目标域分别进行PCA
        pca_source = PCA(n_components=n_components)
        pca_target = PCA(n_components=n_components)
        
        X_source_pca = pca_source.fit_transform(X_source)
        X_target_pca = pca_target.fit_transform(X_target)
        
        # 计算源域和目标域主成分之间的变换矩阵
        min_comp = min(pca_source.components_.shape[0], 
                      pca_target.components_.shape[0])
        source_components = pca_source.components_[:min_comp, :].T
        target_components = pca_target.components_[:min_comp, :].T
        
        # 对齐变换
        self.transform_matrix = np.linalg.pinv(target_components) @ source_components
        
        # 将源域映射到目标域子空间
        X_source_aligned = X_source_pca[:, :min_comp] @ \
                          self.transform_matrix[:min_comp, :min_comp]
        
        return X_source_aligned, X_target_pca[:, :min_comp], \
               pca_source, pca_target
6.2.2 特征对齐 + 迁移学习
def transfer_with_feature_alignment(X_source, y_source, X_target_train, y_target_train):
    """迁移学习 + 特征对齐"""
    # 特征对齐
    da = DomainAdaptation(method='feature_alignment')
    X_source_aligned, X_target_aligned = da.feature_alignment(X_source, X_target_train)
    
    # 在对齐后的源域上训练
    source_model = MLPRegressor(
        hidden_layer_sizes=(64, 32, 16),
        activation='relu',
        solver='adam',
        alpha=0.001,
        learning_rate_init=0.001,
        max_iter=1000,
        random_state=42
    )
    source_model.fit(X_source_aligned, y_source)
    
    # 在目标域上微调
    target_model = MLPRegressor(
        hidden_layer_sizes=(64, 32, 16),
        activation='relu',
        solver='adam',
        alpha=0.001,
        learning_rate_init=0.0001,
        max_iter=300,
        random_state=42,
        warm_start=False
    )
    target_model.fit(X_target_aligned, y_target_train)
    target_model.coefs_ = [c.copy() for c in source_model.coefs_]
    target_model.intercepts_ = [i.copy() for i in source_model.intercepts_]
    target_model.warm_start = True
    target_model.fit(X_target_aligned, y_target_train)
    
    return target_model, da.scaler
6.2.3 子空间对齐 + 迁移学习
def transfer_with_subspace_alignment(X_source, y_source, X_target_train, y_target_train):
    """迁移学习 + 子空间对齐"""
    # 子空间对齐
    da = DomainAdaptation(method='subspace_alignment')
    X_source_sub, X_target_sub, pca_s, pca_t = da.subspace_alignment(
        X_source, X_target_train, n_components=3
    )
    
    # 在源域子空间上训练
    source_model = MLPRegressor(
        hidden_layer_sizes=(32, 16),
        activation='relu',
        solver='adam',
        alpha=0.001,
        learning_rate_init=0.001,
        max_iter=1000,
        random_state=42
    )
    source_model.fit(X_source_sub, y_source)
    
    # 在目标域子空间上微调
    target_model = MLPRegressor(
        hidden_layer_sizes=(32, 16),
        activation='relu',
        solver='adam',
        alpha=0.001,
        learning_rate_init=0.0001,
        max_iter=300,
        random_state=42,
        warm_start=False
    )
    target_model.fit(X_target_sub, y_target_train)
    target_model.coefs_ = [c.copy() for c in source_model.coefs_]
    target_model.intercepts_ = [i.copy() for i in source_model.intercepts_]
    target_model.warm_start = True
    target_model.fit(X_target_sub, y_target_train)
    
    return target_model, da, pca_s, pca_t

6.3 多域实验结果

6.3.1 目标域1:铝合金-室温
方法 MAPE
源模型直接应用 86.88% -0.0771
从头训练 46.60% 0.8821
特征对齐 + 迁移 81.21% 0.2078
子空间对齐 + 迁移 12252.09% -2.1072

分析:

  • 铝合金与钢材的Basquin参数差异显著
  • 特征对齐有一定效果,但不如从头训练
  • 子空间对齐在此场景下失效
6.3.2 目标域2:钢材-高温
方法 MAPE
源模型直接应用 1107858.96% -18.6749
从头训练 206.28% 0.6809
特征对齐 + 迁移 525559.66% -15.5741
子空间对齐 + 迁移 极大误差 -932.2272

分析:

  • 高温对钢材疲劳性能影响巨大
  • 源模型直接应用完全失效
  • 域适应方法在此极端域偏移下效果有限
6.3.3 目标域3:铝合金-高温
方法 MAPE
源模型直接应用 131721.43% -21.8892
从头训练 57.73% 0.7451
特征对齐 + 迁移 63.21% 0.0639
子空间对齐 + 迁移 极大误差 -188.6245

分析:

  • 材料和工况同时变化,域偏移最大
  • 特征对齐在此场景下表现相对最好
  • 说明简单的标准化对齐对复杂域偏移有一定鲁棒性

6.4 实验结论

关键发现:

  1. 域偏移程度影响迁移效果

    • 铝合金-室温(材料变化):域偏移中等
    • 钢材-高温(工况变化):域偏移大
    • 铝合金-高温(双重变化):域偏移最大
  2. 从头训练在小样本下表现稳健

    • 在所有目标域都获得了可接受的性能
    • 说明神经网络在小样本回归任务中的强大能力
  3. 域适应方法的选择性

    • 特征对齐在多数场景下有效
    • 子空间对齐对参数敏感,容易失效
    • 需要根据域偏移类型选择合适的方法
  4. 负迁移风险

    • 不当的域适应可能导致性能严重下降
    • 需要建立域相似性评估机制

七、高级迁移学习技术

7.1 多任务学习(Multi-task Learning)

核心思想: 同时学习多个相关任务,共享表示层。

网络结构:

  • 共享层:提取通用特征
  • 任务特定层:处理各任务的特有信息

损失函数:
L = ∑ t = 1 T λ t L t L = \sum_{t=1}^{T} \lambda_t L_t L=t=1TλtLt

其中 L t L_t Lt 是第 t t t 个任务的损失, λ t \lambda_t λt 是任务权重。

在疲劳预测中的应用:

  • 同时预测不同材料的疲劳寿命
  • 联合预测疲劳寿命和裂纹扩展速率
  • 多工况联合建模

7.2 元学习(Meta-Learning)

核心思想: “学习如何学习”,使模型能够快速适应新任务。

7.2.1 MAML(Model-Agnostic Meta-Learning)

算法流程:

  1. 内循环(Inner Loop):在支持集上更新任务特定参数
    θ i ′ = θ − α ∇ θ L t a s k i ( f θ ) \theta'_i = \theta - \alpha \nabla_\theta L_{task_i}(f_\theta) θi=θαθLtaski(fθ)

  2. 外循环(Outer Loop):在查询集上更新元参数
    θ = θ − β ∇ θ ∑ i L t a s k i ( f θ i ′ ) \theta = \theta - \beta \nabla_\theta \sum_{i} L_{task_i}(f_{\theta'_i}) θ=θβθiLtaski(fθi)

优势:

  • 少量梯度步即可适应新任务
  • 模型无关,适用于任何基于梯度的模型
7.2.2 原型网络(Prototypical Networks)

核心思想: 学习一个度量空间,使同类样本距离近,异类样本距离远。

原型计算:
c k = 1 ∣ S k ∣ ∑ ( x i , y i ) ∈ S k f ϕ ( x i ) c_k = \frac{1}{|S_k|} \sum_{(x_i, y_i) \in S_k} f_\phi(x_i) ck=Sk1(xi,yi)Skfϕ(xi)

分类:
P ( y = k ∣ x ) = exp ⁡ ( − d ( f ϕ ( x ) , c k ) ) ∑ k ′ exp ⁡ ( − d ( f ϕ ( x ) , c k ′ ) ) P(y=k|x) = \frac{\exp(-d(f_\phi(x), c_k))}{\sum_{k'} \exp(-d(f_\phi(x), c_{k'}))} P(y=kx)=kexp(d(fϕ(x),ck))exp(d(fϕ(x),ck))

7.3 生成式迁移学习

7.3.1 域转换网络

使用生成对抗网络(GAN)将源域样本转换为目标域风格:

G : X S → X T G: X_S \rightarrow X_T G:XSXT

应用场景:

  • 合成目标域训练数据
  • 数据增强
  • 跨域图像转换
7.3.2 数据增强策略

Mixup:
x ~ = λ x i + ( 1 − λ ) x j \tilde{x} = \lambda x_i + (1-\lambda) x_j x~=λxi+(1λ)xj
y ~ = λ y i + ( 1 − λ ) y j \tilde{y} = \lambda y_i + (1-\lambda) y_j y~=λyi+(1λ)yj

CutMix: 混合不同样本的图像块

在疲劳预测中的应用:

  • 混合不同材料的疲劳数据
  • 生成中间状态的训练样本
  • 提高模型泛化能力

7.4 贝叶斯迁移学习

7.4.1 层次贝叶斯模型

模型结构:

  • 超先验:跨任务共享
  • 任务特定参数:各任务独立

θ t ∼ P ( θ ∣ ϕ ) , ϕ ∼ P ( ϕ ) \theta_t \sim P(\theta|\phi), \quad \phi \sim P(\phi) θtP(θϕ),ϕP(ϕ)

推理:
使用变分推断或MCMC估计后验分布。

7.4.2 神经过程(Neural Processes)

结合神经网络和随机过程的优势:

  • 学习从上下文数据到预测分布的映射
  • 提供不确定性估计
  • 适合小样本学习

八、工程应用案例

8.1 航空发动机叶片疲劳预测

背景:

  • 叶片材料从传统镍基合金更换为新型单晶合金
  • 新型材料试验数据有限
  • 需要利用传统材料数据辅助预测

迁移方案:

  1. 在传统镍基合金数据上训练源模型
  2. 使用特征对齐消除材料差异
  3. 在新型合金数据上微调
  4. 结合物理约束提高可靠性

效果:

  • 预测精度提升30%
  • 试验成本降低50%

8.2 汽车底盘跨平台迁移

背景:

  • 轿车平台积累大量疲劳数据
  • SUV平台数据较少
  • 两平台载荷特性不同

域适应策略:

  1. 载荷谱归一化
  2. 特征空间对齐
  3. 渐进式微调

8.3 风电叶片环境适应性预测

背景:

  • 实验室数据与现场数据存在差异
  • 不同风电场环境条件不同
  • 需要快速适应新环境

解决方案:

  • 在线域适应
  • 增量学习
  • 不确定性量化

附录:代码完整实现

附录A:实例一完整代码

"""
主题079:迁移学习与域适应
实例一:基于预训练模型的疲劳寿命预测

本实例演示如何使用迁移学习将源域(钢材)训练好的模型迁移到目标域(铝合金),
解决目标域数据稀缺的问题。
"""

import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib import rcParams
import os
from sklearn.neural_network import MLPRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import r2_score, mean_absolute_error, mean_squared_error

# 设置中文字体
rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
rcParams['axes.unicode_minus'] = False

# 创建输出目录
output_dir = os.path.dirname(os.path.abspath(__file__))

# ==================== 1. 数据生成 ====================
def generate_fatigue_data_steel(n_samples=500, noise_level=0.1, seed=42):
    """
    生成钢材的疲劳寿命数据(源域)
    
    使用Basquin方程: Nf = A * (Δσ)^(-b)
    其中A和b是材料常数
    """
    np.random.seed(seed)
    
    # 钢材的Basquin参数
    A_steel = 1.5e12  # 疲劳强度系数
    b_steel = 5.0     # 疲劳强度指数
    
    # 应力幅值范围 (MPa)
    delta_sigma = np.linspace(100, 600, n_samples)
    
    # 根据Basquin方程计算疲劳寿命
    Nf_steel = A_steel * (delta_sigma ** (-b_steel))
    
    # 添加对数正态噪声(更符合疲劳数据特性)
    log_Nf = np.log10(Nf_steel)
    log_noise = np.random.normal(0, noise_level, n_samples)
    Nf_noisy = 10 ** (log_Nf + log_noise)
    
    # 添加其他特征:应力比、温度、表面粗糙度
    stress_ratio = np.random.uniform(-1, 0.5, n_samples)  # 应力比 R
    temperature = np.random.uniform(20, 100, n_samples)   # 温度 (°C)
    roughness = np.random.uniform(0.5, 3.0, n_samples)    # 表面粗糙度 Ra
    
    # 特征矩阵
    X_steel = np.column_stack([delta_sigma, stress_ratio, temperature, roughness])
    y_steel = np.log10(Nf_noisy)  # 预测对数寿命
    
    return X_steel, y_steel, A_steel, b_steel

def generate_fatigue_data_aluminum(n_samples=50, noise_level=0.15, seed=123):
    """
    生成铝合金的疲劳寿命数据(目标域)
    
    数据量较少,模拟实际工程中目标域数据稀缺的情况
    """
    np.random.seed(seed)
    
    # 铝合金的Basquin参数(与钢材不同)
    A_al = 5.0e10     # 疲劳强度系数
    b_al = 4.0        # 疲劳强度指数
    
    # 应力幅值范围 (MPa)
    delta_sigma = np.linspace(80, 400, n_samples)
    
    # 根据Basquin方程计算疲劳寿命
    Nf_al = A_al * (delta_sigma ** (-b_al))
    
    # 添加对数正态噪声
    log_Nf = np.log10(Nf_al)
    log_noise = np.random.normal(0, noise_level, n_samples)
    Nf_noisy = 10 ** (log_Nf + log_noise)
    
    # 添加其他特征
    stress_ratio = np.random.uniform(-1, 0.5, n_samples)
    temperature = np.random.uniform(20, 150, n_samples)
    roughness = np.random.uniform(0.3, 2.5, n_samples)
    
    X_al = np.column_stack([delta_sigma, stress_ratio, temperature, roughness])
    y_al = np.log10(Nf_noisy)  # 预测对数寿命
    
    return X_al, y_al, A_al, b_al

# ==================== 2. 迁移学习实现 ====================
def train_source_model(X_source, y_source):
    """在源域上训练模型"""
    print("训练源域模型...")
    
    # 标准化特征
    scaler = StandardScaler()
    X_scaled = scaler.fit_transform(X_source)
    
    # 训练神经网络
    model = MLPRegressor(
        hidden_layer_sizes=(64, 32, 16),
        activation='relu',
        solver='adam',
        alpha=0.001,
        learning_rate_init=0.001,
        max_iter=1000,
        random_state=42,
        early_stopping=True,
        validation_fraction=0.1,
        n_iter_no_change=20
    )
    
    model.fit(X_scaled, y_source)
    print(f"源模型训练完成,迭代次数: {model.n_iter_}")
    
    return model, scaler

def fine_tune_model(source_model, source_scaler, X_target, y_target, 
                   learning_rate=0.0001, max_iter=500):
    """
    微调预训练模型
    
    使用源模型的权重初始化,在目标域上继续训练
    """
    print("微调模型...")
    
    # 标准化目标域数据(使用源域的scaler)
    X_target_scaled = source_scaler.transform(X_target)
    
    # 创建新模型,使用源模型的参数初始化
    target_model = MLPRegressor(
        hidden_layer_sizes=(64, 32, 16),
        activation='relu',
        solver='adam',
        alpha=0.001,
        learning_rate_init=learning_rate,
        max_iter=max_iter,
        random_state=42,
        warm_start=False  # 不使用warm_start,而是手动复制权重后训练
    )
    
    # 先拟合一次以初始化所有内部属性
    target_model.fit(X_target_scaled, y_target)
    
    # 然后用源模型的权重覆盖
    target_model.coefs_ = [c.copy() for c in source_model.coefs_]
    target_model.intercepts_ = [i.copy() for i in source_model.intercepts_]
    
    # 使用warm_start继续训练
    target_model.warm_start = True
    target_model.max_iter = max_iter
    target_model.learning_rate_init = learning_rate
    target_model.fit(X_target_scaled, y_target)
    
    return target_model

def progressive_fine_tune(source_model, source_scaler, X_target, y_target):
    """
    渐进式微调策略
    
    使用学习率递减策略模拟渐进式微调
    """
    print("\n=== 渐进式微调 ===")
    
    X_target_scaled = source_scaler.transform(X_target)
    
    # 阶段1:高学习率,快速适应
    print("阶段1:快速适应...")
    model_stage1 = MLPRegressor(
        hidden_layer_sizes=(64, 32, 16),
        activation='relu',
        solver='adam',
        alpha=0.001,
        learning_rate_init=0.001,
        max_iter=200,
        random_state=42,
        warm_start=False
    )
    # 先拟合初始化
    model_stage1.fit(X_target_scaled, y_target)
    # 复制源模型权重
    model_stage1.coefs_ = [c.copy() for c in source_model.coefs_]
    model_stage1.intercepts_ = [i.copy() for i in source_model.intercepts_]
    # 继续训练
    model_stage1.warm_start = True
    model_stage1.fit(X_target_scaled, y_target)
    
    # 阶段2:中等学习率
    print("阶段2:精细调整...")
    model_stage2 = MLPRegressor(
        hidden_layer_sizes=(64, 32, 16),
        activation='relu',
        solver='adam',
        alpha=0.001,
        learning_rate_init=0.0003,
        max_iter=200,
        random_state=42,
        warm_start=False
    )
    model_stage2.fit(X_target_scaled, y_target)
    model_stage2.coefs_ = [c.copy() for c in model_stage1.coefs_]
    model_stage2.intercepts_ = [i.copy() for i in model_stage1.intercepts_]
    model_stage2.warm_start = True
    model_stage2.fit(X_target_scaled, y_target)
    
    # 阶段3:低学习率微调
    print("阶段3:最终优化...")
    model_stage3 = MLPRegressor(
        hidden_layer_sizes=(64, 32, 16),
        activation='relu',
        solver='adam',
        alpha=0.001,
        learning_rate_init=0.0001,
        max_iter=200,
        random_state=42,
        warm_start=False
    )
    model_stage3.fit(X_target_scaled, y_target)
    model_stage3.coefs_ = [c.copy() for c in model_stage2.coefs_]
    model_stage3.intercepts_ = [i.copy() for i in model_stage2.intercepts_]
    model_stage3.warm_start = True
    model_stage3.fit(X_target_scaled, y_target)
    
    return model_stage3

# ==================== 3. 对比实验 ====================
def run_comparison_experiment():
    """运行对比实验"""
    
    print("=" * 60)
    print("迁移学习在疲劳寿命预测中的应用")
    print("=" * 60)
    
    # 生成数据
    print("\n[1] 生成数据...")
    X_steel, y_steel, A_steel, b_steel = generate_fatigue_data_steel(n_samples=500)
    X_al, y_al, A_al, b_al = generate_fatigue_data_aluminum(n_samples=50)
    
    print(f"源域(钢材)数据: {X_steel.shape[0]} 样本")
    print(f"目标域(铝合金)数据: {X_al.shape[0]} 样本")
    
    # 划分目标域的训练集和测试集
    np.random.seed(42)
    indices = np.random.permutation(len(X_al))
    train_size = 30
    train_idx = indices[:train_size]
    test_idx = indices[train_size:]
    
    X_al_train, y_al_train = X_al[train_idx], y_al[train_idx]
    X_al_test, y_al_test = X_al[test_idx], y_al[test_idx]
    
    print(f"目标域训练集: {len(X_al_train)} 样本")
    print(f"目标域测试集: {len(X_al_test)} 样本")
    
    # 方法1:在源域上训练模型
    print("\n[2] 在源域(钢材)上训练模型...")
    source_model, source_scaler = train_source_model(X_steel, y_steel)
    
    # 方法2:直接在目标域上训练(小数据量)
    print("\n[3] 直接在目标域上训练(小数据量)...")
    target_scaler = StandardScaler()
    X_al_train_scaled = target_scaler.fit_transform(X_al_train)
    X_al_test_scaled = target_scaler.transform(X_al_test)
    
    scratch_model = MLPRegressor(
        hidden_layer_sizes=(64, 32, 16),
        activation='relu',
        solver='adam',
        alpha=0.001,
        learning_rate_init=0.001,
        max_iter=1000,
        random_state=42
    )
    scratch_model.fit(X_al_train_scaled, y_al_train)
    print(f"从头训练模型完成,迭代次数: {scratch_model.n_iter_}")
    
    # 方法3:迁移学习 - 简单微调
    print("\n[4] 迁移学习 - 简单微调...")
    transfer_model_simple = fine_tune_model(
        source_model, source_scaler, 
        X_al_train, y_al_train,
        learning_rate=0.0001, max_iter=500
    )
    
    # 方法4:迁移学习 - 渐进式微调
    print("\n[5] 迁移学习 - 渐进式微调...")
    transfer_model_progressive = progressive_fine_tune(
        source_model, source_scaler,
        X_al_train, y_al_train
    )
    
    # ==================== 4. 评估与可视化 ====================
    print("\n[6] 评估模型性能...")
    
    def evaluate_model(model, X_test, y_test, scaler, model_name):
        """评估模型性能"""
        X_test_scaled = scaler.transform(X_test)
        y_pred_log = model.predict(X_test_scaled)
        y_true_log = y_test
        
        # 计算指标(在对数空间)
        mae = mean_absolute_error(y_true_log, y_pred_log)
        rmse = np.sqrt(mean_squared_error(y_true_log, y_pred_log))
        r2 = r2_score(y_true_log, y_pred_log)
        
        # MAPE(在原始空间)
        y_pred = 10 ** y_pred_log
        y_true = 10 ** y_true_log
        mape = np.mean(np.abs((y_pred - y_true) / y_true)) * 100
        
        print(f"\n{model_name}:")
        print(f"  MAE (log): {mae:.4f}")
        print(f"  RMSE (log): {rmse:.4f}")
        print(f"  MAPE: {mape:.2f}%")
        print(f"  R²: {r2:.4f}")
        
        return y_pred, mae, rmse, mape, r2
    
    # 评估所有模型
    y_pred_source, mae_source, rmse_source, mape_source, r2_source = \
        evaluate_model(source_model, X_al_test, y_al_test, source_scaler, 
                      "源模型(未微调)")
    
    y_pred_scratch, mae_scratch, rmse_scratch, mape_scratch, r2_scratch = \
        evaluate_model(scratch_model, X_al_test, y_al_test, target_scaler,
                      "从头训练")
    
    y_pred_transfer_simple, mae_transfer_simple, rmse_transfer_simple, mape_transfer_simple, r2_transfer_simple = \
        evaluate_model(transfer_model_simple, X_al_test, y_al_test, source_scaler,
                      "迁移学习-简单微调")
    
    y_pred_transfer_prog, mae_transfer_prog, rmse_transfer_prog, mape_transfer_prog, r2_transfer_prog = \
        evaluate_model(transfer_model_progressive, X_al_test, y_al_test, source_scaler,
                      "迁移学习-渐进式解冻")
    
    # 可视化
    print("\n[7] 生成可视化结果...")
    
    # 图1:Basquin曲线对比
    fig, axes = plt.subplots(2, 2, figsize=(14, 12))
    
    # 子图1:源域和目标域数据分布
    ax = axes[0, 0]
    ax.scatter(X_steel[:, 0], 10**y_steel, alpha=0.3, c='blue', label='钢材(源域)', s=20)
    ax.scatter(X_al[:, 0], 10**y_al, alpha=0.7, c='red', label='铝合金(目标域)', s=40)
    ax.set_xlabel('应力幅值 Δσ (MPa)', fontsize=11)
    ax.set_ylabel('疲劳寿命 Nf (cycles)', fontsize=11)
    ax.set_yscale('log')
    ax.set_title('源域与目标域数据分布', fontsize=12, fontweight='bold')
    ax.legend()
    ax.grid(True, alpha=0.3)
    
    # 子图2:各模型预测对比
    ax = axes[0, 1]
    x_test = X_al_test[:, 0]
    sort_idx = np.argsort(x_test)
    
    ax.scatter(x_test, 10**y_al_test, c='black', marker='o', 
               s=100, label='真实值', zorder=5)
    ax.plot(x_test[sort_idx], y_pred_source[sort_idx], 'b--', 
            linewidth=2, label='源模型', alpha=0.7)
    ax.plot(x_test[sort_idx], y_pred_scratch[sort_idx], 'g-.', 
            linewidth=2, label='从头训练', alpha=0.7)
    ax.plot(x_test[sort_idx], y_pred_transfer_simple[sort_idx], 'm:', 
            linewidth=2, label='迁移学习-简单微调', alpha=0.7)
    ax.plot(x_test[sort_idx], y_pred_transfer_prog[sort_idx], 'r-', 
            linewidth=2, label='迁移学习-渐进式', alpha=0.7)
    
    ax.set_xlabel('应力幅值 Δσ (MPa)', fontsize=11)
    ax.set_ylabel('疲劳寿命 Nf (cycles)', fontsize=11)
    ax.set_yscale('log')
    ax.set_title('模型预测对比(测试集)', fontsize=12, fontweight='bold')
    ax.legend(loc='upper right')
    ax.grid(True, alpha=0.3)
    
    # 子图3:性能指标对比
    ax = axes[1, 0]
    models = ['源模型\n(未微调)', '从头\n训练', '迁移学习\n(简单)', '迁移学习\n(渐进)']
    r2_scores = [r2_source, r2_scratch, r2_transfer_simple, r2_transfer_prog]
    mapes = [mape_source, mape_scratch, mape_transfer_simple, mape_transfer_prog]
    
    x_pos = np.arange(len(models))
    width = 0.35
    
    bars1 = ax.bar(x_pos - width/2, r2_scores, width, label='R² 分数', color='steelblue')
    ax2 = ax.twinx()
    bars2 = ax2.bar(x_pos + width/2, mapes, width, label='MAPE (%)', color='coral')
    
    ax.set_ylabel('R² 分数', fontsize=11, color='steelblue')
    ax2.set_ylabel('MAPE (%)', fontsize=11, color='coral')
    ax.set_xticks(x_pos)
    ax.set_xticklabels(models, fontsize=9)
    ax.set_title('模型性能对比', fontsize=12, fontweight='bold')
    ax.set_ylim([-1, 1.2])
    ax2.set_ylim([0, max(mapes) * 1.2])
    
    # 添加数值标签
    for bar, val in zip(bars1, r2_scores):
        ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.02,
                f'{val:.3f}', ha='center', va='bottom', fontsize=9)
    for bar, val in zip(bars2, mapes):
        ax2.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1,
                 f'{val:.1f}%', ha='center', va='bottom', fontsize=9)
    
    ax.legend(loc='upper left')
    ax2.legend(loc='upper right')
    
    # 子图4:训练损失曲线对比
    ax = axes[1, 1]
    ax.plot(source_model.loss_curve_, 'b-', linewidth=2, label='源模型训练', alpha=0.7)
    ax.plot(scratch_model.loss_curve_, 'g-', linewidth=2, label='从头训练', alpha=0.7)
    ax.plot(transfer_model_simple.loss_curve_, 'm-', linewidth=2, 
            label='迁移学习-简单', alpha=0.7)
    ax.plot(transfer_model_progressive.loss_curve_, 'r-', linewidth=2, 
            label='迁移学习-渐进', alpha=0.7)
    
    ax.set_xlabel('迭代次数', fontsize=11)
    ax.set_ylabel('损失值', fontsize=11)
    ax.set_title('训练损失曲线', fontsize=12, fontweight='bold')
    ax.legend()
    ax.grid(True, alpha=0.3)
    ax.set_yscale('log')
    
    plt.tight_layout()
    plt.savefig(f'{output_dir}/transfer_01_疲劳寿命预测对比.png', dpi=150, bbox_inches='tight')
    plt.close()
    
    # 图2:预测误差分析
    fig, axes = plt.subplots(2, 2, figsize=(14, 12))
    
    models_data = [
        ('源模型(未微调)', y_pred_source, 'blue'),
        ('从头训练', y_pred_scratch, 'green'),
        ('迁移学习-简单微调', y_pred_transfer_simple, 'magenta'),
        ('迁移学习-渐进式', y_pred_transfer_prog, 'red')
    ]
    
    for idx, (name, y_pred, color) in enumerate(models_data):
        ax = axes[idx // 2, idx % 2]
        
        # 残差图
        y_true = 10 ** y_al_test
        residuals = y_true - y_pred
        ax.scatter(y_pred, residuals, c=color, alpha=0.6, s=60)
        ax.axhline(y=0, color='k', linestyle='--', linewidth=1)
        ax.set_xlabel('预测值 (cycles)', fontsize=11)
        ax.set_ylabel('残差 (cycles)', fontsize=11)
        ax.set_title(f'{name} - 残差图', fontsize=12, fontweight='bold')
        ax.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.savefig(f'{output_dir}/transfer_02_预测误差分析.png', dpi=150, bbox_inches='tight')
    plt.close()
    
    print("\n可视化结果已保存!")
    print(f"  - {output_dir}/transfer_01_疲劳寿命预测对比.png")
    print(f"  - {output_dir}/transfer_02_预测误差分析.png")
    
    return {
        'source_model': source_model,
        'scratch_model': scratch_model,
        'transfer_simple': transfer_model_simple,
        'transfer_progressive': transfer_model_progressive
    }

# ==================== 5. 主程序 ====================
if __name__ == "__main__":
    models = run_comparison_experiment()
    print("\n" + "=" * 60)
    print("实例一完成!")
    print("=" * 60)

附录B:实例二完整代码

"""
主题079:迁移学习与域适应
实例二:域适应在跨材料疲劳预测中的应用

本实例演示如何使用域适应技术(Domain Adaptation)解决源域和目标域特征分布不一致的问题,
实现钢材到铝合金、高温到常温等不同工况下的疲劳寿命预测迁移。
"""

import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib import rcParams
import os
from sklearn.neural_network import MLPRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import r2_score, mean_absolute_error, mean_squared_error
from sklearn.decomposition import PCA

# 设置中文字体
rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
rcParams['axes.unicode_minus'] = False

# 创建输出目录
output_dir = os.path.dirname(os.path.abspath(__file__))

# ==================== 1. 数据生成 ====================
def generate_multi_domain_data():
    """
    生成多个域的疲劳数据
    
    域1:钢材,室温(源域)
    域2:铝合金,室温(目标域1 - 材料不同)
    域3:钢材,高温(目标域2 - 工况不同)
    域4:铝合金,高温(目标域3 - 材料和工况都不同)
    """
    np.random.seed(42)
    
    # 域1:钢材,室温(源域,数据充足)
    n_source = 500
    delta_sigma_1 = np.linspace(100, 600, n_source)
    A1, b1 = 1.5e12, 5.0  # 钢材Basquin参数
    Nf_1 = A1 * (delta_sigma_1 ** (-b1))
    log_Nf_1 = np.log10(Nf_1) + np.random.normal(0, 0.1, n_source)
    stress_ratio_1 = np.random.uniform(-1, 0.5, n_source)
    temp_1 = np.random.normal(25, 5, n_source)  # 室温
    roughness_1 = np.random.uniform(0.5, 3.0, n_source)
    X_source = np.column_stack([delta_sigma_1, stress_ratio_1, temp_1, roughness_1])
    y_source = log_Nf_1
    
    # 域2:铝合金,室温(目标域1,数据稀缺)
    n_target1 = 30
    delta_sigma_2 = np.linspace(80, 400, n_target1)
    A2, b2 = 5.0e10, 4.0  # 铝合金Basquin参数
    Nf_2 = A2 * (delta_sigma_2 ** (-b2))
    log_Nf_2 = np.log10(Nf_2) + np.random.normal(0, 0.12, n_target1)
    stress_ratio_2 = np.random.uniform(-1, 0.5, n_target1)
    temp_2 = np.random.normal(25, 5, n_target1)
    roughness_2 = np.random.uniform(0.3, 2.5, n_target1)
    X_target1 = np.column_stack([delta_sigma_2, stress_ratio_2, temp_2, roughness_2])
    y_target1 = log_Nf_2
    
    # 域3:钢材,高温(目标域2,数据稀缺)
    n_target2 = 30
    delta_sigma_3 = np.linspace(100, 600, n_target2)
    # 高温下疲劳性能下降
    A3, b3 = 8.0e11, 4.5  # 高温修正参数
    Nf_3 = A3 * (delta_sigma_3 ** (-b3))
    log_Nf_3 = np.log10(Nf_3) + np.random.normal(0, 0.15, n_target2)
    stress_ratio_3 = np.random.uniform(-1, 0.5, n_target2)
    temp_3 = np.random.normal(400, 20, n_target2)  # 高温
    roughness_3 = np.random.uniform(0.5, 3.0, n_target2)
    X_target2 = np.column_stack([delta_sigma_3, stress_ratio_3, temp_3, roughness_3])
    y_target2 = log_Nf_3
    
    # 域4:铝合金,高温(目标域3,数据稀缺)
    n_target3 = 30
    delta_sigma_4 = np.linspace(80, 400, n_target3)
    A4, b4 = 2.5e10, 3.5  # 高温铝合金参数
    Nf_4 = A4 * (delta_sigma_4 ** (-b4))
    log_Nf_4 = np.log10(Nf_4) + np.random.normal(0, 0.18, n_target3)
    stress_ratio_4 = np.random.uniform(-1, 0.5, n_target3)
    temp_4 = np.random.normal(400, 20, n_target3)
    roughness_4 = np.random.uniform(0.3, 2.5, n_target3)
    X_target3 = np.column_stack([delta_sigma_4, stress_ratio_4, temp_4, roughness_4])
    y_target3 = log_Nf_4
    
    return {
        'source': (X_source, y_source, '钢材-室温'),
        'target1': (X_target1, y_target1, '铝合金-室温'),
        'target2': (X_target2, y_target2, '钢材-高温'),
        'target3': (X_target3, y_target3, '铝合金-高温')
    }

# ==================== 2. 域适应方法 ====================
class DomainAdaptation:
    """
    域适应类
    
    实现多种域适应策略:
    1. 特征对齐(Feature Alignment)
    2. 实例重加权(Instance Reweighting)
    3. 子空间对齐(Subspace Alignment)
    """
    
    def __init__(self, method='feature_alignment'):
        self.method = method
        self.scaler = StandardScaler()
        self.pca = None
        self.transform_matrix = None
    
    def feature_alignment(self, X_source, X_target):
        """
        特征对齐:通过标准化使源域和目标域特征分布对齐
        """
        # 分别标准化源域和目标域
        X_source_aligned = self.scaler.fit_transform(X_source)
        X_target_aligned = self.scaler.transform(X_target)
        return X_source_aligned, X_target_aligned
    
    def instance_reweighting(self, X_source, y_source, X_target, model):
        """
        实例重加权:根据源域样本与目标域的相似度赋予不同权重
        
        使用核密度估计计算样本权重
        """
        from scipy.stats import gaussian_kde
        
        # 计算源域和目标域的特征分布
        source_kde = gaussian_kde(X_source.T)
        target_kde = gaussian_kde(X_target.T)
        
        # 计算每个源域样本的权重
        source_density = source_kde(X_source.T)
        target_density = target_kde(X_source.T)
        
        # 权重 = 目标域密度 / 源域密度
        weights = target_density / (source_density + 1e-10)
        weights = weights / np.sum(weights) * len(weights)  # 归一化
        
        return weights
    
    def subspace_alignment(self, X_source, X_target, n_components=2):
        """
        子空间对齐:将源域和目标域映射到同一子空间
        
        使用PCA找到主要变化方向,然后对齐子空间
        """
        # 对源域和目标域分别进行PCA
        pca_source = PCA(n_components=n_components)
        pca_target = PCA(n_components=n_components)
        
        X_source_pca = pca_source.fit_transform(X_source)
        X_target_pca = pca_target.fit_transform(X_target)
        
        # 计算源域和目标域主成分之间的变换矩阵
        # 使用最小维度进行对齐
        min_comp = min(pca_source.components_.shape[0], pca_target.components_.shape[0])
        source_components = pca_source.components_[:min_comp, :].T
        target_components = pca_target.components_[:min_comp, :].T
        
        # 对齐变换 - 使用伪逆处理非方阵情况
        self.transform_matrix = np.linalg.pinv(target_components) @ source_components
        
        # 将源域映射到目标域子空间
        X_source_aligned = X_source_pca[:, :min_comp] @ self.transform_matrix[:min_comp, :min_comp]
        
        return X_source_aligned, X_target_pca[:, :min_comp], pca_source, pca_target

# ==================== 3. 迁移学习策略 ====================
def train_baseline_source(X_source, y_source):
    """基线:仅在源域上训练"""
    scaler = StandardScaler()
    X_scaled = scaler.fit_transform(X_source)
    
    model = MLPRegressor(
        hidden_layer_sizes=(64, 32, 16),
        activation='relu',
        solver='adam',
        alpha=0.001,
        learning_rate_init=0.001,
        max_iter=1000,
        random_state=42
    )
    model.fit(X_scaled, y_source)
    return model, scaler

def train_scratch_target(X_target_train, y_target_train):
    """基线:仅在目标域上从头训练(小数据量)"""
    scaler = StandardScaler()
    X_scaled = scaler.fit_transform(X_target_train)
    
    model = MLPRegressor(
        hidden_layer_sizes=(64, 32, 16),
        activation='relu',
        solver='adam',
        alpha=0.001,
        learning_rate_init=0.001,
        max_iter=1000,
        random_state=42
    )
    model.fit(X_scaled, y_target_train)
    return model, scaler

def transfer_with_feature_alignment(X_source, y_source, X_target_train, y_target_train):
    """迁移学习 + 特征对齐"""
    # 特征对齐
    da = DomainAdaptation(method='feature_alignment')
    X_source_aligned, X_target_aligned = da.feature_alignment(X_source, X_target_train)
    
    # 在对齐后的源域上训练
    source_model = MLPRegressor(
        hidden_layer_sizes=(64, 32, 16),
        activation='relu',
        solver='adam',
        alpha=0.001,
        learning_rate_init=0.001,
        max_iter=1000,
        random_state=42
    )
    source_model.fit(X_source_aligned, y_source)
    
    # 在目标域上微调
    target_model = MLPRegressor(
        hidden_layer_sizes=(64, 32, 16),
        activation='relu',
        solver='adam',
        alpha=0.001,
        learning_rate_init=0.0001,
        max_iter=300,
        random_state=42,
        warm_start=False
    )
    # 先拟合初始化
    target_model.fit(X_target_aligned, y_target_train)
    # 复制源模型权重
    target_model.coefs_ = [c.copy() for c in source_model.coefs_]
    target_model.intercepts_ = [i.copy() for i in source_model.intercepts_]
    # 继续训练
    target_model.warm_start = True
    target_model.fit(X_target_aligned, y_target_train)
    
    return target_model, da.scaler

def transfer_with_subspace_alignment(X_source, y_source, X_target_train, y_target_train):
    """迁移学习 + 子空间对齐"""
    # 子空间对齐
    da = DomainAdaptation(method='subspace_alignment')
    X_source_sub, X_target_sub, pca_s, pca_t = da.subspace_alignment(
        X_source, X_target_train, n_components=3
    )
    
    # 在源域子空间上训练
    source_model = MLPRegressor(
        hidden_layer_sizes=(32, 16),
        activation='relu',
        solver='adam',
        alpha=0.001,
        learning_rate_init=0.001,
        max_iter=1000,
        random_state=42
    )
    source_model.fit(X_source_sub, y_source)
    
    # 在目标域子空间上微调
    target_model = MLPRegressor(
        hidden_layer_sizes=(32, 16),
        activation='relu',
        solver='adam',
        alpha=0.001,
        learning_rate_init=0.0001,
        max_iter=300,
        random_state=42,
        warm_start=False
    )
    # 先拟合初始化
    target_model.fit(X_target_sub, y_target_train)
    # 复制源模型权重
    target_model.coefs_ = [c.copy() for c in source_model.coefs_]
    target_model.intercepts_ = [i.copy() for i in source_model.intercepts_]
    # 继续训练
    target_model.warm_start = True
    target_model.fit(X_target_sub, y_target_train)
    
    return target_model, da, pca_s, pca_t

# ==================== 4. 评估与可视化 ====================
def evaluate_model(model, X_test, y_test, scaler, model_name):
    """评估模型性能"""
    if hasattr(scaler, 'transform'):
        X_test_scaled = scaler.transform(X_test)
    else:
        # 子空间对齐的情况
        da, pca_s, pca_t = scaler
        X_test_pca = pca_s.transform(X_test)
        X_test_scaled = X_test_pca @ da.transform_matrix
    
    y_pred_log = model.predict(X_test_scaled)
    y_true_log = y_test
    
    mae = mean_absolute_error(y_true_log, y_pred_log)
    rmse = np.sqrt(mean_squared_error(y_true_log, y_pred_log))
    r2 = r2_score(y_true_log, y_pred_log)
    
    y_pred = 10 ** y_pred_log
    y_true = 10 ** y_true_log
    mape = np.mean(np.abs((y_pred - y_true) / y_true)) * 100
    
    return {
        'MAE': mae,
        'RMSE': rmse,
        'MAPE': mape,
        'R2': r2,
        'y_pred': y_pred
    }

def run_domain_adaptation_experiment():
    """运行域适应实验"""
    
    print("=" * 70)
    print("域适应在跨材料疲劳预测中的应用")
    print("=" * 70)
    
    # 生成数据
    print("\n[1] 生成多域数据...")
    data = generate_multi_domain_data()
    
    X_source, y_source, name_source = data['source']
    X_t1, y_t1, name_t1 = data['target1']
    X_t2, y_t2, name_t2 = data['target2']
    X_t3, y_t3, name_t3 = data['target3']
    
    print(f"源域({name_source}): {len(X_source)} 样本")
    print(f"目标域1({name_t1}): {len(X_t1)} 样本")
    print(f"目标域2({name_t2}): {len(X_t2)} 样本")
    print(f"目标域3({name_t3}): {len(X_t3)} 样本")
    
    # 划分目标域的训练集和测试集
    def split_data(X, y, train_size=20):
        indices = np.random.permutation(len(X))
        train_idx = indices[:train_size]
        test_idx = indices[train_size:]
        return X[train_idx], y[train_idx], X[test_idx], y[test_idx]
    
    X_t1_train, y_t1_train, X_t1_test, y_t1_test = split_data(X_t1, y_t1)
    X_t2_train, y_t2_train, X_t2_test, y_t2_test = split_data(X_t2, y_t2)
    X_t3_train, y_t3_train, X_t3_test, y_t3_test = split_data(X_t3, y_t3)
    
    # 在源域上训练基线模型
    print("\n[2] 在源域上训练基线模型...")
    source_model, source_scaler = train_baseline_source(X_source, y_source)
    
    # 对每个目标域进行实验
    results = {}
    target_domains = [
        ('target1', X_t1_train, y_t1_train, X_t1_test, y_t1_test, name_t1),
        ('target2', X_t2_train, y_t2_train, X_t2_test, y_t2_test, name_t2),
        ('target3', X_t3_train, y_t3_train, X_t3_test, y_t3_test, name_t3)
    ]
    
    for domain_name, X_train, y_train, X_test, y_test, domain_label in target_domains:
        print(f"\n{'='*50}")
        print(f"目标域: {domain_label}")
        print(f"{'='*50}")
        
        # 方法1:直接使用源模型
        print("\n方法1:源模型直接应用...")
        res_source = evaluate_model(source_model, X_test, y_test, source_scaler, 
                                     "源模型")
        print(f"  MAPE: {res_source['MAPE']:.2f}%, R²: {res_source['R2']:.4f}")
        
        # 方法2:从头训练
        print("\n方法2:在目标域从头训练...")
        scratch_model, scratch_scaler = train_scratch_target(X_train, y_train)
        res_scratch = evaluate_model(scratch_model, X_test, y_test, scratch_scaler, 
                                      "从头训练")
        print(f"  MAPE: {res_scratch['MAPE']:.2f}%, R²: {res_scratch['R2']:.4f}")
        
        # 方法3:特征对齐 + 迁移学习
        print("\n方法3:特征对齐 + 迁移学习...")
        transfer_fa_model, fa_scaler = transfer_with_feature_alignment(
            X_source, y_source, X_train, y_train
        )
        res_fa = evaluate_model(transfer_fa_model, X_test, y_test, fa_scaler, 
                                 "特征对齐")
        print(f"  MAPE: {res_fa['MAPE']:.2f}%, R²: {res_fa['R2']:.4f}")
        
        # 方法4:子空间对齐 + 迁移学习
        print("\n方法4:子空间对齐 + 迁移学习...")
        transfer_sa_model, da_obj, pca_s, pca_t = transfer_with_subspace_alignment(
            X_source, y_source, X_train, y_train
        )
        res_sa = evaluate_model(transfer_sa_model, X_test, y_test, 
                                 (da_obj, pca_s, pca_t), "子空间对齐")
        print(f"  MAPE: {res_sa['MAPE']:.2f}%, R²: {res_sa['R2']:.4f}")
        
        results[domain_name] = {
            'source': res_source,
            'scratch': res_scratch,
            'feature_align': res_fa,
            'subspace_align': res_sa,
            'domain_label': domain_label,
            'y_test': 10 ** y_test
        }
    
    # 可视化结果
    print("\n[3] 生成可视化结果...")
    visualize_results(data, results)
    
    return results

def visualize_results(data, results):
    """可视化域适应结果"""
    
    # 图1:各目标域性能对比
    fig, axes = plt.subplots(1, 3, figsize=(18, 5))
    
    methods = ['源模型\n(直接)', '从头\n训练', '特征\n对齐', '子空间\n对齐']
    method_keys = ['source', 'scratch', 'feature_align', 'subspace_align']
    colors = ['#e74c3c', '#3498db', '#2ecc71', '#9b59b6']
    
    for idx, (domain_name, domain_results) in enumerate(results.items()):
        ax = axes[idx]
        
        mapes = [domain_results[k]['MAPE'] for k in method_keys]
        r2s = [domain_results[k]['R2'] for k in method_keys]
        
        x = np.arange(len(methods))
        width = 0.35
        
        bars1 = ax.bar(x - width/2, mapes, width, label='MAPE (%)', color=colors)
        ax2 = ax.twinx()
        bars2 = ax2.bar(x + width/2, r2s, width, label='R²', color=colors, alpha=0.6)
        
        ax.set_ylabel('MAPE (%)', fontsize=11)
        ax2.set_ylabel('R² 分数', fontsize=11)
        ax.set_title(f'{domain_results["domain_label"]}', fontsize=12, fontweight='bold')
        ax.set_xticks(x)
        ax.set_xticklabels(methods, fontsize=9)
        
        # 添加数值标签
        for bar, val in zip(bars1, mapes):
            ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1,
                   f'{val:.1f}%', ha='center', va='bottom', fontsize=8)
        for bar, val in zip(bars2, r2s):
            ax2.text(bar.get_x() + bar.get_width()/2, 
                    bar.get_height() + 0.02 if val > 0 else bar.get_height() - 0.08,
                   f'{val:.3f}', ha='center', 
                   va='bottom' if val > 0 else 'top', fontsize=8)
        
        ax.set_ylim([0, max(mapes) * 1.3])
        ax2.set_ylim([-1, 1.2])
        ax.grid(True, alpha=0.3, axis='y')
    
    plt.tight_layout()
    plt.savefig(f'{output_dir}/domain_01_跨域性能对比.png', dpi=150, bbox_inches='tight')
    plt.close()
    
    # 图2:预测结果对比(以目标域1为例)
    fig, axes = plt.subplots(2, 2, figsize=(14, 12))
    
    domain1_results = results['target1']
    y_test = domain1_results['y_test']
    
    plot_data = [
        ('源模型(直接应用)', domain1_results['source'], axes[0, 0]),
        ('从头训练', domain1_results['scratch'], axes[0, 1]),
        ('特征对齐', domain1_results['feature_align'], axes[1, 0]),
        ('子空间对齐', domain1_results['subspace_align'], axes[1, 1])
    ]
    
    for name, result, ax in plot_data:
        y_pred = result['y_pred']
        
        # 散点图
        ax.scatter(y_test, y_pred, c='blue', alpha=0.6, s=80)
        
        # 理想预测线
        min_val = min(y_test.min(), y_pred.min())
        max_val = max(y_test.max(), y_pred.max())
        ax.plot([min_val, max_val], [min_val, max_val], 'r--', linewidth=2, label='理想预测')
        
        ax.set_xlabel('真实疲劳寿命 (cycles)', fontsize=11)
        ax.set_ylabel('预测疲劳寿命 (cycles)', fontsize=11)
        ax.set_title(f'{name}\nMAPE: {result["MAPE"]:.2f}%, R²: {result["R2"]:.4f}', 
                    fontsize=12, fontweight='bold')
        ax.legend()
        ax.grid(True, alpha=0.3)
        ax.set_xscale('log')
        ax.set_yscale('log')
    
    plt.tight_layout()
    plt.savefig(f'{output_dir}/domain_02_预测结果对比.png', dpi=150, bbox_inches='tight')
    plt.close()
    
    # 图3:特征分布可视化(PCA降维)
    fig, axes = plt.subplots(1, 3, figsize=(18, 5))
    
    X_source, y_source, _ = data['source']
    X_t1, y_t1, _ = data['target1']
    X_t2, y_t2, _ = data['target2']
    X_t3, y_t3, _ = data['target3']
    
    # 合并所有数据
    X_all = np.vstack([X_source, X_t1, X_t2, X_t3])
    pca_viz = PCA(n_components=2)
    X_all_pca = pca_viz.fit_transform(X_all)
    
    n_s = len(X_source)
    n_t1 = len(X_t1)
    n_t2 = len(X_t2)
    n_t3 = len(X_t3)
    
    X_s_pca = X_all_pca[:n_s]
    X_t1_pca = X_all_pca[n_s:n_s+n_t1]
    X_t2_pca = X_all_pca[n_s+n_t1:n_s+n_t1+n_t2]
    X_t3_pca = X_all_pca[n_s+n_t1+n_t2:]
    
    domains = [
        (X_s_pca, X_t1_pca, '钢材-室温 vs 铝合金-室温'),
        (X_s_pca, X_t2_pca, '钢材-室温 vs 钢材-高温'),
        (X_s_pca, X_t3_pca, '钢材-室温 vs 铝合金-高温')
    ]
    
    for idx, (X_s, X_t, title) in enumerate(domains):
        ax = axes[idx]
        ax.scatter(X_s[:, 0], X_s[:, 1], c='blue', alpha=0.3, s=20, label='源域(钢材-室温)')
        ax.scatter(X_t[:, 0], X_t[:, 1], c='red', alpha=0.7, s=50, label='目标域')
        ax.set_xlabel('第一主成分', fontsize=11)
        ax.set_ylabel('第二主成分', fontsize=11)
        ax.set_title(title, fontsize=12, fontweight='bold')
        ax.legend()
        ax.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.savefig(f'{output_dir}/domain_03_特征分布可视化.png', dpi=150, bbox_inches='tight')
    plt.close()
    
    # 图4:MAPE对比汇总
    fig, ax = plt.subplots(figsize=(12, 7))
    
    domain_labels = [results[k]['domain_label'] for k in results.keys()]
    x = np.arange(len(domain_labels))
    width = 0.2
    
    for i, (method_key, method_name) in enumerate(zip(method_keys, methods)):
        mapes = [results[k][method_key]['MAPE'] for k in results.keys()]
        ax.bar(x + i*width, mapes, width, label=method_name, color=colors[i])
    
    ax.set_xlabel('目标域', fontsize=12)
    ax.set_ylabel('MAPE (%)', fontsize=12)
    ax.set_title('不同域适应方法性能对比', fontsize=14, fontweight='bold')
    ax.set_xticks(x + width * 1.5)
    ax.set_xticklabels(domain_labels, fontsize=10)
    ax.legend(fontsize=10)
    ax.grid(True, alpha=0.3, axis='y')
    
    plt.tight_layout()
    plt.savefig(f'{output_dir}/domain_04_MAPE对比汇总.png', dpi=150, bbox_inches='tight')
    plt.close()
    
    print(f"\n可视化结果已保存到: {output_dir}")
    print("  - domain_01_跨域性能对比.png")
    print("  - domain_02_预测结果对比.png")
    print("  - domain_03_特征分布可视化.png")
    print("  - domain_04_MAPE对比汇总.png")

# ==================== 5. 主程序 ====================
if __name__ == "__main__":
    results = run_domain_adaptation_experiment()
    print("\n" + "=" * 70)
    print("实例二完成!")
    print("=" * 70)

附录C:环境配置

# 创建虚拟环境
conda create -n transfer_learning python=3.9

# 激活环境
conda activate transfer_learning

# 安装依赖
pip install numpy matplotlib scikit-learn scipy

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐