Pytorch深度学习框架60天进阶学习计划 - 第53天
自监督学习范式(二)
在第一部分中,我们详细比较了对比学习与掩码建模的差异,并初步了解了MoCo的动量编码器机制。现在,让我们继续深入探讨解锁自监督学习的更多奥秘!
第二部分:掩码建模原理与MoCo进阶分析
8. 掩码建模深入解析
掩码建模(Masked Modeling)最初在NLP领域的BERT模型中取得了巨大成功,后来被引入到计算机视觉领域。让我们更深入地了解这种自监督学习方法。
8.1 掩码建模的工作原理
掩码建模的基本思想是:
- 随机掩盖(mask)输入数据的一部分
- 训练模型预测或恢复被掩盖的内容
- 通过这种方式,模型被迫理解数据的内部结构和语义
在不同领域中,掩码建模的具体实现有所不同:
领域 | 代表模型 | 掩码方式 | 预测目标 |
---|---|---|---|
自然语言处理 | BERT | 随机掩盖15%的单词 | 预测被掩盖的单词 |
计算机视觉 | MAE | 随机掩盖75%的图像块 | 重建被掩盖的像素 |
跨模态 | BEiT | 随机掩盖图像块 | 预测视觉token |
语音 | wav2vec 2.0 | 掩盖时间段 | 预测潜在表示 |
8.2 视觉领域的掩码建模实现
让我们看一个简化的MAE(Masked Autoencoder)实现,这是ViT(Vision Transformer)架构下的掩码建模方法:
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision.transforms as T
from torchvision.datasets import ImageFolder
from torch.utils.data import DataLoader
import numpy as np
import math# 定义图像块Embedding
class PatchEmbed(nn.Module):def __init__(self, img_size=224, patch_size=16, in_chans=3, embed_dim=768):super().__init__()self.img_size = img_sizeself.patch_size = patch_sizeself.n_patches = (img_size // patch_size) ** 2self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_size)def forward(self, x):x = self.proj(x) # (B, embed_dim, H', W')x = x.flatten(2) # (B, embed_dim, H'*W')x = x.transpose(1, 2) # (B, H'*W', embed_dim)return x# 简化的Transformer编码器块
class Block(nn.Module):def __init__(self, dim, num_heads, mlp_ratio=4.0):super().__init__()self.norm1 = nn.LayerNorm(dim)self.attn = nn.MultiheadAttention(dim, num_heads)self.norm2 = nn.LayerNorm(dim)mlp_hidden_dim = int(dim * mlp_ratio)self.mlp = nn.Sequential(nn.Linear(dim, mlp_hidden_dim),nn.GELU(),nn.Linear(mlp_hidden_dim, dim))def forward(self, x):# 自注意力机制norm_x = self.norm1(x)attn_output, _ = self.attn(norm_x, norm_x, norm_x)x = x + attn_output# MLPx = x + self.mlp(self.norm2(x))return x# 简化的MAE模型
class MAE(nn.Module):def __init__(self, img_size=224, patch_size=16, in_chans=3,embed_dim=768, encoder_depth=12,decoder_depth=8, num_heads=12,decoder_embed_dim=512,mask_ratio=0.75):super().__init__()# 图像块嵌入self.patch_embed = PatchEmbed(img_size=img_size, patch_size=patch_size, in_chans=in_chans, embed_dim=embed_dim)self.mask_ratio = mask_ratioself.patch_size = patch_sizenum_patches = self.patch_embed.n_patches# 位置嵌入self.pos_embed = nn.Parameter(torch.zeros(1, num_patches + 1, embed_dim))self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim))# 编码器self.encoder_blocks = nn.ModuleList([Block(embed_dim, num_heads) for _ in range(encoder_depth)])self.encoder_norm = nn.LayerNorm(embed_dim)# 解码器self.decoder_embed = nn.Linear(embed_dim, decoder_embed_dim)self.mask_token = nn.Parameter(torch.zeros(1, 1, decoder_embed_dim))self.decoder_pos_embed = nn.Parameter(torch.zeros(1, num_patches + 1, decoder_embed_dim))self.decoder_blocks = nn.ModuleList([Block(decoder_embed_dim, num_heads) for _ in range(decoder_depth)])self.decoder_norm = nn.LayerNorm(decoder_embed_dim)self.decoder_pred = nn.Linear(decoder_embed_dim, patch_size**2 * in_chans)# 初始化self._init_weights()def _init_weights(self):# 简化的初始化方法nn.init.normal_(self.pos_embed, std=0.02)nn.init.normal_(self.cls_token, std=0.02)nn.init.normal_(self.mask_token, std=0.02)nn.init.normal_(self.decoder_pos_embed, std=0.02)def random_masking(self, x):N, L, D = x.shape # batch, length, dimlen_keep = int(L * (1 - self.mask_ratio))# 生成随机序列noise = torch.rand(N, L, device=x.device) # noise in [0, 1]# 按照每个样本的随机序列排序ids_shuffle = torch.argsort(noise, dim=1)ids_restore = torch.argsort(ids_shuffle, dim=1)# 保留的tokensids_keep = ids_shuffle[:, :len_keep]x_masked = torch.gather(x, dim=1, index=ids_keep.unsqueeze(-1).repeat(1, 1, D))# 生成掩码mask = torch.ones([N, L], device=x.device)mask[:, :len_keep] = 0mask = torch.gather(mask, dim=1, index=ids_restore)return x_masked, mask, ids_restoredef forward_encoder(self, x):# 图像块嵌入x = self.patch_embed(x)# 添加位置嵌入x = x + self.pos_embed[:, 1:, :]# 随机掩码x, mask, ids_restore = self.random_masking(x)# 添加cls tokencls_token = self.cls_token + self.pos_embed[:, :1, :]cls_tokens = cls_token.expand(x.shape[0], -1, -1)x = torch.cat((cls_tokens, x), dim=1)# 应用Transformer块for blk in self.encoder_blocks:x = blk(x)x = self.encoder_norm(x)return x, mask, ids_restoredef forward_decoder(self, x, ids_restore):# 嵌入tokensx = self.decoder_embed(x)# 添加mask tokensmask_tokens = self.mask_token.repeat(x.shape[0], ids_restore.shape[1] - 1, 1)x_ = torch.cat([x[:, 1:, :], mask_tokens], dim=1)# 未排序的tokens + mask tokensx_ = torch.gather(x_, dim=1, index=ids_restore.unsqueeze(-1).repeat(1, 1, x.shape[2]))# 添加cls tokenx = torch.cat([x[:, :1, :], x_], dim=1)# 添加位置嵌入x = x + self.decoder_pos_embed# 应用Transformer块for blk in self.decoder_blocks:x = blk(x)x = self.decoder_norm(x)# 预测像素值x = self.decoder_pred(x)# 移除cls tokenx = x[:, 1:, :]return xdef forward(self, imgs):# 编码器: 输入图像,输出潜在表示latent, mask, ids_restore = self.forward_encoder(imgs)# 解码器: 潜在表示 -> 重建图像pred = self.forward_decoder(latent, ids_restore)# 计算原始图像的块target = patchify(imgs, self.patch_size)# 计算重建损失 (仅计算掩码区域)loss = F.mse_loss(pred, target, reduction='none')loss = loss.mean(dim=-1) # [N, L], 每个块的平均损失loss = (loss * mask).sum() / mask.sum() # 掩码块的平均损失return loss, pred, mask# 辅助函数:将图像转换为块
def patchify(imgs, patch_size):"""将批次图像转换为块。imgs: (N, 3, H, W)x: (N, L, patch_size**2 * 3)"""N, C, H, W = imgs.shapep = patch_sizeassert H == W and H % p == 0h = w = H // px = imgs.reshape(N, C, h, p, w, p)x = x.permute(0, 2, 4, 3, 5, 1).reshape(N, h * w, p**2 * C)return x# 辅助函数:将块转换回图像
def unpatchify(x, patch_size, in_chans=3):"""将块转换回图像。x: (N, L, patch_size**2 * 3)imgs: (N, 3, H, W)"""N, L, _ = x.shapep = patch_sizeh = w = int(math.sqrt(L))x = x.reshape(N, h, w, p, p, in_chans)x = x.permute(0, 5, 1, 3, 2, 4).reshape(N, in_chans, h * p, w * p)return x# 训练MAE模型的函数
def train_mae(model, data_loader, optimizer, device):model.train()total_loss = 0for batch_idx, (imgs, _) in enumerate(data_loader):imgs = imgs.to(device)# 前向传播loss, _, _ = model(imgs)# 反向传播和优化optimizer.zero_grad()loss.backward()optimizer.step()total_loss += loss.item()if batch_idx % 10 == 0:print(f'Batch: {batch_idx}, Loss: {loss.item():.4f}')avg_loss = total_loss / len(data_loader)return avg_loss# 可视化MAE重建结果的函数
def visualize_reconstruction(model, imgs, device):model.eval()with torch.no_grad():imgs = imgs.to(device)loss, pred, mask = model(imgs)# 将预测转换回图像pred = unpatchify(pred, model.patch_size)# 应用掩码可视化mask = mask.unsqueeze(-1).repeat(1, 1, model.patch_size**2 * 3)mask = mask.reshape(mask.shape[0], -1, model.patch_size, model.patch_size, 3)mask = mask.permute(0, 4, 1, 2, 3).reshape(mask.shape[0], 3, -1, model.patch_size * int(math.sqrt(mask.shape[1])))return imgs, pred, mask# 示例:如何使用上述代码
def main():# 设置设备device = torch.device("cuda" if torch.cuda.is_available() else "cpu")# 创建MAE模型model = MAE(img_size=224,patch_size=16,in_chans=3,embed_dim=768,encoder_depth=12,decoder_depth=8,num_heads=12,decoder_embed_dim=512,mask_ratio=0.75).to(device)# 设置优化器optimizer = torch.optim.AdamW(model.parameters(), lr=1.5e-4, weight_decay=0.05)# 数据转换transform = T.Compose([T.RandomResizedCrop(224),T.RandomHorizontalFlip(),T.ToTensor(),T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])])# 创建数据集和加载器# base_dataset = ImageFolder('path/to/dataset', transform=transform)# data_loader = DataLoader(base_dataset, batch_size=128, shuffle=True, num_workers=4)# 训练过程# for epoch in range(100):# avg_loss = train_mae(model, data_loader, optimizer, device)# print(f'Epoch: {epoch}, Average Loss: {avg_loss:.4f}')print("MAE模型定义和训练流程已完成")if __name__ == "__main__":main()
以上是一个简化的MAE(Masked Autoencoder)实现,展示了视觉领域中掩码建模的工作原理。
8.3 掩码建模的流程图
9. MoCo进阶分析:从一致性到泛化性
我们在第一部分已经介绍了MoCo的基本原理,现在让我们深入探讨动量编码器的进阶特性。
9.1 参数更新对比
为了理解动量编码器的重要性,我们可以比较不同参数更新策略的影响:
更新策略 | 公式 | 特点 | 挑战 |
---|---|---|---|
End-to-End | θ_k = θ_q | 最直接,两个编码器相同 | 导致表示崩溃 |
Stop-gradient | θ_k = θ_q(但不传梯度) | 防止崩溃,但表示不一致 | 不稳定,难收敛 |
动量更新 | θ_k = m * θ_k + (1 - m) * θ_q | 平滑更新,保持一致性 | 需要调整动量系数 |
9.2 队列管理的重要性
MoCo的队列机制实际上是一个动态内存库,它具有几个重要特性:
- 样本多样性:队列可以存储大量不同样本的表示
- 样本一致性:动量更新确保队列中的表示相对一致
- 内存效率:队列的大小不影响批大小,节省内存
- 训练稳定性:逐步更新队列,而不是一次性替换
以下是MoCo队列管理的更详细实现:
class MoCoQueue:def __init__(self, dim, K):self.K = K # 队列大小self.dim = dim # 特征维度# 初始化队列self.queue = torch.randn(dim, K)self.queue = F.normalize(self.queue, dim=0)# 初始化指针self.ptr = 0def enqueue_dequeue(self, keys):"""更新队列keys: 当前批次的key特征 (batch_size, dim)"""batch_size = keys.shape[0]# 将keys转置以匹配队列格式keys = keys.T # (dim, batch_size)# 替换队列中的keysptr = int(self.ptr)# 处理环形队列更新if ptr + batch_size <= self.K:self.queue[:, ptr:ptr + batch_size] = keyselse:# 处理越界情况remaining = self.K - ptrself.queue[:, ptr:] = keys[:, :remaining]self.queue[:, :batch_size-remaining] = keys[:, remaining:]# 更新指针ptr = (ptr + batch_size) % self.Kself.ptr = ptrreturn self.queuedef get_negative_samples(self):"""获取当前队列作为负样本"""return self.queue.clone().detach()
9.3 深度分析:动量系数对特征一致性的影响
MoCo的动量系数m是一个关键超参数,它直接影响特征的一致性和表示质量。让我们深入研究不同动量系数的影响:
import torch
import matplotlib.pyplot as plt
import numpy as np# 模拟不同动量系数对参数更新的影响
def simulate_momentum_update(m_values, steps=100):# 初始参数theta_q = torch.ones(1)results = {}for m in m_values:theta_k = torch.zeros(1) # 初始化key编码器参数history = [theta_k.item()]# 模拟梯度更新(简化,假设theta_q每步增加0.1)for step in range(steps):theta_q += 0.1# 动量更新theta_k = m * theta_k + (1 - m) * theta_qhistory.append(theta_k.item())results[m] = historyreturn results# 可视化不同动量值的影响
def plot_momentum_effect():m_values = [0.9, 0.99, 0.999, 0.9999]results = simulate_momentum_update(m_values)plt.figure(figsize=(10, 6))for m, history in results.items():plt.plot(history, label=f'm={m}')# 添加query编码器的参数线(简化模型)plt.plot([i*0.1 for i in range(101)], '--', label='query encoder')plt.xlabel('Update Steps')plt.ylabel('Parameter Value')plt.title('Effect of Momentum Coefficient on Parameter Updates')plt.legend()plt.grid(True)plt.savefig('momentum_effect.png')# 计算参数之间的差距for m, history in results.items():history = np.array(history)query_params = np.array([i*0.1 for i in range(len(history))])avg_diff = np.mean(np.abs(history - query_params))print(f"m={m}, Average parameter difference: {avg_diff:.4f}")# 在实际使用时可以调用此函数
# plot_momentum_effect()
典型的实验结果显示:
- m=0.9时,参数更新较快,但一致性较弱
- m=0.999时,参数更新较慢,但保持较好的一致性
- MoCo作者推荐m=0.999作为最佳平衡点
10. 实际操作:MoCo的改进版本与训练技巧
随着研究的深入,MoCo有了多个改进版本,如MoCo v2和MoCo v3。让我们看看这些改进以及实用的训练技巧。
10.1 MoCo v2的改进
MoCo v2相对于v1的主要改进包括:
-
引入SimCLR中的数据增强策略
- 更强的颜色抖动
- 添加模糊增强
-
在编码器中加入MLP投影头
- 将特征映射到较低维度的空间
- 改善特征表示质量
-
增加温度参数τ
- 调整对比损失的平滑度
- 通常设置为0.2
让我们实现MoCo v2的投影头部分:
# MoCo v2的MLP投影头
class MLPHead(nn.Module):def __init__(self, in_dim, hidden_dim, out_dim):super(MLPHead, self).__init__()self.mlp = nn.Sequential(nn.Linear(in_dim, hidden_dim),nn.BatchNorm1d(hidden_dim),nn.ReLU(inplace=True),nn.Linear(hidden_dim, out_dim))def forward(self, x):return self.mlp(x)# 修改MoCo模型以整合MLP投影头
class MoCoV2(nn.Module):def __init__(self, base_encoder, dim=128, mlp_dim=2048, K=65536, m=0.999, T=0.2):super(MoCoV2, self).__init__()# 创建编码器self.K = Kself.m = mself.T = T# 基础编码器 (没有分类头)self.encoder_q = base_encoder(pretrained=True)self.encoder_k = base_encoder(pretrained=True)# 获取特征维度in_dim = self.encoder_q.fc.in_featuresself.encoder_q.fc = nn.Identity()self.encoder_k.fc = nn.Identity()# MLP投影头self.projection_q = MLPHead(in_dim, mlp_dim, dim)self.projection_k = MLPHead(in_dim, mlp_dim, dim)# 初始化key编码器为query编码器的副本for param_q, param_k in zip(self.encoder_q.parameters(), self.encoder_k.parameters()):param_k.data.copy_(param_q.data)param_k.requires_grad = False# 初始化投影头for param_q, param_k in zip(self.projection_q.parameters(), self.projection_k.parameters()):param_k.data.copy_(param_q.data)param_k.requires_grad = False# 创建队列self.register_buffer("queue", torch.randn(dim, K))self.queue = F.normalize(self.queue, dim=0)self.register_buffer("queue_ptr", torch.zeros(1, dtype=torch.long))@torch.no_grad()def _momentum_update_key_encoder(self):# 更新编码器for param_q, param_k in zip(self.encoder_q.parameters(), self.encoder_k.parameters()):param_k.data = param_k.data * self.m + param_q.data * (1. - self.m)# 更新投影头for param_q, param_k in zip(self.projection_q.parameters(), self.projection_k.parameters()):param_k.data = param_k.data * self.m + param_q.data * (1. - self.m)@torch.no_grad()def _dequeue_and_enqueue(self, keys):batch_size = keys.shape[0]ptr = int(self.queue_ptr)if ptr + batch_size <= self.K:self.queue[:, ptr:ptr + batch_size] = keys.Telse:# 处理队列结束情况remaining = self.K - ptrself.queue[:, ptr:] = keys[:remaining].Tself.queue[:, :batch_size-remaining] = keys[remaining:].T# 更新指针ptr = (ptr + batch_size) % self.Kself.queue_ptr[0] = ptrdef forward(self, im_q, im_k):# 特征提取q = self.encoder_q(im_q) # 编码query图像q = self.projection_q(q) # 投影q = F.normalize(q, dim=1) # 归一化with torch.no_grad():# 动量更新key编码器self._momentum_update_key_encoder()k = self.encoder_k(im_k) # 编码key图像k = self.projection_k(k) # 投影k = F.normalize(k, dim=1) # 归一化# 计算logits# 正样本对的logits: Nx1l_pos = torch.einsum('nc,nc->n', [q, k]).unsqueeze(-1)# 负样本对的logits: NxKl_neg = torch.einsum('nc,ck->nk', [q, self.queue.clone().detach()])# logits: Nx(1+K)logits = torch.cat([l_pos, l_neg], dim=1)# 应用温度系数logits /= self.T# 标签: 正样本在第一位labels = torch.zeros(logits.shape[0], dtype=torch.long, device=logits.device)# 更新队列self._dequeue_and_enqueue(k)return logits, labels
10.2 MoCo v3的进一步改进
MoCo v3是对MoCo系列的进一步改进,主要特点包括:
-
采用Vision Transformer作为骨干网络
- 适应Transformer在视觉领域的崛起
- 提供更好的特征表示
-
简化框架
- 移除队列机制
- 依赖大批次和停止梯度操作
-
增强数据增强
- 多裁剪技术
- 随机遮挡
10.3 MoCo训练技巧
基于大量实验和文献,以下是使用MoCo进行训练的一些实用技巧:
-
数据增强技巧
- 使用强数据增强:随机裁剪、颜色抖动、高斯模糊
- 确保两个视图有足够的差异但保持语义不变
-
超参数选择
- 动量系数m=0.999通常效果最佳
- 温度系数T=0.2比默认的0.07更稳定
- 队列大小K=65536提供足够的负样本
-
训练策略
- 使用较长的训练周期(200-800轮)
- 余弦学习率调度
- 较大的权重衰减(0.0001)
-
监控训练
- 观察正样本和负样本的相似度分布
- 避免表示崩溃(所有特征变得相似)
这些技巧体现在以下训练脚本中:
def train_mocov2(model, train_loader, optimizer, scheduler, epoch, device):model.train()loss_function = nn.CrossEntropyLoss()total_loss = 0correct = 0total = 0for batch_idx, (im_q, im_k, _) in enumerate(train_loader):im_q, im_k = im_q.to(device), im_k.to(device)# 计算输出和损失output, target = model(im_q, im_k)target = target.to(device)loss = loss_function(output, target)# InfoNCE准确率(用于监控)pred = output.argmax(dim=1)correct += pred.eq(target).sum().item()total += target.size(0)# 反向传播optimizer.zero_grad()loss.backward()optimizer.step()total_loss += loss.item()# 打印进度if batch_idx % 10 == 0:print(f'Epoch: {epoch}, Batch: {batch_idx}, Loss: {loss.item():.4f}, 'f'Acc: {100. * correct / total:.2f}%')# 更新学习率scheduler.step()avg_loss = total_loss / len(train_loader)avg_acc = 100. * correct / totalreturn avg_loss, avg_acc
11. 自监督学习范式的融合与未来趋势
随着研究的深入,自监督学习的不同范式开始相互融合,产生了许多混合方法。
11.1 对比学习与掩码建模的融合
最近的研究尝试将对比学习和掩码建模的优势结合起来,例如:
- BEiT: 先使用dVAE学习视觉词典,然后使用BERT风格的掩码预测任务
- Data2Vec: 同时支持图像、语音和文本的自监督学习框架
- SimMIM: 简化的掩码图像建模,借鉴了对比学习的一些思想
11.2 融合方法的实现示例
以下是一个结合对比学习和掩码建模思想的简化实现:
class HybridModel(nn.Module):def __init__(self, base_encoder, dim=768, mask_ratio=0.4):super(HybridModel, self).__init__()# 共享编码器骨干self.encoder = base_encoder# 对比学习投影头self.cl_projector = nn.Sequential(nn.Linear(dim, dim),nn.ReLU(),nn.Linear(dim, 128))# 掩码建模解码器self.mask_decoder = nn.Sequential(nn.Linear(dim, dim),nn.ReLU(),nn.Linear(dim, dim))self.mask_ratio = mask_ratiodef random_masking(self, x):N, L, D = x.shapelen_keep = int(L * (1 - self.mask_ratio))# 随机掩码noise = torch.rand(N, L, device=x.device)ids_shuffle = torch.argsort(noise, dim=1)ids_restore = torch.argsort(ids_shuffle, dim=1)# 保留tokensids_keep = ids_shuffle[:, :len_keep]x_masked = torch.gather(x, dim=1, index=ids_keep.unsqueeze(-1).repeat(1, 1, D))# 生成掩码mask = torch.ones([N, L], device=x.device)mask[:, :len_keep] = 0mask = torch.gather(mask, dim=1, index=ids_restore)return x_masked, mask, ids_restoredef forward(self, img1, img2=None):# 提取特征feat1 = self.encoder(img1)# 掩码建模路径x_masked, mask, ids_restore = self.random_masking(feat1)# 重建pred = self.mask_decoder(x_masked)# 对比学习路径(如果提供了第二个视图)if img2 is not None:feat2 = self.encoder(img2)q1 = F.normalize(self.cl_projector(feat1), dim=1)q2 = F.normalize(self.cl_projector(feat2), dim=1)return pred, mask, ids_restore, q1, q2return pred, mask, ids_restore, None, None
11. 在实际项目中应用自监督学习的最佳实践
在实际项目中应用自监督学习时,以下是一些最佳实践:
11.1 场景选择
场景 | 推荐方法 | 理由 |
---|---|---|
数据量小但未标注 | 对比学习 (MoCo) | 对小数据集具有较好的泛化能力 |
大规模未标注数据 | 掩码建模 (MAE) | 可以充分利用大数据的优势 |
需要迁移到多个下游任务 | 混合方法 | 提供更通用的特征表示 |
特定领域数据 (医学影像等) | 领域适应的对比学习 | 可以结合领域知识设计正负样本对 |
11.2 最佳实践流程图
11.3 性能基准比较
下面是不同自监督方法在ImageNet上的线性探测准确率比较:
方法 | 主干网络 | Top-1准确率 | 预训练时间 | 内存需求 |
---|---|---|---|---|
MoCo v1 | ResNet-50 | 60.6% | 中等 | 中等 |
MoCo v2 | ResNet-50 | 67.5% | 中等 | 中等 |
SimCLR | ResNet-50 | 69.3% | 高 | 高 |
BYOL | ResNet-50 | 72.5% | 高 | 中等 |
SwAV | ResNet-50 | 73.9% | 中等 | 中等 |
MAE | ViT-B | 68.0% | 低 | 低 |
BEiT | ViT-B | 83.2% | 高 | 高 |
11.4 实用代码:模型评估
以下是一个用于评估预训练模型的实用代码:
def evaluate_representations(model, data_loader, device):"""评估特征表示质量的函数"""model.eval()features = []labels = []with torch.no_grad():for images, targets in data_loader:images = images.to(device)# 提取特征(不经过投影头)feat = model.encoder(images)features.append(feat.cpu())labels.append(targets)# 连接所有特征和标签features = torch.cat(features, dim=0)labels = torch.cat(labels, dim=0)# 1. 特征可视化(t-SNE)tsne_results = TSNE(n_components=2, perplexity=30).fit_transform(features.numpy())# 2. 线性可分性评估# 训练一个线性分类器clf = LogisticRegression(max_iter=1000)clf.fit(features.numpy(), labels.numpy())acc = clf.score(features.numpy(), labels.numpy())# 3. 特征聚类性评估# 使用k-means进行聚类kmeans = KMeans(n_clusters=len(torch.unique(labels)))cluster_labels = kmeans.fit_predict(features.numpy())nmi = normalized_mutual_info_score(labels.numpy(), cluster_labels)return {'tsne': tsne_results,'linear_acc': acc,'clustering_nmi': nmi}
总结
在本部分中,我们深入探讨了掩码建模的核心原理和实现,并进一步分析了MoCo的动量编码器机制。我们了解到,对比学习和掩码建模各有优势,而未来的趋势是融合这两种方法的优点,创造更强大的自监督学习框架。
自监督学习的发展正在改变深度学习的范式,从依赖大量标注数据转向充分利用未标注数据。这不仅提高了模型的泛化能力,也降低了对标注的依赖,使得深度学习可以应用到更多领域。
作为PyTorch深度学习框架的学习者,掌握自监督学习的原理和实践技能将使你在未来的深度学习应用中占据先机。希望本教程能够帮助你更好地理解自监督学习的核心思想,并在实际项目中灵活应用这些技术。
清华大学全五版的《DeepSeek教程》完整的文档需要的朋友,关注我私信:deepseek 即可获得。
怎么样今天的内容还满意吗?再次感谢朋友们的观看,关注GZH:凡人的AI工具箱,回复666,送您价值199的AI大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!