初识Torch
PyTorch,简称Torch,主流的经典的深度学习框架,深度学习的框架。
简介
- PyTorch是一个基于Python的深度学习框架,它提供了一种灵活、高效、易于学习的方式来实现深度学习模型。PyTorch最初由Facebook开发,被广泛应用于计算机视觉、自然语言处理、语音识别等领域。
- PyTorch使用张量(tensor)来表示数据,可以轻松地处理大规模数据集,且可以在GPU上加速。
- PyTorch提供了许多高级功能,如自动微分(automatic differentiation)、自动求导(automatic gradients)等,这些功能可以帮助我们更好地理解模型的训练过程,并提高模型训练效率。
其他深度学习框架简介
除了PyTorch,还有很多其它常见的深度学习框架(框架属于第三方库,使用需要自行安装):
- TensorFlow: Google开发,广泛应用于学术界和工业界。TensorFlow提供了灵活的构建、训练和部署功能,并支持分布式计算。
- Keras: Keras是一个高级神经网络API,已整合到TensorFlow中。
- PaddlePaddle: PaddlePaddle(飞桨)是百度推出的开源深度学习平台,旨在为开发者提供一个易用、高效的深度学习开发框架。
- MXNet:由亚马逊开发,具有高效的分布式训练支持和灵活的混合编程模型。
- Caffe:具有速度快、易用性高的特点,主要用于图像分类和卷积神经网络的相关任务。
- CNTK :由微软开发的深度学习框架,提供了高效的训练和推理性能。CNTK支持多种语言的接口,包括Python、C++和C#等。
- Chainer:由Preferred Networks开发的开源深度学习框架,采用动态计算图的方式。
无聊的安装
建议(非常强烈的那种)用Anaconda创建一个虚拟环境,用于运行安装你的PyTorch
conda create -n universal python=3.9
基础认知
- cuDNN(CUDA Deep Neural Network library)和 CUDA(Compute Unified Device Architecture)密切相关,它们共同构成了高性能 GPU计算的基础,尤其在深度学习领域。
CUDA
CUDA(Compute Unified Device Architecture),统一计算设备架构,是由 NVIDIA 提供的并行计算平台和编程模型。它允许开发者利用 NVIDIA GPU 的强大计算能力进行通用计算,包括科学计算、机器学习、图像处理和视频处理等。CUDA提供了GPU并行计算的底层基础,使GPU能够解决复杂的计算问题。
cuDNN
cuDNN是基于CUDA的深度神经网络加速库,提供了针对深度学习常见操作(如卷积、池化、归一化、激活函数等)的高度优化实现。
- 性能优化:cuDNN 为卷积神经网络等深度学习模型的训练和推理提供了极高的性能优化。它利用 CUDA 在 GPU 上进行加速计算,充分发挥了 GPU 的并行计算能力。
- 易用性:cuDNN 被集成在主流的深度学习框架(如 TensorFlow、PyTorch、MXNet 等)中,用户直接通过框架调用 cuDNN 的功能,无需关心底层实现细节。
依赖与协同
- 依赖:cuDNN是建立在CUDA之上的库,它依赖于 CUDA 提供的基础计算能力。因此,使用 cuDNN 必须先安装 CUDA。
- 协同:在深度学习框架中,CUDA 提供了底层的 GPU 计算能力,而 cuDNN 通过调用 CUDA 来优化和加速深度学习操作。这种协同工作大幅提升了深度学习模型的训练和推理速度。
版本兼容
- 使用 cuDNN 时需要确保它与 CUDA 版本的兼容性。
GPU驱动
- 如果有独立显卡,在安装PyTorch时一定要装CUDA,没有先安在cpu上
版本检查
- 在cmd里面,输入nvidia-smi查看GPU驱动程序版本:
- 要保证你选择的CUDA版本号<=你的GPU驱动程序版本
开始安装
- 根据自己的系统及显卡情况灵活选择
- 一般CUDA和cuDNN都无需单独安装,安装包会根据版本自动选择
安装PyTorch
在官方文档里面找到适合你设备的PyTorch版本及对应的安装指令执行即可:
Previous PyTorch Versions | PyTorch
Tensor概述
- PyTorch会将数据封装成张量(Tensor)进行计算,所谓张量就是元素为相同类型的多维矩阵。
张量可以在 GPU 上加速运行。
概念
张量是一个多维数组,通俗来说可以看作是扩展了标量、向量、矩阵的更高维度的数组。张量的维度决定了它的形状(Shape),例如:
- 标量 是 0 阶张量,只有大小没有方向(温度,高度),如
a = torch.tensor(5)
- 向量 是 1 阶张量,具有大小和方向(加速度,力),如
b = torch.tensor([1, 2, 3])
- 2维矩阵 是 2 阶张量,线性变换(旋转矩阵,位移矩阵),,如
c = torch.tensor([[1, 2], [3, 4]])
- 更高维度的张量,如3维、4维等,通常用于表示图像、视频数据等复杂结构。
特点
- 动态计算图:PyTorch 支持动态计算图,这意味着在每一次前向传播时,计算图是即时创建的。
- GPU 支持:PyTorch 张量可以通过
.to('cuda')
移动到 GPU 上进行加速计算。 - 自动微分:通过
autograd
模块,PyTorch 可以自动计算张量运算的梯度,这对深度学习中的反向传播算法非常重要。
数据类型
PyTorch中有3种数据类型:浮点数、整数、布尔。对于场景不同,对数据的精度和速度要求不同。通常,移动或嵌入式设备追求速度,对精度要求相对低一些。精度越高,往往效果也越好,自然硬件开销就比较高。
Tensor的创建
在Torch中张量以 "类" 的形式封装起来,对张量的一些运算、处理的方法被封装在类中,创建tensor的函数中有两个有默认值的参数dtype和device, 分别代表数据类型和计算设备,可以通过属性dtype和device获取。
torch.tensor
- 注意这里的tensor是小写,该API是根据指定的数据创建张量。
标量
python">import torch
import numpy as np
print(torch.__version__)
# tensor是小写,该API是根据指定的数据创建张量
def create_tensor_1():# 创建一个一阶张量(标量)t1 = torch.tensor(6)# t1.shape: 形状 t1.device:设备 t1.dtype:它的类型(它指t1中的数据类型)print(t1, t1.shape, t1.device, t1.dtype)
create_tensor_1()
"""
1.13.0
tensor([7.7143e-39, 1.0010e-38, 8.4490e-39, 4.8674e-39, 1.0194e-38, 4.7755e-39]) torch.Size([6]) cpu torch.float32
"""
numpy数组
python">import torch
import numpy as np
def create_tensor_2():data = np.array([1, 2, 3], dtype=np.int64)# 是Tensor不是tensor,tensor会指定数据类型,dtype=torch.int64t1 = torch.tensor(data, dtype=torch.int64)print("t1.shape:",t1.shape)print("t1.dtype:",t1.dtype)print("tpye:", type(t1))
create_tensor_2()
"""
1.13.0
t1.shape: torch.Size([3])
t1.dtype: torch.int64
tpye: <class 'torch.Tensor'>
"""
列表
python">import torch
import numpy as np
def create_tensor_3():data = [1, 2, 3]t1 = torch.tensor(data)print("t1.shape:",t1.shape)print("t1.dtype:",t1.dtype)print("tpye:", type(t1))create_tensor_3()
"""
t1.shape: torch.Size([3])
t1.dtype: torch.int64
tpye: <class 'torch.Tensor'>
"""
torch.Tensor
- 注意这里的Tensor是大写,该API根据形状创建张量,其也可用来创建指定数据的张量。
规定形状
python">import torch
import numpy as np
# Tensor是大写,该API根据形状创建张量,其也可用来创建指定数据的张量
def create_tensor_4():t1 = torch.Tensor(3,4)print("t1:",t1)print("t1.shape:",t1.shape)print("t1.dtype:",t1.dtype)print("tpye:", type(t1))create_tensor_4()
"""
t1: tensor([[0., 0., 0., 0.],[0., 0., 0., 0.],[0., 0., 0., 0.]])
t1.shape: torch.Size([3, 4])
t1.dtype: torch.float32
tpye: <class 'torch.Tensor'>
"""
列表
python">import torch
import numpy as npdef create_tensor_5():t1 = torch.Tensor([[1,2,4,5],[6,7,8,9]])print("t1.shape:",t1.shape)print("t1.dtype:",t1.dtype)print("tpye:", type(t1))create_tensor_5()
"""
t1.shape: torch.Size([2, 4])
t1.dtype: torch.int64
tpye: <class 'torch.Tensor'>
"""
torch.IntTensor
- 用于创建指定类型的张量,还有诸如Torch.FloatTensor、 torch.DoubleTensor、 torch.LongTensor......等。
- 如果数据类型不匹配,那么在创建的过程中会进行类型转换,要尽可能避免,防止数据丢失。
python">import torch
import numpy as np
# 用于创建指定类型的张量,还有诸如Torch.FloatTensor、 torch.DoubleTensor、 torch.LongTensor......等
def create_tensor_7():t1 = torch.IntTensor([[1,2,4,5]])t2 = torch.FloatTensor([[1,2,4,5]])t3 = torch.DoubleTensor([[1,2,4,5]])t4 = torch.LongTensor([[1,2,4,5]])t5 = torch.ShortTensor([[1,2,4,5]])t6 = torch.HalfTensor([[1,2,4,5]])t7 = torch.tensor([[1,2,4,5]], dtype=torch.int64,device='cpu')print("t1.shape:",t1.shape)print("t1.dtype:",t1.dtype)print("t2.shape:",t2.shape)print("t2.dtype:",t2.dtype)print("tpye:", type(t1))create_tensor_7()
"""
t1.shape: torch.Size([1, 4])
t1.dtype: torch.int32
t2.shape: torch.Size([1, 4])
t2.dtype: torch.float32
tpye: <class 'torch.Tensor'>
"""
创建线性和随机张量
- 在 PyTorch 中,可以轻松创建线性张量和随机张量。
创建线性张量(有一定线性规律的数组)
- 使用torch.arange 和 torch.linspace 创建线性张量:
python"># 创建线性的张量
import torchdef test01():x = torch.arange(1, 5, 2) # 从1开始到5结束,不包含5,步长为2# 等差数列,分成四份y = torch.linspace(1, 10, steps=4)print(x)print(x.dtype)print(y)print(y.dtype)# 2的一次方开始到2的10次方结束,生成steps个,最后的数字形成等差数字z = torch.logspace(1, 10, steps=3, base=2) print(z)print(z.dtype)test01()
"""
tensor([1, 3])
torch.int64
tensor([ 1., 4., 7., 10.])
torch.float32
tensor([ 2.0000, 45.2548, 1024.0000])
torch.float32
"""
随机张量(不一定有线性规律的数组)
- 使用torch.randn 创建随机张量。
随机数种子
- 随机数种子(Random Seed)是一个用于初始化随机数生成器的数值。随机数生成器是一种算法,用于生成一个看似随机的数列,但如果使用相同的种子进行初始化,生成器将产生相同的数列。
- 随机数种子的设置和获取
python">import torch
def test001():# 设置随机数种子torch.manual_seed(123)
# 获取随机数种子print(torch.initial_seed())
if __name__ == "__main__":test001()
随机张量
- 在 PyTorch 中,种子影响所有与随机性相关的操作,包括张量的随机初始化、数据的随机打乱、模型的参数初始化等。通过设置随机数种子,可以做到模型训练和实验结果在不同的运行中进行复现。
python"># 创建随机张量
import torch
# 关闭科学计数法的显示效果
torch.set_printoptions(sci_mode=False)
def test02():# 设置随机数种子,不用赋值,这里的随机数种子是直接作用在了当前的torch中torch.manual_seed(666)# 生成随机张量:10x5个随机数x = torch.rand(10,5) # np.random.rand(10,5)print(x)# 生成正太分布的随机张量:3x3个随机数y = torch.randn(3,3)print(y)# 自己配方差均值和标准差z = torch.normal(mean=2, std=3, size=(1, 4))print(z)# 获取随机数种子(前面设置的)r = torch.initial_seed()print(r)test02()
"""
tensor([[0.3119, 0.2701, 0.1118, 0.1012, 0.1877],[0.0181, 0.3317, 0.0846, 0.5732, 0.0079],[0.2520, 0.5518, 0.8785, 0.5281, 0.4961],[0.9791, 0.5817, 0.4875, 0.0650, 0.7506],[0.2634, 0.3684, 0.5035, 0.9089, 0.3804],[0.7539, 0.4163, 0.0089, 0.0664, 0.8111],[0.2667, 0.0601, 0.3079, 0.2885, 0.8916],[0.6119, 0.4041, 0.4576, 0.4031, 0.0272],[0.9485, 0.4893, 0.1942, 0.0322, 0.0489],[0.5390, 0.5608, 0.5176, 0.7500, 0.4930]])
tensor([[-0.3549, -0.5196, -0.4290],[-1.3804, 1.1152, 0.1016],[ 0.2362, -0.2319, 1.0263]])
tensor([[ 1.2796, 2.4664, -4.9672, 5.7614]])
666
"""
注:不设置随机种子时,每次打印的结果不一样。
创建01张量
- 在 PyTorch 中,你可以通过几种不同的方法创建一个只包含 0 和 1 的张量。
创建全0张量
- torch.zeros 和 torch.zeros_like 创建全0张量。
python"># 创建01张量,0和1一样,省略1的实列
import torch
import numpy as np
def test_0():# 创建指定形状的张量有0填充data_0 = torch.zeros(2, 3)print(data_0, data_0.shape)data_0_1 = torch.tensor([[10,20,30],[40,50,60]])data_0_2 = torch.rand(2, 3)data_0_3 = torch.zeros_like(data_0_1)print(data_0_3)# 传入的数据容器只能是torch,不能是列表和数组# data_np = np.array([[1,2,3],# [4,5,6]])# data_0_np = torch.zeros_like(data_np)y = torch.tensor(np.array([10,20,30]), dtype=torch.int64)y_torch = torch.zeros_like(y)print(y_torch)test_0()
"""
tensor([[0., 0., 0.],[0., 0., 0.]]) torch.Size([2, 3])
tensor([[0, 0, 0],[0, 0, 0]])
tensor([0, 0, 0])
"""
创建全1张量
- torch.ones 和 torch.ones_like 创建全1张量
创建指定值张量
torch.full 和 torch.full_like 创建全为指定值张量。
python"># 指定填充
def test():# 手动指定数组的形状x = torch.full(size=(2, 3), fill_value=5)print(x)# 和x的形状一样,但用2来填充# 和传入的容器的形状一样y = torch.full_like(x, 2)print(y)
test()
"""
tensor([[5, 5, 5],[5, 5, 5]])
tensor([[2, 2, 2],[2, 2, 2]])
"""
创建单位矩张量
- 创建对角线上为1的单位张量
python"># 创建单位矩阵
import torch
def test_dan():# 行列为4的单位举证(方正)x = torch.eye(4)print(x)
test_dan()
"""
tensor([[1., 0., 0., 0.],[0., 1., 0., 0.],[0., 0., 1., 0.],[0., 0., 0., 1.]])
"""
Tensor常见属性
- 张量有device、dtype、shape等常见属性。
获取属性
python"># Tensor创建属性
import torchdef test01():x = torch.tensor([1, 2, 3])# 创建时可以指定在cuda还是cpu上print(x.dtype) # tensor中数据类型print(x.device) # tensor在那个设备上运行,默认创建在cpu上# 获取tensor形状# []0阶张量 # 比如:[3]一阶张量有三个数据# [3,4]二阶张量,有3行4列# [4,2,512,512] 四阶张量,第一层有4个,每个数组中有两个512*512的矩阵print(x.shape)
test01()
切换设备
- 默认在cpu上运行,可以显式的切换到GPU:不同设备上的数据是不能相互运算的。
python"># 设备的切换
import torchdef test02():# 不同地设备上(tensor)的数据是不能运算的# x = torch.tensor([1,2,3], device='cpu')# y = torch.tensor([1,2,3], device='cuda')# c = x + y# 切换设备1x = torch.tensor([1,2,3], device='cpu')print(1,x)x = torch.tensor([1,2,3])# 本机没有cuda,所以报错# x=x.to('cuda:0') # 会返回一个新的数据,这个新的x在cuda上,原变量的值依然在cpu上# print(2, x.device)# 可以通过api获取当前设备中是否有cudares = torch.cuda.is_available()# 有显卡的为 true没有显卡的返回Falseprint(res)c = x.to('cuda:0') if res else x.to('cpu:0')print(c.device)# 把创建的tensor移动到cuda上y = x.cuda() if res else x.cpu() # 返回一个新值,在指定的设备上,原变量的值在cpu上print(y.device)
test02()
"""
1 tensor([1, 2, 3])
False
cpu
cpu
"""
类型转换
- 在训练模型或推理时,类型转换也是张量的基本操作,是需要掌握的。
python"># 类型转换:tensor中的数据转变
def test03():# 类型转换1x = torch.tensor([10,20,30,40], dtype=torch.float16)print(x.dtype, x)# 类型转换2,不修改原函数,返回一个新函数y=x.type(torch.int8)print(y.dtype, x)# 类型转换3x = x.half()print(x.dtype, x)x = x.double()print(x.dtype, x)x = x.long()print(x.dtype, x)test03()
"""
torch.float16 tensor([10., 20., 30., 40.], dtype=torch.float16)
torch.int8 tensor([10., 20., 30., 40.], dtype=torch.float16)
torch.float16 tensor([10., 20., 30., 40.], dtype=torch.float16)
torch.float64 tensor([10., 20., 30., 40.], dtype=torch.float64)
torch.int64 tensor([10, 20, 30, 40])
"""
五、Tensor数据转换
Tensor与Numpy
- Tensor和Numpy都是常见数据格式
张量转Numpy
浅拷贝
- 调用numpy()方法可以把Tensor转换为Numpy,此时内存是共享的。
python"># 数据转换
import torch
# 浅拷贝
def test_torch_data_trans():x1 = torch.tensor([1,2,3])print(x1)# 转换为numpyx2 = x1.numpy()print(x2)print(type(x2))# x1和x2是两个不同的对象,但数据是共享的x2[0] = 100print(x1)test_torch_data_trans()
"""
tensor([1, 2, 3])
[1 2 3]
<class 'numpy.ndarray'>
tensor([100, 2, 3])
"""
深拷贝
使用copy()方法可以避免内存共享:
python">import torch
# 深拷贝
def test_torch_data_trans02():x = torch.tensor([1,2,3])x2 = x.numpy().copy() # numpy的copy方法,numpy还有一个犯法viewprint(x2)x2[0] = 100print(x)
test_torch_data_trans02()
"""
[1 2 3]
tensor([1, 2, 3])"""
Numpy转张量
浅拷贝
from_numpy方法转Tensor默认是内存共享的
python">import torch
import numpy as np
def test_numpy_torch():# 第二种, 浅拷贝x3 = np.array([1,2,3])x4 = torch.from_numpy(x3)x3[0] = 100x4[1] = 200print(x3)print(x4)
test_numpy_torch()
"""
[1000 2 3]
tensor([ 1, 666, 3], dtype=torch.int32)
"""
深拷贝
使用传统的torch.tensor()则内存是不共享的
python">import torch
import numpy as npdef test_numpy_torch():# 第一种,深拷贝x = np.array([1,2, 3])x2 = torch.tensor(x)# numpy转tensorx[0] = 1000x2[1] = 666print(x)print(x2)
test_numpy_torch()
"""
[100 200 3]
tensor([100, 200, 3], dtype=torch.int32)
"""
Tensor与图像
- 图像是我们视觉处理中最常见的数据
图片转Tensor
python"># Tensor与图像
# 图片转Tensor
import torch
import cv2
from PIL import Image
from torchvision import transformsdef test_picture():img = cv2.imread('./data/1.png')# 像素通道矩阵# print(img)# print(type(img))img = torch.from_numpy(img)# torch像素通道的张量print(img)print(type(img))test_picture()
python"># Tensor与图像
# 图片转Tensor
import torch
import cv2
from PIL import Image
from torchvision import transformsdef test_picture_2():path = './data/1.png'img = Image.open(path)# 打印的是图像对象print(img)# transforms.ToTensor()转换器来转图像# img_tensor = transforms.ToTensor()(img)# 或者分开写,将图片对象装换为torch的张量# 生成转换器transfer = transforms.ToTensor()# 转换图片对象img_tensor = transfer(img)# 像素四通道的张量# print(img_tensor)# 取像素四通道中的第一个通道的值print(img_tensor[0])print(img_tensor.shape)# 对象类型没有形状# print(img.shape)test_picture_2()
Tensor转图片
python"># Tensor与图像
# Tensor转图片
import torch
import cv2
from PIL import Image
from torchvision import transformsdef test_picture_3():# r = torch.rand(315, 315)# g = torch.rand(315, 315)# b = torch.rand(315, 315)img_tensor = torch.rand(4, 315, 315)# tensor转PIL图片对象transfer = transforms.ToPILImage()img_pil = transfer(img_tensor)img_pil.show()print(img_tensor)test_picture_3()
3. PyTorch图像处理
- 通过一个Demo加深对Torch的API理解和使用
python"># Tensor与图像
# Tensor转图片
import torch
import cv2
from PIL import Image
from torchvision import transformsdef test_picture_4():path = './data/1.png'img = Image.open(path)print(img)transfer = transforms.ToTensor()img_tensor = transfer(img)print(img_tensor)img_tensor[0] += 100tensor2pil = transforms.ToPILImage()img_pil = tensor2pil(img_tensor)img_pil.show()img_pil.save('./data/2.png')
test_picture_4()
效果:
Tensor常见操作
- 在深度学习中,Tensor是一种多维数组,用于存储和操作数据,我们需要掌握张量各种运算。
获取元素值
我们可以把单个元素tensor转换为Python数值,这是非常常用的操作
python">import torch
def get_element_value():# 标量x = torch.tensor(1)print("标量:",x.item())# 一阶x = torch.tensor([1])print("一阶:",x.item())# 如果输入的数据超过一个,就不可以用item函数取数值了# x = torch.tensor([1,2,3]) 报错
get_element_value()
"""
标量 1
一阶 1
"""
注意:
- 和Tensor的维度没有关系,都可以取出来!
- 如果有多个元素则报错;
元素值运算
- 常见的加减乘除次方取反开方等各种操作,带有_的方法则会替换原始值。
python">import torchdef test01():torch.manual_seed(66)x = torch.randint(1,10,(3,3))print(x)print("加操作")# 每个数字加上100,返回一个新数据add_x=x.add(100)print(add_x)# 带下划线结尾的函数,基本都是操作原数据add_x_01 = x.add_(100)print(x)print("减操作")sub_x = x.sub(1)print(sub_x)x.sub_(100)print(x)print("乘操作")mul_x = x.mul(2)print(mul_x)x.mul_(2)print(x)print("除操作")div_x = x.div(2)print(div_x)x.div_(2, rounding_mode='trunc')print(x)print("平方操作")pow_x = x.pow(x)print(pow_x)print("直接运算法操作")x = x**2print(x)x = x+10print(x)x = x-10print(x)x = x/2print(x)x= x//2print(x)x = x%2x -= 100 # 等价于 x = x - 100
test01()
阿达玛积
- 阿达玛积指的是矩阵对应位置的元素相乘,可以使用mul函数或者*来实现;
python">import torchdef test_adam():x1 = torch.tensor([[1,2], [3,4]])x2 = torch.tensor([[5,6], [7,8]])# 阿达玛积时必须形状一样x3 = x1*x2print(x3)x4 = x1.mul(x2)print(x4)
test_adam()
"""
tensor([[ 5, 12],[21, 32]])
tensor([[ 5, 12],[21, 32]])"""
Tensor相乘
- 点积运算将两个向量映射为一个标量,是向量之间的基本操作。
- 点积运算要求如果第一个矩阵的shape是 (N, M),那么第二个矩阵 shape必须是 (M, P),最后两个矩阵点积运算的shape为 (N, P)。
- 使用@或者matmul完成Tensor的乘法。
- mm方法也可以用于矩阵相乘 但是只能用于2维矩阵即:和 得到 的矩阵
python">import torchdef testmul():x1 = torch.tensor([[1,2], [3,4]])x2 = torch.tensor([[5,6], [7,8]])x3 = torch.matmul(x1, x2)print(x3)x4 = x1.matmul(x2)print(x4)x5 = x1 @ x2print(x5)x6 = x1.mm(x2)print(x6)y1 = torch.randint(0, 10, (3,3,3))y2 = torch.randint(0, 10, (3,3,3))y3 = torch.matmul(y1, y2)print(y3)y4 = y1.matmul(y2)print(y4)y5 = y1 @ y2print(y5)# 报错# y6 = y1.mm(y2)# print(y6)testmul()
索引操作
- 掌握张量的花式索引在处理复杂数据时非常有用。花式索引可以让你灵活地访问、修改张量中的特定元素或子集,从而简化代码并提高操作效率。
简单索引
索引,就是根据指定的下标选取数据。
python">import torchdef simple_index():torch.manual_seed(66)x = torch.randint(1, 10, (5, 5, 3))print(x.shape)print(x[1])print(x[1,2])# 获取元素,不是被tensor包装的元素print(x[1,2,1].item())print(x[0:2]) # 前2行print(x[0:2,1])print(x[0:2,1,1])print(x[0:2,1:3,2])print(x[1][1][1])simple_index()
列表索引
- 使用list批量的制定要索引的元素位置,此时注意list的维度!
python">import torch
def index_list():torch.manual_seed(66)x = torch.randint(1, 10, (5, 5, 3))print(x)print(x[[1,3]]) # 1,3行print(x[[1,3], 1]) # 1,3行的第二个# 并非笛卡尔积坐标, 而是x[1,2] x[3,4]]print(x[[1,3], [2,4]]) # 1行的第二个和2行第三个print(x[[1,3], [2,4], 2]) # 1行的第三个的第2个元素和3行的第四个的第2个元素print(x[2, [1,3], 0:2]) # x[2,1,0:2]和x[2,3,0:2]# # 如果填列表,那么列表中的下标的数字是讲究顺序的print(x[[3,1]]) # x[3]和x[1],先取3,再取1print(x[[1,3]]) # 同理# # 切片冒号左右两边不写表示到开头或者末尾print(x[0,1,:1])
index_list()
布尔索引
- 根据条件选择张量中的元素。
python">import torch
def tool(x):return x%2 == 0 # 得出布尔x的tensor
def booltest():torch.manual_seed(66)x = torch.randint(1, 10, (5, 5))print(x)x2 = x>8print(x2)# 取出大于8的元素,返回新值print(x[x2])# 取出奇数元素print(x[x%2 == 1])print(x[tool(x)])booltest()
- 行级别的条件索引
python">import torch# 找出第一列是偶数,第二列是奇数,第三列是闰年的行中的第四列和第五列数据
torch.manual_seed(66)
x = torch.randint(100, 102, (50, 5))
# 这里面是去取索引,所有同时满足条件,只能选择位运算符& |,而不能用布尔运算符and or
y = x[(x[:,0]%2 == 0) & (x[:,1]%2 != 0) & ((x[:,2]%4 == 0) & (x[:,2]%100 != 0) | (x[:,2]%400 == 0)), 3:5]
print(y)
索引赋值
- 使用花式索引轻松进行批量元素值修改
python">import torchdef test():torch.manual_seed(66)x = torch.randint(1,10, (5, 5))print(x)x2 = x[1,1]print(x2)# 选中索引数据,在对索引对应的数据进行赋值更改值x[1,1] = 100print(x)x[:, 3] = 200print(x)# x = 300 # 这个是直接将x赋值给了300,而不是x[:] = 300# x[:] = 999和x[:,:] = 666两个效果是一样的x[:] = 999print(x)x[:,:] = 666print(x)
test()
张量拼接
在 PyTorch 中,cat 和 stack 是两个用于拼接张量的常用操作,但它们的使用方式和结果略有不同:
- cat:在现有维度上拼接,不会增加新维度。
- stack:在新维度上堆叠,会增加一个维度。
torch.cat
- 元素级别的torch.cat(concatenate 的缩写)用于沿现有维度拼接张量。换句话说,它在现有的维度上将多个张量连接在一起。
python">import torch
def cattest():torch.manual_seed(66)x = torch.randint(1, 10, (3, 3))y = torch.randint(1, 10, (3, 3))# 拼接方向的维度要相同x_cat_y_1 = torch.cat([x, y], dim=1)print(x_cat_y_1)x_cat_y_0 = torch.cat([x, y], dim=0)print(x_cat_y_0)cattest()
"""
tensor([[8, 1, 3, 5, 5, 2],[2, 3, 4, 3, 2, 1],[5, 3, 1, 8, 1, 5]])
tensor([[8, 1, 3],[2, 3, 4],[5, 3, 1],[5, 5, 2],[3, 2, 1],[8, 1, 5]])
"""
注意:要拼接的张量在除了指定拼接的维度之外的所有维度上的大小必须相同。
torch.stack
张量级别的
- torch.stack 用于在新维度上拼接张量。换句话说,它会增加一个新的维度,然后沿指定维度堆叠张量。
python">import torch
def stacktest():x = torch.randint(0, 10, (3, 3))y = torch.randint(0, 10, (3, 3))x_stack_y_0 = torch.stack([x, y], dim=0)print(x_stack_y_0)x_stack_y_1 = torch.stack([x, y], dim=1)print(x_stack_y_1)
stacktest()
"""
tensor([[[0, 0, 9],[0, 4, 0],[6, 2, 9]],[[2, 7, 2],[9, 4, 4],[8, 5, 6]]])
tensor([[[0, 0, 9],[2, 7, 2]],[[0, 4, 0],[9, 4, 4]],[[6, 2, 9],[8, 5, 6]]])
"""
注意:要堆叠的张量必须具有相同的形状。 技巧:堆叠指一人出一个交替添加 拼接指一人出完下个人在出完
形状操作
- 在 PyTorch 中,张量的形状操作是非常重要的,因为它允许你灵活地调整张量的维度和结构,以适应不同的计算需求。
reshape
- 可以用于将张量转换为不同的形状,但要确保转换后的形状与原始形状具有相同的元素数量。
python">import torchdef shapetest():# 张量中的数量应该保持一致x1 = torch.randint(1,10,(4,3))print(x1)x2 = torch.reshape(x1, (2,6))print(x2)x3 = torch.reshape(x1, (2,2,3))print(x3)# 报错,元素个素被改变了# x4 = torch.reshape(x1, (6,2,2))# -1自动填充x5 =torch.reshape(x1, (-1,2))print(x5)x6 = torch.reshape(x1, (2, 2, -1))print(x6)# 报错,填充不明确# x7 = torch.reshape(x1, (2, -1, -1))
shapetest()
view
view进行形状变换的特征:
- 张量在内存中是连续的;
- 返回的是原始张量视图,不重新分配内存,效率更高;
- 如果张量在内存中不连续,view 将无法执行,并抛出错误。
内存连续性
我们在进行变形或转置操作时,很容易造成内存的不连续性。
python">import torch# 同一个内存地址,为所有数据重新编了一个下标
def viewtest():x4 = torch.randint(1,10, (3,4))x5 = torch.reshape(x4, (2,6))print(x5)x6 = x5.view((4,3))# 因为x5是更改后,与下先x4不同享空间地址,但是x5也是一个新的内存空间# 所有x6可以对x5进行操作,x5是一个新的内存地址的开始,不存在连不连续print(x6)x4 = torch.randint(1,10, (3,4))x7 = x4.t()x7[1,1] = 100print(x4)print(x7)# print(x7) # 转置的是不连续的# x8 = x7.view((4,3))# 报错,不连续的存储x6 = torch.randint(1,10, (3,4))x7 = x6.view((2,6))x6[1,1] = 100print(x7)print(x6)# x6和x7共同改变了viewtest()
和reshape比较
- view:高效,但需要张量在内存中是连续的;
- reshape:更灵活,但涉及内存复制;
view变形操作
python">import torch# 同一个内存地址,为所有数据重新编了一个下标
def viewtest():x1 = torch.randint(1,10, (3,4))print(x1)# 改变形状,由于没有改变原x中的数据内存空间,因此它改变形状比reshape快x2 = x1.view((2,6))print(x2)x3 = x2.view((4,3))print(x3)
viewtest()
transpose
- transpose 用于交换张量的两个维度,注意,是2个维度,它返回的是原张量的视图。
python">import torch
# 返回的是原张量的视图表示共享内存def transposetest():x1 = torch.randint(0, 10, (4, 3))print(x1, x1.shape)# 后面的1,0位置顺序是什么都表示交换维度x2 = torch.transpose(x1, 1, 0)print(x2, x2.shape)# 值调换前两个维度x3 = torch.randint(0, 10, (4, 3, 2))print(x3, x3.shape)x4 = torch.transpose(x3, 0, 1)print(x4, x4.shape)transposetest()
permute
- permute 用于改变张量的所有维度顺序。与 transpose 类似,但它可以交换多个维度。
python">import torchdef permutetest():x1 = torch.randint(0, 255, (3, 512, 360))print(x1.shape)# (c, h, w)x2 = x1.permute(1, 2, 0) # (h, w, c)print(x2.shape)permutetest()
"""
torch.Size([3, 512, 360])
torch.Size([512, 360, 3])
"""
flatten
- flatten 用于将张量展平为一维向量。
tensor.flatten(start_dim=0, end_dim=-1)
- start_dim:从哪个维度开始展平。
- end_dim:在哪个维度结束展平。默认值为
-1
,表示展平到最后一个维度。
python">import torch
def flattentest():x1 = torch.randint(0, 255, (3,4))print(x1)x2 = x1.flatten()print(x2)x3 = torch.randint(0, 255, (3, 4, 2))print(x3)# 定位到第1维度,将第一维度中的维度平铺x4 = x3.flatten(start_dim=1)print(x4)"""tensor([[[234, 119],[ 88, 150],[132, 176],[ 31, 133]],[[101, 186],[244, 251],[ 34, 26],[ 45, 79]],[[ 84, 164],[198, 154],[155, 222],[ 37, 130]]])
tensor([[234, 119, 88, 150, 132, 176, 31, 133],[101, 186, 244, 251, 34, 26, 45, 79],[ 84, 164, 198, 154, 155, 222, 37, 130]])"""# 定位到第1维,将第2为平铺,但是第2为往下的维度就不平铺了x3 = torch.randint(0, 255, (3, 4, 2, 2))x4 = x3.flatten(start_dim=1, end_dim=2)print(x4)print(x4.shape)
flattentest()
升维和降维
在后续的网络学习中,升维和降维是常用操作,需要掌握。
- unsqueeze:用于在指定位置插入一个大小为 1 的新维度。
- squeeze:用于移除所有大小为 1 的维度,或者移除指定维度的大小为 1 的维度。
squeeze降维
python">import torchdef squeezetest():# 数据降维x1 = torch.randint(0, 255, (1, 3, 4, 1))# print(x1)# 只有维度有1就降维x2 = x1.squeeze()print(x2)# 降维指定维度x3 = x1.squeeze(0).squeeze(-1)print(x3)squeezetest()
"""
tensor([[106, 90, 23, 31],[ 77, 203, 86, 79],[ 42, 204, 42, 160]])
tensor([[106, 90, 23, 31],[ 77, 203, 86, 79],[ 42, 204, 42, 160]])
"""
unsqueeze升维
python">def unsqueezetest():# 数据降维x1 = torch.randint(0, 255, (3, 4))# print(x1)# 必须指定维度x2 = x1.unsqueeze(0)print(x2.shape)x3 = x1.unsqueeze(0).unsqueeze(-1)print(x3.shape)unsqueezetest()
"""
torch.Size([1, 3, 4])
torch.Size([1, 3, 4, 1])
"""
张量分割
- 可以按照指定的大小或者块数进行分割。
python">import torchdef split_tensor():x1 = torch.randint(0, 255, (7, 4))# 分成两个tensor张量,用元组保存x2 = torch.split(x1, 2)# 每一块有两行,不足的放剩余的行print(x2)# 分几块x3 = torch.chunk(x1, 3)print(x3)
split_tensor()
"""
(tensor([[252, 142, 76, 67],[245, 137, 87, 164]]), tensor([[ 37, 69, 231, 193],[ 46, 176, 232, 102]]), tensor([[237, 221, 224, 13],[134, 11, 198, 130]]), tensor([[144, 214, 105, 163]]))
(tensor([[252, 142, 76, 67],[245, 137, 87, 164],[ 37, 69, 231, 193]]), tensor([[ 46, 176, 232, 102],[237, 221, 224, 13],[134, 11, 198, 130]]), tensor([[144, 214, 105, 163]]))"""
广播机制
- 广播机制允许在对不同形状的张量进行计算,而无需显式地调整它们的形状。广播机制通过自动扩展较小维度的张量,使其与较大维度的张量兼容,从而实现按元素计算。
广播机制规则
广播机制需要遵循以下规则:
- 每个张量的维度至少为1
- 满足右对齐
广播案例
- 1D和2D张量广播
python">import torchdef broasttest():torch.manual_seed(66)x1 = torch.randint(0, 10, (4, 3))print(x1)# (2, 3)行的广播有歧义,所以报错x2 = torch.randint(0, 10, (1, 3))x3 = x1 + x2print(x3)x4 = torch.randint(0, 10, (4, 1))x5 = x1 + x4print(x5)
broasttest()
"""
tensor([[6, 2, 5],[3, 4, 3],[5, 6, 9],[1, 2, 5]])
tensor([[10, 9, 8],[ 7, 11, 6],[ 9, 13, 12],[ 5, 9, 8]])
tensor([[15, 11, 14],[ 8, 9, 8],[12, 13, 16],[10, 11, 14]])
"""
2D 和 3D 张量广播
- 广播机制会根据需要对两个张量进行形状扩展,以确保它们的形状对齐,从而能够进行逐元素运算。广播是双向奔赴的。
python">def broasttest2():# 2D 张量(2,3), 广播[[1, 2, 3], [4, 5, 6]]# [[[1, 2, 3], [4, 5, 6]], [[1, 2, 3], [4, 5, 6]]]x1 = torch.tensor([[1, 2, 3], [4, 5, 6]])# 3D 张量(2,1,3), 广播第2维度的1, [2, 3, 4], [5, 6, 7]# [[[2, 3, 4], [2, 3, 4]], [[5, 6, 7], [5, 6, 7]]]x2 = torch.tensor([[[2, 3, 4]], [[5, 6, 7]]])print(x1.shape, x2.shape)# 进行运算result = x1 + x2print(result, result.shape)
broasttest2()
"""
torch.Size([2, 3]) torch.Size([2, 1, 3])
tensor([[[ 3, 5, 7],[ 6, 8, 10]],[[ 6, 8, 10],[ 9, 11, 13]]]) torch.Size([2, 2, 3])
"""
数学运算
基本操作
- floor: 向下取整;
- ceil:向上取整;
- round:四舍五入;
- trunc:裁剪,只保留整数部分;
- frac:只保留小数部分;
- fix:向零方向舍入;
- %:取余。
abs:取绝对值
python">import torchdef mathtest():data = torch.tensor([[1, 2, -3.5], # 1[4, 5, 6], # 2[10.5, 18.6, 19.6], # 3[11.05, 19.3, 20.6], # 4])# 对tensor中的每个数据进行操作# 向下取整:往小取整print(torch.floor(data))# 向上取整:往大取整print(torch.ceil(data))# 四舍五入(python的四舍五入round:四舍六入,五看奇偶,看整数的各位,奇进偶不进)print(torch.round(data))# 10.5 -> 10# 裁剪:只保留整数的部分print(torch.trunc(data))# 截断:保留小数部分print(torch.frac(data))# 向0 方向舍入:负数时的小数向大了取整(0的方向), 整数反之print(torch.fix(data))# 取余:整数减2,算余数,负数加2取整数的余数:-3.5取余为0.5print(torch.fmod(data,2))print(data%2)# 绝对值print(torch.abs(data))mathtest()
三角函数
- torch.cos(input,out=None)
- torch.cosh(input,out=None) # 双曲余弦函数
- torch.sin(input,out=None)
- torch.sinh(input,out=None) # 双曲正弦函数
- torch.tan(input,out=None)
- torch.tanh(input,out=None) # 双曲正切函数
python">import torch# 三角函数运算
def sin_cos():torch.set_printoptions(sci_mode=False)# π值print(torch.pi)# 1弧度等于多少度deg = 180/torch.pideg1 = torch.pi/180s = deg * 90 # 90个一度/弧度,90度对应的弧度# tensor中的数值作为弧度进行三角函数的计算,传入的都应该是弧度值data = torch.tensor([1,90*deg1,3,8])x = torch.sin(data)print(x)y = torch.sinh(data)print(y)sin_cos()
统计学函数
- torch.mean(): 计算张量的平均值。
- torch.sum(): 计算张量的元素之和。
- torch.std(): 计算张量的标准差。
- torch.var(): 计算张量的方差。
- torch.median(): 计算张量的中位数。
- torch.max(): 计算张量的最大值。
- torch.min(): 计算张量的最小值。
- torch.sort(): 对张量进行排序。
- torch.topk(): 返回张量中的前 k 个最大值或最小值。
- torch.histc(): 计算张量的直方图。
- torch.unique(): 返回张量中的唯一值。
- torch.bincount(): 计算张量中每个元素的出现次数。
python">import torch
import cv2
# 统计学函数:如果是一维张量就和二维的行操作是一样的def maxttest():torch.manual_seed(66)x1 = torch.randint(1, 10, (3,3)).type(torch.float32)print(x1)# 求均值,对所有的元素print("mean:", x1.mean(),"\n")print(torch.mean(x1),"\n")# 求和,对所有的元素print("sum:", x1.sum(),"\n")# 标准差,对所有的元素print("std:", x1.std(),"\n")# 方差,对所有的元素print("var", x1.var(),"\n")# 中位数,偶数取中位数中的值小的,对所有的元素print("median:", torch.median(x1),"\n")# 众数:对行进行众数查找,但是如果没有众数就会返回这行的最小值
# """
# mode: torch.return_types.mode(
# values=tensor([1., 2., 1.]), # 返回的每行返回的众数值
# indices=tensor([1, 0, 2])) # 众数值对应的每行的下标
# mode.values: tensor([1., 2., 1.])
# """print("mode:", torch.mode(x1),"\n")print("mode.values:", torch.mode(x1).values,"\n")print('mode.values[0]', torch.mode(x1).values[0], "\n")# # 最大值,,对所有的元素求最大值print("max:", torch.max(x1),"\n")# # 最小值,对所有的元素求最小值print("min:", torch.min(x1),"\n")# 排序:对行进行排序
# sort: torch.return_types.sort(
# values=tensor([[1., 3., 8.],
# [2., 3., 4.],
# [1., 3., 5.]]),
# indices=tensor([[1, 2, 0],
# [0, 1, 2],
# [2, 1, 0]])) print("sort:", torch.sort(x1),"\n")# 列表的sort函数,减了10后的绝对值大小,对其进行从小到大排序y = [8, 10, 11, 13, 14]y.sort(key = lambda x:abs(x - 10))print(y,"\n")# 返回前几个中的最大值或最小值:对行进行操作,对x1操作,取每行中最大的两个值和值的下标
# topk: torch.return_types.topk(
# values=tensor([[8., 3.],
# [4., 3.],
# [5., 3.]]),
# indices=tensor([[0, 2],
# [2, 1],
# [0, 1]])) print("topk:", torch.topk(x1, 2),"\n")# 直方图统计# """# histc: tensor([6., 2., 1.]) # 计算全部张量元素,只要用于一维张量,多维张量就相当于平铺后再去计算# 将x1中的最小值最大值作为了阈值(最大值范围包含了最大值的下一位:max(3:取到-4但不包含4的下界))# 将最值之间的范围分成3分,计算这三份范围中的数据每份有多少个,返回一个一维张量# """print("histc:", torch.histc(x1, bins=3),"\n")# 唯一值:找tensor中全部的数据中的唯一值,返回一个一维tensor数据集print("unique:", torch.unique(x1),"\n")# 应用场景:到时候分类的数据集中有几种类型# 元素出现的次数,只能操作非负整数并且是一维张量张量操作x2 = x1.type(torch.int64).flatten()print("bincount:", torch.bincount(x2),"\n")img = cv2.imread("../data/1.png")img_tensor = torch.from_numpy(img).flatten()bincount = torch.bincount(img_tensor)print(bincount)print(torch.topk(bincount, 1))maxttest()
保存和加载
- 张量数据可以保存下来并再次加载使用
python">import torch
def save_model():x = torch.tensor([1,2,3])torch.save(x,'./data/x.pt')return x
save_model()def load_model():x = torch.load('./data/x.pt')print(x)print(x.device)
load_model()
并行化
- 在 PyTorch 中,你可以查看和设置用于 CPU 运算的线程数。PyTorch 使用多线程来加速 CPU 运算,但有时你可能需要调整线程数来优化性能。
查看线程数
- 使用 torch.get_num_threads() 来查看当前 PyTorch 使用的线程数:
python">import torchdef get_threads():# 获取pytorch运行的线程数print(torch.get_num_threads())pass
设置线程数
- 使用 torch.set_num_threads() 设置 PyTorch 使用的线程数:
python">import torchdef set_get_threads():# 设置pytorch运行的线程数torch.set_num_threads(4)print(torch.get_num_threads())
设置线程数时,确保考虑到你的 CPU 核心数和其他进程的资源需求,以获得最佳性能。
注意事项
- 线程数设置过高可能会导致线程竞争,反而降低性能;
- 线程数设置过低可能会导致计算资源未得到充分利用;
- 当使用 GPU 进行计算时,线程数设置对性能影响较小,因为 GPU 计算并不依赖于 CPU 线程数。
自动微分
- 自动微分模块torch.autograd负责自动计算张量操作的梯度,具有自动求导功能。自动微分模块是构成神经网络训练的必要模块,可以实现网络权重参数的更新,使得反向传播算法的实现变得简单而高效。
基础概念
张量
Torch中一切皆为张量,属性requires_grad决定是否对其进行梯度计算。默认是 False,如需计算梯度则设置为True。
计算图
torch.autograd通过创建一个动态计算图来跟踪张量的操作,每个张量是计算图中的一个节点,节点之间的操作构成图的边。
-
反向传播:使用tensor.backward()方法执行反向传播,从而计算张量的梯度。这个过程会自动计算每个张量对损失函数的梯度。
-
梯度:计算得到的梯度通过tensor.grad访问,这些梯度用于优化模型参数,以最小化损失函数。
计算梯度
- 使用tensor.backward()方法执行反向传播,从而计算张量的梯度~
标量梯度计算
python">import torchdef torchtest():# 标量的梯度计算# 此时的张量必须是浮点数x = torch.tensor(5, dtype=torch.float32, requires_grad=True)y = x**2 + 2*x - 5 # 计算梯度# 1.y函数对x求导函数 # 2.把x当前的值带入导函数计算出结果)y.backward()print(x.grad) # 打印梯度(x的导数值)# 这样更新有错误,不能手动更新# y = 3*x**2-3*x+2# x = x + 5# y.backward()# print(x.grad)# x变了在此调用需要有个y参数torchtest()if __name__ == "__main__":test001()
向量梯度计算
- 错误预警:RuntimeError: grad can be implicitly created only for scalar outputs
- 百度翻译:RuntimeError:只能为标量输出隐式创建grad
python">import torchdef get_grad():# 向量的梯度计算x = torch.tensor([1,2,3], requires_grad=True, dtype=torch.float64)y = x**2 + 2*x - 5# x带入求出的y是向量print(y)y = y.sum()# 梯度计算, y必须是一个标量结果, 才能用标量对x求导y.backward()# 导数值print(x.grad)get_grad()
"""
tensor([-2., 3., 10.], dtype=torch.float64, grad_fn=<SubBackward0>)
tensor([4., 6., 8.], dtype=torch.float64)
"""# 5. 读取梯度值print(x.grad)if __name__ == "__main__":test002()
多标量梯度计算
python">import torchdef get_grad_norm():
# 多标量的梯度计算x1 = torch.tensor(1., requires_grad=True)x2 = torch.tensor(2., requires_grad=True)y = x1**2 + 3*x2 - 5y.backward()print(x1.grad)print(x2.grad)get_grad_norm()
"""
tensor(2.)
tensor(3.)
"""
多向量梯度计算
python">import torchdef grad_test():# 对多向量的梯度计算x1 = torch.tensor([1, 2, 3], requires_grad=True, dtype=torch.float32)x2 = torch.tensor([2, 2, 1], requires_grad=True, dtype=torch.float32)y = x1**2 + 3*x2 - 5y = y.sum()y.backward()print(x1.grad)print(x2.grad)grad_test()
"""
tensor([2., 4., 6.])
tensor([3., 3., 3.])
"""
梯度上下文控制
- 梯度计算的上下文控制和设置对于管理计算图、内存消耗、以及计算效率至关重要。下面我们学习下Torch中与梯度计算相关的一些主要设置方式。
控制梯度计算
- 梯度计算是有性能开销的,有些时候我们只是简单的运算,并不需要梯度
python">import torchdef grad_context_01():x = torch.tensor(5, requires_grad=True, dtype=torch.float64)y = x**2 + 2*x - 5print(y)y.backward()print(y.requires_grad) # True# 实际开发时y已经是最后一个表达式了# 但是y的梯度计算功能是开启的状态z = y**2 + 3print(z)# 要将前面的保存了才能继续求导# z.backward()grad_context_01()"""
tensor(30., dtype=torch.float64, grad_fn=<SubBackward0>)
True
tensor(903., dtype=torch.float64, grad_fn=<AddBackward0>)
"""# 关闭梯度功能
def grad_context_02():x = torch.tensor(5, requires_grad=True, dtype=torch.float64)# 需要在with这个作用域里面y梯度关闭才能生效# 局部对y进行关闭with torch.no_grad():y = x**2 + 2*x - 5print(y.requires_grad) # Falseprint(y.requires_grad) # False
grad_context_02()# 装饰器去关闭梯度
@torch.no_grad()
def grad_context_03():x = torch.tensor([1,2,3], requires_grad=True, dtype=torch.float64)y = x**2 + 2*x - 5y = y.sum()print(y)print(y.requires_grad)#False
grad_context_03()# 自己写装饰器
def my_no_grad(func):def wrapper():with torch.no_grad():res = func()return resreturn wrapper
@my_no_grad
def grad_context_04():x = torch.tensor([1,2,3], requires_grad=True, dtype=torch.float64)y = x**2 + 2*x - 5y = y.sum()print(y)print(y.requires_grad)
grad_context_04() # 全局关闭梯度更新
def stop_grad():x = torch.tensor([1,2,3], requires_grad=True, dtype=torch.float32)torch.set_grad_enabled(False)y = x**2 + 2*x -5y = y.sum()print(y.requires_grad)print(x.requires_grad)# y.backward()# print(x.grad)# x也不能求梯度了stop_grad()
累计梯度
- 默认情况下,当我们重复对一个自变量进行梯度计算时,梯度是累加的
python">import torch
# 累计梯度
def accumulate_grad():x = torch.tensor(4, requires_grad=True, dtype=torch.float64)# 同一个函数多次反向传播,梯度累加,x梯度不断累计,每次x的梯度不断更改# _不用循环变量只做循环for _ in range(5):y = x**2 + 2*x - 5print(y.requires_grad)y.backward()print(x.grad)# 逐渐增大accumulate_grad()
"""
True
tensor(10., dtype=torch.float64)
True
tensor(20., dtype=torch.float64)
True
tensor(30., dtype=torch.float64)
True
tensor(40., dtype=torch.float64)
True
tensor(50., dtype=torch.float64)
"""
梯度清零
- 大多数情况下是不需要梯度累加的,反向传播之前可以先对梯度进行清零
python">import torch
# 梯度清零
def zero_grad():x = torch.tensor(4, requires_grad=True, dtype=torch.float64)y = 2*x**2 + 7y.backward()print(x.grad) z = 3*x**2+7*x# 在反向传播之前对x的梯度进行清零x.grad.zero_()z.backward()print(x.grad)
zero_grad()
"""
tensor(16., dtype=torch.float64)
tensor(31., dtype=torch.float64)
"""import torchdef clear_grad():x = torch.tensor(4, requires_grad=True, dtype=torch.float64)for _ in range(5):y = x**2 + 2*x - 5 # 清零操作if x.grad is not None: x.grad.zero_()y.backward()print(x.grad)# 每一次的梯度 不是累计的梯度clear_grad()
"""
tensor(10., dtype=torch.float64)
tensor(10., dtype=torch.float64)
tensor(10., dtype=torch.float64)
tensor(10., dtype=torch.float64)
tensor(10., dtype=torch.float64)
"""
案例-函数最优解
python">import torchdef updat_grad():# 生成初始化参数ww = torch.tensor(5, requires_grad=True, dtype=torch.float64)# 定义训练参数lr = 0.01epoch = 100for i in range(epoch):# 生成损失函数loss = 3*w**2 + 2*w - 5# 梯度清零if w.grad is not None: w.grad.data.zero_()# 反向传播(求当前w的导数值[梯度值,斜率])loss.backward()# 求得当前w的斜率print("w.grad", w.grad)# 更新梯度# w这个tensor的张量是不能改的, 否则未来的w就变成了新的数据,就不是tensor对象了# 正确更改应该是更爱w.data进行更改print("old:", w)# w.data.add_(-0.1*w.grad.data)# w = w - 0.1*w.grad 返回的依旧是tensor,但是这个可能会有问题,现在的w可能是一个新的tensor对象,而不是之前的w,所以不能这样更改w.data -= lr*w.gradprint("new", w)# .data是返回tensor的data并可以进行修改,.item()是单纯的取出tensor的数值,但tensor中只能有一个元素# 如果是多个向量, 用data取出值print("训练结束后的w:", w.item())updat_grad()
python">import torchdef updat_grad():# 生成初始化参数ww = torch.tensor([1,2,3], requires_grad=True, dtype=torch.float64)# 定义训练参数lr = 0.01epoch = 100for i in range(epoch):# 生成损失函数loss = 3*w**2 + 2*w - 5loss = loss.sum()# 梯度清零if w.grad is not None: w.grad.data.zero_()# 反向传播(求当前w的导数值[梯度值,斜率])loss.backward()# 求得当前w的斜率print("w.grad", w.grad)# 更新梯度# w这个tensor的张量是不能改的, 否则未来的w就变成了新的数据,就不是tensor对象了# 正确更改应该是更爱w.data进行更改print("old:", w)# w.data.add_(-0.1*w.grad.data)# w = w - 0.1*w.grad 返回的依旧是tensor,但是这个可能会有问题,现在的w可能是一个新的tensor对象,而不是之前的w,所以不能这样更改# *是数值分别乘以tensor对象中每一个元素值w.data -= lr*w.gradprint("new", w)# .data是返回tensor的data并可以进行修改,.item()是单纯的取出tensor的数值,但tensor中只能有一个元素# 如果是多个向量, 用data取出值print("训练结束后的w:", w.data)# 保存wtorch.save(w, "..\data\weight.pkl")updat_grad()def load_weight():w = torch.load("..\data\weight.pkl", map_location='cpu')print("加载的w:", w)load_weight()
梯度计算小结
- 在进行梯度计算或者类型转换时,有一些细节需要注意下
转换错误
- 当requires_grad=True时,在调用numpy转换为ndarray时报错如下:
RuntimeError: Can't call numpy() on Tensor that requires grad. Use tensor.detach().numpy() instead.
错误处理
- 使用detach()方法创建张量的叶子节点即可
python">import torch
import numpy as npdef leaftest():x1 = torch.tensor([1,2,3], requires_grad=True, dtype=torch.float64)# 梯度开启后不能进行numpy转换# 如果x是一个可以求梯度的张量,就不可以当做普通的tensor使用# x2 = x1.numpy()# 报错# 正确的转换方法x2 = x1.detach()print(x1)print(x2)X3 = x2.numpy()print(X3)leaftest()
"""
tensor([1., 2., 3.], dtype=torch.float64, requires_grad=True)
tensor([1., 2., 3.], dtype=torch.float64)
[1. 2. 3.]
"""
叶子结点
- detach()产生的张量是作为叶子结点存在的,并且该张量和原张量共享数据,只是该张量不需要计算梯度。
python">import torch
def leaftest_01():x = torch.tensor([1, 2, 3], requires_grad=True, dtype=torch.float32)x_np = x.detach()print("是两个东西:", id(x), id(x_np))print("数据是共享的:", id(x.data), id(x_np.data))# 修改其中一个张量的值x_np[1:] = 100print(x, x_np)
leaftest_01()
"""
是两个东西: 2261576624624 2261576623504
数据是共享的: 2261576622704 2261576622704
tensor([ 1., 100., 100.], requires_grad=True) tensor([ 1., 100., 100.])
"""
手动构建模型实战
模型训练基础概念
在进行模型训练时,有三个基础的概念我们需要颗粒度对齐下:
名词 | 定义 |
---|---|
Epoch | 使用训练集的全部数据对模型进行一次完整训练,被称为“一代训练” |
Batch | 使用训练集中的一小部分样本对模型权重进行一次反向传播的参数更新,这一小部分样本被称为“一批数据” |
Iteration | 使用一个Batch数据对模型进行一次参数更新的过程,被称为“一次训练” |
数据处理这里简单的构建一批数据集
构建数据集
python">import math
import random
import torch
import numpy as np
from sklearn.datasets import make_regression
import pandas as pddef build_dataset():"""使用 sklearn 的 make_regression 方法来构建一个模拟的回归数据集。make_regression 方法的参数解释:- n_samples: 生成的样本数量,决定了数据集的规模。- n_features: 生成的特征数量,决定了数据维度。- noise: 添加到目标变量的噪声标准差,用于模拟真实世界数据的不完美。- coef: 如果为 True, 会返回生成数据的真实系数,用于了解特征与目标变量间的真实关系。- random_state: 随机数生成的种子,确保在多次运行中能够复现相同的结果。返回:- X: 生成的特征矩阵。- y: 生成的目标变量。- coef: 如果在调用时 coef 参数为 True,则还会返回真实系数。"""noise = random.randint(1, 5)X, y, coef = make_regression(n_samples=1000, n_features=5, bias=14.5, noise=noise, coef=True, random_state=0)# 数据转换为张量X = torch.tensor(X, dtype=torch.float32)y = torch.tensor(y, dtype=torch.float32)coef = torch.tensor(coef, dtype=torch.float32)return X, y, coef
构建数据加载器
- 数据需要分批次加载到模型进行训练
python">import math
import random
import torch
import numpy as np
from sklearn.datasets import make_regression
import pandas as pddef data_loader(x, y):"description:数据加载器"# 配置参数batch_size = 16 n_samples = x.shape[0] # 样本数量len(x)b_batchs = math.ceil(n_samples//batch_size) # 向上取整,确定训练轮次# 打乱数据indexs = [i for i in range(n_samples)]random.shuffle(indexs) # 打乱列表的数据for i in range(0, b_batchs):# 列表切片index = indexs[i*batch_size:min((i+1)*batch_size, n_samples)]x[index]y[index]yield x[index], y[index] # 循环遇到yied就结束,掉几次就可以产生几次的值
模型函数
python"># 模型函数
def myregreser(x,w,b):# 这个的x不是未知数,w才是未知数,梯度1也是和w相关的return x@w + b # 一个容器中装的每一条样本的预测值
损失函数
python">def MSE(y_pred, y_true):# 因为y的真实值和预测试都都是tensor数组,所以不用对y的预测值的tensor数组进行求和return torch.mean((y_pred - y_true)**2)
优化器
- 使用梯度下降对参数进行调整...
python">def optim_step(w, b, dw, db, lr):# 更新梯度,朝着梯度下降的方向更新梯度w = w.data - lr * dw.datab = b.data - lr * db.datareturn w, b
参数初始化
python">def initialize(n_features):torch.manual_seed(666)# 因为要更新权重梯度,所以要用requires_grad=True,默认为False# 如果没有设置为True,在optim_step()中,无法更新参数,会报没有w和b,没有梯度的data属性w = torch.randn(n_features, requires_grad=True, dtype=torch.float32)b= torch.tensor(14.5, requires_grad=True, dtype=torch.float32)return w, b
训练函数
- 训练函数完成对数据的训练和参数调整等,是一个完整的功能函数
python">import mathimport random
from sklearn.datasets import make_regression
import torch# 训练函数
def train():# 1.生成数据x, y, coef, bias = build_data()# 2.初始化参数w, b = initialize(x.shape[1])# 输入参数的维度,shape[1]有多少个权重系数# 3.定义训练参数lr = 0.01epochs = 100for i in range(epochs):# i循环一次,data_loader()返回一个batch的数据e = 0count =0 for x_batch, y_batch in data_loader(x, y):y_batch_pred_sum = myregreser(x_batch, w, b)loss = MSE(y_batch_pred_sum, y_batch)e += losscount +=1# 梯度清零if w.grad != None:w.grad.data.zero_()if b.grad != None: b.grad.data.zero_()# 方向传播(梯度计算)loss.backward()optim_step(w, b, w.grad, b.grad, lr)mean_loss = e / count# print('mean_loss:', mean_loss)return w, b, coef
项目整合
代码整合:
python">import mathimport random
from sklearn.datasets import make_regression
import torchdef build_data():noise = 14.6 # 噪声n_samples = 1000 # 样本数量bias = 14.5# coef为系数,相当于在make_regression函数中简单的进行了回归# 那么bias参数的设置就会影响到coef的取值x, y, coef = make_regression(n_samples=n_samples, n_features=4,bias=bias, noise=noise, coef=True , random_state=666)x = torch.tensor(x, dtype=torch.float32)y = torch.tensor(y, dtype=torch.float32, requires_grad=True)return x, y, coef, biasdef data_loader(x, y):"description:数据加载器"# 配置参数batch_size = 16 n_samples = x.shape[0] # 样本数量len(x)b_batchs = math.ceil(n_samples//batch_size) # 向上取整,确定训练轮次# 打乱数据indexs = [i for i in range(n_samples)]random.shuffle(indexs) # 打乱列表的数据for i in range(0, b_batchs):# 列表切片index = indexs[i*batch_size:min((i+1)*batch_size, n_samples)]x[index]y[index]yield x[index], y[index] # 循环遇到yied就结束,掉几次就可以产生几次的值def initialize(n_features):torch.manual_seed(666)# 因为要更新权重梯度,所以要用requires_grad=True,默认为False# 如果没有设置为True,在optim_step()中,无法更新参数,会报没有w和b,没有梯度的data属性w = torch.randn(n_features, requires_grad=True, dtype=torch.float32)b= torch.tensor(14.5, requires_grad=True, dtype=torch.float32)return w, b# 模型函数
def myregreser(x,w,b):# 这个的x不是未知数,w才是未知数,梯度1也是和w相关的return x@w + b # 一个容器中装的每一条样本的预测值def MSE(y_pred, y_true):# 因为y的真实值和预测试都都是tensor数组,所以不用对y的预测值的tensor数组进行求和return torch.mean((y_pred - y_true)**2)def optim_step(w, b, dw, db, lr):# 更新梯度,朝着梯度下降的方向更新梯度w = w.data - lr * dw.datab = b.data - lr * db.datareturn w, b# 训练函数
def train():# 1.生成数据x, y, coef, bias = build_data()# 2.初始化参数w, b = initialize(x.shape[1])# 输入参数的维度,shape[1]有多少个权重系数# 3.定义训练参数lr = 0.01epochs = 100for i in range(epochs):# i循环一次,data_loader()返回一个batch的数据e = 0count =0 for x_batch, y_batch in data_loader(x, y):y_batch_pred_sum = myregreser(x_batch, w, b)loss = MSE(y_batch_pred_sum, y_batch)e += losscount +=1# 梯度清零if w.grad != None:w.grad.data.zero_()if b.grad != None: b.grad.data.zero_()# 方向传播(梯度计算)loss.backward()optim_step(w, b, w.grad, b.grad, lr)mean_loss = e / count# print('mean_loss:', mean_loss)return w, b, coef if __name__ == '__main__':X, y, coef, bias = build_data()print(X.shape)print(y.shape)print(coef)# data = data_loader(X, y)# print(data) # 生成器对象,运行一次生成一次值# for x, y in data:# print(x.shape)# print(y.shape)print(initialize(4))w, b, coef = train()print(w)"""
torch.Size([1000, 4])
torch.Size([1000])
[27.20319965 81.32000351 89.13847136 80.53196512]
(tensor([-2.1188, 0.0635, -1.4555, -0.0126], requires_grad=True), tensor(14.5000, requires_grad=True))
tensor([-2.1188, 0.0635, -1.4555, -0.0126], requires_grad=True)
"""
模型定义组件
- 模型(神经网络,深度神经网络,深度学习)定义组件帮助我们在 PyTorch 中定义、训练和评估模型等。
基本组件认知
- 先初步认知,他们用法基本一样的,后续在学习深度神经网络和卷积神经网络的过程中会很自然的学到更多组件!
- 官方文档:torch.nn — PyTorch 2.5 documentation
损失函数组件
- PyTorch已内置多种损失函数,在构建神经网络时随用随取!
python">import torch
import torch.nn as nn# 损失函数组件
def test01():y_true = torch.tensor([[1,2,3], [4,5,6]], dtype=torch.float32)y_pred = torch.tensor([[2,3,4], [5,6,7]], dtype=torch.float32)loss = nn.MSELoss()# 均方误差e = loss(y_true, y_pred)# 计算损失print(e)test01()
"""
tensor(1.)
"""
线性层组件
- 构建一个简单的线性层,后续还有卷积层、池化层、激活、归一化等需要我们去学习和使用...
python">import torch
import torch.nn as nn# 线性层组件
def test02():# 隐式操作,w1 、w2、w3、w4已经初始化了# 初始化权重值和偏置值的组件# 4:输入的特征数量,2:输出的特征数量model = nn.Linear(4,2)#w1x1+w2x2+w3x3+w4x4+b = yprint(model.parameters())#parameter模型的参数属性x = torch.tensor([[1,2,3,4], [1,2,3,4], [1,2,3,4]], dtype=torch.float32)y = model(x)print(y)test02()
"""
<generator object Module.parameters at 0x000001F181F2C660>
tensor([[-0.7248, 0.8096],[-0.7248, 0.8096],[-0.7248, 0.8096]], grad_fn=<AddmmBackward0>)
"""
优化器方法
官方文档:torch.optim — PyTorch 2.5 documentation
这里牵涉到的API有:
- import torch.optim as optim
- params=model.parameters():模型参数获取;
- optimizer=optim.SGD(params):优化器方法;
- optimizer.zero_grad():梯度清零;
- optimizer.step():参数更新;
python">import torch.optim as optim# 优化器组件
def test03():# 一次完整的梯度更新#1.构建数据集input_x = torch.randint(1,10,(400,5)).type(torch.float32)target_y = torch.randint(1,10,(400,1)).type(torch.float32)# 2.线性层模型model = nn.Linear(5,1)# 3.优化器对象,梯度更新的代码,调用才会进行梯度更新sgd = optim.SGD(model.parameters(), lr=0.01)# 4.预测y_pred = model(input_x)# 5.损失函数loss_fn = nn.MSELoss()loss = loss_fn(y_pred,target_y)print(loss)# 6.梯度清零sgd.zero_grad()# 等价于w.grad.data.zero_()# 7.反向传播loss.backward()# 1.求损失函数的导函数2.求梯度# 8.梯度更新sgd.step()# 9.访问更新后的wprint(model.weight)test03()
"""
tensor(44.9972, grad_fn=<MseLossBackward0>)
Parameter containing:
tensor([[0.9197, 0.2355, 0.3049, 0.4886, 0.8214]], requires_grad=True)
"""
注意:这里只是组件认识和用法演示,没有具体的模型训练功能实现
数据加载器
构建数据类
在 PyTorch 中,构建自定义数据加载类通常需要继承 torch.utils.data.Dataset 并实现以下几个方法:
-
__init__ 方法 用于初始化数据集对象:通常在这里加载数据,或者定义如何从存储中获取数据的路径和方法。
python">def __init__(self, data, labels):self.data = dataself.labels = labels
-
__len__ 方法 返回样本数量:需要实现,以便 Dataloader加载器能够知道数据集的大小。
python">def __len__(self):return len(self.data)
-
__getitem__ 方法 根据索引返回样本:将从数据集中提取一个样本,并可能对样本进行预处理或变换。
python">def __len__(self):return len(self.data)
如果你需要进行更多的预处理或数据变换,可以在 __getitem__ 方法中添加额外的逻辑。
-
整体参考代码如下:
python">import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader# 定义数据加载类
class CustomDataset(Dataset):def __init__(self, data, labels):self.data = dataself.labels = labelsself.len = len(self.data)def __len__(self):return self.lendef __getitem__(self, index):index = min(max(index, 0), self.len - 1)sample = self.data[index]label = self.labels[index]return sample, labeldef test001():# 简单的数据集准备data_x = torch.randn(666, 20, requires_grad=True, dtype=torch.float32)data_y = torch.randn(data_x.size(0), 1, dtype=torch.float32)dataset = CustomDataset(data_x, data_y)# 随便打印个数据看一下print(dataset[0])
数据加载器
- 在训练或者验证的时候,需要用到数据加载器批量的加载样本。
python">import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoaderdef test001():# 简单的数据集准备data_x = torch.randn(666, 20, requires_grad=True, dtype=torch.float32)data_y = torch.randn(data_x.size(0), 1, dtype=torch.float32)dataset = CustomDataset(data_x, data_y)# 构建数据加载器data_loader = DataLoader(dataset, batch_size=8, shuffle=True)for i, (batch_x, batch_y) in enumerate(data_loader):print(batch_x, batch_y)break
数据集加载案例
- 通过一些数据集的加载案例,真正了解数据类及数据加载器。
加载Excel数据集
python">import torch
from torch.utils.data import Dataset, DataLoader
import pandas as pdclass my_excel_datasets(Dataset):def __init__(self, path):super(my_excel_datasets).__init__()"""把excle文件读取出来后想办法把特征数据保存在data中把目标值保存在labels中,至于如何处理,看实际的需求"""data_pd = pd.read_excel(path)data_pd.dropna(axis=1, how='all', inplace=True)data_pd.columns = ['zhubie', 'st_id', 'st_name', 'fenggong', 'expresion', 'ppt_make', 'code_show', 'score', 'comments']data_pd.drop(['zhubie', 'st_id', 'st_name', 'fenggong', 'comments'], axis=1, inplace=True)self.data = torch.tensor(data_pd.iloc[:,:-1].to_numpy(), dtype=torch.float32)self.labels = torch.tensor(data_pd.iloc[:,-1].to_numpy(), dtype=torch.float32)def __len__(self):return len(self.data)def __getitem__(self, idx):return self.data[idx], self.labels[idx]# 创建数据集实例
if __name__ == '__main__':data = my_excel_datasets(r'..\data\21级大数据答辩成绩表.xlsx')data_loader = DataLoader(data, batch_size=4, shuffle=True)for x, y in data_loader:print(x,y)"""
tensor([[20., 29., 29.],[19., 27., 25.],[16., 22., 21.],[16., 23., 21.]]) tensor([96., 90., 73., 75.])
tensor([[16., 23., 21.],[20., 28., 28.],[16., 22., 24.],[15., 20., 22.]]) tensor([75., 94., 76., 72.])
tensor([[16., 22., 21.],[16., 21., 22.],[15., 21., 20.],[16., 21., 20.]]) tensor([73., 76., 71., 72.])
tensor([[16., 21., 22.],[16., 21., 22.],[16., 23., 21.],[16., 22., 23.]]) tensor([75., 76., 75., 78.])
tensor([[16., 23., 19.],[16., 20., 22.],[15., 21., 20.],[16., 21., 20.]]) tensor([74., 76., 71., 73.])
tensor([[16., 21., 20.],[16., 21., 22.],[16., 24., 24.],[16., 22., 22.]]) tensor([72., 75., 81., 74.])
tensor([[16., 23., 22.],[15., 20., 21.],[16., 20., 21.],[15., 20., 22.]]) tensor([76., 71., 74., 72.])
tensor([[16., 25., 19.],[16., 22., 22.],[19., 28., 28.],[19., 28., 27.]]) tensor([76., 75., 94., 93.])
tensor([[20., 27., 26.],[16., 20., 22.],[19., 28., 24.],[20., 27., 27.]]) tensor([90., 76., 90., 92.])
tensor([[15., 21., 20.],[15., 21., 20.],[18., 24., 23.],[18., 22., 23.]]) tensor([71., 71., 83., 81.])
tensor([[15., 20., 21.],[18., 25., 24.],[15., 20., 21.],[16., 23., 19.]]) tensor([71., 85., 71., 74.])
tensor([[18., 25., 27.]]) tensor([88.])
"""
加载图片数据集
python">import os
from torch.utils.data import Dataset,DataLoader
import cv2
import torchclass my_imga_datset(Dataset):def __init__(self,path):self.path=path# 存放当前传入路径的文件夹下所有子文件夹名称self.classname = []# 存放完整的文件名路径self.data = []# 存放传入路径下包括传入的路径的所有的文件名self.label = []for root, dirs, files in os.walk(path):if root==path:# ../data/animal# 这个相当于是目标描述self.classname = dirs# ../data/animal/……print(dirs)# returnelse:print(os.path.split(root))for file in files:# 取出的是图像的名称# print("file",file)file_path = os.path.join(root,file)self.data.append(file_path)# 这个就相当于是特征值class_id = self.classname.index(os.path.split(root)[1])self.label.append(class_id)# 这个相当于是目标值def __len__(self):return len(self.data)def __getitem__(self,index):# 取出的是data数据中index的数据img_path = self.data[index]# data中的index对应的是图片路径,它属于什么文件夹下的(目标值是什么)label = self.label[index]# 读取data数据中index索引对应的图片,读取的数据为numpy数组格式img = cv2.imread(img_path)# 设置图片的格式大小,但不改变图片的数组维度img = cv2.resize(img,(336,336))# 将图片数据转化为tensor格式img = torch.from_numpy(img)# print(img.shape)# HWC 2 CHWimg = img.permute(2,0,1)return img, labelif __name__ == '__main__':data = my_imga_datset(path='../data/animal')# print(data[1])print(len(data))train_loader = DataLoader(data,batch_size=32,shuffle=True)for x,y in train_loader:print(x.shape)print(y.shape)
加载官方数据集
- 在 PyTorch 中官方提供了一些经典的数据集,如 CIFAR-10、MNIST、ImageNet 等,可以直接使用这些数据集进行训练和测试。
- 官方地址:Datasets — Torchvision 0.20 documentation
python">from torchvision import transforms, datasets
from torch.utils.data import DataLoaderdef test04():transforms = transforms.Compose([transforms.ToTensor()])data = datasets.MNIST(root = '../data'train = True,download=True,transform=transforms)for i, (x,y) in DataLoader(data, batch_size=4, shuffle=True):print(x.shape, y.shape)
数据增强
- 数据增强是提高模型泛化能力(鲁棒性)的一种有效方法,尤其在图像分类、目标检测等任务中。数据增强可以模拟更多的训练样本,从而减少过拟合风险。数据增强通过torchvision.transforms模块来实现。
来自官方的公用代码:
python">from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
import torch
from torchvision.transforms import v2
plt.rcParams["savefig.bbox"] = "tight"
torch.manual_seed(0)orig_img = Image.open("./origin.jpg")def plot(imgs, title, with_orig=True, row_title=None, **imshow_kwargs):if not isinstance(imgs[0], list):# Make a 2d grid even if there's just 1 rowimgs = [imgs]num_rows = len(imgs)num_cols = len(imgs[0]) + with_origfig, axs = plt.subplots(nrows=num_rows, ncols=num_cols, squeeze=False)plt.title(title)for row_idx, row in enumerate(imgs):row = [orig_img] + row if with_orig else rowfor col_idx, img in enumerate(row):ax = axs[row_idx, col_idx]ax.imshow(np.asarray(img), **imshow_kwargs)ax.set(xticklabels=[], yticklabels=[], xticks=[], yticks=[])if with_orig:axs[0, 0].set(title="Original image")axs[0, 0].title.set_size(8)if row_title is not None:for row_idx in range(num_rows):axs[row_idx, 0].set(ylabel=row_title[row_idx])plt.tight_layout()plt.show()
固定转换
具体参考官方文档:Illustration of transforms — Torchvision 0.20 documentation
参考代码:
python">from PIL import Image
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
import torch
from torchvision.transforms import v2plt.rcParams["savefig.bbox"] = "tight"
torch.manual_seed(0)orig_img = Image.open("./origin.jpg")def plot(imgs, title, with_orig=True, row_title=None, **imshow_kwargs):if not isinstance(imgs[0], list):# Make a 2d grid even if there's just 1 rowimgs = [imgs]num_rows = len(imgs)num_cols = len(imgs[0]) + with_origfig, axs = plt.subplots(nrows=num_rows, ncols=num_cols, squeeze=False)plt.title(title)for row_idx, row in enumerate(imgs):row = [orig_img] + row if with_orig else rowfor col_idx, img in enumerate(row):ax = axs[row_idx, col_idx]ax.imshow(np.asarray(img), **imshow_kwargs)ax.set(xticklabels=[], yticklabels=[], xticks=[], yticks=[])if with_orig:axs[0, 0].set(title="Original image")axs[0, 0].title.set_size(8)if row_title is not None:for row_idx in range(num_rows):axs[row_idx, 0].set(ylabel=row_title[row_idx])plt.tight_layout()plt.show()# 边缘填充
padded_imgs = [v2.Pad(padding=padding)(orig_img) for padding in (3, 10, 30, 50)]
plot([orig_img] + padded_imgs, "v2.Pad")# 大小调整
resized_imgs = [v2.Resize(size=size)(orig_img) for size in (30, 50, 100, orig_img.size)]
plot([orig_img] + resized_imgs, "v2.Resize")# 中心裁剪
center_crops = [v2.CenterCrop(size=size)(orig_img) for size in (30, 50, 100, orig_img.size)
]
plot([orig_img] + center_crops, "v2.CenterCrop")# 周边裁剪
(top_left, top_right, bottom_left, bottom_right, center) = v2.FiveCrop(size=(100, 100))(orig_img
)
plot([orig_img] + [top_left, top_right, bottom_left, bottom_right, center], "v2.FiveCrop"
)
概率控制转换
同上
随机转换
同上
数据增强整合
使用transforms.Compose()把要增强的操作整合到一起:
python">from PIL import Image
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
import torch
from torchvision import transforms, datasets, utilsdef test001():# 定义数据增强和预处理步骤transform = transforms.Compose([transforms.RandomHorizontalFlip(), # 随机水平翻转transforms.RandomRotation(10), # 随机旋转 ±10 度transforms.RandomResizedCrop(32, scale=(0.8, 1.0)), # 随机裁剪到 32x32,缩放比例在0.8到1.0之间transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1), # 随机调整亮度、对比度、饱和度、色调transforms.ToTensor(), # 转换为 Tensortransforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)), # 归一化])# 加载 CIFAR-10 数据集,并应用数据增强trainset = datasets.CIFAR10(root="./data", train=True, download=True, transform=transform)trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2)# 显示增强后的图像dataiter = iter(trainloader)images, labels = next(dataiter)def imshow(img):img = img / 2 + 0.5 # 反归一化npimg = img.numpy()plt.imshow(np.transpose(npimg, (1, 2, 0)))plt.show()imshow(utils.make_grid(images))
重构线性回归
使用pytorch对线性回归项目进行重构,可以看到有多方便!
python">from sklearn.datasets import make_regression
import torch
from torch.utils.data import DataLoader,TensorDatasetdef build_dataset():bias = 14.5X, y, coef = make_regression(n_samples=1000, n_features=4, n_targets=1, noise=10, random_state=666, coef=True, bias=bias)X = torch.tensor(X, dtype=torch.float32)y = torch.tensor(y, dtype=torch.float32)return X, y, coef, biasdef train():# 1.加载数据集X, y, coef, bias = build_dataset()data = TensorDataset(X, y)# 2.构建模型model = torch.nn.Linear(X.shape[1], 1)# 会初始化权重和偏置# 3.初始化参数# 可以自己初始化,如果不自己初始化,就会自动初始化# 4.构建损失函数loss_fn = torch.nn.MSELoss()# 5.构建优化器sgd = torch.optim.SGD(model.parameters(), lr=0.1)# 6.训练(训练参数的配置)# 6.1.训练的次数epochs = 100for epoch in range(epochs):# 6.2.计算算式data_loader = DataLoader(data, batch_size=32, shuffle=True)loss_total = 0count = 0for x,y in data_loader:count += 1# print(x.shape)# print(y.shape)y_pred = model(x)loss = loss_fn(y_pred, y)loss_total += loss# 6.3.梯度清零sgd.zero_grad()# 6.4.反向传播loss.backward()# 6.5.更新参数sgd.step()# 打印每一轮的损失(观察是否除了问题)print(f"epoch:{epoch}, loss:{loss_total / count}")# 7.保存模型参数print(model.weight, model.bias)print(coef, bias)if __name__ == '__main__':train()
模型的保存和加载
- 训练一个模型通常需要大量的数据、时间和计算资源。通过保存训练好的模型,可以满足后续的模型部署、模型更新、迁移学习、训练恢复等各种业务需要求。
标准网络模型构建
python">class MyModle(nn.Module):def __init__(self, input_size, output_size):super(MyModle, self).__init__()self.fc1 = nn.Linear(input_size, 128)self.fc2 = nn.Linear(128, 64)self.fc3 = nn.Linear(64, output_size)def forward(self, x):x = self.fc1(x)x = self.fc2(x)output = self.fc3(x)return output
序列化模型对象
- 模型序列化对象的保存和加载:
python">import torch
import torch.nn as nnclass MyModle(nn.Module):def __init__(self, input_size, output_size):super(MyModle, self).__init__()self.fc1 = nn.Linear(input_size, 128)self.fc2 = nn.Linear(128, 64)self.fc3 = nn.Linear(64, output_size)def forward(self, x):x = self.fc1(x)x = self.fc2(x)output = self.fc3(x)return outputdef test001():model = MyModle(input_size=128, output_size=32)# 序列化方式保存模型对象torch.save(model, "model.pkl")def test002():# 注意设备问题model = torch.load("model.pkl", map_location="cpu")print(model)
保存模型参数
- 这种形式更常用,只需要保存权重、偏执、准确率等相关参数,都可以在加载后打印观察!
python">import torch
import torch.nn as nn
import torch.optim as optim
import pickleclass MyModle(nn.Module):def __init__(self, input_size, output_size):super(MyModle, self).__init__()self.fc1 = nn.Linear(input_size, 128)self.fc2 = nn.Linear(128, 64)self.fc3 = nn.Linear(64, output_size)def forward(self, x):x = self.fc1(x)x = self.fc2(x)output = self.fc3(x)return outputdef test003():model = MyModle(input_size=128, output_size=32)optimizer = optim.SGD(model.parameters(), lr=0.01)# 自己构建要存储的模型参数save_dict = {"init_params": {"input_size": 128, # 输入特征数"output_size": 32, # 输出特征数},"accuracy": 0.99, # 模型准确率"model_state_dict": model.state_dict(),"optimizer_state_dict": optimizer.state_dict(),}torch.save(save_dict, "model_dict.pth")def test004():save_dict = torch.load("model_dict.pth")model = MyModle(input_size=save_dict["init_params"]["input_size"],output_size=save_dict["init_params"]["output_size"],)# 初始化模型参数model.load_state_dict(save_dict["model_state_dict"])optimizer = optim.SGD(model.parameters(), lr=0.01)# 初始化优化器参数optimizer.load_state_dict(save_dict["optimizer_state_dict"])# 打印模型信息print(save_dict["accuracy"])print(model)
只保存模型参数时时简单如下:
python"># 保存模型状态字典
torch.save(model.state_dict(), 'model.pth')# 加载模型状态字典
model = MyModel(128, 32)
model.load_state_dict(torch.load('model.pth'))