自监督学习与对比学习详解
自监督学习 (Self-Supervised Learning)
例子:图像旋转预测
自监督学习的一个简单例子是图像旋转预测任务。在这个任务中,我们对输入图像进行随机旋转,然后要求模型预测图像旋转的角度。这种方式不需要手动标注数据,因为旋转的角度是已知的。
代码示例
import torch
import torch.nn as nn
import torchvision.transforms as transforms
from torch.utils.data import DataLoader, Dataset
from torchvision.datasets import CIFAR10
import random
# 创建自定义数据集
class RotatedCIFAR10(Dataset):
def __init__(self, root, train=True, transform=None, download=False):
self.dataset = CIFAR10(root=root, train=train, transform=transform, download=download)
self.rotations = [0, 90, 180, 270] # 定义旋转角度
def __len__(self):
return len(self.dataset)
def __getitem__(self, index):
img, _ = self.dataset[index] # 忽略原始标签
rotation = random.choice(self.rotations) # 随机选择一个旋转角度
rotated_img = transforms.functional.rotate(img, rotation) # 对图像进行旋转
return rotated_img, rotation // 90 # 返回旋转后的图像和旋转角度的标签(0, 1, 2, 3)
# 定义简单的CNN模型
class SimpleCNN(nn.Module):
def __init__(self, num_classes=4): # 旋转角度分类为4类
super(SimpleCNN, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(3, 32, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2),
nn.Conv2d(32, 64, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2)
)
self.fc = nn.Sequential(
nn.Linear(64*8*8, 128),
nn.ReLU(),
nn.Linear(128, num_classes)
)
def forward(self, x):
x = self.conv(x)
x = x.view(x.size(0), -1)
x = self.fc(x)
return x
# 超参数和数据准备
batch_size = 64
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,))])
train_dataset = RotatedCIFAR10(root='./data', train=True, transform=transform, download=True)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
# 模型、损失函数和优化器
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = SimpleCNN().to(device)
criterion = nn.CrossEntropyLoss() # 使用交叉熵损失函数进行分类
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# 训练模型
num_epochs = 10
for epoch in range(num_epochs):
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images) # 前向传播
loss = criterion(outputs, labels) # 计算损失
optimizer.zero_grad()
loss.backward() # 反向传播
optimizer.step() # 优化更新
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
解释
-
自定义数据集:
-
RotatedCIFAR10
类继承自Dataset
,用于加载旋转后的CIFAR-10数据集。 -
__getitem__
方法随机选择旋转角度并返回旋转后的图像和相应的标签。 - 这种方式使得我们能够使用旋转角度作为标签进行训练,而不需要人工标注数据。这就是自监督学习的体现。
-
-
简单的CNN模型:
-
SimpleCNN
类定义了一个简单的卷积神经网络,用于分类旋转后的图像。 -
forward
方法实现前向传播。
-
-
训练过程:
- 使用
DataLoader
加载数据。 - 定义损失函数和优化器。
- 在训练循环中进行前向传播、计算损失、反向传播和参数更新。
- 使用
对比学习 (Contrastive Learning)
例子:Siamese Network
对比学习的一个常见例子是Siamese网络。该网络由两个共享权重的子网络组成,分别处理一对输入图像。目标是最小化相似图像之间的距离,最大化不同图像之间的距离。
代码示例
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision.transforms as transforms
from torch.utils.data import DataLoader, Dataset
from torchvision.datasets import CIFAR10
import random
# 创建自定义数据集
class SiameseCIFAR10(Dataset):
def __init__(self, root, train=True, transform=None, download=False):
self.dataset = CIFAR10(root=root, train=train, transform=transform, download=download)
def __len__(self):
return len(self.dataset)
def __getitem__(self, index):
img1, label1 = self.dataset[index]
# 随机选择一个图像,可能是相同类也可能是不同类
index2 = random.randint(0, len(self.dataset) - 1)
img2, label2 = self.dataset[index2]
label = torch.tensor(int(label1 == label2), dtype=torch.float32) # 标签为1表示相同类,0表示不同类
return img1, img2, label
# 定义Siamese Network
class SiameseNetwork(nn.Module):
def __init__(self):
super(SiameseNetwork, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(3, 32, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2),
nn.Conv2d(32, 64, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2)
)
self.fc = nn.Sequential(
nn.Linear(64*8*8, 128),
nn.ReLU(),
nn.Linear(128, 128)
)
def forward_once(self, x):
x = self.conv(x)
x = x.view(x.size(0), -1)
x = self.fc(x)
return x
def forward(self, x1, x2):
out1 = self.forward_once(x1)
out2 = self.forward_once(x2)
return out1, out2
# 对比损失函数
class ContrastiveLoss(nn.Module):
def __init__(self, margin=1.0):
super(ContrastiveLoss, self).__init__()
self.margin = margin
def forward(self, output1, output2, label):
euclidean_distance = F.pairwise_distance(output1, output2)
loss_contrastive = torch.mean((1 - label) * torch.pow(euclidean_distance, 2) +
label * torch.pow(torch.clamp(self.margin - euclidean_distance, min=0.0), 2))
return loss_contrastive
# 超参数和数据准备
batch_size = 64
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,))])
train_dataset = SiameseCIFAR10(root='./data', train=True, transform=transform, download=True)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
# 模型、损失函数和优化器
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = SiameseNetwork().to(device)
criterion = ContrastiveLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# 训练模型
num_epochs = 10
for epoch in range(num_epochs):
for img1, img2, labels in train_loader:
img1, img2, labels = img1.to(device), img2.to(device), labels.to(device)
output1, output2 = model(img1, img2)
loss = criterion(output1, output2, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
解释
-
自定义数据集:
-
SiameseCIFAR10
类继承自Dataset
,用于加载图像对。 -
__getitem__
方法返回图像对及其相似性标签。
-
-
Siamese网络:
-
SiameseNetwork
类定义了一个共享权重的卷积神经网络,用于处理图像对。 -
forward_once
方法处理单个图像,forward
方法处理图像对。
-
-
对比损失函数:
-
ContrastiveLoss
类定义了对比损
-
失函数。
-
forward
方法计算两个输出之间的欧氏距离,并根据相似性标签计算损失。
-
训练过程:
- 使用
DataLoader
加载数据。 - 定义损失函数和优化器。
- 在训练循环中进行前向传播、计算损失、反向传播和参数更新。
- 使用
总结
自监督学习和对比学习都是利用无标签数据进行训练的方法。自监督学习通过设计预训练任务生成标签,而对比学习通过最小化相似样本之间的距离和最大化不同样本之间的距离来学习有用的特征。上面的例子详细展示了如何实现这两种学习方法,希望能帮助各位更好的理解。