0. 生成train.txt
像一个文件夹代表一类的数据集,我们可以将它的文件名读取出来,按照{ 文件名;类别 }的格式将它们保存到train.txt文件中以供我们使用;同理,对于语义分割或目标检测的数据集,我们按照 { img;label } 的格式保存到train.txt文件中。
文件目录:
这里我们先从ImageNet数据集每个类别中随机抽取30%来作为训练集,剩下的70%以后再用。
to_train_txt.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import os, random
_label = {'n01924916':0, 'n01943899':1, 'n01950731':2, 'n01968897':3, 'n02317335':4, 'n02319095':5, 'n02321529':6}
train_path = './train/'
pathDir = os.listdir(train_path)
f = open('./train_3.txt', 'w')
ff = open('./train_7.txt', 'w')
for ii in pathDir:
if ii != '.DS_Store':
label = str(_label[ii])
rate = 0.3
filename = os.listdir(train_path + ii)
filenumber = len(filename)
picknumber = int(filenumber * rate)
sample = random.sample(filename, picknumber)
for ob in filename:
if ob in sample:
f.write(ii + '/' + ob + ';' + label + '\n')
else:
ff.write(ii + '/' + ob + ';' + label + '\n')
f.close()
ff.close()
部分train_3.txt展示:
...
n02319095/n02319095_350.JPEG;5
n02319095/n02319095_3527.JPEG;5
n02319095/n02319095_10100.JPEG;5
n02319095/n02319095_583.JPEG;5
n02319095/n02319095_1263.JPEG;5
n02319095/n02319095_8412.JPEG;5
n02319095/n02319095_7026.JPEG;5
n02319095/n02319095_2398.JPEG;5
n02319095/n02319095_7030.JPEG;5
n02319095/n02319095_487.JPEG;5
n02319095/n02319095_6733.JPEG;5
n02319095/n02319095_1019.JPEG;5
n02319095/n02319095_8238.JPEG;5
n02319095/n02319095_4077.JPEG;5
n02319095/n02319095_4630.JPEG;5
n02319095/n02319095_444.JPEG;5
n02319095/n02319095_1936.JPEG;5
n02319095/n02319095_2025.JPEG;5
n02319095/n02319095_956.JPEG;5
n02319095/n02319095_3272.JPEG;5
...
random.sample()函数用法
用于随机截取指定长度的列表,不会改变原列表;
1
2
3
4
5
6
7
list = [0,1,2,3,4]
rs = random.sample(list, 2)
print(rs)
print(list)
# [2, 4]
# [0, 1, 2, 3, 4]
PyTorch输入数据Pipline:
- 创建一个
Dataset
对象; - 创建一个
DataLoader
对象; - 循环这个
DataLoader
对象,将img
和label
加载到模型中进行训练;
1. 创建Dataset对象
创建的时候需继承from torch.utils.data.dataset import Dataset
类。
Dataset中主要有3个方法:
- init: 初始化信息,包括训练数据和标签的路径, transform信息等;
- getitem: 在这个方法里根据传入的下标返回label和transform之后的图片tensor;
- len: 返回Dataset的长度;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
from PIL import Image
import torch
import torchvision.transforms as transforms
from torch.utils.data import Dataset
class MyDataset(Dataset): # 创类:MyDataset,继承torch.utils.data.Dataset
def __init__(self, datatxt, transform=None):
super(MyDataset, self).__init__()
fh = open(datatxt, 'r') # 打开txt,读取内容
data = []
for line in fh: # 按行循环txt文本中的内容
words = line.split(';') # 通过指定分隔符对字符串进行切片
data.append((words[0], int(words[1]))) # 把txt里的内容读入data列表保存,words[0]是图片信息,words[1]是label
self.data = data
self.transform = transform
def __getitem__(self, index): # 按照索引读取每个元素的具体内容
fn, label = self.data[index] # fn是图片path
img = Image.open(fn).convert('RGB') # from PIL import Image
if self.transform is not None: # 是否进行transform
img = self.transform(img)
return img, label # return回哪些内容,在训练时循环读取每个batch,就能获得哪些内容
def __len__(self): # 它返回的是数据集的长度,必须有
return len(self.imgs)
'''标准化、图片变换'''
train_transforms = transforms.Compose([
transforms.RandomCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor()
])
train_data = MyDataset(datatxt='train.txt', transform=train_transforms)
train_loader = torch.utils.data.DataLoader(dataset=train_data, batch_size=64, shuffle=True)
""" 训练时:"""
for epoch in range(num_epoches):
for i, (img, label) in enumerate(dataloader):
...
transform各参数的作用:
1. 裁剪——Crop
中心裁剪:transforms.CenterCrop
随机裁剪:transforms.RandomCrop
随机长宽比裁剪:transforms.RandomResizedCrop
上下左右中心裁剪:transforms.FiveCrop
上下左右中心裁剪后翻转,transforms.TenCrop
2. 翻转和旋转——Flip and Rotation
依概率p水平翻转:transforms.RandomHorizontalFlip(p=0.5)
依概率p垂直翻转:transforms.RandomVerticalFlip(p=0.5)
随机旋转:transforms.RandomRotation
3. 图像变换
resize:transforms.Resize
标准化:transforms.Normalize
转为tensor,并归一化至[0-1]:transforms.ToTensor
填充:transforms.Pad
修改亮度、对比度和饱和度:transforms.ColorJitter
转灰度图:transforms.Grayscale
线性变换:transforms.LinearTransformation()
仿射变换:transforms.RandomAffine
依概率p转为灰度图:transforms.RandomGrayscale
将数据转换为PILImage:transforms.ToPILImage
transforms.Lambda:Apply a user-defined lambda as a transform.
4. 对transforms操作,使数据增强更灵活
transforms.RandomChoice(transforms), 从给定的一系列transforms中选一个进行操作
transforms.RandomApply(transforms, p=0.5),给一个transform加上概率,依概率进行操作
transforms.RandomOrder,将transforms中的操作随机打乱
2. 创建DataLoader对象
DataLoader:
将自定义的Dataset根据batch size大小、是否shuffle等封装成一个Batch Size大小的Tensor,用于后面的训练。
1
2
3
from torch.utils.data import Dataset, DataLoader
train_loader = DataLoader(dataset=train_data, batch_size=64, shuffle=True)
3. 循环DataLoader进行训练
1
2
3
for epoch in range(num_epoches):
for i, (img, label) in enumerate(dataloader):
...
** 注意
损失函数torch.nn.CrossEntropyLoss()
中已经包含了Softmax
函数,所以我们的神经网络直接线性输出即可。
1
2
3
4
5
net = nn.Sequential(
nn.Linear(8,50),
nn.ReLU(),
nn.Linear(50,4)
)
4. transforms.Normalize 计算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import numpy as np
import cv2
import os
import torch
import torchvision
means = [0, 0, 0]
stdevs = [0, 0, 0]
imgs_path = '/content/drive/MyDrive/search/mmdetection/data/imagenet-underwater/train/' # 图片路径
_txt_path = '/content/drive/MyDrive/search/mmdetection/data/imagenet-underwater/train_3.txt' # train.txt
imgs_path_list = []
_txt = open(_txt_path, 'r')
for line in _txt:
name, _ = line.split(';')
print(name)
imgs_path_list.append(imgs_path + name)
num_imgs = 0
for data in imgs_path_list:
num_imgs += 1
img = cv2.imread(data)
img = img.astype(np.float32) / 255.
for i in range(3):
means[i] += img[:, :, i].mean()
stdevs[i] += img[:, :, i].std()
means.reverse()
stdevs.reverse()
means = np.asarray(means) / num_imgs
stdevs = np.asarray(stdevs) / num_imgs
print("normMean = {}".format(means))
print("normStd = {}".format(stdevs))
print('transforms.Normalize(normMean = {}, normStd = {})'.format(means, stdevs))