文章目录
- 1 基础环境配置(CPU版)
- 2 PyTorch学习
- 2.1 Dataset和DataLoader
- 2.1.1 Dataset
- 2.1.2 DataLoader
- 2.2 Tensorboard
- add_scalar
- add_image
- add_graph
- 2.3 Transforms
- 2.3.1 ToTensor
- 2.3.2 Normalize
- 2.3.3 Resize
- 2.3.4 Compose
- 2.4 torchvision中的数据集使用
- 3 网络学习
- 3.1 nn.Module
- 3.2 卷积层
- 3.2.1 torch.nn.functional.conv2d
- 3.2.2torch.nn.Conv2d
- 3.3 池化层
- 3.3.1 nn.MaxPool2d
- 3.4 非线性激活
- 3.5 线性层
- nn.Linear
- 3.6 自定义网络
- 3.7 损失函数和反向传播
- nn.L1Loss
- nn.MSELoss
- nn.CrossEntropyLoss ????
- 反向传播
- 3.8 torch.optim优化器
- 3.9 对现有模型的操作
- 4.0 模型训练一条龙
- 4.1 模型训练(cpu版)
- 4.2 模型测试
- 4.3 利用GPU提速
1 基础环境配置(CPU版)
-
安装anconda,类似于Maven的包管理,可以创建多个相互隔离的虚拟环境,自带的基础环境是base。
-
安装pytorch(AMD集显,不支持CUDA)
注意事项:
安装anconda需要配置相应的环境变量,可以搜博客参考
例:
自定义的安装路径\anaconda3 自定义的安装路径\Scripts 自定义的安装路径\Library\bin
安装pytorch,直接复制安装命令,在指定的环境中执行该条指令即可。(开梯子,速度快)
- base环境下的操作
# 列出所有的环境 conda env list # 创建名为“环境名”的虚拟环境,并指定 Python 的版本 conda create -n 环境名 python=3.9 # 创建名为“环境名”的虚拟环境,并指定 Python 的版本与安装路径 conda create --prefix=安装路径\环境名 python=3.9 # 删除名为“环境名”的虚拟环境 conda remove -n 环境名 --all # 进入名为“环境名”的虚拟环境 conda activate 环境名
- 虚拟环境下的操作
# 列出当前环境下的所有库 conda list # 安装 NumPy 库,并指定版本 x.x.x 并指定安装的镜像源 pip install numpy==x.x.x -i https://pypi.tuna.tsinghua.edu.cn/simple # 查看当前环境下某个库的版本(以 numpy 为例) pip show numpy # 退出虚拟环境 conda deactivate
可以通过导入torch包,并通过 torch.cuda.is_available()去验证是否安装成功。(导入包不报错,且支持CUDA的返回True,不支持的是False)
修改Jupyter的工作路径,即指定默认打开后的文件所在路径位置(依据需求,不改就是C:\Users\用户名 路径位置)
Jupyter 初始的工作路径为【C:\Users\用户名】,需要进行修正,将其转移到新建的【D:\Jupyter】位置。 - 新建 D:\Jupyter; - 打开桌面快捷方式中的 Prompt; - 输入 jupyter notebook --generate-config 命令并执行; - 打开上一步生成的配置文件地址,即C:\Users\用户名\.jupyter - 在 jupyter_notebook_config.py(以记事本方式打开)中使用 Ctrl + F 查找 并且修改如下配置项: 修改前:# c.ServerApp.notebook_dir = '' 修改后:c.ServerApp.notebook_dir = 'D:\\Jupyter' 并删除前面的#号注释,注意,'D:\\Jupyter' 中不能有空格,否则 Jupyter 打开就闪退。保存后关闭。 -找到桌面的 jupyter notebook 快捷图标,鼠标反键>>属性>>快捷方式>>目标,删除最后的"%USERPROFILE%/"。
虚拟环境连接 Jupyter
当我们在 Anaconda 里创建了好虚拟环境后,该环境并没有连接 Jupyter,换句话说,Jupyter 现在仅仅能与 base 环境相连。
解决方法:
第一种(在虚拟环境中安装Jupyter,将虚拟环境与其进行绑定):
# 安装 ipykernel pip install ipykernel -i https://pypi.tuna.tsinghua.edu.cn/simple # 将虚拟环境导入 Jupyter 的 kernel 中 python -m ipykernel install --user --name=环境名 # 删除虚拟环境的 kernel 内核 jupyter kernelspec remove 环境名 # 查看jupyter中的虚拟环境 jupyter kernelspec list # 删除指定的虚拟环境 jupyter kernelspec uninstall 环境名
第二种(在base中安装pytorch,也就是直接使用base环境做实验,不推荐)
2 PyTorch学习
两个重要函数【查API】:
- dir(传入库或者类) 可以显示该库下有什么类或类下有什么函数
- help(传入具体的函数名) 可以显示该函数的作用是什么
python中两种包的引入方式的区别:
在Python中,使用
import
和from...import
来引入包或模块的主要区别在于如何访问它们的属性和方法。
- 使用
import
引入时,需要通过包或模块的名称来访问其属性和方法。例如:import math [as xxx --起别名] result = math.sqrt(4)
- 使用
from...import
引入时,可以直接访问属性和方法,而无需通过包或模块的名称。例如:from math import sqrt result = sqrt(4)
总结:
import
引入需要通过包或模块名称访问属性和方法,而from...import
引入可以直接访问属性和方法。
对python中self参数的理解:
在Python中,"self"是一个约定俗成的参数名,用于指代实例对象本身。当你在类定义的方法中使用"self"时,Python会自动将调用该方法的实例(实例就是java中new 的对象,可将self理解为java中的this)传递给该参数。
例如,假设你有一个名为"Dog"的类,它有一个方法叫做"bark"。如果你创建了一个"Dog"的实例并调用它的"bark"方法,那么在"bark"方法内部,"self"就代表那个被调用方法的"Dog"实例。
class Dog: def bark(self): print("Woof!") my_dog = Dog() my_dog.bark() # 输出 Woof!
当调用
my_dog.bark()
时,Python会将my_dog
这个实例传递给bark
方法的self
参数。然后你就可以在bark
方法内部通过self
来访问或修改my_dog
的属性和方法。
self
的作用是:
- 作为实例方法的第一个参数,代表实例对象本身。
- 允许你在类的方法中访问和修改实例的属性和其他方法。
def __init__(self)
是Python类中的一个特殊方法,用于定义类的初始化方法。当创建一个类的实例时,这个方法会自动被调用。它的主要作用是设置类实例的初始状态,比如为类的属性赋值等。
2.1 Dataset和DataLoader
- Dataset:是一个抽象类,用于表示数据集。使用时需要继承这个类并实现
__getitem__
和__len__
方法。__getitem__
方法用于获取数据集中的一个样本,而__len__
方法用于获取数据集中的样本数量。这样,就可以自定义自己的数据集,并将其用于训练和测试模型。 - DataLoader:是一个迭代器,用于从数据集中批量加载数据。它可以自动处理数据的采样、打乱和并行加载等操作。通过设置
batch_size
参数,可以指定每个批次的大小。此外,DataLoader还支持多线程加载数据,以提高数据加载速度。
注:可以将Dataset理解为一个容器,我们将数据交给其保管,我们可以借助他获取任意位置我们想要的数据,但是他只知道拿着数据却不会对数据进行任何操作,而DataLoader是集成了多种操作数据的方法,但是他没有数据。所以就是二者结合,一个提供数据,一个对提供的数据进行各种处理。
2.1.1 Dataset
PyTorch中的Dataset
是一个抽象类,用于表示数据集。它有两个必须实现的方法:__len__()
和__getitem__()
。__len__()
方法返回数据集中样本的数量,而__getitem__()
方法根据给定的索引返回数据集中的一个样本。
Dataset
类的主要作用是:
- 提供一个统一的方式来访问数据集中的样本。
- 允许对数据集进行批处理、打乱等操作。
- 可以与其他PyTorch组件(如
DataLoader
)无缝集成,以便在训练和验证过程中高效地加载数据。
要使用自定义数据集,你需要创建一个继承自Dataset
的子类,并实现__len__()
和__getitem__()
方法。例如:
from torch.utils.data import Dataset
from PIL import Image
import os
#继承Dataset类,并实现其中的方法
class MyDataSet(Dataset):
# 初始化拼接路径
def __init__(self, root_path, dir_path):
self.root_path = root_path
self.dir_path = dir_path
self.last_path = os.path.join(root_path, dir_path) # 将路径进行拼接
self.imgArr = os.listdir(self.last_path) # 读取目录下的数据
# 返回某一索引对应的图片数据的open状态【根据需要,也可以返回多值,多值以元组的方式存储,可以通过索引去获取值,也可以通过定义相同数量的变量来接受】
def __getitem__(self, index):
# 获取某一个具体图片的名称
img = self.imgArr[index]
img_path = os.path.join(self.last_path, img)
img_open = Image.open(img_path)
return img_open
def __len__(self):
return len(self.imgArr)
ants_dataset = MyDataSet("../dataset/train", "ants")
lens = ants_dataset.__len__()
ants_0=ants_dataset.__getitem__(0)
print(lens)
ants_0.show()
2.1.2 DataLoader
用于从数据集中批量加载数据,也支持多线程地读取数据【num_workers】,它可以自动处理数据的采样、打乱和并行加载等操作,在训练神经网络时,可将大量数据分批次地输入到模型中,以减少内存消耗和提高训练速度。【每一批次的大小通过batc_size指定,这样难免会出现最后剩余数不够一批次的情况,那么最后不够一批次的数据要或者不要,则有drop_last决定】
import torchvision
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
test_set = torchvision.datasets.CIFAR10(root="./test_img", train=False, transform=torchvision.transforms.ToTensor(),download=True)
"""
dataset=test_set: 指定要加载的数据集,这里是test_set。
batch_size=64: 设置每个批次的大小为64。这意味着每次从数据集中取出64个样本进行处理。
shuffle=False: 设置为False表示不对数据进行随机打乱;为True就是打乱。
num_workers=0: 设置工作进程的数量。这里设置为0意味着数据加载将在主进程中进行,不会使用额外的子进程来加速数据加载。
drop_last=True: 如果数据集的大小不能被批次大小整除,设置为True会丢弃最后一个不完整的批次。这可以确保所有批次都有相同的大小,避免因批次大小不一致而导致的问题。
"""
test_loader = DataLoader(dataset=test_set, batch_size=64, shuffle=False, num_workers=0, drop_last=True)
# imgs, tags = test_set[0]
# print(imgs.shape)
# print(tags)
#迭代器取出test_loader的第一个被打包的imgs和tags
# it_lo=iter(test_loader)
# imgs, tags = next(it_lo)
# print(imgs.shape) --> torch.Size([64, 3, 32, 32]) -->[ N, C, H, W ]
"""
print(tags) --> tensor([3, 8, 8, 0, 6, 6, 1, 6, 3, 1, 0, 9, 5, 7, 9, 8, 5, 7, 8, 6, 7, 0, 4, 9,
5, 2, 4, 0, 9, 6, 6, 5, 4, 5, 9, 2, 4, 1, 9, 5, 4, 6, 5, 6, 0, 9, 3, 9,
7, 6, 9, 8, 0, 3, 8, 8, 7, 7, 4, 6, 7, 3, 6, 3]) -->64个类别的打包
"""
writer = SummaryWriter("./logs")
for i in range(2):
step = 0
for data in test_loader:
imgs, tags = data
writer.add_images("Epoch:{}".format(i), imgs, step)
step += 1
writer.close()
注意:这里是writer.add_images
2.2 Tensorboard
TensorBoard 的作用是提供一套用于数据可视化的工具,帮助开发者理解和分析机器学习模型的训练过程。
用法:
- 安装和配置:
- 安装TensorBoard:需要确认已经安装TensorBoard。使用命令
pip install tensorboard
。 - 配置代码:在PyTorch中,通过
torch.utils.tensorboard
模块使用TensorBoard。要记录事件,需要导入并初始化SummaryWriter
对象,指定日志的存放路径。 - 记录信息:在训练循环中,使用
writer.add_scalar
等方法记录如损失和准确率等信息。这些信息之后可以通过TensorBoard进行可视化展示。
- 安装TensorBoard:需要确认已经安装TensorBoard。使用命令
- 数据记录:
- 标量数据:通过
add_scalar
方法记录标量数据,例如每个epoch的损失和准确率。这样可以在TensorBoard中绘制它们的变化曲线。 - 图像数据:使用
add_image
方法可以将图像数据记录到TensorBoard。这对于观察生成模型的输出或者监控训练过程中的数据增强效果非常有用。 - 直方图:使用
add_histogram
方法可以记录权重、偏差等变量的直方图,这有助于检查模型参数的分布情况。
- 标量数据:通过
- 可视化展示:
- 启动TensorBoard:训练过程记录的信息可以通过运行
tensorboard --logdir=<directory_name>
来启动可视化服务,并在浏览器中访问指定的端口查看结果。 - 解读界面:在TensorBoard界面中,可以查看标量(如损失和准确率)的趋势图,直方图,图像样本以及其他一些如模型结构和嵌入向量的信息。
- 启动TensorBoard:训练过程记录的信息可以通过运行
tensorboard --logdir=事件文件所在文件夹名 [--port=端口号 ]
add_scalar
将标量数据添加到TensorBoard的Scalars标签页上
Args:
tag(str):数据标识符
scalar_value(浮点数或字符串/blobname):要保存的值
global_step(int):要记录的全局步长值
walltime(浮动):可选覆盖默认walltime(time.time())事件发生几秒钟后
new_style(boolean):是使用新样式(张量字段)还是旧样式
style(simple_value字段)。新风格可能会导致更快的数据加载。
示例:
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter("logs")
x = range(100)
for i in x:
writer.add_scalar('y=2x', i * 2, i)
writer.close()
add_image
将图像数据添加到TensorBoard的Images标签页上
Args:
tag(str):数据标识符
img_tensor(torch.tensor、numpy.ndarray或string/blobname):图像数据 #注意图像的数据类型要求
global_step(int):要记录的全局步长值
walltime(浮动):可选覆盖默认walltime(time.time())事件发生后的几秒钟
dataformats(str):表单的图像数据格式规范CHW、HWC、HW、WH等。 # c-通道 H-高度 w-宽度 对于RGB图像,C=3;对于灰度图像,C=1
示例:
# TensorBoard的使用
from torch.utils.tensorboard import SummaryWriter
import numpy as np
from PIL import Image
writer=SummaryWriter("logs")
image_path= "../dataset/train/ants/0013035.jpg"
image_PIL=Image.open(image_path)
# 转换图片格式 (torch.tensor、numpy.ndarray或string/blobname)
image_array=np.array(image_PIL)
print(type(image_array))
print(image_array.shape)
writer.add_image("test123",image_array,1,dataformats="HWC")
writer.close()
使用
np.array(image_PIL)
将PIL图像转换为NumPy数组时,它会保留原始图像的维度信息。因此,image_array.shape
将显示一个包含三个元素的元组,分别对应于高度、宽度和通道数。在TensorBoard中,add_image
方法用于将图像添加到日志中。参数dataformats="HWC"
告诉TensorBoard,传入的图像数据采用高度、宽度和通道数的顺序。这与image_array
的形状相匹配,因此可以正确地显示图像。
add_graph
add_graph(self, model, input_to_model=None, verbose=False, use_strict_trace=True )
函数是一个用于将模型添加到图形中的函数.
参数详解:
model
: 这是一个模型对象,通常是一个神经网络模型【自定义模型或者已有的模型】,这个模型将被添加到图形中。input_to_model
: 这个参数是一个可选的输入张量,用于指定模型的输入。如果提供了输入张量,那么图形将包含一个节点,该节点表示模型的输入。如果没有提供输入张量,那么图形将只包含模型本身。verbose
: 这是一个布尔值,默认为False。如果设置为True,那么在添加模型到图形时,会输出一些额外的信息,例如模型的结构等。use_strict_trace
: 这是一个布尔值,默认为True。如果设置为True,那么在追踪模型时,会使用严格的追踪方式。这意味着只有那些对模型输出有直接影响的操作才会被包含在图形中。如果设置为False,那么所有操作都将被包含在图形中,即使它们对模型输出没有直接影响。
2.3 Transforms
Transforms是一个用于数据预处理的模块,主要用于图像处理、文本处理和音频处理等任务。Transforms提供了一系列的图像处理函数,例如尺寸缩放、张量转换、数据中心化或标准化等操作。这些操作在深度学习中的数据预处理阶段非常重要,能有效提升模型训练的效果和稳定性。
使用Transforms:
首先需要从torchvision中导入transforms模块。然后,通过创建不同的transforms实例来对图像进行各种变换。
例如,使用
transforms.ToTensor()
将PIL Image或numpy.ndarray类型的图像转换为PyTorch能处理的tensor类型。此外,还可以使用
transforms.Resize()
来调整图像尺寸,或者使用transforms.Normalize()
来进行图像的标准化处理。
Transforms类的主要作用是将输入图片经过一系列指定的变换操作(如裁剪、翻转、缩放等)后,输出预期的结果。
2.3.1 ToTensor
ToTensor是深度学习中常用的数据预处理函数,主要用于将图片数据转换成神经网络可以处理的格式。具体来说,它的功能包括:
- 格式转换:ToTensor的主要作用是将PIL Image或numpy.ndarray格式的图像转换为Tensor对象。
- 数据归一化:在转换过程中,如果输入图像的像素值范围是0到255,ToTensor会自动将这些值归一化到[0,1]之间。这样做是为了提高训练过程中数值的稳定性和收敛速度。
- 通道调整:ToTensor还会将图像的格式从HWC(高度×宽度×颜色通道)转换为CHW(颜色通道×高度×宽度),这是大多数深度学习框架所要求的格式。
2.3.2 Normalize
Normalize数据归一化,在深度学习中,其实就是对数据进行一种“标准化”处理,使得不同规模和量纲的数据能够在同一个尺度上比较。想象一下,如果在不同的国家买东西,每个国家的货币都不一样,这就像数据有不同的量纲一样。为了让这些数据能够公平地被模型处理,就需要把它们转换到同一个标准,也就是进行归一化处理。这样做的好处有两个:一是可以加快模型学习的速度,二是可以提高模型的精度。具体来说,Normalize通常是通过以下步骤实现的:
- 计算均值和标准差:你要先知道数据的平均值(均值)和数据的波动程度(标准差)。
- 应用公式:然后用每个数据点减去均值,再除以标准差。这样处理后的数据均值就会变成0,标准差变成1,即N(0,1)分布【标准正态分布】。
- 使用Normalize函数:在实际使用中,比如在PyTorch里,你可以直接用
transforms.Normalize
这个类来实现上面的步骤。需要提供每个通道的均值和标准差作为参数。
from torchvision import transforms
from PIL import Image
from torch.utils.tensorboard import SummaryWriter
#创建一个SummaryWriter对象,用于将数据写入到指定的日志目录(这里是"logs")。
writer=SummaryWriter("logs")
#使用PIL库的Image.open()方法打开一张图片
img=Image.open("../dataset/train/ants/0013035.jpg")
# print(img)
#将图片转换为可以被处理的tensor格式
tans_to_tensor=transforms.ToTensor()
img_tensor=tans_to_tensor(img)
# print(img_tenorm)
#打印转换后的张量的第一个通道的第一个像素值
print(img_tensor[0][0][0])
#使用SummaryWriter的add_image()方法将原始图片张量添加到TensorBoard中,命名为"IMG_Tensor"
writer.add_image("IMG_Tensor",img_tensor,0)
#使用torchvision.transforms.Normalize()对图片张量进行归一化处理,
#这里的参数[0.5, 0.5, 0.5]表示每个通道的均值,[0.5, 0.5, 0.5]表示每个通道的标准差。
tans_norm=transforms.Normalize([0.5,0.5,0.5],[0.5,0.5,0.5])
img_norm=tans_norm(img_tensor)
#打印归一化后的图片张量的第一个通道的第一个像素值。
print(img_norm[0][0][0])
writer.add_image("IMG_Norm",img_norm,1)
#关闭SummaryWriter对象,完成数据的写入。
writer.close()
列表中0.5的作用是设置归一化的均值和标准差。在这个例子中,每个通道的像素值将被减去0.5(均值),然后除以0.5(标准差),从而实现归一化。这样做的目的是使得图片的像素值分布在-1到1之间,有助于神经网络的训练。
归一化是将数据按比例缩放,使之落入一个小的特定区间。
- 将输入值减去0.5,这会将所有数值平移,使得原本中心在0.5的数值现在以0为中心;
- 结果除以0.5,这一步是一个缩放操作,目的是调整数值范围到[0,1]之间;
- 最后等式右侧的形式2*input-1是上述操作的代数简化,便于计算。
这个公式通过平移和缩放操作,将原始数据转换到[-1,1]范围内,这对于许多数据处理任务来说是有用的预处理步骤,可以提升模型性能并减少计算复杂度。
归一化和标准化
标准化:
主要是针对数据的分布进行处理,使数据呈现出均值为0,标准差为1的正态分布,即使原始数据不是正态分布,标准化后也会变为正态分布。
这种处理方式有助于消除不同特征间由于量纲或数值范围差异带来的影响,特别是在涉及梯度下降等优化算法时,可以加快收敛速度。
在许多机器学习模型中,比如支持向量机或逻辑回归,使用标准化可以提升模型的性能。
归一化:
- 是将数据按比例缩放,使之落入一个小的特定区间,常用的区间是0到1。
- 这种方法适用于处理不同度量单位的数据,或者当数据分布未知而无法进行标准化时。
- 在某些情况下,比如神经网络中的一些激活函数,归一化可以防止数据过大导致的计算问题。
两者之间的联系与区别:
- 联系:两者都是线性变换,都不会改变数据的相对位置关系,只改变其尺度【图像的比例缩放】。
- 区别:标准化关注的是数据的分布状态,而归一化更关注将数据限定在一个特定的范围内。
2.3.3 Resize
Resize函数在transforms库中用于改变图像的尺寸。它接受两个参数,第一个是图像的新高度,第二个是新宽度。如果只提供一个参数,另一个参数默认为None,此时图像会被按比例缩放。
示例:
from torchvision import transforms
from PIL import Image
img=Image.open("../dataset/train/ants/0013035.jpg")
print(img)
#两个参数,第一个是图像的新高度,第二个是新宽度
tans_resize=transforms.Resize((512,512))
tabs_img_1=tans_resize(img)
print(tabs_img_1)
#只提供一个参数,另一个参数默认为None,此时图像会被按比例缩放。
tans_resize_2=transforms.Resize(300)
tabs_img_2=tans_resize_2(img)
print(tabs_img_2)
2.3.4 Compose
Compose用于将多个数据变换操作组合成一个变换序列,以便能够按顺序执行多个操作。通过使用transforms.Compose()
,可以方便地串联起多个单独的变换操作,例如裁剪、缩放、旋转等,并将它们应用到数据集中的每张图像上。
from torchvision import transforms
# 定义两个图像变换操作
transform1 = transforms.RandomHorizontalFlip()
transform2 = transforms.ToTensor()
# 使用Compose将这两个变换操作组合在一起
composed_transform = transforms.Compose([transform1, transform2])
# 应用组合后的变换操作到图像上
image = load_image("example.jpg") # 假设已经加载了一张名为"example.jpg"的图像
transformed_image = composed_transform(image)
"""
在这个示例中,定义了两个图像变换操作:`transform1`(随机水平翻转图像)和`transform2`(将图像转换为张量)。使用`transforms.Compose`将这两个变换操作组合在一起,创建了一个名为`composed_transform`的组合变换。最后,我们将这个组合变换应用到了一张名为"example.jpg"的图像上,得到了变换后的图像`transformed_image`。
"""
2.4 torchvision中的数据集使用
数据集链接[https://pytorch.org/pytorch-domains]
以CIFAR10数据集为例,其他的数据集相似:CIFAR-10是一个常用的计算机视觉数据集,包含了60,000张32x32像素的彩色图像,分为10个类别,每个类别有6,000张图像。这些图像涵盖了飞机、汽车、鸟、猫、鹿、狗、青蛙、马、船和卡车等常见物体。
"""
torchvision.datasets.CIFAR10: 这是PyTorch库中的一个类,用于加载CIFAR-10数据集
root="./test_img": 这是一个参数,指定了数据集下载和存储的位置。
train=True: 表示想要加载的是训练集,为False,则会加载测试集。
download=True: 这是一个布尔值参数。为True:表示如果数据集尚未下载,就会自动下载。如果设置为False,则不会下载数据集。
"""
train_set=torchvision.datasets.CIFAR10(root="./test_img",train=True,download=True)
test_set=torchvision.datasets.CIFAR10(root="./test_img",train=False,download=True)
3 网络学习
torch.reshape
torch.reshape(tensor, shape)
,其中 tensor
是要重塑的原始张量(tensor),shape
是一个int类型的元组,是要指定的新的形状(维数)。
- 返回的新张量与原张量拥有相同的数据和元素个数,只是形状发生了变化。
- 数据顺序保持一致,即不改变张量中数据的排列顺序。
torch.reshape(tensor, (-1,..)))
如果新形状中的一个维度大小未知,可以设置为-1,系统会自动计算该维度的大小,前提是其他维度的大小已明确。
注意:确保修改后的形状能够满足原有张量的元素个数,否则会引发错误
my_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]])
print(my_input.shape)
print(my_input)
my_input=torch.reshape(my_input,(-1,1,5,5))
print(my_input.shape)
print(my_input)
my_input=torch.reshape(my_input,(1,25,1,1))
print(my_input.shape)
print(my_input)
my_input=torch.reshape(my_input,(1,1,1,5,5))
print(my_input.shape)
print(my_input)
torch.flatten
torch.flatten(input, start_dim=0, end_dim=-1)
是一个PyTorch函数,用于将输入张量展平为一维张量。参数的作用如下:
input
: 这是要展平的输入张量。start_dim
: 这是一个整数,表示从哪个维度开始展平。默认值为0,表示从第一个维度开始展平。end_dim
: 这是一个整数,表示展平到哪个维度结束。默认值为-1,表示展平到最后一个维度。
例如,假设我们有一个形状为 (2, 3, 4) 的张量,使用 torch.flatten(input, start_dim=0, end_dim=-1)
会将其展平为一个形状为 (24,) 的一维张量。
3.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, tensor
class MyNN(nn.Module):
def __init__(self):
super().__init__()
"""
添加自己所需的层
"""
def forward(self, input):
#简单示例
return input + 1
#实例化MyNN对象
my_nn=MyNN()
#转换输入数据类型
x=tensor(1)
"""
当调用my_nn(x)时,实际上是在调用模型的forward方法。这是因为在PyTorch等深度学习框架中,模型对象通常实现了一个名为forward的方法,该方法定义了模型的前向传播逻辑【也有后向传播,暂未涉及到】。当我们将输入数据传递给模型时,它会自动调用`forward`方法并返回输出数据。
"""
output=my_nn(x)
3.2 卷积层
3.2.1 torch.nn.functional.conv2d
torch.nn.functional(通常简称为F)是PyTorch库中的一个模块,它提供了大量的功能函数,这些函数可以对张量(tensor)进行操作。
==output_1 = F.conv2d(my_input, kernel, stride=1) 【import torch.nn.functional as F】==是一个函数,直接传入tensor、卷积核权重、移动步长等参数即可进行卷积操作。简单说:就是执行一次卷积操作
import torch
import torch.nn.functional as F
my_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]])
kernel = torch.tensor([[1, 2, 1],
[0, 1, 0],
[2, 1, 0]])
print("未修改之前:{}".format(my_input.shape))
# 将一个形状为(5, 5)的二维张量转换为一个形状为(1, 1, 5, 5)的四维张量。
my_input = torch.reshape(my_input, (1, 1, 5, 5))
print("修改之后:{}".format(my_input.shape))
# 将一个形状为(3, 3)的二维张量转换为一个形状为(1, 1, 3, 3)的四维张量。
kernel = torch.reshape(kernel, (1, 1, 3, 3))
# 卷积计算
# 1 每次移动1位
output_1 = F.conv2d(my_input, kernel, stride=1)
print("output_1:{} ".format(output_1))
# 2 每次移动2位
output_2 = F.conv2d(my_input, kernel, stride=2)
print("output_2:{} ".format(output_2))
# 3 每次移动1位,并在外圈【上下左右】补上0(默认为0)
output_3 = F.conv2d(my_input, kernel, stride=1, padding=1)
print("output_3:{} ".format(output_3))
------------输出结果---------------
"""
未修改之前:torch.Size([5, 5])
修改之后:torch.Size([1, 1, 5, 5])
output_1:tensor([[[
[10, 12, 12],
[18, 16, 16],
[13, 9, 3]]]])
output_2:tensor([[[
[10, 12],
[13, 3]]]])
output_3:tensor([[[
[ 1, 3, 4, 10, 8],
[ 5, 10, 12, 12, 6],
[ 7, 18, 16, 16, 8],
[11, 13, 9, 3, 4],
[14, 13, 9, 7, 4]]]])
"""
torch.reshape(my_input, (1, 1, 5, 5))的参数解析:
- 第一个维度的大小为1,表示我们有一个批次的数据,但只有一个样本。
- 第二个维度的大小也为1,表示每个样本只有一个通道。在图像处理中,通道通常表示颜色通道(例如,RGB图像有3个通道),这里是自定义的二维数组,不是图像转换的,所以通道数设为1。
- 第三个维度的大小为5,表示每个通道的高度为5。
- 第四个维度的大小为5,表示每个通道的宽度为5。
以output_1为例:
output_2同上,只是每次移动2位。
output_3 移动步长为1,且外圈扩充一圈,填补值为0
padding=(1,3)表示,输入卷积神经网络的图像,高度填充1,宽度填充3
3.2.2torch.nn.Conv2d
==torch.nn.Conv2d()==是一个类,需要在实例化时指定卷积层的参数,如输入通道数、输出通道数、卷积核大小等。实例化后,可以通过调用其 forward() 方法进行卷积操作。简单说:就是定义一个卷积层
torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode=‘zeros’, device=None, dtype=None)
- in_channels (int) – Number of channels in the input image
- out_channels (int) – Number of channels produced by the convolution
- kernel_size (int or tuple) – Size of the convolving kernel
- stride (int or tuple, optional) – Stride of the convolution. Default: 1
- padding (int, tuple or str, optional) – Padding added to all four sides of the input. Default: 0
- padding_mode (str, optional) –
'zeros'
,'reflect'
,'replicate'
or'circular'
. Default:'zeros'
- dilation (int or tuple, optional) – Spacing between kernel elements. Default: 1
- groups (int, optional) – Number of blocked connections from input channels to output channels. Default: 1
- bias (bool, optional) – If
True
, adds a learnable bias to the output. Default:True
import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
# 自定义网络
class MyNN(nn.Module):
def __init__(self):
super(MyNN, self).__init__()
"""
in_channels=3: 输入图像的通道数。在这个例子中,输入图像有3个通道,通常对应于RGB颜色空间中的红、绿、蓝三个通道。
out_channels=6: 输出特征图的通道数。在这个例子中,卷积层将产生6个不同的特征图,每个特征图对应一个特定的过滤器。
kernel_size=3: 卷积核的大小。这里使用的是3x3的卷积核,意味着在计算特征时,会考虑输入图像中每个3x3的区域。
stride=1: 卷积核在输入图像上移动的步长。这里步长为1,表示每次移动一个像素。较大的步长可以减少输出特征图的空间尺寸,但可能会丢失一些细节信息。
padding=0: 在输入图像周围添加的零填充的数量。0-->是无填充
即: 创建了一个二维卷积层,用于处理具有3个通道的输入图像,并生成具有6个不同特征的输出特征图。卷积核大小为3x3,步长为1,且不使用填充。
"""
self.conv1 = nn.Conv2d(in_channels=3, out_channels=6, kernel_size=3, stride=1, padding=0)
def forward(self, my_input):
return self.conv1(my_input)
# 创建一个CIFAR-10测试数据集,将其转换为tensor格式,并将其存储在test_set变量中。同时设置批量大小为64
test_set = torchvision.datasets.CIFAR10(
root="./test_img", train=False, transform=torchvision.transforms.ToTensor(), download=True
)
# 使用DataLoader加载数据集,以便在训练过程中以批量方式获取数据
dataloader = DataLoader(dataset=test_set, batch_size=64)
my_nn = MyNN()
writer = SummaryWriter("./logs")
step = 0
for data in dataloader:
imgs, targets = data
output = my_nn(imgs)
print(imgs.shape) # torch.Size([64, 3, 32, 32])
print(output.shape) # torch.Size([64, 6, 30, 30])
writer.add_images("tran_img", imgs, step)
"""
-1: 这个特殊的值表示该维度的大小将根据其他维度的大小自动计算,即第一个数值批次大小根据情况自动赋值。
3: 通道数设置为3。
30: 将W,H都设置为30
由于add_images只能接受3通道的,大于3的,就不直如何处理了,上面指定了out_channels=6,所以在经过自定义网络处理后,得到的是6通道的,
所以处理为3通道的【练习,代码并无实际用途】
"""
output = torch.reshape(output, (-1, 3, 30, 30))
writer.add_images("output_img", output, step)
step += 1
writer.close()
对out_channels=6: 输出特征图的通道数。在这个例子中,卷积层将产生6个不同的特征图【卷积核】,每个特征图对应一个特定的过滤器的理解。
将6简化为2:
3.3 池化层
3.3.1 nn.MaxPool2d
torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False) 最大池化
kernel_size (Union[int, Tuple[int, int]]) – the size of the window to take a max over
stride (Union[int, Tuple[int, int]]) – the stride of the window. Default value is kernel_size
padding (Union[int, Tuple[int, int]]) – Implicit negative infinity padding to be added on both sides
#控制窗口中元素的间距,默认值为1。当dilation>1时,会在窗口内部的元素之间插入空格,这被称为空洞卷积或膨胀卷积。
dilation (Union[int, Tuple[int, int]]) – a parameter that controls the stride of elements in the window
return_indices (bool) – if
True
, will return the max indices along with the outputs. Useful fortorch.nn.MaxUnpool2d
laterceil_mode (bool) – when True, will use ceil instead of floor to compute the output shape
nn.MaxPool2d
是PyTorch中的一个类,用于实现二维的最大池化层。最大池化是一种取局部区域最大值的操作,它可以将输入的特征图尺寸减小,同时保留最重要的特征信息,以减少后续的计算量。例如,如果我们有一个4x4的特征图,使用2x2的最大池化操作,那么输出的特征图尺寸就会变成2x2。这就是下采样的过程。降维则是通过选择窗口大小(kernel_size)和滑动步长(stride)来实现的。例如,如果我们有一个8x8的特征图,使用3x3的窗口和2的步长进行最大池化,那么输出的特征图尺寸就会变成3x3。这就是降维的过程。
- ceil_mode:布尔值,决定输出大小计算时使用向上取整还是向下取整的方法。默认为False,即使用向下取整的方法。
import torchvision
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
# 自定义网络
class MyNN(nn.Module):
def __init__(self):
super(MyNN, self).__init__()
# 定义一个最大池化层
self.max_pool_1 = nn.MaxPool2d(kernel_size=3,ceil_mode=False)
def forward(self, my_input):
return self.max_pool_1(my_input)
# 创建一个CIFAR-10测试数据集,将其转换为tensor格式,并将其存储在test_set变量中
test_set = torchvision.datasets.CIFAR10(
root="./test_img", train=False, transform=torchvision.transforms.ToTensor(), download=True
)
# 使用DataLoader加载数据集,以便在训练过程中以批量方式获取数据,设置批量大小为64
dataloader = DataLoader(dataset=test_set, batch_size=64)
my_nn = MyNN()
writer = SummaryWriter("./logs")
step = 0
for data in dataloader:
imgs, targets = data
output = my_nn(imgs)
writer.add_images("max_pool_tran_img", imgs, step)
writer.add_images("max_pool_output_img", output, step)
step += 1
writer.close()
3.4 非线性激活
非线性激活函数是在神经网络中用于引入非线性特性的一种函数。线性激活函数只能处理线性关系,而非线性激活函数可以处理更复杂的非线性关系。常见的非线性激活函数有ReLU(Rectified Linear Unit)、Sigmoid、Tanh等。
-
ReLU(Rectified Linear Unit):ReLU函数是一种简单的非线性激活函数,它将所有负数映射为0,保留所有正数不变。 R e L U ( x ) = ( x ) + = m a x ( 0 , x ) ReLU(x)=(x)^+=max(0,x) ReLU(x)=(x)+=max(0,x)
- 主要参数是inplace:inplace为真时,将处理后的结果赋值给原来的参数;为假时,原值不会改变。
-
Sigmoid函数:Sigmoid函数是一个常用的非线性激活函数,它将输入值映射到0和1之间。 S ( x ) = 1 1 + e x p ( − x ) S(x)=\frac{1}{1+exp(-x)} S(x)=1+exp(−x)1
- exp(-x)
表示指数函数 𝑒 的幂次为
-x,即exp(-x)
可以写作 e − x e^{-x} e−x。
- exp(-x)
-
Tanh函数:Tanh函数是双曲正切函数,它将输入值映射到-1和1之间。
# 自定义网络
class MyNN(nn.Module):
def __init__(self):
super(MyNN, self).__init__()
# 实例化非线性对象
self.relu1 = nn.ReLU()
self.sigmoid1=nn.Sigmoid()
def forward(self, my_input):
return self.sigmoid1(my_input)
3.5 线性层
线性层是神经网络中的一种基本层,也被称为全连接层【全连接意味着每个神经元都与前一层的所有神经元相连。】或密集层。线性层对输入数据进行线性变换,通过权重矩阵和偏置向量将输入数据映射到输出数据。线性层能够连接不同层的神经元,实现信息的传递和转换。
nn.Linear
**torch.nn.Linear(in_features, out_features, bias=True, device=None, dtype=None)**是PyTorch中的一个线性层,用于实现全连接神经网络。它的作用是将输入数据进行线性变换,然后输出到下一层。具体来说,它将输入数据矩阵乘以权重矩阵,然后加上偏置向量(如果启用了偏置)。
参数详解如下:
in_features
:输入数据的维度,即输入特征的数量。out_features
:输出数据的维度,即输出特征的数量。bias
:布尔值,表示是否使用偏置项。默认为True,表示使用偏置项。device
:指定该层所在的设备,可以是CPU或GPU。默认为None,表示使用当前设备。dtype
:指定该层的权重和偏置的数据类型。默认为None,表示使用全局默认的数据类型。
import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
# 自定义网络
class MyNN(nn.Module):
def __init__(self):
super(MyNN, self).__init__()
# 定义线性层
self.linear1 = nn.Linear(196608, 10)
def forward(self, my_input):
return self.linear1(my_input)
# 创建一个CIFAR-10测试数据集,将其转换为tensor格式,并将其存储在test_set变量中。同时设置批量大小为64
test_set = torchvision.datasets.CIFAR10(
root="./test_img", train=False, transform=torchvision.transforms.ToTensor(), download=True
)
# 使用DataLoader加载数据集,以便在训练过程中以批量方式获取数据
dataloader = DataLoader(dataset=test_set, batch_size=64)
my_nn = MyNN()
for data in dataloader:
imgs, targets = data
print(imgs.shape)
# 确定Liner的in_features数值的方式
# 方式一:通过reshape改变形状,最后给-1,由方法自行确定
output1 = torch.reshape(imgs, (1, 1, 1, -1))
print("out_1:{}".format(output1.shape))
# 方式二:将数据展平开
output2 = torch.flatten(imgs)
print("out_2:{}".format(output2.shape))
output = my_nn(output2)
print(output.shape)
self.linear1 = nn.Linear(196608, 10)中in_features数值确定方式:
3.6 自定义网络
nn.Sequential是PyTorch中一个非常重要的类,用于构建简单的顺序连接模型。可以将其看做一个容器,它允许用户通过顺序堆叠多个层来创建神经网络模型。这个类的主要作用是简化模型的构建过程,使得用户可以通过简单、直观的方式定义复杂的网络结构。具体介绍如下:
- 基本特点
- 顺序性:
nn.Sequential
的核心特点是其顺序性,即其中包含的层会按照它们被添加的顺序依次进行计算。这意味着,数据首先通过第一个层,然后其输出会作为下一个层的输入,这种前向传播的方式一直持续到最后一个层。 - 模块化:
nn.Sequential
本身就是一个模块,可以将其看作一个独立的网络组件,并易于集成到更大的模型中。
- 顺序性:
- 功能特性
- 索引访问:可以通过索引访问
nn.Sequential
中的任意层,这为模型提供了灵活性和可操作性。 - 自动前向传播:在调用模型的前向传播函数时,不需要手动指定每一层,
nn.Sequential
会自动按顺序执行其包含的所有层。 - 非训练参数设置:可以在训练过程中冻结某些层,这在迁移学习等场景中非常有用。
- 索引访问:可以通过索引访问
import torch
from torch import nn
from torch.utils.tensorboard import SummaryWriter
class MyNN(nn.Module):
def __init__(self):
super(MyNN,self).__init__()
self.model = nn.Sequential(
nn.Conv2d(3,6,5,stride=1,padding=0),
# 这里经过最大池化后 (6,28,28)--> (6,27,27)
nn.MaxPool2d(kernel_size=2, stride=1),
nn.Conv2d(6,16,5,stride=1,padding=0),
nn.Flatten(), # 16*23*23=8464
nn.Linear(8464,120),
nn.Linear(120,84),
nn.Linear(84,10),
)
def forward(self, input):
return self.model(input)
my_nn = MyNN()
input=torch.zeros((64,3,32,32))
output=my_nn(input)
print(output.shape)
writer=SummaryWriter("./logs_model")
writer.add_graph(my_nn,input)
writer.close()
# 自建神经网络
import torch
from torch import nn
from torch.nn import Sequential, Conv2d, MaxPool2d, Flatten, Linear
from torch.utils.tensorboard import SummaryWriter
class MyNN(nn.Module):
def __init__(self):
super(MyNN, self).__init__()
self.model1 = Sequential(
Conv2d(3, 32, 5, stride=1, padding=2),
MaxPool2d(2),
Conv2d(32, 32, 5, stride=1, padding=2),
MaxPool2d(2),
Conv2d(32, 64, 5, stride=1, padding=2),
MaxPool2d(2),
Flatten(), # 数据展平 64*4*4=1024
Linear(1024, 64),
Linear(64, 10)
)
def forward(self, input):
return self.model1(input)
my_nn = MyNN()
print(my_nn)
input = torch.ones((64, 3, 32, 32))
output = my_nn(input)
print(output.shape)
writer=SummaryWriter("./logs_model")
writer.add_graph(my_nn,input)
writer.close()
stride和padding的计算公式:一般stride和dilation都取默认值=1,只需计算padding的值
3.7 损失函数和反向传播
损失值越小,模型越准确,训练效果越好
nn.L1Loss
torch.nn.L1Loss(size_average=None, reduce=None, reduction='mean')
是一种用于计算预测值与实际值之间差异的损失函数。在神经网络中,它通常用于回归问题和一些特定的分类问题。L1损失函数的计算公式为:
其中,x表示预测值,y表示实际值。L1损失函数对预测值与实际值之间的差距进行度量,差距越大,损失值越高。
# 实例化模型网络
model=MyNN()
# 实例化损失函数
criterion = nn.L1Loss()
# 定义数据,要求是浮点数
input_data = torch.tensor([[3.0]])
target_data = torch.tensor([[2.0]])
output = model(input_data)
# 计算损失值
loss = criterion(output, target_data)
nn.MSELoss
torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')
是PyTorch中的一个损失函数,用于计算均方误差(Mean Squared Error, MSE)。它主要用于回归问题,即预测连续值。
下面是一个使用torch.nn.MSELoss
的简单示例:
import torch
import torch.nn as nn
# 假设我们有一个批次的数据,包含5个样本,每个样本有3个特征
predictions = torch.randn(5, 3)
targets = torch.randn(5, 3)
# 创建一个MSELoss对象,设置reduction为'mean'
mse_loss = nn.MSELoss()
# 计算损失
loss = mse_loss(predictions, targets)
print("Loss:", loss.item())
nn.CrossEntropyLoss ????
torch.nn.CrossEntropyLoss(weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean', label_smoothing=0.0)
是PyTorch中的一个损失函数,用于多分类问题。它结合了nn.LogSoftmax()
和nn.NLLLoss()
两个函数的功能。
-
weight
(可选): 一个权重张量,用于给每个类别分配不同的权重。如果为None,则所有类别的权重都被认为是1。默认值为None。 -
size_average
(已弃用): 这个参数已经被弃用,现在应该使用reduction
参数来代替。如果设置为True,损失值会被平均到批次中的所有样本上;如果设置为False,损失值会被累加到批次中的所有样本上。默认值为None。 -
ignore_index
(可选): 指定一个标签值,该值在计算损失时将被忽略。默认值为-100。 -
reduce
(已弃用): 这个参数已经被弃用,现在应该使用reduction
参数来代替。如果设置为True,损失值会被求和或平均(取决于size_average
参数);如果设置为False,损失值不会被求和或平均,而是返回每个样本的损失值。默认值为None。 -
reduction
(可选): 指定损失值应该如何被归约。可选的值有’none’、‘mean’和’sum’。'none’表示不进行归约,直接返回每个样本的损失值;'mean’表示对损失值求平均值;‘sum’表示对损失值求和。默认值为’mean’。 -
label_smoothing
(可选): 一个浮点数,用于平滑标签。这可以帮助防止模型过拟合。默认值为0.0。
反向传播
反向传播(Backpropagation)是一种在神经网络中用于训练模型的核心算法,主要通过计算损失函数对每个参数的梯度,并利用这些梯度更新网络中的权重和偏置,以最小化损失函数。反向传播是深度学习中不可或缺的部分,其目的在于为输出层的误差提供一种方式来更新网络中的权重和偏差,从而提高模型的预测性能。此外,权重的更新规则是基于梯度下降的,其中学习率是一个可调参数,决定了权重更新的速度和稳定性。
下面将详细解释反向传播是如何工作的:
- 前向传播:
- 神经网络通过各层进行前向传播,输入数据从输入层经过隐藏层,最终到达输出层,产生预测输出。
- 每一层的神经元都通过权重与下一层的神经元相连,这些权重决定了输入数据如何影响后续层的计算。
- 误差计算:
- 在输出层,系统会计算网络输出与真实标签之间的差异,产生一个误差信号。
- 这个误差信号是优化网络性能的关键点,它衡量了当前网络状态下预测的准确性。
- 误差反向传播:
- 误差计算完成后,反向传播算法将这个误差信号从输出层向隐藏层传播,逐层传递直到输入层。
- 在这个过程中,算法会根据链式法则计算每个权重对误差的贡献度,即计算误差对权重的偏导数。
- 权重更新:
- 计算完误差对权重的偏导数后,利用梯度下降法等优化算法更新网络中的权重和偏置。
- 这个过程不断重复,迭代优化网络参数,直至网络输出的误差减少到可接受的水平。
3.8 torch.optim优化器
torch.optim
是PyTorch中用于优化神经网络模型参数的模块,它提供了多种优化算法,通过调整学习率等参数来最小化损失函数。
torch.optim
中的一些常用优化器包括SGD、Adam、RMSprop和Adagrad等。这些优化器有不同的参数,如学习率(lr)、动量(momentum)和权重衰减(weight_decay)等,可以根据具体需求进行选择和调整。优化器的使用通常包括以下步骤:
"""
1. 初始化优化器:选择适当的优化器并设置相关参数。
2. 清除梯度:在每个训练步骤开始前调用`optimizer.zero_grad()`。
3. 前向传播:通过神经网络传递输入数据并获得输出。
4. 计算损失:使用损失函数计算预测值和真实值之间的差异。
5. 反向传播:调用`loss.backward()`来计算梯度。
6. 更新参数:调用`optimizer.step()`来应用计算出的梯度并更新模型参数。
"""
# 初始化优化器
optim=torch.optim.xxx(my_nn.parameters(),lr=0.01)
for input, target in dataset:
optim.zero_grad()
output = model(input)
loss = loss_fn(output, target)
loss.backward()
optim.step()
代码示例:
# 目的是训练一个卷积神经网络(CNN)来对CIFAR-10数据集进行分类
import torch
import torchvision
from torch import nn
from torch.nn import Sequential, Conv2d, MaxPool2d, Flatten, Linear
from torch.utils.data import DataLoader
#自定义网络,训练分类[CIFAR10]
class MyNN(nn.Module):
def __init__(self):
super(MyNN, self).__init__()
self.model1 = Sequential(
Conv2d(3, 32, 5, stride=1, padding=2),
MaxPool2d(2),
Conv2d(32, 32, 5, stride=1, padding=2),
MaxPool2d(2),
Conv2d(32, 64, 5, stride=1, padding=2),
MaxPool2d(2),
Flatten(), # 数据展平 64*4*4=1024
Linear(1024, 64),
Linear(64, 10)
)
def forward(self, input):
return self.model1(input)
my_data = torchvision.datasets.CIFAR10(
root="./test_img", train=False, transform=torchvision.transforms.ToTensor(), download=True
)
dataLoader=DataLoader(my_data,batch_size=64)
# 实例化自定义网络
my_nn = MyNN()
# 定义损失函数
loss=nn.CrossEntropyLoss()
# 定义优化器(SGD 随机递归)
# my_nn.parameters(): 是一个方法,用于获取神经网络模型的所有可训练参数。这些参数包括权重和偏置等。
# lr=0.01: 是学习率(learning rate)的设置。学习率是一个超参数,用于控制每次迭代时参数更新的幅度。较小的学习率意味着参数更新较小,可能导致收敛速度较慢;较大的学习率可能导致训练不稳定或跳过最优解。
optim=torch.optim.SGD(my_nn.parameters(),lr=0.01)
# 进行20轮训练,每轮遍历整个数据集一次
for epoch in range(20):
runing_loss = 0.0
for data in dataLoader:
imgs, targets = data
outputs = my_nn(imgs)
res_loss = loss(outputs, targets)
optim.zero_grad()
res_loss.backward()
optim.step()
runing_loss += res_loss
print(runing_loss)
3.9 对现有模型的操作
以VGG16模型为例:
模型的创建
import torchvision.models as models
# 下载预训练参数
vgg16 = models.vgg16(weights='DEFAULT')
# 不下载预训练参数
vgg16_no = models.vgg16(weights=None)
# 展示模型
print(vgg16_no)
在现有模型中添加模块
vgg16_no.add_module("add_liner ",nn.Linear(1000,10))
print(vgg16_no)
在现有模型中指定位置添加模块
vgg16_no.classifier.add_module("add_liner ",nn.Linear(1000,10))
print(vgg16_no)
直接修改现有模型的某一模块
vgg16_no.classifier[6]=nn.Linear(1000,10)
print(vgg16_no)
保存网络模型
# 方式一 后缀一般取为pth 【自定义网络有陷阱】
torch.save(vgg16_no,"自定义文件名.pth") --模型结构和参数都保存了
# 方式二 【推荐】
torch.save(vgg16_no.state_dict(),"自定义文件名.pth") --只保留模型参数
读取网络模型
"""
对应方式一的读取
对于自定义的,需要先引入网络模型类
class MyNN(nn.Module):
xxxx
"""
model=torch.load("自定义文件名.pth")
# 对应方式二的读取
vgg16=vgg16_no = models.vgg16(weights=None)
vgg16.load_state_dict(torch.load("自定义文件名.pth"))
4.0 模型训练一条龙
通过debug,可以看到CIFAR10数据集中,分类对应的索引值。
# 定义预测图片名称字典
tar = {
0: "airplane-飞机", 1: "automobile-汽车", 2: "bird-鸟", 3: "cat-猫", 4: "deer-鹿",
5: "dog-狗", 6: "frog-青蛙", 7: "horse-马", 8: "ship-船", 9: "truck-卡车"
}
4.1 模型训练(cpu版)
import time
from datetime import datetime
import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
# 获取数据集
data_test = torchvision.datasets.CIFAR10(
root="../test_img", train=False, transform=torchvision.transforms.ToTensor(), download=True
)
data_train = torchvision.datasets.CIFAR10(
root="../test_img", train=True, transform=torchvision.transforms.ToTensor(), download=True
)
# 获取数据集的长度
data_train_len = len(data_train)
data_test_len = len(data_test)
# DataLoader加载数据
train_dataLoader = DataLoader(batch_size=64, dataset=data_train)
test_dataLoader = DataLoader(batch_size=64, dataset=data_test)
# 搭建自定义CIFAR10-10分类网络模型
class MyNN(nn.Module):
def __init__(self):
super(MyNN, self).__init__()
self.model1 = nn.Sequential(
nn.Conv2d(3, 32, 5, stride=1, padding=2),
nn.MaxPool2d(2),
nn.Conv2d(32, 32, 5, stride=1, padding=2),
nn.MaxPool2d(2),
nn.Conv2d(32, 64, 5, stride=1, padding=2),
nn.MaxPool2d(2),
nn.Flatten(), # 数据展平 64*4*4=1024
nn.Linear(1024, 64),
nn.Linear(64, 10)
)
def forward(self, input):
return self.model1(input)
# 实例化模型、损失函数、优化器
my_nn = MyNN()
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(my_nn.parameters(),lr=0.01) # learn_rate=1e-2 ---> 1*(10)^-2 ---> 0.01
# writer=SummaryWriter("./logs")
# 训练模型
# 训练轮次
train_round=20
# 训练步数
train_step=0
# 计时
start_time=datetime.now()
for epoch in range(train_round):
print("---第{}轮训练开始---".format(epoch+1))
train_step = 0
# 训练
my_nn.train()
for data in train_dataLoader:
imgs,targets=data
outputs=my_nn(imgs)
# 损失值
loss=loss_fn(outputs,targets)
# 优化器优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 步长+1
train_step+=1
if train_step%100==0:
print("训练步数:{},Loss:{}".format(train_step,loss.item()))
# writer.add_scalar("train_loss",loss.item(),train_step)
# 测试,输出正确率
my_nn.eval()
total_test_loss=0.0
total_test_ac=0.0
with torch.no_grad():
for data in test_dataLoader:
# imgs是测试集中,我们想要进行预测的一批数据图像,targets是这一批测试集对应的正确的分类序号
imgs, targets = data
# 将该批数据放入上面训练过的自定义模型中,获取预测情况
outputs = my_nn(imgs)
# 放入损失函数,比对预测结果和正确的分类号比对,计算出损失值(反应模型好坏,值越小,越精确,模型越好)
loss = loss_fn(outputs, targets)
total_test_loss+=loss
"""
outputs.argmax(1) 返回每一行(即每个样本)中最大值的索引,这些索引对应于预测的类别【1-横向(行) 0-纵向(列)】
outputs.argmax(1)==targets 模型预测的图片序号和图片实际的序号比对,一致是True,不一致是False
sum 计算出True的个数,也就是预测正确的个数
将每一批次的预测正确的个数累加到total_test_ac中,最后得到该模型下,最终的预测正确的图片数量
"""
total_test_ac+=(outputs.argmax(1)==targets).sum()
print("整体测试集上的损失值:{}".format(total_test_loss))
# 预测正确的图片数量/测试集图片总数
print("整体测试集上的正确率:{}".format(total_test_ac/data_test_len))
# writer.add_scalar("test_loss", total_test_loss, epoch)
# 模型保存,保存在当前路径下的models目录下(要先创建该目录)
torch.save(my_nn,"./models/cl10_model_{}.pth".format(epoch))
print("save ok!")
# writer.close()
end_time=datetime.now()
print("cpu训练总用时:{}".format(end_time-start_time))
输出结果:
---第1轮训练开始---
训练步数:100,Loss:2.3019371032714844
训练步数:200,Loss:2.2842328548431396
训练步数:300,Loss:2.2702579498291016
训练步数:400,Loss:2.1892921924591064
训练步数:500,Loss:2.0352327823638916
训练步数:600,Loss:2.018749475479126
训练步数:700,Loss:2.0144147872924805
整体测试集上的损失值:313.41259765625
整体测试集上的正确率:0.2775000035762787
save ok!
...
---第20轮训练开始---
训练步数:100,Loss:0.7725099325180054
训练步数:200,Loss:0.854060173034668
训练步数:300,Loss:0.8265358805656433
训练步数:400,Loss:0.7128964066505432
训练步数:500,Loss:0.7738572955131531
训练步数:600,Loss:0.8521035313606262
训练步数:700,Loss:0.8715721964836121
整体测试集上的损失值:165.7497100830078
整体测试集上的正确率:0.6437000036239624
save ok!
cpu训练总用时:0:10:59.857844 --20轮次11分钟
4.2 模型测试
import torch
import torchvision
from PIL import Image
from torch import nn
# 方式一保存的,所以需要先引入类
class MyNN(nn.Module):
def __init__(self):
super(MyNN, self).__init__()
self.model1 = nn.Sequential(
nn.Conv2d(3, 32, 5, stride=1, padding=2),
nn.MaxPool2d(2),
nn.Conv2d(32, 32, 5, stride=1, padding=2),
nn.MaxPool2d(2),
nn.Conv2d(32, 64, 5, stride=1, padding=2),
nn.MaxPool2d(2),
nn.Flatten(), # 数据展平 64*4*4=1024
nn.Linear(1024, 64),
nn.Linear(64, 10)
)
def forward(self, input):
return self.model1(input)
# 自定义工具类
class Utils:
def reSizeAndShape(self, input):
img = Image.open(input)
transform = torchvision.transforms.Compose(
[
torchvision.transforms.Resize((32, 32)), # 模型输入的图片尺寸是32*32
torchvision.transforms.ToTensor() # 转化为可以被接受的tensor类型
]
)
img = transform(img)
# print(img.shape)
img = torch.reshape(img, (1, 3, 32, 32))
# print(img.shape)
return img
# 定义预测图片名称字典
tar = {
0: "airplane-飞机", 1: "automobile-汽车", 2: "bird-鸟", 3: "cat-猫", 4: "deer-鹿",
5: "dog-狗", 6: "frog-青蛙", 7: "horse-马", 8: "ship-船", 9: "truck-卡车"
}
# 待测试图片
imgs = [
"automobile.png","cat.png","deer.png","frog.png","ship.png","bird.png","dog.png", "horse.png", "plane.png", "truck.png"
]
# 加载模型
model = torch.load("./models/cl10_model_19.pth")
# 实例化工具类
utils = Utils()
# 测试
for name in imgs:
img_path = "./imgs/" + name
print(img_path)
img = utils.reSizeAndShape(img_path)
output = model(img)
# print(output.argmax(1))
# print(output.argmax(1).item())
print("图片预测结果为:{}".format(tar.get(output.argmax(1).item())))
输出结果:
4.3 利用GPU提速
是AMD集显,不能使用CUDA加速,可以在谷歌的colab上进行体验。colab:https://colab.google/
利用GPU,同样的训练20轮次,只需要3分31秒左右,比cpu快了将近4倍【仅是20轮】
想要运行terminal语句,在语句前加上!,即 !命令
方式 一:为模型、数据【输入,标注】、损失函数上加.cuda
在加.cuda之前,应该先要if判断,支持GPU才用
if torch.cuda.is_avabliable():
xxx.cuda()
import ...
# 获取数据集
data_test =...
# 获取数据集的长度
data_train_len =...
# DataLoader加载数据
train_dataLoader = ...
# 搭建自定义CIFAR10-10分类网络模型
class MyNN(nn.Module):
...
"""改动"""【1】
# 实例化模型、损失函数、优化器
if torch.cuda.is_avabliable():
my_nn = MyNN().cuda()
loss_fn = nn.CrossEntropyLoss().cuda()
else:
my_nn = MyNN()
loss_fn = nn.CrossEntropyLoss()
optimizer = ...
for epoch in range(train_round):
print("---第{}轮训练开始---".format(epoch+1))
train_step = 0
# 训练
my_nn.train()
for data in train_dataLoader:
imgs,targets=data
"""改动"""【2】
if torch.cuda.is_avabliable():
imgs=imgs.cuda()
targets=targets.cuda()
outputs=my_nn(imgs)
...
with torch.no_grad():
for data in test_dataLoader:
# imgs是测试集中,我们想要进行预测的一批数据图像,targets是这一批测试集对应的正确的分类序号
imgs, targets = data
"""改动"""【3】
if torch.cuda.is_avabliable():
imgs=imgs.cuda()
targets=targets.cuda()
# 将该批数据放入上面训练过的自定义模型中,获取预测情况
outputs = my_nn(imgs)
...
方式二:用.to(device)
其中device推荐写法:
device=torch.device[ "cuda" if torch.cuda.is_avaliable() else "cpu"] #支持GPU就用,不支持就用cpu
import...
# cuda:0 使用第一张显卡 【cuda:0 等价于 cuda】,cuda:1 使用第二张显卡,对于有多张显卡的
# cpu就是使用cpu训练
device=torch.device[ "cuda" if torch.cuda.is_avaliable() else "cpu"]
# 获取数据集
data_test = ...
# 获取数据集的长度
data_train_len = ...
# DataLoader加载数据
train_dataLoader = ...
# 搭建自定义CIFAR10-10分类网络模型
class MyNN(nn.Module):
...
"""改动"""【1】
my_nn = MyNN()
my_nn.to(device)
loss_fn = nn.CrossEntropyLoss()
loss_fn.to(device)
...
start_time=datetime.now()
for epoch in range(train_round):
print("---第{}轮训练开始---".format(epoch+1))
train_step = 0
# 训练
my_nn.train()
for data in train_dataLoader:
imgs,targets=data
"""改动"""【2】
imgs=imgs.to(device)
targets=targets.to(device)
outputs=my_nn(imgs)
...
with torch.no_grad():
for data in test_dataLoader:
# imgs是测试集中,我们想要进行预测的一批数据图像,targets是这一批测试集对应的正确的分类序号
imgs, targets = data
"""改动"""【3】
imgs = imgs.to(device)
targets = targets.to(device)
# 将该批数据放入上面训练过的自定义模型中,获取预测情况
outputs = my_nn(imgs)
...