news 2026/10/17 6:46:19

元学习简单入门

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
元学习简单入门

元学习概念

传统机器学习得到的模型擅长的是已经学习过的任务,现在希望训练出一个模型 ,在遇到新任务时,只需要很少的样本、梯度更新,就能获得不错的表现。

元学习目标是得到一个能利用少量样本快速进行新任务的模型。

MAML:最经典的基于优化的元学习方法

论文地址:[1703.03400] Model-Agnostic Meta-Learning for Fast Adaptation of Deep Networks

MAML 的核心思想:

寻找一个好的模型初始化,使模型能够通过少量梯度更新,快速适应新任务。

主要改变了模型初始化参数的优化目标。

MAML有两个阶段

Inner Loop:学习当前任务

Outer Loop:学习如何更快地学习任务

Inner Loop:针对一个任务进行适应

内循环就是用初始化模型做一次梯度下降,如果有三个任务,得到三组不同的参数

Outer Loop:评价模型适应之后的表现

假设三个任务都完成了内循环,得到了三组不同的参数

再跑一次,评价得到的三组参数

元学习内层循环相当于是把初始模型经过小幅度计算得到更新后的模型,外层循环的目标只是希望找到让内层循环效果最好的初始模型参数。

MAML论文精读

学习过程可以被理解为:最大化新任务损失函数对模型参数的敏感性,当这种敏感性较高时,模型参数在局部发生较小变化就能使任务损失得到显著改善。

MAML 可以通过学习一个便于适应的初始化来解决不同任务,而不一定需要学习到某种共同的高级语义特征。

MAML的通用算法:任务采样 -> 任务内梯度更新 -> 计算适应后损失 ->元参数更新

MAML的两个核心公式:

无论做图像分类、回归任务还是强化学习,只要能获得适当的梯度估计,就可以使用这套框架。区别主要体现在不同任务使用不同的损失函数。

监督任务(回归、分类)

在监督学习任务中,目标是利用从相似任务中获得的先验知识再进行元学习,使模型能仅根据某个新任务的少量数据学习一个新的函数。

监督分类和回归中常用的两种损失函数是交叉熵损失和均方误差MSE,下面以其为例。

PyTorch MAML 简单实现

对应 Finn 等人在《Model-Agnostic Meta-Learning for Fast Adaptation of Deep Networks》第 5.1 节中的正弦函数回归实验。

代码包含:

  1. 随机生成不同的正弦函数任务。
  2. 定义神经网络。
  3. 实现 MAML 的 Inner Loop。
  4. 实现 MAML 的 Outer Loop。
  5. 完成元训练。
  6. 在从未见过的新任务上进行快速适应。
  7. 绘图对比适应前后的预测效果。
  8. 支持切换一阶 MAML(FOMAML)和完整二阶 MAML。
import copy import random import numpy as np import matplotlib.pyplot as plt import torch import torch.nn as nn import torch.nn.functional as F from collections import OrderedDict from torch.func import functional_call # ============================================================ # 1. 配置 # ============================================================ SEED = 42 META_ITERATIONS = 2000 # 元训练迭代次数 META_BATCH_SIZE = 4 # 每次元训练采样多少个任务 SUPPORT_SIZE = 10 # 每个任务用于 Inner Loop 的样本数 QUERY_SIZE = 20 # 每个任务用于 Outer Loop 的样本数 INNER_STEPS = 1 # 每个任务执行几次梯度下降 INNER_LR = 0.01 # Inner Loop 学习率 META_LR = 0.001 # Outer Loop 学习率 FIRST_ORDER = False # False: 完整 MAML; True: FOMAML DEVICE = torch.device( "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu" ) def set_seed(seed): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) set_seed(SEED) print("Device:", DEVICE) # ============================================================ # 2. 生成任务 # # 每个 Task 是一个正弦函数: # # y = A * sin(x + phase) # # A ~ Uniform(0.1, 5.0) # phase ~ Uniform(0, pi) # # ============================================================ class SineTask: def __init__(self): self.amplitude = random.uniform(0.1, 5.0) self.phase = random.uniform(0, np.pi) def sample(self, K): # K 表示从当前任务中随机采样多少个数据点(样本) # torch.rand(K, 1)生成一个形状为 (K, 1) 的张量,其中有 K 个随机数,范围是 [0, 1)。 x = torch.rand(K, 1, device=DEVICE) * 10 - 5 # 将随机数映射到 [-5, 5)。 y = self.amplitude * torch.sin(x + self.phase) return x, y # ============================================================ # 3. 定义模型 # # 输入:x # 输出:预测的 y # # 网络结构: # # Linear(1, 40) # ReLU # Linear(40, 40) # ReLU # Linear(40, 1) # # ============================================================ class SineModel(nn.Module): def __init__(self): super().__init__() self.net = nn.Sequential(# Sequential 的意思是:按照定义的顺序,依次执行里面的网络层。 nn.Linear(1, 40), # 表示一个全连接层:输入 1 个特征,输出 40 个特征。 # y = Wx + b,其中 W(40*1)是权重矩阵,b 是偏置向量。 x(1*1)是输入特征,y(40*1)是输出特征。 nn.ReLU(), # 表达更加复杂的非线性关系,从而近似正弦函数。 nn.Linear(40, 40), nn.ReLU(), nn.Linear(40, 1) ) # 输入 x # │ # ▼ # Linear(1, 40) # │ # ▼ # ReLU # │ # ▼ # Linear(40, 40) # │ # ▼ # ReLU # │ # ▼ # Linear(40, 1) # │ # ▼ # 输出预测值 y def forward(self, x): return self.net(x) # ============================================================ # 4. Inner Loop # # 输入: # model 当前元模型 # support_x 当前任务的训练数据 # support_y 当前任务的标签 # # 输出: # adapted_params # # 核心: # # theta' = theta - alpha * grad(L_support) # # ============================================================ def inner_loop( model, support_x, support_y, inner_steps=INNER_STEPS, inner_lr=INNER_LR, first_order=FIRST_ORDER ): # 取出当前模型参数 params = OrderedDict(model.named_parameters()) # params = { # "net.0.weight": tensor(...), # "net.0.bias": tensor(...), # "net.2.weight": tensor(...), # "net.2.bias": tensor(...), # "net.4.weight": tensor(...), # "net.4.bias": tensor(...) # } # 每个任务都有自己的一份适应后参数 for step in range(inner_steps): # 使用当前参数进行前向传播 predictions = functional_call( model, params, (support_x,) ) # Support Loss support_loss = F.mse_loss( predictions, support_y ) # 计算梯度 # MAMl 后续会根据W‘的梯度来更新W,也就是二阶导数。所以这里手动计算梯度 grads = torch.autograd.grad( support_loss, tuple(params.values()), create_graph=not first_order ) # 梯度下降 adapted_params = OrderedDict() for (name, param), grad in zip( params.items(), grads ): adapted_params[name] = ( param - inner_lr * grad ) # 下一次 Inner Step 使用更新后的参数 params = adapted_params return params # ============================================================ # 5. Outer Loop # # 每次: # # 1. 采样多个任务 # 2. 每个任务执行 Inner Loop # 3. 在 Query Set 上计算适应后损失 # 4. 汇总 Meta Loss # 5. 更新共同初始化 theta # # ============================================================ def meta_train(model): optimizer = torch.optim.Adam( model.parameters(), lr=META_LR ) history = [] for iteration in range(1, META_ITERATIONS + 1): optimizer.zero_grad() meta_loss = 0.0 # 采样一批任务 for task_index in range(META_BATCH_SIZE): # 随机生成一个新任务 task = SineTask() # 同一任务中的 Support / Query support_x, support_y = task.sample(SUPPORT_SIZE) query_x, query_y = task.sample(QUERY_SIZE) # ------------------------------- # Inner Loop # ------------------------------- adapted_params = inner_loop( model, support_x, support_y ) # ------------------------------- # Query Forward # ------------------------------- query_predictions = functional_call( model, adapted_params, (query_x,) ) # ------------------------------- # Query Loss # ------------------------------- query_loss = F.mse_loss( query_predictions, query_y ) meta_loss = meta_loss + query_loss # 对任务数量取平均 meta_loss = meta_loss / META_BATCH_SIZE # ------------------------------- # Outer Loop # ------------------------------- # 元梯度 meta_loss.backward() # 更新初始化 theta optimizer.step() history.append(meta_loss.item()) if iteration % 100 == 0: print( f"Iteration {iteration:4d} | " f"Meta Loss: {meta_loss.item():.6f}" ) return history # ============================================================ # 6. Meta-Test # # 在一个从未见过的新任务上: # # 1. 从元学习得到的初始化出发 # 2. 使用少量 Support 样本进行适应 # 3. 评价适应前后的预测效果 # # ============================================================ def meta_test(model): print("\n========== META TEST ==========") # 新任务 task = SineTask() print( f"New Task: " f"y = {task.amplitude:.3f} * " f"sin(x + {task.phase:.3f})" ) # 新任务的少量训练样本 support_x, support_y = task.sample(SUPPORT_SIZE) # 测试曲线 x_test = torch.linspace( -5, 5, 200, device=DEVICE ).reshape(-1, 1) y_true = ( task.amplitude * torch.sin(x_test + task.phase) ) # ------------------------------- # 适应前 # ------------------------------- with torch.no_grad(): y_before = model(x_test) # ------------------------------- # Inner Loop:适应新任务 # ------------------------------- adapted_params = inner_loop( model, support_x, support_y, inner_steps=5, inner_lr=INNER_LR, first_order=True ) # ------------------------------- # 适应后 # ------------------------------- with torch.no_grad(): y_after = functional_call( model, adapted_params, (x_test,) ) # ------------------------------- # 计算误差 # ------------------------------- mse_before = F.mse_loss( y_before, y_true ).item() mse_after = F.mse_loss( y_after, y_true ).item() print(f"MSE Before Adaptation: {mse_before:.6f}") print(f"MSE After Adaptation: {mse_after:.6f}") # ------------------------------- # 绘图 # ------------------------------- plt.figure(figsize=(10, 6)) plt.plot( x_test.cpu().numpy(), y_true.cpu().numpy(), label="True Function", linewidth=2 ) plt.plot( x_test.cpu().numpy(), y_before.cpu().numpy(), label="Before Adaptation", linestyle="--" ) plt.plot( x_test.cpu().numpy(), y_after.cpu().numpy(), label="After Adaptation", linewidth=2 ) plt.scatter( support_x.cpu().numpy(), support_y.cpu().numpy(), label="Support Samples", marker="o", s=60 ) plt.title("MAML: Fast Adaptation to a New Sine Task") plt.xlabel("x") plt.ylabel("y") plt.legend() plt.grid(alpha=0.3) plt.savefig( "maml_result.png", dpi=200, bbox_inches="tight" ) plt.show() # ============================================================ # 7. 主函数 # ============================================================ def main(): print("\n========== MAML TRAINING ==========") model = SineModel().to(DEVICE) print( "Model Parameters:", sum(p.numel() for p in model.parameters()) ) # ------------------------------- # Meta Training # ------------------------------- history = meta_train(model) # 保存模型 torch.save( model.state_dict(), "maml_sine.pth" ) print("\nModel saved to maml_sine.pth") # ------------------------------- # 绘制训练曲线 # ------------------------------- plt.figure(figsize=(10, 5)) plt.plot(history) plt.xlabel("Meta Iteration") plt.ylabel("Meta Loss") plt.title("MAML Training Loss") plt.grid(alpha=0.3) plt.savefig( "maml_training.png", dpi=200, bbox_inches="tight" ) plt.show() # ------------------------------- # Meta Testing # ------------------------------- meta_test(model) if __name__ == "__main__": main()

关于一阶 MAML(FOMAML)和完整二阶 MAML

二阶导数的由来:

一阶 MAML(FOMAML):First-Order Model-Agnostic Meta-Learning,一阶模型无关元学习

核心思想:计算二阶导数太昂贵就忽略

FOMAML 直接使用适应后模型的 Query Loss 梯度,近似作为初始模型的元梯度。

实验结果分析

========== MAML TRAINING ==========
Model Parameters: 1761
Iteration 100 | Meta Loss: 3.788455
Iteration 200 | Meta Loss: 1.681703
Iteration 300 | Meta Loss: 2.237962
Iteration 400 | Meta Loss: 2.531215
Iteration 500 | Meta Loss: 2.465373
Iteration 600 | Meta Loss: 4.893228
Iteration 700 | Meta Loss: 3.005637
Iteration 800 | Meta Loss: 3.053581
Iteration 900 | Meta Loss: 0.725297
Iteration 1000 | Meta Loss: 1.229570
Iteration 1100 | Meta Loss: 2.742692
Iteration 1200 | Meta Loss: 1.627140
Iteration 1300 | Meta Loss: 0.964882
Iteration 1400 | Meta Loss: 1.556150
Iteration 1500 | Meta Loss: 1.551608
Iteration 1600 | Meta Loss: 1.505540
Iteration 1700 | Meta Loss: 0.492393
Iteration 1800 | Meta Loss: 3.379267
Iteration 1900 | Meta Loss: 2.190859
Iteration 2000 | Meta Loss: 1.860071

模型确实完成了 2000 次元训练迭代,Meta Loss 在部分迭代中明显降低。metaloss是outer loop的优化目标,这里其实应该放outer loop最低的模型拿去训练和基础模型训练准确率的对比,但这里只是学习需要就没做那么严谨😝。

这张图就是meta loss数据的可视化,没有引入新的评价指标。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 6:45:19

Mamba学习笔记(前置知识版)

一、RMSNorm(均方根归一化) “均方根”这个名字就是计算顺序:原始数值平方 → 求平均 → 开平方。 以[-5,-5,-5]举例,RMS, 然后每个原始特征都除以这个尺度: [-5/5,-5/5,-5/5][-1,-1,-1] 二、输入投影…

作者头像 李华
网站建设 2026/10/17 6:45:13

PHP代码注入深度解析:从漏洞利用到蚁剑Shell与防御实战

1. 从一条请求说起:PHP代码注入到底在防什么很多做Web安全的朋友,第一次接触代码执行类漏洞,都是从PHP代码注入开始的。它不像SQL注入那样有明确的“数据与指令混淆”边界,也不像XSS那样主要影响前端用户,PHP代码注入的…

作者头像 李华
网站建设 2026/10/17 6:44:58

Ionic 头部与底部:Header 与 Footer 组件实战详解

1. 引言在移动端应用开发中,页面顶部和底部的固定区域是承载导航、标题、操作按钮等核心功能的重要部分。Ionic 框架提供了 Header(头部) 和 Footer(底部) 两个基础组件,帮助开发者快速搭建结构清晰、体验一…

作者头像 李华
网站建设 2026/10/17 6:44:04

Linux:信号的认识、信号的产生、信号的保存、信号的处理

一、对信号的宏观认识从实际的场景来举例:红绿灯、上课铃声,电话铃声等都是一种信号的产生,而我们对这种信号处理的方式是后天训练而形成的,如:红灯停,绿灯行。 所以对于OS的信号而言,也就是工程师们内置提前写好的,对于对信号怎么…

作者头像 李华