在机器学习里,我们经常默认一件事:数据越多,模型越聪明。
比如,你想训练一个模型识别“跑步”“跳绳”“打篮球”,通常会希望准备成千上万段带标签的视频。
但现实常常不是这样。
很多时候,我们只能拿到很少的数据:
- 一种罕见手术动作,只有几段示范视频
- 某个工厂里的危险行为,历史上几乎没发生过
- 某种特殊手语动作,采集成本很高
- 新增的一类体育动作,刚出现不久,没有大规模数据集
这时候,就轮到 小样本行为识别(Few-Shot Action Recognition, FSAR) 登场了。它要解决的问题很直接:
只给模型看很少几个例子,它还能不能认出一种“行为”是什么?
这类任务和普通图像分类不同,因为它面对的是视频,而视频里最关键的不只是“看到了什么”,还有“这些内容是怎么随时间变化的”。近年来的综述也专门强调了这一点:小样本行为识别相较于小样本图像分类,最大的难点之一就在于时空信息建模。
一、先别急着上公式:什么叫“行为识别”?
先从最朴素的例子开始。
假设你给电脑看一张图片,它要判断这是“猫”还是“狗”,这叫图像分类。
而如果你给电脑看一段视频,它要判断这个人是在:
- 挥手
- 跑步
- 拿起杯子喝水
- 跳起并投篮
这就叫行为识别,也常叫动作识别(Action Recognition)。
注意这里的关键区别:
- 图像分类:看一个瞬间
- 行为识别:看一段过程
举个生动一点的例子:
“拿起杯子”和“放下杯子”
某一帧画面里,看起来都可能是“手 + 杯子 + 桌子”。
但如果把前后几秒连起来看,你才能知道到底是“拿起”还是“放下”。
所以,行为识别不是只看“长什么样”,还要看“怎么动”。
二、那什么又叫“小样本”?
“小样本”就是:每个类别只有很少的标注样本。
比如,我们要识别 5 种行为:
- 挥手
- 鼓掌
- 跳跃
- 坐下
- 起立
如果每一种行为只给模型 1 个样本,这叫:
- 5-way 1-shot
如果每一种行为给 5 个样本,这叫:
- 5-way 5-shot
这里有两个常见术语:
- Way:一共有多少个类别
- Shot:每个类别给多少个示例
这类设置通常会把样本分成两部分:
- Support Set(支持集):给模型参考的少量已知样本
- Query Set(查询集):让模型去判断的新样本
很多经典小样本方法都会采用这种“按任务切分”的 episodic training(情节式训练) 思路:每次训练不是喂一整个大数据集,而是不断构造“小任务”,让模型学会“看到几个例子后立刻分类”。
三、为什么“小样本行为识别”比想象中难?
这个问题非常重要。
如果你是初学者,可以把它理解成一句话:
它同时继承了“小样本学习难”和“视频理解难”这两种难。
1. 样本太少,模型容易“死记硬背”
深度学习模型参数很多,如果每类只给 1~5 个视频,它很容易记住训练样本,而不是学到真正的规律。
这就是常说的 过拟合(Overfitting)。
比如:
- 训练时“挥手”样本都发生在教室里
- 测试时“挥手”发生在操场上
模型可能根本没学会“挥手”,只是误把“教室背景”当成了线索。
2. 行为是“时间过程”,不是单帧图片
“坐下”和“站起”某些帧很像。
“开门”和“关门”某些帧也很像。
真正的区别在于动作发生的顺序和方向。
所以,行为识别必须处理时间维度。
3. 同一种行为,不同人做出来差别很大
比如“鼓掌”:
- 有人动作大
- 有人动作小
- 有人快
- 有人慢
- 有人坐着鼓掌
- 有人站着鼓掌
这叫 类内差异大。
4. 不同行为之间又可能非常像
比如:
- “拿起杯子”
- “端起碗”
- “举起手机”
这些动作局部上很相似。
这叫 类间差异小。
5. 视频里还有很多“无关信息”
比如:
- 背景
- 光照
- 摄像机角度
- 遮挡
- 衣服颜色
- 是否有别的人经过
这些都会干扰模型判断。
一些较新的方法会专门强调“时间关系”“局部与全局时空特征”的重要性。例如 STRM 这类方法的核心目标之一,就是增强类别区分能力,同时学习更高阶的时间表示。
四、用一个生活例子理解:人是怎么做“小样本行为识别”的?
假设你教一个小朋友认识“打招呼”这个行为。
你只给他看两段视频:
- 视频 A:一个人举手挥了挥
- 视频 B:另一个人笑着摆了摆手
然后你再给他看第三段新视频,问:
这是不是“打招呼”?
小朋友大概率能答对。
为什么?
因为人类会自动做几件事:
- 抓关键特征:手臂抬起、来回摆动
- 忽略无关因素:衣服、背景、男女老少
- 做相似度匹配:这个新动作像不像刚才那两个例子?
这其实就很像很多小样本方法的基本思想:
- 先提取特征
- 再比较相似度
- 最后归类
五、小样本行为识别的核心思路,到底在做什么?
虽然论文很多,但对初学者来说,可以先把它们归纳成下面这条主线:
第一步:先把视频“变成特征”
模型不会直接理解“挥手”“跑步”,它会先把视频编码成一串数字,这叫 特征表示(Feature Representation)。
你可以把它理解成:
一段视频 → 一个高维向量
这个向量里,希望包含:
- 人的姿态信息
- 物体变化信息
- 时间顺序信息
- 动作节奏信息
通常会用一个预训练的视频模型来提特征,比如 3D CNN、时序网络,或者近年的视觉大模型变体。很多新方法不是从零训练,而是先利用大模型或预训练骨干网络,再做任务适配,以减轻小样本场景下的过拟合问题。
第二步:把每一类的少量样本“总结成一个代表”
假设“挥手”类只有 3 个支持视频。
那我们可以把这 3 个视频的特征求平均,得到一个“挥手原型(prototype)”。
于是每一类都有一个“代表向量”。
第三步:新视频来了,就和这些“代表”比谁更像
比如测试视频的特征是 q:
- 它和“挥手原型”最像
- 和“鼓掌原型”次之
- 和“跳跃原型”最不像
那就判成“挥手”。
这就是很多 matching-based / prototype-based 方法最朴素的思路。
你可以把它理解成:
先记住每类长什么样,再看新样本更像谁。
六、一个极简例子:原型分类为什么有效?
假设我们已经把视频变成了二维特征,方便理解。
- “挥手”这类的 3 个样本分别是:
(1, 2)、(2, 1)、(1, 1.5)
那这个类别的原型可以近似看成:
(1.33, 1.5)
现在来了一个新视频,它的特征是:
(1.4, 1.6)
如果它离“挥手原型”最近,那就大概率属于“挥手”。
这和生活里“看谁最像谁”是一个道理。
七、给初学者的第一段代码:用 JavaScript 写一个“原型分类”示意
真实研究代码通常用 Python + PyTorch,但为了符合博客排版风格,这里我用 JavaScript 风格的示意代码 来讲清核心思想。
function average(vectors) {
const dim = vectors[0].length;
const result = new Array(dim).fill(0);
for (const vec of vectors) {
for (let i = 0; i < dim; i++) {
result[i] += vec[i];
}
}
for (let i = 0; i < dim; i++) {
result[i] /= vectors.length;
}
return result;
}
function euclideanDistance(a, b) {
let sum = 0;
for (let i = 0; i < a.length; i++) {
sum += (a[i] - b[i]) ** 2;
}
return Math.sqrt(sum);
}
function classifyByPrototype(supportSet, queryFeature) {
const prototypes = {};
for (const label in supportSet) {
prototypes[label] = average(supportSet[label]);
}
let bestLabel = null;
let bestDistance = Infinity;
for (const label in prototypes) {
const distance = euclideanDistance(queryFeature, prototypes[label]);
if (distance < bestDistance) {
bestDistance = distance;
bestLabel = label;
}
}
return bestLabel;
}
const supportSet = {
wave: [
[1.0, 2.0],
[2.0, 1.0],
[1.2, 1.5]
],
clap: [
[5.0, 5.0],
[4.8, 5.2],
[5.3, 4.9]
]
};
const queryFeature = [1.3, 1.7];
console.log(classifyByPrototype(supportSet, queryFeature)); // wave
这段代码做了三件事:
- 每个类别的支持样本先求平均,形成 prototype
- 新样本和每个 prototype 计算距离
- 距离最小的类别就是预测结果
这就是很多小样本方法最核心的“雏形”。
八、但真实世界没这么简单:视频特征怎么来?
上面的代码默认我们已经有了“视频特征向量”。
可现实里最难的部分,往往恰恰是:
怎么把一段视频编码成足够好的特征?
因为一段视频不只是很多帧堆起来,它包含:
- 空间信息:这一帧里有什么
- 时间信息:动作是怎么变化的
- 局部信息:手、腿、物体接触区域
- 全局信息:整个动作过程的节奏与结构
这也是为什么一些小样本行为识别方法会强调:
- 局部帧级特征
- 全局时间上下文
- 时空关系建模
例如 STRM 这类方法就明确围绕“时空关系”展开设计。
九、初学者可以把主流方法分成哪几类?
如果你去读论文,会看到很多名字,但可以先粗略分成下面几类。
1. 基于匹配的方法(Matching-based)
核心思想是:
新视频和支持集视频,谁更像?
它们通常会做相似度比较,比如:
- 欧氏距离
- 余弦相似度
- 原型距离
- 关系网络打分
这类方法通常直观、可解释、适合入门。
2. 基于元学习的方法(Meta-learning)
核心思想是:
不是直接学“分类器”,而是学“如何快速学会一个新任务”。
也就是说,模型要学会一种能力:
- 以后遇到新类别
- 只看几个样本
- 就能很快适应
很多 few-shot 方法都会使用 episodic training,本质上就是在模拟“未来只给你几个样本”的场景。
3. 基于预训练 + 适配的方法
这类方法近几年非常常见。
先在大规模数据上训练一个通用模型,学到比较好的视频表示;
然后在小样本任务上只做轻量适配,而不是全部重训。
这样做的好处是:
- 利用已有大模型的知识
- 降低小数据下过拟合的风险
Task-Adapter 这类工作就属于很典型的“冻结主干、进行任务特定适配”的路线。
十、一个更生动的例子:为什么“时间”这么重要?
假设我们要区分两个动作:
- 坐下
- 站起
如果只截取中间某一帧,可能都只是“人 + 椅子 + 半蹲姿势”。
但如果看视频顺序:
-
站立 → 弯腿 → 接触椅子 → 坐稳
这是“坐下” -
坐着 → 身体前倾 → 用腿发力 → 站直
这是“站起”
所以很多行为识别问题,真正的答案不是藏在某一帧里,而是藏在:
帧与帧之间的变化关系里
这也是为什么“行为识别”不能简单等同于“每帧做图像分类再投票”。
十一、第二段代码:一个“视频特征 + 原型分类”的简化流程
下面用更接近任务流程的方式写一段示意代码。仍然是 JavaScript 风格,重点不是运行,而是帮助理解整个 pipeline。
function extractVideoFeature(videoFrames) {
// 这里只是示意:
// 真实情况会由视频模型提取时空特征
let motionScore = 0;
let appearanceScore = 0;
for (const frame of videoFrames) {
motionScore += frame.motion;
appearanceScore += frame.appearance;
}
return [
motionScore / videoFrames.length,
appearanceScore / videoFrames.length
];
}
function buildClassPrototype(videos) {
const features = videos.map(video => extractVideoFeature(video));
return average(features);
}
function cosineSimilarity(a, b) {
let dot = 0;
let normA = 0;
let normB = 0;
for (let i = 0; i < a.length; i++) {
dot += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
}
function predictAction(supportVideos, queryVideo) {
const queryFeature = extractVideoFeature(queryVideo);
let bestLabel = null;
let bestScore = -Infinity;
for (const label in supportVideos) {
const prototype = buildClassPrototype(supportVideos[label]);
const score = cosineSimilarity(queryFeature, prototype);
if (score > bestScore) {
bestScore = score;
bestLabel = label;
}
}
return bestLabel;
}
这段代码对应的思路是:
- 一段视频先提取特征
- 每个类别的多个支持视频形成原型
- 查询视频和各类别原型计算相似度
- 分数最高的类别就是预测结果
这就是“小样本行为识别”的最基本骨架。
十二、真实研究里还会解决哪些更细的问题?
到了这里,你已经能理解“它在干什么”了。
再往下一步,就是理解研究人员在优化哪些细节。
1. 怎么减少支持集里的“坏样本”影响?
有些支持视频质量很差:
- 拍糊了
- 动作没做完整
- 背景干扰太大
一些方法会引入注意力机制,降低坏样本权重。
例如 PAL 这类方法就讨论了如何应对支持集离群点和类别重叠问题。
2. 怎么更好地理解长时间动作?
像“投篮”“倒水”“开门”这类动作,并不是一个瞬时动作,而是一个连续过程。
有些方法会专门强化时间建模,甚至关注更细粒度的时间原子动作。
3. 能不能借助图文模型或大模型知识?
近年也出现了结合预训练视觉语言模型、任务适配模块等方向的方法,思路是把大模型已有的泛化能力迁移到小样本动作任务里。
十三、初学者最容易踩的几个误区
误区 1:以为“小样本”只是“数据少一点”
不是。
它不是从 10 万样本降到 1 万样本,而往往是:
- 每类只有 1 个
- 每类只有 5 个
- 甚至是从未见过的新类别
误区 2:以为视频识别就是“逐帧识别”
不够。
行为识别关心的是动作过程,不是单帧标签投票。
误区 3:以为模型学到的是“动作本质”
很多时候,模型学到的是背景、摄像角度、衣服颜色这些捷径。
所以研究里非常重视泛化能力和抗干扰能力。
误区 4:以为论文只是“换个网络堆参数”
其实不少改进点都很具体:
- 怎么抽取时序信息
- 怎么比较支持集和查询集
- 怎么避免少样本过拟合
- 怎么利用预训练知识
十四、如果你现在想入门,建议怎么学?
给初学者一个实用路线:
第 1 步:先学懂三个基础概念
- 图像分类
- 视频动作识别
- 小样本学习
第 2 步:理解 few-shot 的任务设置
重点搞清楚:
- N-way K-shot
- support/query
- episodic training
第 3 步:先自己实现一个“原型网络”玩具版
哪怕不是真视频,只用二维向量做分类,也能帮助你建立直觉。
第 4 步:再去看真实论文
可以优先关注这些关键词:
- prototype
- matching
- meta-learning
- temporal modeling
- adapter
- CLIP / pretrained backbone
第 5 步:找开源代码跑通一个 baseline
目前公开实现并不总是很完整,但已经有一些共享代码库在整理和复现 few-shot action recognition 方法,适合做入门实验参考。
十五、用一句最通俗的话总结它
如果让我只用一句话解释“小样本行为识别”,我会这样说:
它是在教电脑:只看几个示范视频,也要学会认出一个动作。
难点不只是“样本少”,还包括:
- 行为发生在时间里
- 同类差异大、异类差异小
- 视频噪声多
- 容易过拟合
而主流方法的共同目标,就是:
学到一个好的视频表示,并让模型具备“少看几个例子就能快速判断”的能力。
十六、结语
小样本行为识别是一个很有代表性的方向,因为它很贴近真实世界:
现实世界里,数据并不总是充足;
真正昂贵、有价值、稀有的行为样本,往往恰恰是最少的。
所以,这个方向的价值不仅在于“论文很前沿”,更在于它回答了一个现实问题:
当数据不够时,机器还能不能学会理解人的动作?
答案是:可以,但前提是我们要让模型学会更聪明地“看”、更合理地“比”、更稳健地“泛化”。
参考阅读
- 小样本行为识别综述,对该领域的问题设置、方法分类和挑战做了系统梳理。
https://arxiv.org/abs/2407.14744 - PAL 论文,适合了解 episodic training、prototype 和注意力机制如何结合。
https://arxiv.org/abs/2101.08085 - STRM 论文,适合理解为什么时空关系建模对 few-shot action recognition 很关键。
https://openaccess.thecvf.com/content/CVPR2022/papers/Thatipelli_Spatio-Temporal_Relation_Modeling_for_Few-Shot_Action_Recognition_CVPR_2022_paper.pdf - Task-Adapter,适合理解“预训练骨干 + 轻量适配”的路线。
https://openreview.net/forum?id=5aXZqp3KII - 复现代码库与官方实现,可用于入门实验。
https://github.com/tobyperrett/few-shot-action-recognition