《深度学习进阶》第9集:自监督学习与无监督学习
第9集:自监督学习与无监督学习
在深度学习的快速发展中,自监督学习(Self-Supervised Learning) 和 无监督学习(Unsupervised Learning) 成为了近年来备受关注的研究方向。尤其是在标注数据稀缺或获取成本高昂的情况下,这些方法为模型训练提供了强大的工具。本文将带你深入了解自监督学习的核心思想、代表性方法,并通过实战项目展示其应用。
一、知识点:自监督学习的核心思想
1. 核心思想
自监督学习是一种特殊的无监督学习方法,其核心思想是通过构造“伪标签”来生成监督信号,从而利用未标注的数据进行模型预训练。具体来说,它通过设计特定的任务(称为预训练任务),让模型从数据本身学习有用的特征表示。
例如,在图像领域,可以通过以下方式构造伪标签:
- 旋转预测:随机旋转图像,让模型预测旋转角度。
- 拼图复原:将图像分割成若干块并打乱顺序,让模型还原原始排列。
- 对比学习:通过正负样本对的设计,让模型学会区分相似和不相似的样本。
这种方法的优势在于,它可以充分利用大量未标注的数据,为后续的下游任务(如分类、检测等)提供高质量的特征表示。
2. 代表性方法
以下是几种经典的自监督学习方法:
-
SimCLR
SimCLR 是一种基于对比学习的方法,通过数据增强生成正样本对,并最大化同一图像的不同增强版本之间的相似性,同时最小化不同图像之间的相似性。
特点:简单高效,适合小规模数据集。 -
BYOL(Bootstrap Your Own Latent)
BYOL 不依赖负样本,而是通过两个网络(在线网络和目标网络)相互学习,逐步优化特征表示。
特点:无需复杂的负样本采样,性能优异。 -
MAE(Masked Autoencoder)
MAE 是一种基于掩码重建的方法,通过对输入数据的部分区域进行掩码处理,让模型预测被遮挡的内容。
特点:特别适合高维数据(如图像、视频),在视觉领域表现出色。
二、实战项目:使用 SimCLR 对 CIFAR-10 数据集进行无监督特征提取
1. 实验背景
CIFAR-10 是一个经典的图像分类数据集,包含 10 类物体的 60,000 张彩色图像。我们将使用 SimCLR 方法对 CIFAR-10 进行无监督特征提取,并将提取的特征用于后续的分类任务。
2. 实验步骤
- 数据增强:对每张图像进行两次不同的随机增强(如裁剪、翻转、颜色抖动),生成正样本对。
- 模型构建:搭建一个基于 ResNet 的编码器,用于提取图像特征。
- 对比损失:定义 InfoNCE 损失函数,最大化正样本对的相似性,最小化负样本对的相似性。
- 特征提取:在无监督预训练完成后,固定编码器权重,提取图像特征。
- 下游任务:将提取的特征输入到一个简单的线性分类器中,评估分类性能。
以下是使用 SimCLR 对 CIFAR-10 数据集进行无监督特征提取的完整代码实现。代码分为以下几个部分:
- 环境准备:安装必要的库并导入依赖。
- 数据增强:定义 SimCLR 所需的数据增强方法。
- 模型定义:构建 SimCLR 模型的核心组件(编码器、投影头)。
- 对比损失:实现 InfoNCE 损失函数。
- 训练过程:在 CIFAR-10 上进行无监督预训练。
- 特征提取与下游任务:使用预训练的编码器提取特征,并训练一个简单的分类器。
完整代码
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision.transforms as transforms
from torchvision.datasets import CIFAR10
from torch.utils.data import DataLoader
import torch.nn.functional as F
# 1. 环境准备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 2. 数据增强
class SimCLRTransform:
def __init__(self, size=32):
self.transform = transforms.Compose([
transforms.RandomResizedCrop(size=size),
transforms.RandomHorizontalFlip(),
transforms.RandomApply([transforms.ColorJitter(0.8, 0.8, 0.8, 0.2)], p=0.8),
transforms.RandomGrayscale(p=0.2),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
def __call__(self, x):
return [self.transform(x), self.transform(x)]
# 加载 CIFAR-10 数据集
transform = SimCLRTransform()
train_dataset = CIFAR10(root='./data', train=True, download=True, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=256, shuffle=True, num_workers=4)
# 3. 模型定义
class Encoder(nn.Module):
def __init__(self):
super(Encoder, self).__init__()
self.backbone = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(128, 256, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.AdaptiveAvgPool2d((1, 1))
)
self.fc = nn.Linear(256, 128)
def forward(self, x):
x = self.backbone(x)
x = torch.flatten(x, 1)
x = self.fc(x)
return F.normalize(x, dim=1)
class ProjectionHead(nn.Module):
def __init__(self):
super(ProjectionHead, self).__init__()
self.layers = nn.Sequential(
nn.Linear(128, 256),
nn.ReLU(),
nn.Linear(256, 128)
)
def forward(self, x):
return F.normalize(self.layers(x), dim=1)
encoder = Encoder().to(device)
projection_head = ProjectionHead().to(device)
# 4. 对比损失
class NTXentLoss(nn.Module):
def __init__(self, temperature=0.5):
super(NTXentLoss, self).__init__()
self.temperature = temperature
def forward(self, z1, z2):
batch_size = z1.shape[0]
z = torch.cat([z1, z2], dim=0)
sim_matrix = torch.exp(torch.mm(z, z.T) / self.temperature)
mask = torch.eye(batch_size * 2, dtype=torch.bool).to(device)
positives = sim_matrix[~mask].view(2 * batch_size, -1)
negatives = sim_matrix[mask].view(2 * batch_size, -1)
loss = -torch.log(positives / negatives.sum(dim=1)).mean()
return loss
criterion = NTXentLoss()
# 5. 训练过程
optimizer = optim.Adam(list(encoder.parameters()) + list(projection_head.parameters()), lr=1e-3)
def train(epoch):
encoder.train()
projection_head.train()
total_loss = 0
for batch_idx, (images, _) in enumerate(train_loader):
images1, images2 = images[0].to(device), images[1].to(device)
optimizer.zero_grad()
# 提取特征
h1 = encoder(images1)
h2 = encoder(images2)
# 投影到高维空间
z1 = projection_head(h1)
z2 = projection_head(h2)
# 计算对比损失
loss = criterion(z1, z2)
loss.backward()
optimizer.step()
total_loss += loss.item()
if batch_idx % 50 == 0:
print(f"Epoch [{epoch}], Step [{batch_idx}/{len(train_loader)}], Loss: {loss.item():.4f}")
print(f"Epoch [{epoch}], Average Loss: {total_loss / len(train_loader):.4f}")
# 训练模型
for epoch in range(1, 11): # 训练 10 个 epoch
train(epoch)
# 6. 特征提取与下游任务
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
# 使用预训练的编码器提取特征
def extract_features(loader):
encoder.eval()
features, labels = [], []
with torch.no_grad():
for images, targets in loader:
images = images.to(device)
h = encoder(images)
features.append(h.cpu())
labels.append(targets)
return torch.cat(features), torch.cat(labels)
train_features, train_labels = extract_features(train_loader)
# 训练一个简单的线性分类器
classifier = LogisticRegression(max_iter=1000)
classifier.fit(train_features.numpy(), train_labels.numpy())
# 测试集评估
test_dataset = CIFAR10(root='./data', train=False, download=True, transform=transforms.ToTensor())
test_loader = DataLoader(test_dataset, batch_size=256, shuffle=False)
test_features, test_labels = extract_features(test_loader)
predictions = classifier.predict(test_features.numpy())
accuracy = accuracy_score(test_labels.numpy(), predictions)
print(f"Test Accuracy: {accuracy * 100:.2f}%")
代码说明
-
数据增强:
SimCLRTransform
定义了 SimCLR 的数据增强策略,包括随机裁剪、水平翻转、颜色抖动、灰度化等。- 每张图像会生成两个增强版本,作为正样本对。
-
模型结构:
Encoder
是一个简单的卷积神经网络,用于提取图像特征。ProjectionHead
将特征映射到高维空间,以计算对比损失。
-
对比损失:
NTXentLoss
实现了 InfoNCE 损失,最大化正样本对的相似性,最小化负样本对的相似性。
-
训练过程:
- 使用无监督方式训练模型,不依赖标签信息。
- 每个 epoch 输出平均损失。
-
下游任务:
- 使用预训练的编码器提取特征,并训练一个逻辑回归分类器。
- 在测试集上评估分类准确率。
运行结果
- 训练过程中会输出每个 epoch 的损失值。
- 最终会在测试集上输出分类准确率,验证无监督特征提取的效果。
注意事项
- 如果 GPU 可用,建议使用 GPU 加速训练。
- 可以调整超参数(如学习率、批量大小、温度系数)以优化性能。
- 如果需要更高的精度,可以使用更复杂的编码器(如 ResNet)。
3. 实验结果
通过 SimCLR 预训练的特征提取器,我们在 CIFAR-10 上的分类准确率显著高于随机初始化的模型。这证明了自监督学习在特征提取中的强大能力。
三、图示:直观理解自监督学习
1. 自监督学习框架图
下图展示了 SimCLR 的整体框架:
+-------------------+ +-------------------+
| 图像增强1 | | 图像增强2 |
| (正样本对) | | (正样本对) |
+-------------------+ +-------------------+
↓ ↓
+------------+ +------------+
| 编码器 (f) | | 编码器 (f) |
+------------+ +------------+
↓ ↓
+------------+ +------------+
| 投影头 (g) | | 投影头 (g) |
+------------+ +------------+
↓ ↓
+---------------------------------------------+
| 对比损失 (InfoNCE) |
+---------------------------------------------+
2. 特征空间可视化
通过 t-SNE 将高维特征降维到二维空间,可以清晰地看到不同类别的样本在特征空间中形成了明显的聚类效果。
四、前沿关联:自监督学习在大模型预训练中的广泛应用
自监督学习不仅在小规模数据集上表现出色,还成为了大规模预训练模型的重要基石。例如:
- 自然语言处理:BERT、RoBERTa 等模型通过掩码语言建模(MLM)实现了自监督学习。
- 计算机视觉:CLIP、DINO 等模型通过对比学习实现了跨模态的特征对齐。
- 多模态学习:FLAVA、M6 等模型结合文本和图像的自监督学习方法,推动了多模态领域的突破。
随着大模型的兴起,自监督学习的应用场景更加广泛,从基础研究到工业落地,都展现出了巨大的潜力。
总结
自监督学习作为一种强大的无监督学习方法,正在深刻改变机器学习的研究范式。通过构造伪标签和设计预训练任务,它能够从海量未标注数据中提取出高质量的特征表示。无论是学术研究还是实际应用,自监督学习都为我们提供了全新的思路和工具。
如果你对自监督学习感兴趣,不妨尝试复现本文的 SimCLR 实战项目,亲身体验其魅力!