前言: 本篇文章是为后续更进一步学习经典神经网络,以及更先进的技术筑基,一定要牢牢掌握,年薪百万不是梦!!
一、Dataset 1 2 3 4 from torch.utils.data import Datasetfrom PIL import Imageimport osimport random
用torch.utils.data中的Dataset组件来实现图片数据集的的合并以及标记标签的功能
1.1 MyData类 这里需要我们自己来写一个MyData类用于初始化根目录,图片目录,并将他们拼接。以及实现取出单条数据和获取数据长度的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class MyData (Dataset ): def __init__ (self,root_dir,label_dir ): """ 获取根目录和标签目录图片地址 将他们拼接后输出成图片相对路径地址列表 """ self .root_dir = root_dir self .label_dir = label_dir self .path = os.path.join(self .root_dir,self .label_dir) self .img_path = os.listdir(self .path) random.shullfe(self .img_path) def __getitem__ (self, index ): """ 获取单个图片以及对应的标签 """ img_name = self .img_path[index] img_item_path = os.path.join(self .path,img_name) print (self .img_path) img = Image.open (img_item_path) label = self .label_dir return img,img_name,label def __len__ (self ): """ 返回对应数据集文件长度 """ return len (self .img_path) root_dir = r"data\hymenoptera_data\train" label_dir = r"ants" ants_dataset = MyData(root_dir,label_dir) img,img_name,label = ants_dataset[1 ] print (label)img
1.2 实例化 接下来是实例化蚂蚁和蜜蜂两个数据集类,并且将他们相加,这里需要MyData继承Dataset才能相加成功,因为前面定义的类中有一步random.shullfe(self.img_path)操作,因此相加后两个数据集中的数据是相互交叉的,这一点比较好
实例化蚂蚁类
1 2 3 4 5 6 7 8 root_dir = r"data\hymenoptera_data\train" ants_dir = r"ants" ants_dataset = MyData(root_dir,ants_dir) img,img_name,label = ants_dataset[1 ] print (label)img
实例化蜜蜂类
1 2 3 4 5 6 7 8 9 root_dir = r"data\hymenoptera_data\train" bees_dir = r"bees" bees_dataset = MyData(root_dir,bees_dir) img,img_name,label = bees_dataset[1 ] print (img_name)print (label)img
1.3 制作训练集类 将两个类合并成一个类,即我们的训练集类
1 2 train_dataset = ants_dataset+bees_dataset print (len (train_dataset))
245
将数据集中每张图片的类别标签写入对应的.txt
文件,文件名与图片名一致。这种做法在机器学习中非常实用,可用于数据标注、管理、清洗、模型训练与评估等环节。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 output_dir = "data/train_files" os.makedirs(output_dir, exist_ok=True ) for img,img_name,label in train_dataset: file_name = os.path.splitext(os.path.basename(img_name))[0 ] print (file_name) txt_file_path = os.path.join(output_dir, f"{file_name} .txt" ) with open (txt_file_path, "w" ) as f: f.write(str (label)) print ("所有txt文件已生成,每个文件名对应图片文件名,文件内容为图片的类别。" )
二、TensorBoard 1 2 3 from torch.utils.tensorboard import SummaryWriter import numpy as npfrom PIL import Image
TensorBoard 是 TensorFlow 提供的一个可视化工具,用于帮助开发者更好地理解和调试机器学习模型的训练过程。它可以可视化各种指标(如损失函数、准确率等)、模型结构、梯度信息等
2.1 add_scalar 该函数用于将标量值(如损失值、准确率等标量数据)记录到 TensorBoard 中,方便在训练过程中可视化这些标量值的变化,下面仅展示一个比较简单的对数函数示例
1 2 3 4 5 writer = SummaryWriter("log" ) for i in range (1 ,100 ): y = np.log2(i) writer.add_scalar("y = log2(x)" ,i,y) writer.close()
执行程序后会在同级目录下创建一个“log”文件夹,里面记录了程序日志文件,打开终端,输入
1 tensorboard --logdir="log" --port=6007
点击跳转出来的网址就可以看到日志情况
2.2 add_images 用于将图像数据批量记录到 TensorBoard 中,方便在训练过程中可视化图像的变化。这在处理图像数据(如计算机视觉任务)时非常有用,例如可视化输入图像、生成的图像、特征图等。 下面仅仅展示向日志中传入一张图片。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 writer = SummaryWriter("logs" ) image_path = r"data\hymenoptera_data\train\bees\21399619_3e61e5bb6f.jpg" img_PIL = Image.open (image_path) img_array = np.array(img_PIL) print (img_array.shape)writer.add_image("ants" , img_array, 1 , dataformats='HWC' ) writer.close()
查看日志:
add_images 方法的输入必须是一个 4 维张量,形状为 (batch_size, channels, height, width)。
channels 必须是 3 或 1,因为 TensorBoard 只支持单通道(灰度图)或多通道(RGB 图)的图像
1 2 3 from torchvision import transformsfrom PIL import Imageimport numpy as np
torchvision.transforms
提供了许多用于图像预处理和数据增强的工具。这些工具可以帮助我们对图像数据进行各种操作,从而提高模型的泛化能力和训练效果。以下是torchvision.transforms
中一些常用的功能和类的介绍
先实例化调用一个Transforms方法,再用这个方法去对图片或者数组进行对应操作
3.1 ToTensor张量化 将输入的 PIL 图像或 NumPy 数组转换为 PyTorch 张量。并将像素值从 [0, 255] 归一化到 [0.0, 1.0]输出。
1 img = Image.open (r"data\hymenoptera_data\train\ants\5650366_e22b7e1065.jpg" )
1 2 3 4 trans_totensor = transforms.ToTensor() img = Image.open (img_path) img_tensor = trans_totensor(img) img_tensor
3.2 Normalize归一化 这里是对图像的三个通道分别进行标准化,因此均值和方差要设置三个
[0.5, 0.5, 0.5] 表示对 RGB 三个通道分别使用均值 0.5 和标准差 0.5 进行归一化
1 2 3 trans_norm = transforms.Normalize([0.5 ,0.5 ,0.5 ],[0.5 ,0.5 ,0.5 ]) img_norm = trans_norm(img_tensor) img_norm
3.3 Resize 参数(512,512)表示不考虑纵横比,强制将图片设置成512×512大小
1 2 3 4 trans_resize = transforms.Resize((512 ,512 )) img_resize = trans_resize(img_norm) print (img_norm.shape)print (img_resize.shape)
仅有一个512参数表示将图像的较短边调整为 512 像素,而较长边会按比例缩放,以保持图像的宽高比
1 2 3 4 trans_resize = transforms.Resize(512 ) img_resize = trans_resize(img_norm) print (img_norm.shape)print (img_resize.shape)
3.4 Compose 用于将多个图像预处理操作组合成一个完整的流程
例如它可以将图像先进行缩放(trans_resize_2),然后转换为张量(trans_totensor)
列表中前一个输出是后一个输入
1 2 trans_compose = transforms.Compose([trans_resize_2,trans_totensor]) img_resize_2 = trans_compose(img)
3.5 RandomCrop随机裁剪 通过从原始图像中随机选择一个区域并裁剪出来,从而生成一个新的图像。这种方法可以增加数据的多样性,有助于提高模型的泛化能力
1 2 3 4 trans_crop = transforms.RandomCrop(300 ) img_crop = trans_crop(img_resize) print (img_resize.shape)print (img_crop.shape)
四、 torchvision.datasets 这个模块提供了许多常用的数据集,方便用户快速加载和使用这些数据集进行深度学习模型的训练和测试
1 2 import torchvisionfrom torchvision import transforms
4.1下载torchvision中的数据集 1 2 train_set = torchvision.datasets.CIFAR10(root="data" ,train=True ,download=True ) test_set = torchvision.datasets.CIFAR10(root="data" ,train=False ,download=True )
4.2取出一张图片以及它对应的标签 1 2 3 4 5 img,target = test_set[1 ] print (img)print (target)print (test_set.classes[target])img.show()
<PIL.Image.Image image mode=RGB size=32x32 at 0x1C0E6F2CAC0> 8 ship
4.3 将图片转换成tensor类型并归一化输出 1 2 3 4 5 6 7 8 9 dataset_transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize([0.5 ,0.5 ,0.5 ],[0.5 ,0.5 ,0.5 ]), transforms.Resize((512 ,512 )) ]) train_set = torchvision.datasets.CIFAR10(root="data" ,train=True ,transform=dataset_transform,download=True ) test_set = torchvision.datasets.CIFAR10(root="data" ,train=False ,transform=dataset_transform,download=True ) test_set[0 ]
五、DataLoader DataLoader
的主要作用是将数据集(如 torchvision.datasets
中的数据集或自定义的数据集)封装成一个可迭代的对象,方便在训练和测试过程中逐批次地获取数据
1 2 import torchvisionfrom torch.utils.data import DataLoader
5.1 加载数据并转换成张量 1 test_set = torchvision.datasets.CIFAR10(root="data" ,train=False ,transform=torchvision.transforms.ToTensor())
5.2 加载每一批次数据 1 2 3 4 5 6 step = 0 for data in test_loader: img,target = data print (img) print (target) step += 1
六、nn_module 1 2 from torch import nnimport torch
这个部分主要讲怎么来搭建一个基础的神经网络 ,以及如何使用更加便捷的代码表示,并且使用tensorboard可视化神经网络
6.1 卷积神经网络骨架 1 2 3 4 5 6 7 8 9 10 11 12 class Network (nn.Module): def __init__ (self, *args, **kwargs ): super ().__init__(*args, **kwargs) def forward (self,input ): output = input + 1 return output network = Network() x = torch.tensor([10 ]) re = network(x) re
6.2 卷积层
注意:
一维卷积的输入数据是 3 维的。
二维卷积的输入数据是 4 维的。
三维卷积的输入数据是 5 维的。
对于常用的图片二位卷积,对应的4维数据为(BatchSize,Channels,Height,Width),dataloader里加载的数据也是4维
首先创建输入数据和卷积核
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 torch.manual_seed(42 ) input = torch.randint(0 , 10 , (5 , 5 )) print ("input tensor:" )print (input )print ("-" *50 )kernel = torch.tensor([[1 ,2 ,1 ], [0 ,1 ,0 ], [2 ,1 ,0 ]]) print ("kernel tensor:" )print (kernel)print ("-" *50 )input = torch.reshape(input ,(1 ,1 ,5 ,5 ))kernel = torch.reshape(kernel,(1 ,1 ,3 ,3 )) print ("input tensor_resize:" )print (input )print ("kernel tensor_resize:" )print (kernel)print ("-" *50 )
input tensor: tensor([[2, 7, 6, 4, 6], [5, 0, 4, 0, 3], [8, 4, 0, 4, 1], [2, 5, 5, 7, 6], [9, 6, 3, 1, 9]]) -————————————————- kernel tensor: tensor([[1, 2, 1], [0, 1, 0], [2, 1, 0]]) -————————————————- input tensor_resize: tensor([[[[2, 7, 6, 4, 6], [5, 0, 4, 0, 3], [8, 4, 0, 4, 1], [2, 5, 5, 7, 6], [9, 6, 3, 1, 9]]]]) kernel tensor_resize: tensor([[[[1, 2, 1], [0, 1, 0], [2, 1, 0]]]]) -————————————————-
进行卷积操作
1 2 3 4 output_1 = F.conv2d(input ,kernel,stride=1 ) print ("output tensor of 1step:" )print (output_1)print ("-" *50 )
output tensor of 1step: tensor([[[[42, 35, 24], [22, 23, 28], [45, 28, 23]]]]) -————————————————-
可以通过控制stride和padding的参数来控制步长和填充值
一个很重要的计算卷积神经网络参数的公式,这个公式可以计算卷积层的输入输出维度,以及步长和填充值,也可以迁移到池化层,也可以倒推卷积核维度
6.3 池化层卷积核用于增加通道数,池化层用于缩小特征图
定义池化层:
1 self .maxpool1 = nn.MaxPool2d(kernel_size=3 ,ceil_mode=True )
6.4 非线性激活
更具实际情况二者选其一
1 2 3 self .relu1 = nn.ReLU()self .sigmoid = nn.Sigmoid()
6.5 线性层 也称为全连接层,用于创建神经元以及最后输出预测结果
1 self .linear1 = nn.Linear(196608 ,10 )
6.6 nn_Sequential 用于按顺序堆叠多个神经网络层。它会自动将输入数据依次传递给每个层,并返回最终的输出,
实际上就是简化神经网络搭建流程,特别是在前向传播forward()阶段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class Network (nn.Module): def __init__ (self, *args, **kwargs ): super ().__init__(*args, **kwargs) self .model1 = Sequential( Conv2d(3 ,32 ,5 ,padding=2 ), MaxPool2d(2 ), Conv2d(32 ,32 ,5 ,padding=2 ), MaxPool2d(2 ), Conv2d(32 ,64 ,5 ,padding=2 ), MaxPool2d(2 ), Flatten(), Linear(1024 ,64 ), Linear(64 ,10 ) ) def forward (self,x ): x = self .model1(x) return x network = Network() print (network)
如果想要在原有模型上添加层,可以使用以下代码来进行添加
1 network.add_module(“model2”,Linear(10 ,5 ))
6.7 可视化搭建流程 可以直观地在里面看到神经网络中一系列内容
1 2 3 writer = SummaryWriter("logs" ) writer.add_graph(network,input ,verbose=True ) writer.close()
经过输入层,隐藏层,全连接层过后,输入的 (64,3,32,32)形状数据输出为(64,10),即代表1个bath_size中64条数据在10个类别中的概率值
七、loss_function 1 2 3 4 5 6 7 8 9 import torchfrom torch.nn import L1Loss,MSELoss,CrossEntropyLossfrom torch.nn import Conv2d,MaxPool2d,Flatten,Linear,Sequentialfrom torch import nnimport torchvisionfrom torch.utils.data import DataLoaderfrom torch import nn,optimfrom tqdm import tqdmimport matplotlib.pyplot as plt
经过神经网络计算的结果可以使用torch框架下的损失函数来计算损失值,并且可以由此使用梯度下降来优化模型
7.1 L1Loss和MSELoss 适用于回归任务的损失计算,这里给出一个小的示例
1 2 3 4 5 6 7 8 9 10 11 12 13 input = torch.tensor([1 ,2 ,3 ],dtype=torch.float32)target = torch.tensor([1 ,2 ,5 ],dtype=torch.float32) input = torch.reshape(input ,(1 ,1 ,1 ,3 ))target = torch.reshape(target,(1 ,1 ,1 ,3 )) loss1 = L1Loss(reduction="sum" ) result1 = loss1(input ,target) print (result1)loss2 = MSELoss() result2 = loss2(input ,target) print (result2)
tensor(2.) tensor(1.3333)
7.2 交叉熵CrossEntropyLoss CrossEntropyLoss 的输入要求是二维张量,形状为 [batch_size, num_classes],每一行表示一个样本的类别得分。
1 2 3 4 5 6 7 8 9 x = torch.tensor([0.1 , 0.2 , 0.3 ]) y = torch.tensor([1 ]) x = torch.reshape(x, (1 , 3 )) loss_cross = CrossEntropyLoss() result3 = loss_cross(x, y) print (result3)
tensor(1.1019)
7.3 综合示例 接下来对之前介绍的神经网络框架的搭建进行一个实战,我们来实现这个网络,具体参数计算这里不进行叙述
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Network (nn.Module): def __init__ (self, *args, **kwargs ): super ().__init__(*args, **kwargs) self .model1 = Sequential( Conv2d(3 ,32 ,5 ,padding=2 ), MaxPool2d(2 ), Conv2d(32 ,32 ,5 ,padding=2 ), MaxPool2d(2 ), Conv2d(32 ,64 ,5 ,padding=2 ), MaxPool2d(2 ), Flatten(), Linear(1024 ,64 ), Linear(64 ,10 ) ) def forward (self,x ): x = self .model1(x) return x
使用该网络背景下的数据来进行训练
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 transform = transforms.Compose( [ transforms.ToTensor(), transforms.Resize((32 ,32 )), transforms.Normalize([0.5 ,0.5 ,0.5 ],[0.5 ,0.5 ,0.5 ])] ) dataset = torchvision.datasets.CIFAR10(root="data" , download=True , train=True , transform=transform) dataloader = DataLoader(dataset, batch_size=64 ) network = Network() loss_cross = CrossEntropyLoss() optim = optim.SGD(network.parameters(), lr=0.01 ) steps = 5 losses = [] for step in tqdm(range (steps), desc="Training Progress" , unit="epoch" ): running_loss = 0.0 for data in dataloader: img, target = data output = network(img) result_loss = loss_cross(output, target) optim.zero_grad() result_loss.backward() optim.step() running_loss += result_loss.item() avg_loss = running_loss / len (dataloader) losses.append(avg_loss) tqdm.write(f"Step {step + 1 } /{steps} , Running Loss: {avg_loss:.4 f} " ) break plt.plot(range (1 , steps + 1 ), losses, marker='o' ) plt.title("Training Loss Over Epochs" ) plt.xlabel("Epochs" ) plt.ylabel("Loss" ) plt.grid(True ) plt.show()
CPU训练比较缓慢,可以用Google cloud上的算力资源提速
八、pre_models 1 2 3 4 import torchvisionimport torchimport torchvision.models as modelsfrom torchvision.models import VGG16_Weights
8.1 加载模型
在神经网络框架中,我们可以下载已经搭建好的网络来进行训练,必要时可以进行微调,既神经网络层的添加和删除
1 2 3 4 5 6 torch.hub.set_dir("models" ) vgg16_without_weights = models.vgg16(weights=None ) vgg16_with_weights = models.vgg16(weights=VGG16_Weights.DEFAULT)
8.2 在原有vgg16模型上添加或修改 vgg16_with_weights.classifier.add_module
是将新层添加到 VGG16 模型的 classifier
部分 ,前向传播时,输入数据会先通过 features
部分(卷积层),然后通过 classifier
中的所有层,包括新添加的层。输出结果是新层的输出
而 vgg16_with_weights.add_module
是将新层添加到整个模型的末尾 。前者使新层成为 classifier
的一部分,后者则使新层成为整个模型的最后一个层。前向传播时,输入数据会先通过 features
部分,然后通过 classifier
,最后通过新添加的层。输出结果是新层的输出,但新层的输入是 classifier
的输出。
1 2 vgg16_with_weights.add_module("add_model" ,nn.Linear(1000 ,10 )) vgg16_with_weights
1 2 vgg16_with_weights.classifier.add_module("add_model" ,nn.Linear(1000 ,10 )) vgg16_with_weights
虽然两种方式都会将新层添加到模型中,但它们在模型中的位置不同,导致前向传播的顺序和最终输出结果不同
九、save_load 1 2 3 4 import torchvisionimport torchimport torchvision.models as modelsfrom torchvision.models import VGG16_Weights
9.1 保存模型 1 2 vgg16 = torchvision.models.vgg16(weights=VGG16_Weights.DEFAULT) torch.save(vgg16,"vgg16_metnod1.pth" )
9.2 调用模型 1 2 model1 = torch.load(r"models\checkpoints\vgg16-397923af.pth" ) print (model1)