Pytorch实践——先胡乱开始一个项目
1、Data文件夹用于保存数据; 2、Checkpoint文件夹用于保存训练过程中的权重文件;
3、Log文件夹用于存放训练日志,如Loss和准确率变化曲线等等; 4、utils.py用于定义一些自定义的子函数,具体内容因项目而异;
5、config.py用于一些参数设置,如数据读入路径、学习率大小、迭代步长等等。
6、network(.py)用于定义网络结构。之所以加括号是因为network有时候是一个文件夹(或者说包),因为在网络结构比较复杂的时候,我们采用多个py文件(或者叫模块)对网络进行嵌套定义会更加方便。下图是一个Net类的基本结构。
除了自己搭网络,也可以使用torchvision.models中已有的网络模型,或者在这些已有模型的基础上进行适当修改:
torchvision由以下四个部分组成:
torchvision.datasets : Data loaders for popular vision datasets
torchvision.models : Definitions for popular model architectures, such as AlexNet, VGG, and ResNet and pre-trained models.
torchvision.transforms : Common image transformations such as random
crop, rotations etc.
torchvision.utils : Useful stuff such as saving tensor (3 x H x W) as image to disk, given a mini-batch creating a grid of images, etc.
对搭建网络进行测试:在网络搭建完成后,可以简单地测试一下自己搭的网络有没有问题。我一般会采用如下代码来测试。如果能跑通且输出与预期一致,则至少中间层的输入输出的格式是对上的。
7、dataloader.py用于定义数据加载与预处理相关的变量。其中Dataset子类定义了数据集读取的方式;Dataloader则是将实例化后的Dataset子类包装成一个可迭代的对象,用大白话说就是将单个单个的样本打包成一个个的batch,在后续的训练环节通过迭代,源源不断地输入网络进行训练。而transforms用于定义数据增强的方式。
pytorch数据预处理三剑客之——Dataset,DataLoader,Transform:https://blog.csdn.net/qq_27825451/article/details/96130126
数据加载环节是一个很重要的环节。因此在完成数据加载后,可以对dataloader中的数据进行可视化,检查数据是否被正确地加载。
8、train.py
(1)核心代码:
9、tensorboard:训练过程可视化工具
模型调优
以UNet为例
Jack Cui:pytorch+unet
pytorch中文手册
1、加载自己的数据集
(1)图片的预处理:
Pytorch:transforms二十二种数据预处理方法及自定义transforms方法【附代码】
pytorch数据预处理三剑客之——Dataset,DataLoader,Transform
通过编写dataloader.py 函数对 Dataset 的三个函数进行重载最终把图像转换成 tensor 类型,通过 dataset参数被
DataLoader 封装成一个一个batch传到网络。
dataloader.py
重载Dataset的三个函数:
def __init__(self,):
def __getitem__(self, index):
def __len__(self):
一般的图像预处理都可以通过torchvision的transforms解决:
from PIL import Image
from torchvision import transforms
import torch
image = Image.open('/home/xie/图片/lena.jpg')
transfer = transforms.Compose([transforms.Resize(224),
transforms.CenterCrop(224),
transforms.ToTensor()]) # Resize:缩放
image = transfer(image) #传入transforms中的数据是PIL数据
#image现在为tensor
格式:这部分搞清楚每一步输入图片的格式是ndarray, PIL还是tensor,通道数是多少(RGB(jpg),RGBA(.png),Grey),是 HWC 还是 CHW
1、image = cv2.imread('路径‘) #返回图片是uint8格式的数组ndarray,并且默认是H,W,C 的BGR颜色空间图片,也就是三通道图。.png图片只能采用cv2读取。
> `#1.Image对象->cv2(np.adarray)
img = Image.open(path)
img_array = np.array(img)
#2.cv2(np.adarray)->Image对象
img = cv2.imread(path)
img_Image = Image.fromarray(np.uint8(img))
原文链接:https://blog.csdn.net/weixin_42213622/article/details/109110140`
!!!这里注意cv2读取图片路径不能包含中文!!!否则会返回None!!!
https://blog.csdn.net/scut_salmon/article/details/78878533
2、image = Image.open('路径‘) #返回的是PIL格式的图片,默认按照灰度图读取,返回PIL格式的图像,此时注意图像可能是只有H,W两个通道的,无法读取三通道的 .png 图片。
最好不要采用plt 中的方法进行读取。
注1:>输入到 torchvision 的
transfroms
中的图像格式是 PIL
注2:>输入到torch.utils.data.DataLoader
中的图是转化为 tensor 之后的
注3:>tensor图像通道必须是CHW顺序
Pytorch中的Tensor常用的类型转换函数(inplace操作):
数据类型转换:
在Tensor后加 .long(), .int(), .float(),
.double()等即可,也可以用.to()函数进行转换,所有的Tensor类型可参考https://pytorch.org/docs/stable/tensors.html
数据存储位置转换
CPU张量 ----> GPU张量,使用data.cuda()
GPU张量 ----> CPU张量,使用data.cpu()
与numpy数据类型转换
Tensor---->Numpy 使用 data.numpy(),data为Tensor变量
Numpy ----> Tensor 使用 torch.from_numpy(data),data为numpy变量
与Python数据类型转换
Tensor ----> 单个Python数据,使用data.item(),data为Tensor变量且只能为包含单个数据
Tensor ----> Python list,使用data.tolist(),data为Tensor变量,返回shape相同的可嵌套的list
剥离出一个tensor参与计算,但不参与求导
Tensor后加 .detach()
(2)验证自己的数据加载的是否正确:
1.tensor 转 ndarray, plt.imshow() 显示图片,只可以显示HWC(且C=1)或者HW通道的图
2.tensor 转 PIL,‘图像名’.show() 显示图片,只可以显示三通道图HWC(C=3)
3.保存结果图为 .png格式的时候必须是8位【0,255】(24位用不到),通道数可以只有一个通道,不用RGB三通道也可以显示正常结果,但是【0,1】肯定保存不成 .png
编写测试代码:
data = load_data(data_path,scalesize,cropsize)
train_loader = torch.utils.data.DataLoader(dataset=data,
batch_size=2,
shuffle=False
)
for image, label in train_loader:
image = torchvision.utils.make_grid(image,nrow=2)
image = np.transpose(image.numpy(),(1,2,0))
plt.figure()
plt.axis('off')
plt.imshow(image)
# 注意图像位深为 uint8 位还是归一化为【0,1】后的
label = torchvision.utils.make_grid(label[0],nrow=2)
label = np.transpose(label.numpy(),(1,2,0))
plt.figure()
plt.axis('off')
plt.imshow(label[:,:,0])
plt.show()
batch_size=2, nrow = 2显示
2、加载网络
比如原网络是针对二维数据的,和三维数据有很大不同。用
torch.nn.BatchNorm2d
和torch.nn.BatchNorm1d
举个例子,前者是对3维数据组成的NCHW进行操作,后者是对
2 维(NHW)或者 1 维(NL)数据进行操作。
一般情况下网络的代码都适合自己的需求,只在自己搭网络的时候需要注意对什么维度的数据进行操作应该使用torch.nn
中的什么函数。
(1)对网络参数进行初始化两种方法:
不做任何操作的网络初始化是随机的初始化。
法1:用预训练网络批量初始化很多层的权重:
注1:用pytorch预训练模型来测试,不带BN的模型预测出来的结果是可以的,但是一旦用带有BN的模型预测的结果是乱的
注2:这里面权重的维度和输入图片的通道数一定要匹配好,比如用预训练的模型的权重初始化的结果第一层 conv2 是一个 64×3×3×3 的,那么输入的图片一定是三通道的才可以。
1.pretrained=False 是用原网络的初始化参数进行自己网络的初始化
1.pretrained=True 是用原网络训练后的参数初始化自己的网络参数
model = torchvision.models.vgg19(pretrained=False, progress=True)
new_state_dict = model.state_dict()
# 查看网络层和参数
weights_keys = model.state_dict().keys()
for key in weights_keys:
weight_value = model.state_dict()[key].numpy()
2.自定义的一个网络,假设叫selfmodel:
self_dict = selfmodel.state_dict()
3.用预训练的权重对自己的网络进行初始化(前提网络结构一致)
pre_parameter = list(new_state_dict.values())
self_dict_key = list(self_dict.keys())
for l in len(pre_parameter):
if l < 32 # 这里代表16层卷积层(每层卷积包含weight和bias)
self_dict[self_dict_key[l]] = pre_parameter[l]
selfmodel.load_state_dict(self_dict) # 将模型参数加载进入自定义模型中
torch.save(selfmodel.state_dict())
torch.save(selfmodel,'/{}.pth/').format('best')
法2:在网络定义的时候自己编写初始化特定层的参数
法一:
class selfmodel(nn.Module):
def __init__(self, features, num_classes=1000, init_weights=True):
super(selfmodel, self).__init__()
if init_weights:
self._initialize_weights()
def _initialize_weights(self):
for m in self.modules(): # modules()是nn.Moudle中的函数
# 对自己模型中的所有层类型进行遍历
if isinstance(m, nn.Conv2d):
# m具有nn.Conv2d的类型,对m的权重和偏差进行初始化
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
# kaiming权重初始化方法保留了反向传递中权重方差的大小
if m.bias is not None:
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.BatchNorm2d):
#对每种层初始化不一样的权重,BN层初始化平均值为零,方差为1
nn.init.constant_(m.weight, 1)
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.Linear):
nn.init.normal_(m.weight, 0, 0.01)
nn.init.constant_(m.bias, 0)
法二:
class selfmodel(nn.Module):
def __init__(self, block, layers, num_classes=1000):
super(selfmodel, self).__init__()
for m in self.modules():
if isinstance(m, nn.Conv2d):
n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
m.weight.data.normal_(0, math.sqrt(2. / n))
elif isinstance(m, nn.BatchNorm2d):
m.weight.data.fill_(1)
m.bias.data.zero_()
#.data含义:转化为tensor
法三:self.children():
class RPNHead(nn.Module):
def __init__(self, in_channels, num_anchors): # here just use the num_anchors from rpn_anchor_generator
super(RPNHead, self).__init__()
# 3x3 滑动窗口
self.conv = nn.Conv2d(in_channels, in_channels, kernel_size=3, stride=1, padding=1)
# 计算预测的目标分数(这里的目标只是指前景或者背景)
self.cls_logits = nn.Conv2d(in_channels, num_anchors, kernel_size=1, stride=1)
# 计算预测的目标bbox regression参数
self.bbox_pred = nn.Conv2d(in_channels, num_anchors * 4, kernel_size=1, stride=1)
for layer in self.children(): # here children() is instead of self.conv()\self.cls_logits\self.bbox_pred
if isinstance(layer, nn.Conv2d):
torch.nn.init.normal_(layer.weight, std=0.01)
torch.nn.init.constant_(layer.bias, 0)
(2)基于迁移学习的网络加载方法
net = resnet34()
in_channel = net.fc.in_features # 取最后的FC层的输出通道数,赋值给添加的fc层的输入通道数
net.fc = nn.Linear(in_channel, 5) #在主程序train中添加一层全连接层将最后的num_classes改成5
net.to(device)
冻结一部分参数的方法:
冻结前置特征提取网络权重(backbone):
for param in model.backbone.parameters():
param.requires_grad = False
# define optimizer
params = [p for p in model.parameters() if p.requires_grad]
optimizer = torch.optim.SGD(params, lr=0.005,
momentum=0.9, weight_decay=0.0005)