文章目录
- pdb
- jupyter notebook
- import
- python格式化字符串
- 输入输出
- 函数
- python内置函数
- lambda 匿名函数
- 时间
- argparse
- numpy
- range
- matplotlib.pylab
- 字典
- 可视化
- torch
- torchvision
pdb
pdb开始调试python
python -m pdb 源程序.py
pdb设置断点
- linenumber是断点所在行数
break linenumber
b linenumber
- 取消断点
clear linenumber
- 设置python文件内断点
b test.py:124
b methods/cosine_classifier.py:83 #文件夹下的py文件设置断点
重新运行
run
注意分别:r是return的意思,就是执行到函数结束
单步执行
n(ext)
进入到函数的内部
s(tep)
查看变量
- p(rint) 变量
查看类的属性
- print 类名.dict 查出的是一个字典,key是属性名,value是属性值
执行到下一个断点或程序结束
c(ontinue)
调用栈查看命令
w(here)
查看当前函数调用堆栈
up
向上一层函数查看调用堆栈
down
查看源代码
ll 查看当前函数的源代码
jupyter notebook
!和%
-
相同点
- ! 和 % 都可以在Jupyter notebook 运行 shell 命令。
-
不同点
- % 运行shell的环境是当前jupyter运行的虚拟环境比如kernel是pytorch,输入%pip list,就会显示当前虚拟环境安装的库。
- ! 运行shell的环境是jupyter的base主环境,输入!pip list,就会显示主环境安装的库。
import
import 用法
-
导入整个模块。基本的import语句用于导入整个模块,可以使用模块名直接访问模块中的内容。例如,import math后可以使用math.sqrt()或math.fabs()等函数。
-
为模块指定别名。使用as关键字可以为导入的模块指定别名,例如import math as m,然后可以使用m.sqrt()代替math.sqrt()。
-
导入模块中的特定成员。使用from…import…语句可以导入模块中的特定函数、变量或类,例如from math import sqrt,这样可以直接使用sqrt()函数,而不需要通过模块名前缀。
-
导入模块中的所有内容。from…import *语句用于导入模块中的所有函数、类和变量,这样可以直接使用这些内容,而无需通过模块名前缀。但这种方法可能导致命名冲突,因此不推荐在大型项目中使用。
+ 导入包中的模块。可以使用点模块名来导入包中的模块,例如import os或import math
+
python格式化字符串
print(f"epoches:{epoch},accurate={acc}")
输入输出
直到EOF停止输入
import sys
arr = []
while True:
line = sys.stdin.readline()
if not line:
break
arr.extend( [int(x) for x in line.split() ] )
函数
*args
接收任意数量参数
**kwargs
接受任意数量键值对参数
python内置函数
map()
- 应用一个函数到一个可迭代对象,并返回一个可迭代的map
enumerate()
- 用于将一个可遍历的数据对象(如列表,元组或字符串)组合为一个索引序列,列出数据下标和数据
fruits = ['apple', 'banana','orange']
for index,fruit in enumerate(fruits):
print(f"Index:{index},Fruit:{fruit}")
zip()
- 在Python中,zip() 函数是一个内置函数,它用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的zip对象(在Python 3.x中,它返回的是一个迭代器)。这个函数在处理多个列表(或其他可迭代对象)时特别有用,因为它允许你并行地遍历这些列表。
语法
python
zip(*iterables)
# iterables:一个或多个迭代器;zip函数会将这些迭代器中对应的元素打包成一个个元组。
- 假设你有两个列表,分别代表学生的名字和分数,你想要将它们组合起来以便更容易地访问每个学生的名字和分数。
names = ['Alice', 'Bob', 'Charlie']
scores = [88, 92, 78]
# 使用zip函数
for name, score in zip(names, scores):
print(f'{name}: {score}')
#输出
#Alice: 88
#Bob: 92
#Charlie: 78
生成表达式和列表表达式
# 列表推导式,返回一个列表
[x*2 for x in range(5)]
# 生成器表达式,返回一个迭代对象,只能迭代一次
(x*2 for x in range(5))
使用生成器表达式
lambda 匿名函数
用法
时间
生成当前时间的字符串
from datetime import datetime
now = datetime.now()
time_str = now.strftime("%Y-%m-%d %H:%M:%S")
argparse
add_argument
默认参数
- 参数前–表示默认参数,不输入该参数时,用默认参数
import math
import argparse #1.导入参数对象
def parse_args():
parse = argparse.ArgumentParser(description='Calculate cylinder volume') #创建参数对象
parse.add_argument('--radius', default=2, type = int, help = 'Radius of Cylinder') #往参数对象添加参数
parse.add_argument('--height', default=4,type = int, help = 'height of Cylinder')
args = parse.parse_args() #解析参数对象获得解析对象
return args
#计算圆柱体积
def cal_vol(radius,height):
vol = math.pi * pow(radius,2) * height
return vol
if __name__ == '__main__':
args = parse_args()
print(cal_vol(args.radius, args.height) )
不输入 --height 4,会用它的默认值
必填参数
parser.add_argument("data", metavar="DIR", help="path to dataset")
numpy
导入numpy模块
import numpy as np #导入numpy并设置别名
numpy常用函数
shape和ndim
科学计数法
1e-6 = 1乘10的负6次方
reshape
reshape()函数用于在不更改数据的情况下为数组赋予新形状
>>> c=np.array([[2,3,4],[5,6,7]])
>>> c
array([[2, 3, 4],
[5, 6, 7]])
>>> c=c.reshape(3,2)
>>> c
array([[2, 3],
[4, 5],
[6, 7]])
np.argmax
import numpy as np
a = np.arange(6).reshape(2,3)
print('a数组:\n{}'.format(a))
print('a数组中最大值的索引:{}'.format(np.argmax(a)))
print('a数组沿列方向最大值的索引:{}'.format(np.argmax(a, axis=0)))
print('a数组沿行方向最大值的索引:{}'.format(np.argmax(a, axis=1)))
b = np.arange(6)
b[1] = 5
print('b数组:\n{}'.format(b))
print('b数组最大值的索引:{}'.format(np.argmax(b))) # 仅仅返回5第一次出现的位置
a数组:
[[0 1 2]
[3 4 5]]
a数组中最大值的索引:5
a数组沿列方向最大值的索引:[1 1 1]
a数组沿行方向最大值的索引:[2 2]
b数组:
[0 5 2 3 4 5]
b数组最大值的索引:1
np.sum
- 不传参数是对所有元素求和
传axis(看懂了一点,以后要用的时候再看看)
传送門
range
生成连续序列
for i in range(start,end): #start 到 end-1间隔为1 的序列
生成不连续序列
for i in range(start,end,interval): #start 到 end-1间隔为interval 的序列
matplotlib.pylab
导入
import matplotlib.pylab as plt
xlabel ylabel
- x轴,y轴的介绍
plt.xlabel("x")
plt.ylabel("f(x)")
plot
linestyle:
plt.plot(x,y,linestyle='--')
plt.legend
plt.show
- 用来显示图标
print 格式化输出
方式一:格式说明符+%()
name = 'Alice'
age = 25
print("My name is %s and I am %d years old." % (name,age))
方式二:str.format()
name = 'Alice'
age = 25
format_string = "Name:{name}, Age:{age}".format(name,age)
格式化浮点数
打印格式化浮点数
print('%10.3f' % 3.1415926)
字典
定义
- 和C++的map差不多,都是保存键值对
初始化
- 空字典
network = {}
- 赋值
#法一
network = {'W1':np.array([[0.1,0.3,0.5],[0.2,0.4,0.6] ]) }
#法二
network['W1'] = np.array([[0.1,0.3,0.5],[0.2,0.4,0.6]])
可视化
visdom
启动visdom
python -m visdom.server
torch
常见概念
epoch
- 整个训练集被完整地遍历一次,就叫一个epoch。即所有样本完成一次前向传播和反向传播。(随机加载样本的话,只需要加载的样本数量到达整个训练集的数目)
监督学习
- 数据带有标签
无监督学习
- 数据不带有标签
Tensor
生成标准正态分布Tensor(均值为0,标准差为1)
x = t.randn(2,3)
生成正态分布Tensor(均值mean和标准差std需自己定义)
x = t.normal(mean=0,std=1,size=(3,4))
生成[0,1)均匀分布的Tensor
x = t.rand(2,3)
tensor.detatch()
- 在PyTorch中,detach() 函数是一个非常有用的方法,它用于从计算图中分离出一个tensor,使其成为一个新的tensor,这个新的tensor不会要求梯度(即,它不会被用于梯度计算)。这通常用于在训练过程中,当你想要使用某个tensor的值进行进一步计算,但又不想这些计算影响到反向传播(backward pass)时。
- 使用.detach()后,得到的tensor的requires_grad属性会被设置为False。
- 如果在调用.detach()之前tensor的requires_grad已经是False,那么.detach()调用实际上不会改变tensor的内容或属性,只是返回了一个相同的tensor。
- 在某些情况下,如果你只是想停止梯度传播而不改变tensor的requires_grad属性,可以考虑使用with torch.no_grad():上下文管理器。这会在块内临时禁用梯度计算,但不会影响tensor的requires_grad属性。
广播机制
- 从末尾维度依次向前对齐
- 维度相同,则直接在该维度上相加
- 其中一个维度为1,则广播至与另一个维度对齐
- 维度不同,且都不为1,不能广播,不能计算
增加,减少维度
torch.unsqueeze(input, dim)
- input:要操作的张量
- dim: 要增加维度的位置索引,dim=0表示在最前面增加维度
拼接两个tensor
torch.cat
- dim=0 上下拼接
- dim=1 左右拼接
torch.cat
改变tensor形状
tensor.view()
-
正常用法
-
view(x,-1)
是-1的维度,在该维度上自动计算
torch常用函数
torch.repeat_interleave()
- torch.repeat_interleave 是 PyTorch 中的一个函数,用于重复张量中的元素。这个函数可以沿着指定的维度重复张量中的每个元素,返回一个新的张量。
torch.repeat_interleave(input, repeats, dim=None) -> Tensor
'''参数说明:
input (Tensor): 输入张量。
repeats (int 或 Tensor): 每个元素的重复次数。如果 repeats 是一个整数,则所有元素都将重复相同的次数;如果是一个张量,则需要与 input 张量的形状相同,并且会被广播以适应输入张量的维度。ps:与dim的维度对应
dim (int, 可选): 重复操作的维度。如果不指定 (None),则默认将整个张量视为一维。
返回值:
返回一个新的张量,其形状与输入张量相同,但沿给定维度 dim 的大小会根据重复次数进行调整。'''
# 如果不指定维度dim=None,则会将输入张量展平,并重复每个元素。
- 例子
>>> x = torch.tensor([1, 2, 3])
>>> x.repeat_interleave(2)
tensor([1, 1, 2, 2, 3, 3])
>>> y = torch.tensor([[1, 2], [3, 4]])
>>> torch.repeat_interleave(y, 2)
tensor([1, 1, 2, 2, 3, 3, 4, 4])
>>> torch.repeat_interleave(y, 3, dim=1)
tensor([[1, 1, 1, 2, 2, 2],
[3, 3, 3, 4, 4, 4]])
>>> torch.repeat_interleave(y, torch.tensor([1, 2]), dim=0)
tensor([[1, 2],
[3, 4],
[3, 4]])
矩阵乘法
"""
1.a*b 按位相乘
2.torch.matmul(a,b) == a @ b,支持broadcast
- 2.1.torch.dot(),1d,点积,不支持broadcast
- 2.2.torch.mm(),2d,不支持broadcast
- 2.3.torch.bmm(),3d,不支持broadcast
"""
a*b 按位相乘
torch.dot(),一维,向量内积
torch.mm 二维矩阵乘法 (n,s) *(s,t) -> (n,t)
torch.matmul 高维矩阵乘法,具有广播机制(torch.mm的进阶版)
torch.bmm 三维矩阵乘法 (b,n,s) *(b,s,t) -> (b,n,t)
看标题就已经懂了
torch.utils
torch.utils.data.Dataset
- 传入DataLoader需要是这个类
- 可以自定义类继承Dataset类,也可以用torch.utils.data.TensorDataset转换
torch.utils.data.TensorDataset
torch.utils.data.DataLoader
DataLoader(TensorDataset对象,batch_size每批大小,shuffle是否打乱顺序,num_workers加载线程数)
torch.save
保存数组
#保存tensor
import torch
# 创建一个Tensor
tensor = torch.randn(3, 4)
# 保存Tensor到文件
torch.save(tensor, 'tensor.pt')
#加载tensor
loaded_tensor = torch.load('tensor.pt')
print(loaded_tensor)
保存模型
- 保存整个模型状态
- 保存整个模型状态意味着将模型的结构、参数、优化器的状态(如学习率、动量等)以及任何可能的随机状态(如随机数生成器的状态)都保存下来。这种方式的好处是可以完全恢复模型的训练状态,包括模型的当前学习阶段和参数配置。
- 实现方法:
使用torch.save()函数,将模型对象作为第一个参数,文件路径作为第二个参数。
torch.save({
'model_state_dict': model.state_dict(),
'optimizer_state_dict': optimizer.state_dict(),
'epoch': epoch,
'loss': loss,
...
}, 'checkpoint.pth.tar')
#这里,我们还保存了优化器的状态和当前训练的epoch及loss等信息,
#以便恢复训练时能够从上次中断的地方继续。但请注意,这种方式可能会比较耗内存,
#因为整个模型和优化器的状态都被保存了。
示例
torch.nn
torch.nn.Module
简介
- nn.Module是PyTorch中构建神经网络的核心基类,它提供了丰富的功能和灵活性,使得用户可以轻松地定义复杂的神经网络结构。通过继承nn.Module并实现自定义的forward函数,用户可以创建出满足自己需求的神经网络模型。
import torch
import torch.nn as nn
class Tudui(nn.Module): #继承nn.Module
def __init__(self):
super(Tudui, self).__init__() # 继承nn.Module的初始化
# 在这里可以添加自定义层,但在这个例子中我们没有添加
def forward(self, input):
# 实现前向传播逻辑
# 这里只是一个简单的示例,实际上你需要根据网络结构来编写
output = input + 1 # 假设我们的网络只是简单地将输入加1
return output
# 实例化网络
tudui = Tudui()
# 创建一个tensor作为输入
x = torch.tensor(1.0)
# 通过网络进行前向传播
output = tudui(x)
# 打印输出
print(output) # 输出应该是tensor(2.)
#在这个示例中,我们定义了一个名为Tudui的类,它继承自nn.Module。
#我们没有在__init__方法中添加任何自定义层,但在forward方法中实现了网络的前向传播逻辑。
#然后,我们实例化了这个类,并创建了一个tensor作为输入,最后通过网络进行了前向传播并打印了输出。
nn.Module.train()和nn.Module.eval()
- 在PyTorch中,model.eval() 和 model.train() 方法是用来设置模型为评估模式(evaluation mode)或训练模式(training mode)的。这两种模式在模型的行为上存在一些关键差异,主要涉及到Dropout和BatchNorm等层的行为。
- model.train()
- 当调用model.train()时,模型被设置为训练模式。在训练模式下,模型中的特定层(如Dropout和BatchNorm层)会按照其设计初衷工作:
- Dropout层:在训练模式下,Dropout层会随机将输入的一部分(通常是50%,但可配置)设置为0,这有助于防止模型过拟合。在评估模式(或称为测试模式)下,Dropout层不会执行任何操作,即所有输入都会保留。
- BatchNorm层:在训练模式下,BatchNorm层会根据当前批次的数据来更新其运行中的平均值和方差,这些统计信息随后用于标准化后续批次的数据。这对于稳定训练过程非常重要。
- 当调用model.train()时,模型被设置为训练模式。在训练模式下,模型中的特定层(如Dropout和BatchNorm层)会按照其设计初衷工作:
- model.eval()
- 当调用model.eval()时,模型被设置为评估模式。在评估模式下,模型会关闭那些仅在训练阶段需要的功能,以确保模型在评估或测试数据集上的行为是一致的。
- Dropout层:如前所述,在评估模式下,Dropout层不会对输入进行任何修改,即所有输入都会被保留。
- BatchNorm层:在评估模式下,BatchNorm层会使用训练过程中计算得到的固定平均值和方差来标准化输入数据,而不是基于当前批次的数据动态计算。这有助于模型在未见过的数据上保持稳定的性能。
- 当调用model.eval()时,模型被设置为评估模式。在评估模式下,模型会关闭那些仅在训练阶段需要的功能,以确保模型在评估或测试数据集上的行为是一致的。
- 注意事项
- 在调用model.eval()之前,确保模型已经完成了训练过程,因为评估模式会关闭一些有助于训练的功能。
- 如果你在评估模型后想要继续训练模型,记得调用model.train()来重新启用这些功能。
- 对于某些模型,可能还需要在评估模式下手动设置其他层的特定行为(例如,将某些层的.requires_grad属性设置为False,以阻止计算梯度)。
- 总之,model.train()和model.eval()是PyTorch中非常重要的两个方法,它们允许开发者根据需要切换模型的工作模式,从而确保模型在训练和评估阶段都能表现出最佳性能。
torch.nn.Module.register_buffer
-
在PyTorch中,self.register_buffer是一个用于在模块(如nn.Module的子类)中注册不参与梯度计算的参数(即不需要训练的参数)的方法。这些参数在模型训练过程中保持不变,但可以被模型在前向传播过程中使用。
-
当你需要在模型中存储一些状态信息或配置参数,而这些信息不需要在训练过程中被优化时,self.register_buffer就显得非常有用。例如,你可能需要存储一些固定的嵌入向量、词汇表大小、或者是一些用于控制模型行为的超参数等。
-
使用self.register_buffer注册的参数会自动添加到模型的buffers()迭代器中,并且这些参数会被模型的状态字典(state_dict)保存和加载。但是,与通过self.parameters()返回的模型参数不同,这些buffer不会通过优化器进行更新。
import torch
import torch.nn as nn
class MyModule(nn.Module):
def __init__(self, buffer_size):
super(MyModule, self).__init__()
# 创建一个tensor作为buffer,并注册到模型中
# 注意:这里我们不需要设置requires_grad=True,因为buffer默认不参与梯度计算
self.register_buffer('my_buffer', torch.randn(buffer_size))
def forward(self, x):
# 在前向传播中使用buffer
# 这里只是一个示例,实际使用时你可能需要根据具体需求来操作
return x + self.my_buffer
# 实例化模块
module = MyModule(10)
# 查看模块的buffers
print(list(module.buffers()))
# 尝试修改buffer(这是允许的)
module.my_buffer.fill_(0)
print(module.my_buffer)
# 注意:由于buffer不参与梯度计算,所以你不能直接对它进行梯度下降等操作
Layer们
torch.nn.Linear 线性层/全连接层
y = xw + b # 输入x [num_samples, in_features]
# 训练网络w [in_features, out_features]
# 训练偏置 b[num_samples, out_features]
torch.nn.Conv2d 二维卷积层
- 例子:
torch.nn.ConvTranspose2d 二维转置卷积层
- 插入0,增加特征的维度,经常用于GAN(对抗生成网络)
torch.nn.Dropout
torch.nn.Sequential
- 按照特定的顺序排列一系列的网络层
import torch.nn as nn
model = nn.Sequential(
nn.Linear(in_features=10, out_features=20), #第一个全连接层
nn.ReLU(), #激活函数
nn.Linear(in_features=20, out_features=1) #第二个全连接层,输入特征为20,输出特征为1
)
损失函数
nn.CrossEntropyLoss
- 先softmax算出每一类的概率,再NLLLoss算出损失数
nn.MSELoss
# -*- coding: utf-8 -*-
import torch
import torch.optim as optim
loss_fn = torch.nn.MSELoss(reduce=False, size_average=False)
#loss_fn = torch.nn.MSELoss(reduce=True, size_average=True)
#loss_fn = torch.nn.MSELoss()
input = torch.autograd.Variable(torch.randn(3,4))
target = torch.autograd.Variable(torch.randn(3,4))
loss = loss_fn(input, target)
print(input); print(target); print(loss)
print(input.size(), target.size(), loss.size())
激活函数
nn.ReLU和nn.LeakyReLU
torch.tanh
torch.nn.functional
torch.nn.functional.normalize
-
L-p范数标准化
详情 -
个人理解:
默认情况:
torch.nn.functional.normalize(input, p=2, dim=1, eps=1e-12, out=None)
#input:输入任何形式的torch.tensor
#p :理解了L-p范数的定义,显然这里的p是用来指代L-p范数的。
#eps: 因为要除以L-p范数,为避免分母过于小,因此设定了一个阈值,其默认为1e-12
#dim=0:按每个列规范化,dim=1按每个行规范化
torch.optim 优化算法
torch.optim.SGD
- 梯度下降法
torch.optim.Adam
更新学习率
- 在PyTorch中,当你想要动态地调整优化器(optimizer)的学习率(learning rate, lr)时,通常会遍历优化器的param_groups属性。每个param_group都是一个字典,包含了优化器管理的参数集以及这些参数共享的优化选项,比如学习率、权重衰减等。
# 假设这是某个训练循环中的一部分
for epoch in range(num_epochs):
# ... 其他训练代码 ...
# 根据epoch调整学习率
if epoch % decay_every == 0 and epoch > 0:
lr *= 0.5 # 学习率减半
for param_group in optimizer.param_groups:
param_group["lr"] = lr
# ... 继续训练循环 ...
torch.cuda
torch.cuda.set_device(device) 设置当前CUDA可见的设备
- 在PyTorch中,torch.cuda.set_device(device)函数用于设置当前CUDA可见的设备。这个函数通常用于在多个GPU可用的情况下,明确指定某个GPU用于当前的PyTorch进程。device参数是一个整数,它对应于CUDA设备的ID,其中0通常代表第一个GPU,1代表第二个GPU,依此类推。
- 然而,在你的例子中,args.gpu被用作set_device的参数。这通常意味着args是一个解析了命令行参数(比如通过argparse库)的对象,并且gpu是其中一个参数,用户在启动脚本时通过命令行指定了它。例如,如果用户通过–gpu 0启动脚本,那么args.gpu就会是0,表示将使用第一个GPU。
import argparse
import torch
# 解析命令行参数
parser = argparse.ArgumentParser(description='PyTorch GPU example')
parser.add_argument('--gpu', type=int, default=0, help='GPU ID to use (default: 0)')
args = parser.parse_args()
# 设置CUDA设备
torch.cuda.set_device(args.gpu)
# 现在你可以安全地使用CUDA操作,并且它们会在指定的GPU上执行
device = torch.device(f"cuda:{args.gpu}")
tensor = torch.randn(10, 10).to(device)
print(tensor)
torch.multiprocessing
- 多进程运行worker函数
import torch.multiprocessing as mp
def worker(rank, size):
print(f'Hello from {rank}')
if __name__ == '__main__':
mp.set_start_method('spawn') # 设置启动方法为'spawn'
size = 4
processes = []
for rank in range(size):
p = mp.Process(target=worker, args=(rank, size))
p.start()
processes.append(p)
for p in processes:
p.join()
torchvision
torchvision.utils
torchvision.utils.save_image
torchvision.datasets
torchvision.datasets.数据集
- 下载数据示例:fashion-mnist
import torchvision
from torchvision import transforms
trans = transforms.ToTensor()
mnist_train = torchvision.datasets.FashionMNIST(root="../data",train=True, transform=trans, download=True)
mnist_test = torchvision.datasets.FashionMNIST(root="../data",train=False, transform=trans, download=True)
len(mnist_train), len(mnist_test) #60000, 10000
mnist_train[0][0].shape #[1,28,28]
torchvision.datasets.ImageFolder
+ 返回值是一个列表,image[0]是图像数据(batch,C,H,W),image[1]是标签数据(按图像所在文件夹分配标签)(batch)。
- 结合torch.utils.data.DataLoader使用
import torch
from torchvision import datasets, transforms
# 定义数据转换
transform = transforms.Compose([
transforms.Resize((224, 224)), # 调整图像大小
transforms.ToTensor(), # 将图像转换为Tensor
])
# 加载数据集
dataset = datasets.ImageFolder('path/to/dataset', transform=transform)
# 创建数据加载器
data_loader = torch.utils.data.DataLoader(dataset, batch_size=32, shuffle=True)
torchvision.transforms
torchvision.transforms.Compose()
- 对图片进行操作,可以用transforms.Compose()组合起来
from torchvision import transforms as T
transform = T.Compose([
T.Resize(224), #缩放图片保持长宽比不变,最短边为224像素
T.CenterCrop(224), #从图片中间减出224×224的图片
T.ToTensor(), #将图片(Image)转成Tensor,归一化至[0,1]
T.Normalize(mean=[0.5,0.5,0.5], std=[0.5,0.5,0.5] ) #标准化至[-1,1]
])
from PIL import Image
data = Image.open(img_path)
data = transform(data)
torchvision.transforms.RandomResizedCrop
- 随机裁剪图片到一个固定的面积,会随机改变图像的宽高比