目录
4.2.1 torch.nn.functional.conv2d
1 Python学习中两大重要函数
1、dir()函数:能让我们知道工具箱及工具箱中的分隔区有什么工具。
2、help()函数:能让我们知道工具如何使用。
查看torch工具箱下有哪些分隔区:
我们发现torch工具箱有很多分隔区,包括cuda。
查看cuda分隔区有哪些工具:
我们发现cuda分隔区有很多工具,包括is_available。
查看is_available工具的作用:
我们发现is_available工具的作用是返回CUDA是否可用的bool值。
2 Python代码编辑的三种方式
1、Python文件
运行Python文件,即运行文件中所有行的代码。
优点:通用,适用于大型项目。
缺点:如果某行出错,修改改行后,需要从头运行整个Python文件。
2、Python控制台
通常情况下,每次运行一行。
优点:可以显示每个变量属性,可以用于程序调试。
缺点:不利于代码阅读及修改。
3、Jupyter
每个单元格(块)内可以编写一行或者多行代码,每次运行一个单元格。
优点:利于代码阅读及修改。
缺点:环境需要配置。
3 Pytorch学习
3.1 Dataset和DataLoader
3.1.1 Dataset
from torch.utils.data import Dataset
help(Dataset)
Dataset??
Dataset和DataLoader是Pytorch读取数据的两个类。
Dataset将数据和label进行组织编号0,1,2,3...,使得Pytorch可以根据编号读取数据。若需要获取样本数量、每个数据及其label,则需要实现len()方法和getitem()方法。len()方法是用来返回数据集的样本数量,getitem()方法是用来根据给定的索引编号返回对应的数据样本。
from torch.utils.data import Dataset
import os
from PIL import Image
# 若需要自定义数据集,则需要创建一个继承自Dataset的子类,并实现__len__()方法和__getitem__()方法
class MyDataset(Dataset):
# 初始化拼接路径
def __init__(self,root_path,label_path):
self.root_path = root_path
self.label_path = label_path
# 拼接路径
self.path = os.path.join(root_path,label_path)
# 以列表的形式返回路径下的图片名
self.imgArr = os.listdir(self.path)
# 根据索引返回某个图片的open状态
def __getitem__(self, index):
# 根据索引获取某个图片名
img = self.imgArr[index]
img_path = os.path.join(self.path, img)
img_open = Image.open(img_path)
return img_open
# 返回列表长度
def __len__(self):
return len(self.imgArr)
# 创建ants_dataset对象,初始化root_path和label_path
ants_dataset = MyDataset("dataset/train", "ants")
# dataset/train\ants
print(ants_dataset.path)
# ['0013035.jpg', '1030023514_aad5c608f9.jpg', '1095476100_3906d8afde.jpg', '1099452230_d1949d3250.jpg', '116570827_e9c126745d.jpg', '1225872729_6f0856588f.jpg', '1262877379_64fcada201.jpg', '1269756697_0bce92cdab.jpg', '1286984635_5119e80de1.jpg', '132478121_2a430adea2.jpg', '1360291657_dc248c5eea.jpg', '1368913450_e146e2fb6d.jpg', '1473187633_63ccaacea6.jpg', '148715752_302c84f5a4.jpg', '1489674356_09d48dde0a.jpg', '149244013_c529578289.jpg', '150801003_3390b73135.jpg', '150801171_cd86f17ed8.jpg', '154124431_65460430f2.jpg', '162603798_40b51f1654.jpg', '1660097129_384bf54490.jpg', '167890289_dd5ba923f3.jpg', '1693954099_46d4c20605.jpg', '175998972.jpg', '178538489_bec7649292.jpg', '1804095607_0341701e1c.jpg', '1808777855_2a895621d7.jpg', '188552436_605cc9b36b.jpg', '1917341202_d00a7f9af5.jpg', '1924473702_daa9aacdbe.jpg', '196057951_63bf063b92.jpg', '196757565_326437f5fe.jpg', '201558278_fe4caecc76.jpg', '201790779_527f4c0168.jpg', '2019439677_2db655d361.jpg', '207947948_3ab29d7207.jpg', '20935278_9190345f6b.jpg', '224655713_3956f7d39a.jpg', '2265824718_2c96f485da.jpg', '2265825502_fff99cfd2d.jpg', '226951206_d6bf946504.jpg', '2278278459_6b99605e50.jpg', '2288450226_a6e96e8fdf.jpg', '2288481644_83ff7e4572.jpg', '2292213964_ca51ce4bef.jpg', '24335309_c5ea483bb8.jpg', '245647475_9523dfd13e.jpg', '255434217_1b2b3fe0a4.jpg', '258217966_d9d90d18d3.jpg', '275429470_b2d7d9290b.jpg', '28847243_e79fe052cd.jpg', '318052216_84dff3f98a.jpg', '334167043_cbd1adaeb9.jpg', '339670531_94b75ae47a.jpg', '342438950_a3da61deab.jpg', '36439863_0bec9f554f.jpg', '374435068_7eee412ec4.jpg', '382971067_0bfd33afe0.jpg', '384191229_5779cf591b.jpg', '386190770_672743c9a7.jpg', '392382602_1b7bed32fa.jpg', '403746349_71384f5b58.jpg', '408393566_b5b694119b.jpg', '424119020_6d57481dab.jpg', '424873399_47658a91fb.jpg', '450057712_771b3bfc91.jpg', '45472593_bfd624f8dc.jpg', '459694881_ac657d3187.jpg', '460372577_f2f6a8c9fc.jpg', '460874319_0a45ab4d05.jpg', '466430434_4000737de9.jpg', '470127037_513711fd21.jpg', '474806473_ca6caab245.jpg', '475961153_b8c13fd405.jpg', '484293231_e53cfc0c89.jpg', '49375974_e28ba6f17e.jpg', '506249802_207cd979b4.jpg', '506249836_717b73f540.jpg', '512164029_c0a66b8498.jpg', '512863248_43c8ce579b.jpg', '518773929_734dbc5ff4.jpg', '522163566_fec115ca66.jpg', '522415432_2218f34bf8.jpg', '531979952_bde12b3bc0.jpg', '533848102_70a85ad6dd.jpg', '535522953_308353a07c.jpg', '540889389_48bb588b21.jpg', '541630764_dbd285d63c.jpg', '543417860_b14237f569.jpg', '560966032_988f4d7bc4.jpg', '5650366_e22b7e1065.jpg', '6240329_72c01e663e.jpg', '6240338_93729615ec.jpg', '649026570_e58656104b.jpg', '662541407_ff8db781e7.jpg', '67270775_e9fdf77e9d.jpg', '6743948_2b8c096dda.jpg', '684133190_35b62c0c1d.jpg', '69639610_95e0de17aa.jpg', '707895295_009cf23188.jpg', '7759525_1363d24e88.jpg', '795000156_a9900a4a71.jpg', '822537660_caf4ba5514.jpg', '82852639_52b7f7f5e3.jpg', '841049277_b28e58ad05.jpg', '886401651_f878e888cd.jpg', '892108839_f1aad4ca46.jpg', '938946700_ca1c669085.jpg', '957233405_25c1d1187b.jpg', '9715481_b3cb4114ff.jpg', '998118368_6ac1d91f81.jpg', 'ant photos.jpg', 'Ant_1.jpg', 'army-ants-red-picture.jpg', 'formica.jpeg', 'hormiga_co_por.jpg', 'imageNotFound.gif', 'kurokusa.jpg', 'MehdiabadiAnt2_600.jpg', 'Nepenthes_rafflesiana_ant.jpg', 'swiss-army-ant.jpg', 'termite-vs-ant.jpg', 'trap-jaw-ant-insect-bg.jpg', 'VietnameseAntMimicSpider.jpg']
print(ants_dataset.imgArr)
# 获取索引为0的图片的open状态
ants_0= ants_dataset[0] # ants_0 = ants_dataset.__getitem__(0)
# <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=768x512 at 0x19611329BE0>
print(ants_0)
# 展示图片
ants_0.show()
# 124
print(len(ants_dataset)) # print(ants_dataset.__len__())
3.1.2 DataLoader
DataLoader对数据进行打包,将数据集划分为小批量,按batchsize送入网络模型。可以接收一个Dataset对象作为输入,并根据指定的批量大小、是否打乱数据、是否使用多线程等参数,来构建一个用于数据加载的迭代器。
dataset=test_set: 指定要加载的数据集,这里是test_set。
batch_size=64: 设置每个批次的大小为64。这意味着每次从数据集中取出64个样本进行处理。
shuffle=True: 设置为True就是表示每个epoch开始时,对数据进行打乱,为False表示不打乱。
num_workers=0: 设置工作进程的数量。这里设置为0意味着数据加载将在主进程中进行,不会使用额外的子进程来加速数据加载。
drop_last=True: 如果数据集的大小不能被批次大小整除,设置为True会丢弃最后一个不完整的批次。这可以确保所有批次都有相同的大小,避免因批次大小不一致而导致的问题。
import torchvision
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
test_set = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
test_loader = DataLoader(dataset=test_set, batch_size=64, shuffle=True, num_workers=0, drop_last=True)
img,target = test_set[0]
# torch.Size([3, 32, 32]) (C,H,W)
print(img.shape)
# 3
print(target)
# 迭代器取出test_loader的第一个被打包的imgs和tags
iterator_loader = iter(test_loader)
imgs,targets = next(iterator_loader)
# torch.Size([64, 3, 32, 32]) (N,C,H,W)
print(imgs.shape)
# tensor([7, 2, 3, 1, 4, 1, 9, 8, 8, 5, 6, 9, 4, 5, 8, 7, 6, 3, 8, 3, 4, 5, 2, 6,
# 7, 1, 4, 1, 1, 1, 0, 5, 5, 5, 5, 0, 0, 8, 8, 3, 6, 2, 2, 5, 8, 0, 4, 4,
# 5, 9, 2, 8, 3, 9, 3, 4, 5, 0, 9, 2, 1, 6, 8, 6])
# 这个包64个数据所对应的类别
print(targets)
writer = SummaryWriter("log")
# Tensorboard中生成两个标签页Epoch0和Epoch1
for epoch in range(2):
# 在每个标签页中每一步step都会呈现64张图片
step = 0
for data in test_loader:
imgs,targets = data
writer.add_images("Epoch:{}".format(epoch),imgs,step)
step += 1
writer.close()
注意:add_image与add_images的区别
3.2 TensorBoard
TensorBoard的作用是提供一套用于数据可视化的工具,帮助开发者理解和分析机器学习模型的训练过程。
3.2.1 add_scalar
add_scalar用于在TensorBoard中添加标量数据。该方法可以用来添加训练过程中的损失值、准确率等指标,以便于在TensorBoard中进行可视化和比较。
tag(字符串):数据标识,用作图表的标题。
scalar_value(数值):要记录的标量数据的值,例如损失值、准确率等。
global_step(整数):迭代次数。例如,在训练神经网络时,可以将当前的迭代次数传递给global_step,以便在TensorBoard中可视化某些指标随着训练步数变化而变化的曲线。
walltime(时间戳):记录的时间。如果不指定,则默认使用当前时间。
案例:使用TensorBoard绘制y=x的函数图像
from torch.utils.tensorboard import SummaryWriter
# 日志文件存储位置
writer = SummaryWriter("log")
for i in range(100):
# 参数:标题,纵坐标,横坐标
writer.add_scalar("y=2x",2*i,i)
writer.close()
运行上述代码,产生log文件夹,里面存放了一个文件。
在terminal中运行如下代码,再点击网址跳转。
出现了y=2x图像
补充:可通过tensorboard --logdir=log --port=XXXX 指定端口号
当未改变图像标题,重复修改y值,如writer.add_scalar(“y = x”, i, i),writer.add_scalar(“y = x”, 2i, i),writer.add_scalar(“y = x”, 3i, i),会导致新绘制会包含之前绘制的图像。
解决方法:删除所有log文件,重新执行程序,再在tensorboard中查看
3.2.2 add_image
tag(字符串):数据标识,用作图表的标题。
img_tensor(torch.tensor、numpy.ndarray或string/blobname):图像数据,注意类型的要求。
global_step(整数):迭代次数。可以通过滑块来查看不同图片。
walltime(时间戳):记录的时间。如果不指定,则默认使用当前时间。
dataformats(字符串):表单的图像数据格式规范CHW、HWC、HW、WH等。
案例:将图像数据添加到TensorBoard的img标签页上
from torch.utils.tensorboard import SummaryWriter
from PIL import Image
import numpy as np
img_path = "dataset/train/ants/0013035.jpg"
img_PIL = Image.open(img_path)
# 将图片PIL类型转换为numpy类型
img_array = np.array(img_PIL)
# (512, 768, 3) (高度、宽度、通道数)
print(img_array.shape)
writer = SummaryWriter("log")
# 标题,图片数据,迭代次数,数据形式(高度H、宽度W、通道数C)
writer.add_image("img",img_array,1,dataformats='HWC')
writer.close()
问题:PIL报错`AttributeError: module ‘PIL.Image‘ has no attribute ‘ANTIALIAS
原因及解决方法:因为10.0.0
以后版本的Pillow
已经舍弃了ANTIALIAS,我们可以先卸载掉现有的Pillow,再安装9.5.0版本的Pillow。
#卸载当前的pillow(我的版本是10.4.0)
pip uninstall pillow
#安装9.5.0版本的pillow(镜像源是清华的)
pip install pillow==9.5.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
3.3 Transforms
Transforms用于对图像进行预处理和数据增强操作,如调整图像大小、中心裁剪、随机裁剪、随机水平翻转、归一化、将 PIL 图像转换为 Tensor 等等。
3.3.1 ToTensor
ToTensor可以将图像PIL或者numpy的数据类型转换为tensor的数据类型。
from torchvision import transforms
from PIL import Image
# img_path = "F:\\研0学习\\DeepLearning\\Project\\Pytorch\\dataset\\train\\ants\\0013035.jpg"(绝对路径)
img_path = "dataset/train/ants/0013035.jpg"
img_PIL = Image.open(img_path)
print(img_PIL)
# 实例化一个tensor_trans对象
tensor_trans = transforms.ToTensor()
# 将图像PIL的数据类型转换为tensor的数据类型,默认调用ToTensor类的call方法
img_tensor = tensor_trans(img_PIL)
print(img_tensor)
ToTensor类的call方法(参数:图像PIL或者numpy的数据类型,返回:图像tensor的数据类型)
运行结果:
可以看到,张量数据是一个三维数组,由3个通道(3个二维数组)构成。
补充:Pycharm中查看包的结构Structure(快捷键Alt+7)
查看需要传入的参数(快捷键Ctrl+P)
案例:将图像数据添加到TensorBoard的Img标签页上
注意:本案例在add_image函数中传入的参数是图像tensor的数据类型,而在3.2.2 add_image,传入的参数是图像numpy的数据类型。
from PIL import Image
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms
img_path = "dataset/train/bees/16838648_415acd9e3f.jpg"
img_PIL = Image.open(img_path)
# 实例化一个tensor_trans对象
tensor_trans = transforms.ToTensor()
# 将图像PIL的数据类型转换为tensor的数据类型
img_tensor = tensor_trans(img_PIL)
writer = SummaryWriter("log")
# 传入的参数是图像tensor的数据类型
writer.add_image("Img",img_tensor)
writer.close()
3.3.2 Normalize
我们采用标准化对图片张量进行处理:
列表中0.5的作用是设置标准化的均值和标准差。在这个例子中,每个通道的像素值将被减去0.5(均值),然后除以0.5(标准差),从而实现标准化。这样做的目的是使得图片的像素值分布在-1到1之间,有助于神经网络的训练。
from PIL import Image
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms
img_path = "dataset/train/ants/5650366_e22b7e1065.jpg"
img_PIL = Image.open(img_path)
# 实例化一个tensor_trans对象
tensor_trans = transforms.ToTensor()
# 将图像PIL的数据类型转换为tensor的数据类型
img_tensor = tensor_trans(img_PIL)
# 打印图片张量的第一个通道的第一个通道值
# tensor(0.3804)
print(img_tensor[0][0][0])
writer = SummaryWriter("log")
# 将图片张量添加到Tensorboard中
writer.add_image("img_ant",img_tensor,1)
# 使用Normalize对图片张量进行标准化处理
# 前面的参数[0.5,0.5,0.5]表示每个通道的均值,后面的参数[0.5,0.5,0.5]表示每个通道的标准差
norm_trans = transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
img_norm_tensor = norm_trans(img_tensor)
# 打印归一化后的图片张量的第一个通道的第一个通道值
# tensor(-0.2392)
print(img_norm_tensor[0][0][0])
# 将归一化后的图片张量添加到Tensorboard中
writer.add_image("img_ant",img_norm_tensor,2)
writer.close()
图片张量添加到Tensorboard中
将标准化后的图片添加到Tensorboard中
归一化和标准化都是对数据做变换的方式,将原始的一列数据转换到某个范围,或者某种形态。
归一化:通过对原始数据进行变换把数据映射到(默认为[0,1])之间。如果出现异常点,影响了最大值和最小值,那么结果显然会发生改变,所以这种方法健壮性较差,只适合传统精确小数据场景。
公式:
标准化:通过对原始数据进行变换把数据变换到均值为0,标准差为1范围内。如果出现异常点,由于具有一定数据量,少量的异常点对于平均值的影响并不大,从而方差改变较小,所以在已有样本足够多的情况下比较稳定,适合现代嘈杂大数据场景。
公式:
3.3.3 Resize
Resize函数在transforms库中用于改变图像的尺寸。它接受两个参数,第一个是图像的新高度,第二个是新宽度。如果只提供一个参数,另一个参数默认为None,此时图像会被按比例缩放。
from torchvision import transforms
from PIL import Image
img_path = "dataset/train/ants/0013035.jpg"
img_PIL = Image.open(img_path)
# <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=768x512 at 0x23DEDD6B0D0>
print(img_PIL)
# 实例化一个resize_trans对象
resize_trans = transforms.Resize((512, 512))
# 传入的参数可以是图片PIL或者tensor
img_resize_PIL = resize_trans(img_PIL)
# <PIL.Image.Image image mode=RGB size=512x512 at 0x23DEC241DC0>
print(img_resize_PIL)
# 实例化一个resize_trans2对象
resize_trans2 = transforms.Resize(300)
# 传入的参数可以是图片PIL或者tensor
img_resize2_PIL = resize_trans2(img_PIL)
# <PIL.Image.Image image mode=RGB size=450x300 at 0x23DEC1370A0>
print(img_resize2_PIL)
3.3.4 Compose
Compose用于将多个数据变换操作组合成一个变换序列,以便能够按顺序执行多个操作。通过使用transforms.Compose()
,可以方便地串联起多个单独的变换操作,例如裁剪、缩放、旋转等,并将它们应用到数据集中的每张图像上。
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms
from PIL import Image
img_path = "dataset/train/ants/0013035.jpg"
img_PIL = Image.open(img_path)
# 实例化一个resize_trans的对象
resize_trans = transforms.Resize(300)
# 实例化一个tensor_trans对象
tensor_trans = transforms.ToTensor()
# 实例化一个compose_trans对象,将上面创建好的两个对象依次传入
compose_trans = transforms.Compose([resize_trans, tensor_trans])
# img_PIL -> img_resize_PIL -> img_resize_tensor
img_resize_tensor = compose_trans(img_PIL)
writer = SummaryWriter("log")
# 将img_resize_tensor写入Tensorboard的img_compose标签页中
writer.add_image("img_compose",img_resize_tensor)
writer.close()
3.3.5 RandomCrop
RandomCrop 是 PyTorch 中用于图像数据增强(data augmentation)的函数之一,它可以在图像或张量的随机位置裁剪出指定大小的区域。
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms
from PIL import Image
writer = SummaryWriter("log")
img_path = "dataset/train/ants/0013035.jpg"
img_PIL = Image.open(img_path)
# 实例化一个crop_trans对象
crop_trans = transforms.RandomCrop(30)
# 实例化一个tensor_trans对象
tensor_trans = transforms.ToTensor()
# 实例化一个compose_trans对象
compose_trans = transforms.Compose([crop_trans, tensor_trans])
# 循环将img_crop_tensor写入Tensorboard的RandomCrop标签页中
for i in range(10):
img_crop_tensor = compose_trans(img_PIL)
writer.add_image("RandomCrop",img_crop_tensor,i)
writer.close()
3.4 torchvision中的数据集使用
数据集链接:https://pytorch.org/pytorch-domains
以CIFAR10数据集为例,其他的数据集相似:CIFAR-10是一个常用的计算机视觉数据集,包含了60,000张32x32像素的彩色图像,分为10个类别,每个类别有6,000张图像。这些图像涵盖了飞机、汽车、鸟、猫、鹿、狗、青蛙、马、船和卡车等常见物体。
torchvision.datasets.CIFAR10: 这是PyTorch库中的一个类,用于加载CIFAR-10数据集。
root="./dataset": 指定了数据集下载和存储的位置。
train=True: 表示想要加载的是训练集,为False,则会加载测试集。
download=True: 这是一个布尔值参数,为True,则表示如果数据集尚未下载,就会自动下载。如果设置为False,则不会下载数据集。
import torchvision
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter("log")
compose_tensor = torchvision.transforms.Compose([
torchvision.transforms.ToTensor()
])
train_set = torchvision.datasets.CIFAR10("./dataset", train=True, transform=compose_tensor, download=True)
test_set = torchvision.datasets.CIFAR10("./dataset",train=False,transform=compose_tensor,download=True)
# (tensor(...),3)
print(test_set[0])
# img对应img的tensor数据形式,target对应分类的编号
img,target = test_set[0]
# tensor(...)
print(img)
# 3
print(target)
# ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck'] 一共有10个类别
print(test_set.classes)
# cat
print(test_set.classes[target])
# 所以test_set[0]表示的是cat数据
# 循环将test_set前10个数据写入Tensorboard中的dataset_transform的标签页中
for i in range(10):
img,target = test_set[i]
writer.add_image("dataset_transform",img,i)
writer.close()
4 神经网络
4.1 nn.Module
文档连接:https://pytorch.org/docs/stable/generated/torch.nn.Module.html#torch.nn.Module
nn.Module是PyTorch中的一个类,用于表示神经网络的层。使用步骤如下:
- 定义网络结构:通过继承nn.Module类并实现__init__()和forward()方法来定义自己的网络结构。
- 添加层:在__init__()方法中,使用super().__init__()调用父类的初始化方法,然后添加所需的层,如卷积层、全连接层等。
- 前向传播:在forward()方法中,定义输入数据在网络中的传播过程。
- 实例化网络:创建自定义网络类的实例,并传入所需的参数。
- 训练和评估:将实例化的网络用于训练和评估。
import torch
from torch import nn
class MyModule(nn.Module):
def __init__(self):
# 使用super调用父类的构造方法
super().__init__()
# 重写forward构造方法
def forward(self,input):
output = input + 1
return output
# 实例化一个module对象
module = MyModule()
# 创建一个张量作为输入
input = torch.tensor(1.0)
# 将输入传入到模型中,调用模型的forward方法,得到输出
output = module(input)
# tensor(2.)
print(output)
4.2 卷积层
4.2.1 torch.nn.functional.conv2d
torch.nn.functional(通常简称为F)是PyTorch库中的一个模块,它提供了大量的功能函数,这些函数可以对张量tensor进行操作。
import torch
import torch.nn.functional as F
# 输入图像(5×5)
input = torch.tensor([[1,2,0,3,1],
[0,1,2,3,1],
[1,2,1,0,0],
[5,2,3,1,1],
[2,1,0,1,1]])
# 卷积核(3×3)
kernal = torch.tensor([[1,2,1],
[0,1,0],
[2,1,0]])
# torch.Size([5, 5])
print(input.shape)
# torch.Size([3, 3])
print(kernal.shape)
# 修改张量的shape (H,W)->(minibatch,channel,H,W)
input = input.reshape(1,1,5,5)
kernal = kernal.reshape(1,1,3,3)
# torch.Size([1, 1, 5, 5])
print(input.shape)
# torch.Size([1, 1, 3, 3])
print(kernal.shape)
print("卷积运算:-----------------------")
# 每次移动1位
output = F.conv2d(input,kernal,stride=1)
print(output)
# 每次移动2位
output2 = F.conv2d(input,kernal,stride=2)
print(output2)
# 在input的上下左右补上0(默认为0),且每次移动1位
output3 = F.conv2d(input,kernal,stride=1,padding=1)
print(output3)
torch.Size([1, 1, 5, 5])的参数解析:
第一个维度的大小为1,表示我们有一个批次的数据,但只有一个样本。
第二个维度的大小也为1,表示每个样本只有一个通道。在图像处理中,通道通常表示颜色通道(例如,RGB图像有3个通道,灰度图像通道数是1。)
第三个维度的大小为5,表示每个通道的高度为5。
第四个维度的大小为5,表示每个通道的宽度为5。
output = F.conv2d(input,kernal,stride=1)图解如下:
output3 = F.conv2d(input,kernal,stride=1,padding=1)图解如下:
4.2.2 nn.Conv2d
torch.nn.Conv2d()是一个类,需要在实例化时指定卷积层的参数,如输入通道数、输出通道数、卷积核大小等。实例化后,可以通过调用其 forward() 方法进行卷积操作。简单说:就是定义一个卷积层。
import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
class MyModule(nn.Module):
def __init__(self):
super().__init__()
self.conv = nn.Conv2d(in_channels=3,out_channels=6,kernel_size=3,stride=1,padding=0)
def forward(self,input):
output = self.conv(input)
return output
test_set = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
dataloader = DataLoader(test_set,batch_size=64)
module = MyModule()
# MyModule(
# (conv): Conv2d(3, 6, kernel_size=(3, 3), stride=(1, 1))
# )
print(module)
writer = SummaryWriter("./log")
step = 0
for data in dataloader:
imgs,target = data
output = module(imgs)
# # torch.Size([64, 3, 32, 32])
# print(imgs.shape)
# # torch.Size([64, 6, 30, 30])
# print(output.shape)
# 分别将imgs和output_imgs输入到Tensorboard中,进行对比
writer.add_images("imgs",imgs,step)
# ouput是6通道的,而add_images只能接受3通道的,所以需要reshape
# (64,6,30,30) -> (-1,3,30,30) "-1"表示该维度的大小根据其他维度的大小自动计算
output_imgs = torch.reshape(output,(-1,3,30,30))
writer.add_images("output_imgs",output_imgs,step)
step = step + 1
writer.close()
self.conv = nn.Conv2d(in_channels=3,out_channels=6,kernel_size=3,stride=1,padding=0)
解释如下:
in_channels=3: 输入图像的通道数。在这个例子中,输入图像有3个通道,通常对应于RGB颜色空间中的红、绿、蓝三个通道。
out_channels=6: 输出特征图的通道数。在这个例子中,卷积层将产生6个不同的特征图,每个特征图对应一个特定的过滤器。
kernel_size=3: 卷积核的大小。这里使用的是3x3的卷积核,意味着在计算特征时,会考虑输入图像中每个3x3的区域。
stride=1: 卷积核在输入图像上移动的步长。这里步长为1,表示每次移动一个像素。较大的步长可以减少输出特征图的空间尺寸,但可能会丢失一些细节信息。
padding=0: 在输入图像周围添加的零填充的数量。"0"表示无填充。
即:创建了一个二维卷积层conv,用于处理具有3个通道的输入图像,并生成具有6个不同特征的输出特征图。6个卷积核大小都为3x3,步长为1,且不使用填充。
如下图:in_channel=1,卷积核2个,out_channel=2。2个卷积核分别会与输入图像进行一轮计算,得到两个输出。(1×2=2)
有多少个输出通道,就有几个卷积核;有多少个输入通道,每个卷积核就有多少个通道。
一个卷积核作用完输入图像的所有通道后,会把得到的所有矩阵的对应值相加,最终产生一个通道。
4.3 池化层 nn.MaxPool2d
nn.MaxPool2d是PyTorch中的一个类,用于实现二维的最大池化层。最大池化是一种取局部区域最大值的操作,它可以将输入的特征图尺寸减小,同时保留最重要的特征信息,以减少后续的计算量。降维是通过选择窗口大小(kernel_size)和滑动步长(stride)来实现的。例如,如果我们有一个8×8的特征图,使用3×3的窗口和2的步长进行最大池化,那么输出的特征图尺寸就会变成3×3。
注意,降维和下采样的区别:
- 降维:降维的主要目的是减少数据的维度,同时尽可能保留原始数据中的重要信息。
- 下采样:下采样的主要目的是减少数据样本的数量。
import torchvision
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
class MyModule(nn.Module):
def __init__(self):
super().__init__()
# 定义一个最大池化层
# kernel_size=3,池化核3×3,默认情况下stride也为3
# ceil_mode:决定输出大小计算时采用向上取整还是向下取整。为True,向上取值,为False,向下取整
self.max_pool = nn.MaxPool2d(kernel_size=3,ceil_mode=False)
def forward(self,input):
output = self.max_pool(input)
return output
test_set = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
dataloader = DataLoader(test_set, batch_size=64)
module = MyModule()
# MyModule(
# (max_pool): MaxPool2d(kernel_size=3, stride=3, padding=0, dilation=1, ceil_mode=False)
# )
print(module)
writer = SummaryWriter("./log")
step = 0
for data in dataloader:
imgs,targets = data
output_imgs = module(imgs)
writer.add_images("imgs_maxpool",imgs,step)
writer.add_images("output_imgs_maxpool",output_imgs,step)
step = step + 1
writer.close()
4.4 非线性激活
非线性激活函数是在神经网络中用于引入非线性特性的一种函数。线性激活函数只能处理线性关系,而非线性激活函数可以处理更复杂的非线性关系。常见的非线性激活函数有ReLU、Sigmoid、Tanh等。
ReLU(Rectified Linear Unit):ReLU函数是一种简单的非线性激活函数,它将所有负数映射为0,保留所有正数不变。
- inplace参数的含义:为True时,对原输入进行激活函数的计算,计算结果赋给原输入;为False时,对原输入进行激活函数的计算,生成计算结果,原输入不变。
Sigmoid函数:Sigmoid函数是一个常用的非线性激活函数,它将输入值映射到0和1之间。
()
Tanh函数:Tanh函数是双曲正切函数,它将输入值映射到-1到1之间。
import torch
from torch import nn
input = torch.tensor([[1,-0.5],
[-1,3]])
# 增加一个参数batchsize
input = torch.reshape(input, (-1, 1, 2, 2))
# torch.Size([1, 1, 2, 2])
print(input.shape)
class MyModule(nn.Module):
def __init__(self):
super().__init__()
self.relu = nn.ReLU()
def forward(self,input):
output = self.relu(input)
return output
module = MyModule()
output = module(input)
# tensor([[[[1., 0.],
# [0., 3.]]]])
print(output)
class MyModule(nn.Module):
def __init__(self):
super().__init__()
self.sigmoid = nn.Sigmoid()
def forward(self,input):
output = self.sigmoid(input)
return output
test_set = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
dataloader = DataLoader(test_set, batch_size=64)
writer = SummaryWriter("./log")
module = MyModule()
step = 0
for data in dataloader:
imgs,target = data
output_imgs = module(imgs)
writer.add_images("imgs_sigmoid",imgs,step)
writer.add_images("output_imgs_sigmoid",output_imgs,step)
step = step + 1
writer.close()
4.5 线性层
线性层是神经网络中的一种基本层,也被称为全连接层(全连接意味着每个神经元都与前一层的所有神经元相连)。线性层对输入数据进行线性变换,通过权重矩阵和偏置向量将输入数据映射到输出数据。线性层能够连接不同的神经元,实现信息的传递和转换。
nn.Linear是PyTorch中的一个线性层,用于实现全连接神经网络。它的作用是将输入数据进行线性变换,然后输出到下一层。具体来说,它将输入数据矩阵乘以权重矩阵,然后加上偏置向量(如果启用了偏置)。
import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
class MyModule(nn.Module):
def __init__(self):
super().__init__()
# 定义线性层
# in_features:输入数据的维度,即输入特征的数量。196608是根据后续reshape或者flatten得到的。
# out_features:输出数据的维度,即输出特征的数量。
# bias:布尔值,表示是否使用偏置项。默认为True,表示使用偏置项。
self.linear = nn.Linear(196608,10)
def forward(self,input):
return self.linear(input)
test_set = torchvision.datasets.CIFAR10("./dataset",train=False,transform=torchvision.transforms.ToTensor(),download=True)
dataloader = DataLoader(test_set, batch_size=64,drop_last=True)
module = MyModule()
for data in dataloader:
imgs,targets = data
# torch.Size([64, 3, 32, 32])
# print(imgs.shape)
# 法一:通过reshape改变形状
input = torch.reshape(imgs, (1, 1, 1, -1))
# torch.Size([1, 1, 1, 196608])
print(input.shape)
output = module(input)
# torch.Size([1, 1, 1, 10])
print(output.shape)
# 法二:将数据展平开,它将输入张量沿着指定的维度范围进行扁平化处理,并返回一个一维张量作为结果
input2 = torch.flatten(imgs)
# torch.Size([196608])
print(input2.shape)
output2 = module(input2)
# torch.Size([10])
print(output2.shape)
4.6 自定义网络
nn.Sequential是PyTorch中一个非常重要的类,用于构建简单的顺序连接模型。可以将其看做一个容器,它允许用户通过顺序堆叠多个层来创建神经网络模型。这个类的主要作用是简化模型的构建过程,使得用户可以通过简单、直观的方式定义复杂的网络结构。
CIFAR 10 model结构图如下:
最后两步的线性层略有省略,在此补充:
搭建上图的网络,各层的padding和stride需要手动计算一下:
import torch
from torch import nn
from torch.utils.tensorboard import SummaryWriter
class MyModule(nn.Module):
def __init__(self):
super().__init__()
self.model = nn.Sequential(
nn.Conv2d(3,32,5,padding=2),
nn.MaxPool2d(2),
nn.Conv2d(32,32,5,padding=2),
nn.MaxPool2d(2),
nn.Conv2d(32,64,5,padding=2),
nn.MaxPool2d(2),
nn.Flatten(),
nn.Linear(1024,64),
nn.Linear(64,10)
)
def forward(self,input):
output = self.model(input)
return output
module = MyModule()
# 64个数据,每个数据3个通道,每个通道32×32(每个值都是1.0)
input = torch.ones((64, 3, 32, 32))
output = module(input)
# torch.Size([64, 10])
print(output.shape)
writer = SummaryWriter("./log")
# (模型,模型的输入数据),
# add_graph是将PyTorch的计算图进行可视化,方便查看模型的层次结构和数据流动
writer.add_graph(module,input)
writer.close()
4.7 损失函数和反向传播
损失函数(Loss Function)用于衡量模型的预测输出与实际标签之间的差异或者误差。损失越小越好,根据loss调整参数(反向传播),更新输出,减少损失。
4.7.1 nn.L1Loss
4.7.2 nn.MSELoss
4.7.3 nn.CrossEntropyLoss
交叉熵损失函数(Cross-Entropy Loss Function)是在分类问题中经常使用的一种损失函数,特别是在多分类问题中。它衡量了模型输出的概率分布与真实标签之间的差异,通过最小化交叉熵损失来调整模型参数,使得模型更好地适应分类任务。
import torch
from torch import nn
inputs = torch.tensor([1, 2, 3], dtype=torch.float32)
targets = torch.tensor([1, 2, 5], dtype=torch.float32)
loss = nn.L1Loss()
result = loss(inputs, targets)
# tensor(0.6667) (|1-1|+|2-2|+|3-5|)/3
print(result)
loss2 = nn.L1Loss(reduction="sum")
result2 = loss2(inputs, targets)
# tensor(2.) |1-1|+|2-2|+|3-5|
print(result2)
mse_loss = nn.MSELoss()
mse_result = mse_loss(inputs, targets)
# tensor(1.3333) ((1-1)^2+(2-2)^2+(3-5)^2)/3=4/3
print(mse_result)
x = torch.tensor([0.1, 0.2, 0.3])
y = torch.tensor([1])
x = torch.reshape(x,(1,3))
# tensor([[0.1000, 0.2000, 0.3000]])
print(x)
cross_loss = nn.CrossEntropyLoss()
cross_result = cross_loss(x,y)
# tensor(1.1019) -0.2+ln(exp(0.1)+exp(0.2)+exp(0.3))
print(cross_result)
loss function的使用应根据需求,选定好损失函数后,按损失函数要求的维度(形状)输入 。
使用前面搭建的网络结合CrossEntropyLoss计算损失:
import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
dataset = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
dataloader = DataLoader(dataset, batch_size=1, drop_last=True)
class MyModule(nn.Module):
def __init__(self):
super().__init__()
self.model = nn.Sequential(
nn.Conv2d(3,32,5,padding=2),
nn.MaxPool2d(2),
nn.Conv2d(32,32,5,padding=2),
nn.MaxPool2d(2),
nn.Conv2d(32,64,5,padding=2),
nn.MaxPool2d(2),
nn.Flatten(),
nn.Linear(1024,64),
nn.Linear(64,10)
)
def forward(self,input):
output = self.model(input)
return output
cross_loss = nn.CrossEntropyLoss()
module = MyModule()
for data in dataloader:
imgs,targets = data
outputs = module(imgs)
# print(outputs)
# print(targets)
result_loss = cross_loss(outputs, targets)
result_loss.backward()
print(result_loss)
4.7.4 反向传播
反向传播(Backpropagation)是一种在神经网络中用于训练模型的核心算法,主要通过计算损失函数对每个参数的梯度,并利用优化器,根据这些梯度更新网络中的权重和偏置,以最小化损失函数。在pycharm中对该行打断点,可以看到具体的梯度值。
4.8 优化器 torch.optim
torch.optim是PyTorch中用于优化神经网络模型参数的模块,它提供了多种优化算法,通过调整学习率等参数来最小化损失函数。
torch.optim中的一些常用优化器包括SGD、Adam、RMSprop和Adagrad等。这些优化器有不同的参数,如学习率(lr)、动量(momentum)和权重衰减(weight_decay)等,可以根据具体需求进行选择和调整。
import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
dataset = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
dataloader = DataLoader(dataset, batch_size=1, drop_last=True)
# 自定义网络,训练分类【CIFAR10】
class MyModule(nn.Module):
def __init__(self):
super().__init__()
self.model = nn.Sequential(
nn.Conv2d(3,32,5,padding=2),
nn.MaxPool2d(2),
nn.Conv2d(32,32,5,padding=2),
nn.MaxPool2d(2),
nn.Conv2d(32,64,5,padding=2),
nn.MaxPool2d(2),
nn.Flatten(),
nn.Linear(1024,64),
nn.Linear(64,10)
)
def forward(self,input):
output = self.model(input)
return output
# 实例化自定义网络
module = MyModule()
# 定义交叉熵损失函数
cross_loss = nn.CrossEntropyLoss()
# 定义优化器(SGD 随机梯度下降)
# lr 学习率
optim_sgd = torch.optim.SGD(module.parameters(), lr=0.01)
# 进行20轮数据的训练,每轮遍历整个数据集一次
for epoch in range(20):
running_loss = 0
for data in dataloader:
imgs,targets = data
outputs = module(imgs)
result_loss = cross_loss(outputs, targets)
# 清除每个参数的梯度
optim_sgd.zero_grad()
# 利用反向传播计算每个参数的梯度
result_loss.backward()
# 利用优化器,根据每个参数的梯度,更新模型参数,优化模型
optim_sgd.step()
running_loss = running_loss + result_loss
# 打印每轮遍历总的损失
print(running_loss)
4.9 现有模型的操作
4.9.1 使用及修改
使用PyTorch提供的现有模型vgg16
import torchvision
from torch import nn
# weights=None 加载模型结构,不加载模型参数
vgg16_false = torchvision.models.vgg16(weights=None)
# weights="DEFAULT" 加载模型结构和模型参数
# vgg16_true = torchvision.models.vgg16(weights="DEFAULT")
test_set = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
# 在vgg16网络最后添加一个线性层,在CIFR10上对10个类别进行分类
# vgg16_false.add_module("add_linear",nn.Linear(1000,10))
# print(vgg16_false)
# 将添加的线性层加在classifier中
# vgg16_false.classifier.add_module("add_linear",nn.Linear(1000,10))
# print(vgg16_false)
# 不添加层,仅修改vgg16中的classifier的最后一个线性层
vgg16_false.classifier[6] = nn.Linear(4096,10)
print(vgg16_false)
在vgg16网络最后添加一个线性层,在CIFR10上对10个类别进行分类
将添加的线性层加在classifier中
不添加层,仅修改vgg16中的classifier的最后一个线性层
4.9.2 保存及加载
model_save.py
import torch
import torchvision
vgg16 = torchvision.models.vgg16(weights=None)
# 保存方式1:保存模型结构+模型参数
torch.save(vgg16,"vgg16_method1.pth")
# 保存方式2:保存模型参数(官方推荐)
torch.save(vgg16.state_dict(),"vgg16_method2.pth")
model_load.py
import torch
import torchvision
# 加载方式1:加载模型结构和模型参数
model = torch.load("vgg16_method1.pth")
print(model)
# 加载方式2:先加载模型结构,后加载模型参数
# 先加载模型结构
vgg16 = torchvision.models.vgg16(weights=None)
# 后加载模型参数
vgg16.load_state_dict(torch.load("vgg16_method2.pth"))
print(vgg16)
4.10 完整的模型训练套路
model.py
import torch
from torch import nn
class MyModule(nn.Module):
def __init__(self):
super().__init__()
self.model = nn.Sequential(
nn.Conv2d(3,32,5,1,padding=2),
nn.MaxPool2d(2),
nn.Conv2d(32,32,5,1,padding=2),
nn.MaxPool2d(2),
nn.Conv2d(32,64,5,1,padding=2),
nn.MaxPool2d(2),
# 展平后的序列长度为64*4*4=1024
nn.Flatten(),
nn.Linear(64*4*4,64),
nn.Linear(64,10)
)
def forward(self,x):
x = self.model(x)
return x
# 测试神经网络是否正常运行,调用model.py就不会进行如下测试
if __name__ == '__main__':
module = MyModule()
input = torch.ones((64, 3, 32, 32))
# print(input)
output = module(input)
# print(output)
# torch.Size([64, 10])
print(output.shape)
train.py
# _*_ coding : utf-8 _*_
# @Time : 2024/9/8 18:51
# @Author : 春风吹又生
# @File : train
# @Project : DeepLearning
import torchvision
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from model import *
# 准备数据集
train_data = torchvision.datasets.CIFAR10("../dataset", train=True, transform=torchvision.transforms.ToTensor(),
download=True)
test_data = torchvision.datasets.CIFAR10("../dataset", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
# len()获取数据集长度
train_data_size = len(train_data)
test_data_size = len(test_data)
print("训练集数据集的长度为:{}".format(train_data_size))
print("测试集数据集的长度为:{}".format(test_data_size))
# 利用DataLoader加载数据集
train_dataloader = DataLoader(train_data, batch_size=64, drop_last=True)
test_dataloader = DataLoader(test_data, batch_size=64, drop_last=True)
# 网络模型
module = MyModule()
# 损失函数
loss_func = nn.CrossEntropyLoss()
# 优化器
learning_rate = 1e-2
optimizer = torch.optim.SGD(module.parameters(), lr=learning_rate)
# 设置训练网络的一些参数
# 记录训练的次数
total_train_step = 0
# 记录测试的次数
total_test_step = 0
# 训练的轮数
epoch = 10
# 添加Tensorboard
writer = SummaryWriter("../log")
# 10轮训练
for i in range(epoch):
print("----------第{}轮训练开始----------".format(i+1))
# 训练步骤开始
module.train()
for data in train_dataloader:
imgs,targets = data
outputs = module(imgs)
loss = loss_func(outputs,targets)
# 优化器优化模型
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_train_step = total_train_step + 1
# 如果训练次数能被100整除,就打印loss并且绘制x-y图像
if total_train_step % 100 == 0:
# item()函数用于从只包含单个元素的张量中提取Python数值,将张量转换为标量值
print("训练次数:{},Loss:{}".format(total_train_step,loss.item()))
writer.add_scalar("train_loss",loss.item(),total_train_step)
# 测试步骤开始
module.eval()
total_test_loss = 0
total_accuracy = 0
# 评估模型时,不需要进行反向传播
with torch.no_grad():
for data in test_dataloader:
imgs,targets = data
outputs = module(imgs)
loss = loss_func(outputs, targets)
total_test_loss = total_test_loss + loss.item()
accuracy = (outputs.argmax(1) == targets).sum()
total_accuracy = total_accuracy + accuracy
print("整体测试集上的Loss:{}".format(total_test_loss))
print("整体测试集上的正确率:{}".format(total_accuracy/test_data_size))
writer.add_scalar("test_loss",total_test_loss,total_test_step)
writer.add_scalar("test_accuracy",total_accuracy/test_data_size,total_test_step)
total_test_step = total_test_step + 1
torch.save(module,"torch_{}.pth".format(i))
print("模型已保存")
writer.close()
4.11 利用GPU进行训练
4.11.1 xx = xx.cuda()
if torch.cuda.is_available():
# 网络模型使用GPU
module = module.cuda()
if torch.cuda.is_available():
# 损失函数使用GPU
loss_func = loss_func.cuda()
# 训练数据使用GPU
if torch.cuda.is_available():
imgs = imgs.cuda()
targets = targets.cuda()
# 测试数据使用GPU
if torch.cuda.is_available():
imgs = imgs.cuda()
targets = targets.cuda()
4.11.2 xx = xx.to(device)
# 定义训练的设备
# device = torch.device("cuda:0")
# device = torch.device("cuda:1")
device = torch.device("cuda")
# 网络模型使用GPU
module = module.to(device)
# 损失函数使用GPU
loss_func = loss_func.to(device)
# 训练数据使用GPU
imgs = imgs.to(device)
targets = targets.to(device)
# 测试数据使用GPU
imgs = imgs.to(device)
targets = targets.to(device)
4.12 完整的模型验证套路
import torchvision
from PIL import Image
import torch
img_path = "./dog.jpg"
img = Image.open(img_path)
# <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=1082x810 at 0x26AC11FA850>
print(img)
# 将img转换为RGB的形式
img = img.convert('RGB')
transform = torchvision.transforms.Compose([torchvision.transforms.Resize((32,32)),
torchvision.transforms.ToTensor()])
img = transform(img)
# torch.Size([3, 32, 32])
print(img.shape)
# 加载模型结构和参数
model = torch.load("./torch_0.pth")
# 增加一个batchsize的维度
img = torch.reshape(img, (1, 3, 32, 32))
# 验证开始
model.eval()
# 不进行反向传播,计算梯度
with torch.no_grad():
# img使用GPU
img = img.to("cuda")
output = model(img)
# tensor([[-1.7493, 0.0590, 0.3859, 0.9509, 0.7353, 0.9401, 1.3249, 0.9609,
# -2.0142, -0.2805]], device='cuda:0')
print(output)
# tensor([6], device='cuda:0')
print(output.argmax(1))
# 使用gpu训练保存的模型在cpu上使用
model = torch.load("XXXX.pth",map_location= torch.device("cpu"))
4.13 看看开源项目
python XXX.py --参数名 值