0


Datawhale X 李宏毅苹果书 AI夏令营(Task2)

一、学前概览

  1. 任务内容:critical point并不一定是训练神经网络遇到的最大的阻碍,还有一种叫Adaptive Learning Rate的技术。
  2. 任务目的:了解掌握Learning Rate和分类损失的计算。
  3. 本节出现术语:自适应学习率(root mean squareRMSPropAdam策略)、学习率调度策略、分类、回归、softmax

1.学中疑问

  1. A.训练时候很少卡到saddle point或者local minima,那么上一个task中的图6是怎么画出来的?
  2. B.lr如何自动根据gradient的大小做调整?
  3. C.parameter dependentlr有什么常见的计算方式
  4. D.分类问题中,为什么cross entropy会比mse常用?

2.学后解答

  1. A.一般的gradient descent无法做到,多数的training在走到critical point之前其实就已经停止了。
  2. B.见知识点1
  3. C.见知识点2
  4. D.见图10

二、Task2.1 自适应学习率

  1. 大原则:如果在某一方向上,gradient的值非常小,非常平坦,那么lr会调大一点;如果在某一个方向上坡度很大,lr就可以设的小一点。

1.知识点1:不同的参数需要不同的lr

  1. 训练过程中很容易遇到loss无法下降的情况,这种情况从大量的已知实验来看,很少是因为critical point导致的,所以原先的梯度下降的formulation已经不满足目前的需求,需要随着参数进行克制化的learning rate加入。此处以一个参数为例子,如图1所示,然后依次类推出所有参数的表现形式。


图1:加入克制化的learning rate

  1. 那么,parameter dependentlr有什么常见的计算方式呢?

2.知识点2:parameter dependent的lr常见的计算方式

  1. 方法1root mean square
  2. 有点基础的话,看图2其实就能看得懂了。


图2:root mean square方式

  1. 使用这种方式的一个前提是同一个参数的gradient大小固定。但是实际情况中,很难做到,因此衍生出第二种方法。
  2. 方法2RMSProp
  3. 这种方法并没有文献来源,第一步跟方法一是一样的,区别在第二步以后。见图3


图3:使用RMSProp动态调整参数

  1. 方法3Adam策略
  2. Adam策略,实际上就是RMSPropMomentum

3.知识点3:学习率调度

  1. 出现学习率调度的原因:梯度爆炸,在书籍57页展示了原因,还是很容易理解的。这里放出原文,如图4所示。


图4:梯度爆炸的原因

  1. 解决办法也很简单,让η与时间扯上关系,有两种方法,一种是learning rate decay,使得η随着时间的增加作衰减。;另一种是warm up(远古时代就有,bert使用了它),η先递增后衰减。两者的特征如图5所示,但是对于warm up,其可解释性比较差,目前仍在探索。


图5:learning rate decay和warm up的特征

小结

  1. 见图6


图6:公式总结

三、Task2.2:分类

1.知识点4:分类?回归?

  1. 我们已经知道,回归的本质是模型输出一个数值,这个数值与真实标签的数值差距越小越好,那么接下来的目标便是缩小预测值与真实值之间的差距。
  2. 在多分类的情况下,如果每一分类的输出是一个标量的结果,那我们可以变相把它当成回归问题去看。但这种方式存在一个问题,就是标量与标量之间存在有某种关系,当作预测结果时,说服力会很低。因此,比较常用的做法是将class用独热编码去表示。
  3. 从模型输出的角度,回归问题输出的是一个数值,分类问题可以看成回归问题的输出重复n次。但是在做分类问题的时候,往往会把输出通过softmax,将输出限制在0~1之间。如图7所示。


图7:回归和分类的区别

2.知识点5:softmax的运作过程(简单版)

  1. softmax除了让分类结果限制在0~1之外,还会让大小值之间的差距变大。如图8所示。公式在右上角。


图8:softmax的运作过程

3.知识点6:分类损失

  1. 分类损失有两种计算方式,一种是均方根误差,另一种是交叉熵损失。如图9


图9:损失函数的计算方式

  1. 从优化的角度,交叉熵是被更常用在分类上。解释如下:
  2. 如图10所示:假设有一个三类的分类,网络先输出y1y2 y3,在通过softmax以后,产生 y1y2 y3。假设y1y2的变化都是从-1010y3固定设成-1000。正确答案是[1, 0, 0]T,因为y3的值很小,通过softmax后,y3非常趋近于0,与正确答案非常接近,所以此时我们只需要看y1y2有变化时对损失的影响。
  3. 10中,左上角损失大,右下角损失小,所以希望最后在训练的时候,参数可以“走” 到右下角的地方。假设参数优化开始的时候,对应的损失都是左上角。
  4. 如果选择交叉熵(图10右),左上角圆圈所在的点有斜率的,所以可以通过梯度,一路往右下的地方“走”。
  5. 如果选均方误差(图10左),左上角圆圈就卡住了,均方误差在这种损失很大的地方, 是非常平坦的,其梯度是趋近于 0 的。如果初始时在圆圈的位置,离目标非常远,其梯度又很小,是无法用梯度下降顺利地“走”到右下角的。


图10:交叉熵和均方根误差的损失比较

三、Task2.3:CNN图像分类实践

  1. 其实在任务教程里也有代码的解析,但是我觉得跟着走一遍把代码copy到这边印象会更深。(不会贴源码,只贴我不懂的代码)
  2. 以下范式适用于广泛的深度学习任务:准备数据——>训练模型——>应用模型。具体如下:
  3. 1.导入所需要的库/工具包
  4. 2.数据准备与预处理
  5. 3.定义模型
  6. 4.定义损失函数和优化器等其他配置
  7. 5.训练模型
  8. 6.评估模型
  9. 7.进行预测

1.导包

  1. # 选择随机种子没有特定的规则,它可以是任何整数。有些种子值(如42)因为被知名书籍或文献提及而变得流行。
  2. myseed = 6666
  3. # 确保在使用CUDA时,卷积运算具有确定性,以增强实验结果的可重复性
  4. # 保证了每次运行网络时都会得到相同的结果,代价是牺牲性能
  5. torch.backends.cudnn.deterministic = True
  6. # 设为False时,会告诉CuDNN不进行算法的搜索和选择,而是使用默认的卷积实现。这意味着不会根据当前的GPU和输入数据来选择最快的卷积算法。确保每次运行时都使用相同的算法
  7. torch.backends.cudnn.benchmark = False

2.数据准备与预处理

  1. 分三个部分(预设置、数据加载类和调用读取数据)。
  1. # 图像预设置
  2. # 在测试和验证阶段,通常不需要图像增强。
  3. # 我们所需要的只是调整PIL图像的大小并将其转换为Tensor。
  4. test_tfm = transforms.Compose([transforms.Resize((128, 128)),transforms.ToTensor(),])
  5. # 不过,在测试阶段使用图像增强也是有可能的。
  6. # 你可以使用train_tfm生成多种图像,然后使用集成方法进行测试。
  7. train_tfm = transforms.Compose([
  8. # 将图像调整为固定大小(高度和宽度均为128)
  9. transforms.Resize((128, 128)),
  10. # 可以在此处添加一些图像增强的操作。
  11. # ToTensor()应该是所有变换中的最后一个。
  12. transforms.ToTensor(),
  13. ])
  1. # 数据加载类
  2. class FoodDataset(Dataset):
  3. """
  4. 用于加载食品图像数据集的类。
  5. 该类继承自Dataset,提供了对食品图像数据集的加载和预处理功能。
  6. 它可以自动从指定路径加载所有的jpg图像,并对这些图像应用给定的变换。
  7. """
  8. def __init__(self, path, tfm=test_tfm, files=None):
  9. """
  10. 参数:
  11. - path: 图像数据所在的目录路径。
  12. - tfm: 应用于图像的变换方法(默认为测试变换)。
  13. - files: 可选参数,用于直接指定图像文件的路径列表(默认为None)。
  14. """
  15. super(FoodDataset).__init__()
  16. self.path = path
  17. # 列出目录下所有jpg文件,并按顺序排序
  18. self.files = sorted([os.path.join(path, x) for x in os.listdir(path) if x.endswith(".jpg")])
  19. if files is not None:
  20. self.files = files # 如果提供了文件列表,则使用该列表
  21. self.transform = tfm # 图像变换方法
  22. def __len__(self):
  23. """返回数据集中图像的数量。"""
  24. return len(self.files)
  25. def __getitem__(self, idx):
  26. """
  27. 获取给定索引的图像及其标签。
  28. 参数:
  29. idx: 图像在数据集中的索引。
  30. 返回:
  31. im: 应用了变换后的图像。
  32. label: 图像对应的标签(如果可用)。
  33. """
  34. fname = self.files[idx]
  35. im = Image.open(fname)
  36. im = self.transform(im) # 应用图像变换
  37. # 尝试从文件名中提取标签
  38. try:
  39. label = int(fname.split("/")[-1].split("_")[0])
  40. except:
  41. label = -1 # 如果无法提取标签,则设置为-1(测试数据无标签)
  42. return im, label
  1. # 加载数据
  2. # 构建训练和验证数据集
  3. # "loader" 参数定义了torchvision如何读取数据
  4. train_set = FoodDataset("./hw3_data/train", tfm=train_tfm)
  5. # 创建训练数据加载器,设置批量大小、是否打乱数据顺序、是否使用多线程加载以及是否固定内存地址
  6. train_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True, num_workers=0, pin_memory=True)
  7. # 构建验证数据集
  8. # "loader" 参数定义了torchvision如何读取数据
  9. valid_set = FoodDataset("./hw3_data/valid", tfm=test_tfm)
  10. # 创建验证数据加载器,设置批量大小、是否打乱数据顺序、是否使用多线程加载以及是否固定内存地址
  11. valid_loader = DataLoader(valid_set, batch_size=batch_size, shuffle=True, num_workers=0, pin_memory=True)

3.定义模型

  1. class Classifier(nn.Module):
  2. """
  3. 定义一个图像分类器类,继承自PyTorch的nn.Module。
  4. 该分类器包含卷积层和全连接层,用于对图像进行分类。
  5. """
  6. def __init__(self):
  7. """
  8. 初始化函数,构建卷积神经网络的结构。
  9. 包含一系列的卷积层、批归一化层、激活函数和池化层。
  10. """
  11. super(Classifier, self).__init__()
  12. # 定义卷积神经网络的序列结构
  13. self.cnn = nn.Sequential(
  14. nn.Conv2d(3, 64, 3, 1, 1), # 输入通道3,输出通道64,卷积核大小3,步长1,填充1
  15. nn.BatchNorm2d(64), # 批归一化,作用于64个通道
  16. nn.ReLU(), # ReLU激活函数
  17. nn.MaxPool2d(2, 2, 0), # 最大池化,池化窗口大小2,步长2,填充0
  18. nn.Conv2d(64, 128, 3, 1, 1), # 输入通道64,输出通道128,卷积核大小3,步长1,填充1
  19. nn.BatchNorm2d(128), # 批归一化,作用于128个通道
  20. nn.ReLU(),
  21. nn.MaxPool2d(2, 2, 0), # 最大池化,池化窗口大小2,步长2,填充0
  22. nn.Conv2d(128, 256, 3, 1, 1), # 输入通道128,输出通道256,卷积核大小3,步长1,填充1
  23. nn.BatchNorm2d(256), # 批归一化,作用于256个通道
  24. nn.ReLU(),
  25. nn.MaxPool2d(2, 2, 0), # 最大池化,池化窗口大小2,步长2,填充0
  26. nn.Conv2d(256, 512, 3, 1, 1), # 输入通道256,输出通道512,卷积核大小3,步长1,填充1
  27. nn.BatchNorm2d(512), # 批归一化,作用于512个通道
  28. nn.ReLU(),
  29. nn.MaxPool2d(2, 2, 0), # 最大池化,池化窗口大小2,步长2,填充0
  30. nn.Conv2d(512, 512, 3, 1, 1), # 输入通道512,输出通道512,卷积核大小3,步长1,填充1
  31. nn.BatchNorm2d(512), # 批归一化,作用于512个通道
  32. nn.ReLU(),
  33. nn.MaxPool2d(2, 2, 0), # 最大池化,池化窗口大小2,步长2,填充0
  34. )
  35. # 定义全连接神经网络的序列结构
  36. self.fc = nn.Sequential(
  37. nn.Linear(512*4*4, 1024), # 输入大小512*4*4,输出大小1024
  38. nn.ReLU(),
  39. nn.Linear(1024, 512), # 输入大小1024,输出大小512
  40. nn.ReLU(),
  41. nn.Linear(512, 11) # 输入大小512,输出大小11,最终输出11个类别的概率
  42. )
  43. def forward(self, x):
  44. """
  45. 前向传播函数,对输入进行处理。
  46. 参数:
  47. x -- 输入的图像数据,形状为(batch_size, 3, 128, 128)
  48. 返回:
  49. 输出的分类结果,形状为(batch_size, 11)
  50. """
  51. out = self.cnn(x) # 通过卷积神经网络处理输入
  52. out = out.view(out.size()[0], -1) # 展平输出,以适配全连接层的输入要求
  53. return self.fc(out) # 通过全连接神经网络得到最终输出

4.定义损失函数和优化器等其他配置

  1. # 根据GPU是否可用选择设备类型
  2. device = "cuda" if torch.cuda.is_available() else "cpu"
  3. # 初始化模型,并将其放置在指定的设备上
  4. model = Classifier().to(device)
  5. # 定义批量大小
  6. batch_size = 64
  7. # 定义训练轮数
  8. n_epochs = 8
  9. # 如果在'patience'轮中没有改进,则提前停止
  10. patience = 5
  11. # 对于分类任务,我们使用交叉熵作为性能衡量标准
  12. criterion = nn.CrossEntropyLoss()
  13. # 初始化优化器,您可以自行调整一些超参数,如学习率
  14. optimizer = torch.optim.Adam(model.parameters(), lr=0.0003, weight_decay=1e-5)

5.训练模型

  1. # 初始化追踪器,这些不是参数,不应该被更改
  2. stale = 0
  3. best_acc = 0
  4. for epoch in range(n_epochs):
  5. # ---------- 训练阶段 ----------
  6. # 确保模型处于训练模式
  7. model.train()
  8. # 这些用于记录训练过程中的信息
  9. train_loss = []
  10. train_accs = []
  11. for batch in tqdm(train_loader):
  12. # 每个批次包含图像数据及其对应的标签
  13. imgs, labels = batch
  14. # imgs = imgs.half()
  15. # print(imgs.shape,labels.shape)
  16. # 前向传播数据。(确保数据和模型位于同一设备上)
  17. logits = model(imgs.to(device))
  18. # 计算交叉熵损失。
  19. # 在计算交叉熵之前不需要应用softmax,因为它会自动完成。
  20. loss = criterion(logits, labels.to(device))
  21. # 清除上一步中参数中存储的梯度
  22. optimizer.zero_grad()
  23. # 计算参数的梯度
  24. loss.backward()
  25. # 为了稳定训练,限制梯度范数
  26. grad_norm = nn.utils.clip_grad_norm_(model.parameters(), max_norm=10)
  27. # 使用计算出的梯度更新参数
  28. optimizer.step()
  29. # 计算当前批次的准确率
  30. acc = (logits.argmax(dim=-1) == labels.to(device)).float().mean()
  31. # 记录损失和准确率
  32. train_loss.append(loss.item())
  33. train_accs.append(acc)
  34. train_loss = sum(train_loss) / len(train_loss)
  35. train_acc = sum(train_accs) / len(train_accs)
  36. # 打印信息
  37. print(f"[ 训练 | {epoch + 1:03d}/{n_epochs:03d} ] loss = {train_loss:.5f}, acc = {train_acc:.5f}")
  38. # ---------- 验证阶段 ----------
  39. # 确保模型处于评估模式,以便某些模块如dropout能够正常工作
  40. model.eval()
  41. # 这些用于记录验证过程中的信息
  42. valid_loss = []
  43. valid_accs = []
  44. # 按批次迭代验证集
  45. for batch in tqdm(valid_loader):
  46. # 每个批次包含图像数据及其对应的标签
  47. imgs, labels = batch
  48. # imgs = imgs.half()
  49. # 我们在验证阶段不需要梯度。
  50. # 使用 torch.no_grad() 加速前向传播过程。
  51. with torch.no_grad():
  52. logits = model(imgs.to(device))
  53. # 我们仍然可以计算损失(但不计算梯度)。
  54. loss = criterion(logits, labels.to(device))
  55. # 计算当前批次的准确率
  56. acc = (logits.argmax(dim=-1) == labels.to(device)).float().mean()
  57. # 记录损失和准确率
  58. valid_loss.append(loss.item())
  59. valid_accs.append(acc)
  60. # break
  61. # 整个验证集的平均损失和准确率是所记录值的平均
  62. valid_loss = sum(valid_loss) / len(valid_loss)
  63. valid_acc = sum(valid_accs) / len(valid_accs)
  64. # 打印信息
  65. print(f"[ 验证 | {epoch + 1:03d}/{n_epochs:03d} ] loss = {valid_loss:.5f}, acc = {valid_acc:.5f}")
  66. # 更新日志
  67. if valid_acc > best_acc:
  68. with open(f"./{_exp_name}_log.txt", "a"):
  69. print(f"[ 验证 | {epoch + 1:03d}/{n_epochs:03d} ] loss = {valid_loss:.5f}, acc = {valid_acc:.5f} -> 最佳")
  70. else:
  71. with open(f"./{_exp_name}_log.txt", "a"):
  72. print(f"[ 验证 | {epoch + 1:03d}/{n_epochs:03d} ] loss = {valid_loss:.5f}, acc = {valid_acc:.5f}")
  73. # 保存模型
  74. if valid_acc > best_acc:
  75. print(f"在第 {epoch} 轮找到最佳模型,正在保存模型")
  76. torch.save(model.state_dict(), f"{_exp_name}_best.ckpt") # 只保存最佳模型以防止输出内存超出错误
  77. best_acc = valid_acc
  78. stale = 0
  79. else:
  80. stale += 1
  81. if stale > patience:
  82. print(f"连续 {patience} 轮没有改进,提前停止")
  83. break

6.评估模型

  1. for epoch in range(n_epochs):
  2. # ---------- 训练阶段 ----------
  3. ···
  4. # ---------- 验证阶段 ----------
  5. # 确保模型处于评估模式,以便某些模块如dropout能够正常工作
  6. model.eval()
  7. # 这些用于记录验证过程中的信息
  8. valid_loss = []
  9. valid_accs = []
  10. # 按批次迭代验证集
  11. for batch in tqdm(valid_loader):
  12. # 每个批次包含图像数据及其对应的标签
  13. imgs, labels = batch
  14. # imgs = imgs.half()
  15. # 我们在验证阶段不需要梯度。
  16. # 使用 torch.no_grad() 加速前向传播过程。
  17. with torch.no_grad():
  18. logits = model(imgs.to(device))
  19. # 我们仍然可以计算损失(但不计算梯度)。
  20. loss = criterion(logits, labels.to(device))
  21. # 计算当前批次的准确率
  22. acc = (logits.argmax(dim=-1) == labels.to(device)).float().mean()
  23. # 记录损失和准确率
  24. valid_loss.append(loss.item())
  25. valid_accs.append(acc)
  26. # break
  27. # 整个验证集的平均损失和准确率是所记录值的平均
  28. valid_loss = sum(valid_loss) / len(valid_loss)
  29. valid_acc = sum(valid_accs) / len(valid_accs)
  30. # 打印信息
  31. print(f"[ 验证 | {epoch + 1:03d}/{n_epochs:03d} ] loss = {valid_loss:.5f}, acc = {valid_acc:.5f}")
  32. # 更新日志
  33. if valid_acc > best_acc:
  34. with open(f"./{_exp_name}_log.txt", "a"):
  35. print(f"[ 验证 | {epoch + 1:03d}/{n_epochs:03d} ] loss = {valid_loss:.5f}, acc = {valid_acc:.5f} -> 最佳")
  36. else:
  37. with open(f"./{_exp_name}_log.txt", "a"):
  38. print(f"[ 验证 | {epoch + 1:03d}/{n_epochs:03d} ] loss = {valid_loss:.5f}, acc = {valid_acc:.5f}")
  39. # 保存模型
  40. if valid_acc > best_acc:
  41. print(f"在第 {epoch} 轮找到最佳模型,正在保存模型")
  42. torch.save(model.state_dict(), f"{_exp_name}_best.ckpt") # 只保存最佳模型以防止输出内存超出错误
  43. best_acc = valid_acc
  44. stale = 0
  45. else:
  46. stale += 1
  47. if stale > patience:
  48. print(f"连续 {patience} 轮没有改进,提前停止")
  49. break

7.预测模型

  1. 分两步(加载测试数据、测试并生成预测)
  1. # 构建测试数据集
  2. # "loader"参数指定了torchvision如何读取数据
  3. test_set = FoodDataset("./hw3_data/test", tfm=test_tfm)
  4. # 创建测试数据加载器,批量大小为batch_size,不打乱数据顺序,不使用多线程,启用pin_memory以提高数据加载效率
  5. test_loader = DataLoader(test_set, batch_size=batch_size, shuffle=False, num_workers=0, pin_memory=True)
  1. # 实例化分类器模型,并将其转移到指定的设备上
  2. model_best = Classifier().to(device)
  3. # 加载模型的最优状态字典
  4. model_best.load_state_dict(torch.load(f"{_exp_name}_best.ckpt"))
  5. # 将模型设置为评估模式
  6. model_best.eval()
  7. # 初始化一个空列表,用于存储所有预测标签
  8. prediction = []
  9. # 使用torch.no_grad()上下文管理器,禁用梯度计算
  10. with torch.no_grad():
  11. # 遍历测试数据加载器
  12. for data, _ in tqdm(test_loader):
  13. # 将数据转移到指定设备上,并获得模型的预测结果
  14. test_pred = model_best(data.to(device))
  15. # 选择具有最高分数的类别作为预测标签
  16. test_label = np.argmax(test_pred.cpu().data.numpy(), axis=1)
  17. # 将预测标签添加到结果列表中
  18. prediction += test_label.squeeze().tolist()
  19. # 创建测试csv文件
  20. def pad4(i):
  21. """
  22. 将输入数字i转换为长度为4的字符串,如果长度不足4,则在前面补0。
  23. :param i: 需要转换的数字
  24. :return: 补0后的字符串
  25. """
  26. return "0" * (4 - len(str(i))) + str(i)
  27. # 创建一个空的DataFrame对象
  28. df = pd.DataFrame()
  29. # 使用列表推导式生成Id列,列表长度等于测试集的长度
  30. df["Id"] = [pad4(i) for i in range(len(test_set))]
  31. # 将预测结果赋值给Category列
  32. df["Category"] = prediction
  33. # 将DataFrame对象保存为submission.csv文件,不保存索引
  34. df.to_csv("submission.csv", index=False)

8.可视化

  1. # 导入必要的库和模块
  2. import torch
  3. import numpy as np
  4. from sklearn.manifold import TSNE
  5. import matplotlib.pyplot as plt
  6. from tqdm import tqdm
  7. import matplotlib.cm as cm
  8. import torch.nn as nn
  9. # 根据CUDA是否可用选择执行设备
  10. device = 'cuda' if torch.cuda.is_available() else 'cpu'
  11. # 加载训练好的模型
  12. model = Classifier().to(device)
  13. # 加载模型保存的参数
  14. state_dict = torch.load(f"{_exp_name}_best.ckpt")
  15. # 将参数加载到模型中
  16. model.load_state_dict(state_dict)
  17. # 设置模型为评估模式
  18. model.eval()
  19. # 打印模型结构
  20. print(model)
  1. from tqdm import tqdm
  2. import numpy as np
  3. import matplotlib.pyplot as plt
  4. from sklearn.manifold import TSNE
  5. import matplotlib.cm as cm
  6. import torch
  7. def forward_to_layer(model, input_tensor, layer_index):
  8. outputs = []
  9. for i, layer in enumerate(model.children()):
  10. input_tensor = layer(input_tensor)
  11. if i == layer_index:
  12. break
  13. outputs.append(input_tensor)
  14. return outputs[-1] # 返回所选层的输出
  15. # 假设model, test_tfm, FoodDataset, DataLoader已经被定义且正确初始化
  16. # 加载由TA定义的验证集
  17. valid_set = FoodDataset("./hw3_data/valid", tfm=test_tfm)
  18. valid_loader = DataLoader(valid_set, batch_size=64, shuffle=False, num_workers=0, pin_memory=True)
  19. # 提取模型特定层的表示
  20. index = 19 # 假设你想提取第19层的特征
  21. features = []
  22. labels = []
  23. # 定义设备
  24. device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
  25. for batch in tqdm(valid_loader):
  26. imgs, lbls = batch
  27. imgs, lbls = imgs.to(device), lbls.to(device) # 确保数据在正确的设备上
  28. with torch.no_grad():
  29. # 获取特定层的特征
  30. logits = forward_to_layer(model.cnn, imgs, index)
  31. logits = logits.view(logits.size(0), -1)
  32. labels.extend(lbls.cpu().numpy())
  33. features.extend(logits.cpu().numpy())
  34. # 将features和labels列表转换为numpy数组
  35. features = np.array(features)
  36. labels = np.array(labels)
  37. # 应用t-SNE到特征上
  38. features_tsne = TSNE(n_components=2, init='pca', random_state=42).fit_transform(features)
  39. # 绘制t-SNE可视化图
  40. plt.figure(figsize=(10, 8))
  41. for label in np.unique(labels):
  42. # 使用布尔索引选择特定标签的数据点
  43. mask = (labels == label)
  44. plt.scatter(features_tsne[mask, 0], features_tsne[mask, 1], label=f'Class {label}', s=5)
  45. plt.legend()
  46. plt.title('All Classes t-SNE Visualization')
  47. plt.show()
  48. # 绘制特定类别的t-SNE可视化图
  49. plt.figure(figsize=(10, 8))
  50. selected_label = 5
  51. mask = (labels == selected_label)
  52. if mask.any(): # 使用 .any() 替代 .sum() 来检查是否有True值
  53. plt.scatter(features_tsne[mask, 0], features_tsne[mask, 1], label=f'Class {selected_label}', s=5)
  54. plt.legend()
  55. plt.title(f'Class {selected_label} t-SNE Visualization')
  56. plt.show()

本文转载自: https://blog.csdn.net/mengluohuayexuan/article/details/141572395
版权归原作者 梦落花叶萱 所有, 如有侵权,请联系我们删除。

“Datawhale X 李宏毅苹果书 AI夏令营(Task2)”的评论:

还没有评论