基于paddle的铁轨缺陷检测-铁路智能感知技术实验二

image.png

铁路智能感知技术实验报告

[TOC]

一、整体思路

  1. 分析数据集,包括格式、样本分布

  2. Pytorch框架下的torchvision.datasets.ImageFolder源代码基础上进行修改,编写自己的DataLoader,加载图片与标签。

  3. 图像增强,包括随即裁剪、旋转、翻转及归一化。值得一提的是,由于paddle提供的预训练模型是基于ImageNet训练得到的,这里的归一化应当也取ImageNet数据集的均值和方差。

  4. 利用paddle.vision.models提供的三个模型:mobilenet_v2resnet18resnet34,并修改模型最后一层全连接的unit数量,以化为己用。

  5. 训练、验证。在此过程中,对三个模型分别绘制其训练与验证期间的损失函数变化图像与精度变化图像,以判断模型学习效果并调整超参数

  6. 在测试环节,利用上述三个模型的结果进行投票,取多数结果为预测结果。

二、具体流程

2.1 参数设置

2.1.1 常规参数

参数 取值
batch_size 64
epochs of mobilenetv2 5
epochs of resnet18 10
epochs of resnet34 10
图片resize大小 512*512
图片裁剪大小 448*448

2.1.2 优化器

选用Adam(Adaptive Moment Estimation)。与SGD随机梯度下降优化器相比,Adam加入了对学习率的自适应调整。Adam不仅存储了过去梯度的平方的指数衰减平均值,还向Monentum一样保持了过去提取的指数衰减平均值。

SGD的一阶动量:
$$
m_t = \beta_1 \cdot m_{t-1} + (1-\beta_1)\cdot g_t
$$
加上AdaDelta的二阶动量:
$$
V_t = \beta_2 * V_{t-1} + (1-\beta_2) g_t^2
$$

2.1.3 损失函数

选用交叉熵损失函数。这也是分类任务中常用的损失函数。由于本次实验数据集中,正负样本分布较为均衡,故没有对损失函数进行加权。

交叉熵损失函数的二分类形式为:
$$
L = \frac{1}{N}\sum_{i} L_i = \frac{1}{N}\sum_{i}-[y_i\cdot log(p_i) + (1-y_i)\cdot log(1-p_i)]
$$

2.2 关键代码说明

2.2.1 分析数据集

1655719618(1).png

数据集特点:

  1. 正负样本接近1:1,比较平衡。不需要使用过采样、欠采样等手段,平衡样本数量。
  2. 训练样本共480个,非常少。这表明我们需要采取多种数据增强手段,并应当采用在ImageNet等大型数据集预训练得到的模型参数,以一定程度上避免出现过拟合

2.2.2 针对按类别存放样本方式存储数据集的DataLoader

Pytorch框架下的ImageFolder假设所有的文件按文件夹保存,每个文件夹下存储同一个类别的图片。本次实验的数据集,恰好是按照这种方式存储的。

通过查阅paddle官方文档,我发现paddle框架提供了同名API:paddle.vision.datasets.ImageFolder,并欣然尝试用其读取数据集。然而,在后续训练过程中,却提示我没有读取到标签。

paddle官方文档中,我们可以看到:对于ImageFolder的说明似乎并不完整:

paddle_imagefolder

于是我上GitHub查看paddle.vision.datasets.ImageFolder的源代码。关键部分如下:

1
2
3
4
5
for root, _, fnames in sorted(os.walk(path, followlinks=True)):
for fname in sorted(fnames):
f = os.path.join(root, fname)
if is_valid_file(f):
samples.append(f)

paddle.vision.datasets.ImageFolder只是读取了指定路径下的图片,而没有理会样本按类别存放在不同文件夹下的存储方式。

至此,我决定使用torchvision.datasets.ImageFolder的源代码进行数据加载。

训练集、验证集都是按照样本类别存放图片,而测试集将所有样本存放在同一文件夹下。所以我对torchvision.datasets.ImageFolder的源代码略作修改,增加了mode参数:

  • mode=='train'时,按照ImageFolder方式,读取各个子文件夹下的图片,并根据所属文件夹名称给样本赋予标签
  • mode=='test'时,只读取文件夹下的所有图片

修改后的代码如下:

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
class ImageFolder(Dataset):
"""A generic data loader where the images are arranged in this way: ::
root/dog/xxx.png
root/dog/xxy.png
root/dog/xxz.png
root/cat/123.png
root/cat/nsdf3.png
root/cat/asd932_.png
Args:
root (string): Root directory path.
transform (callable, optional): A function/transform that takes in an PIL image
and returns a transformed version. E.g, ``transforms.RandomCrop``
target_transform (callable, optional): A function/transform that takes in the
target and transforms it.
loader (callable, optional): A function to load an image given its path.
Attributes:
classes (list): List of the class names.
class_to_idx (dict): Dict with items (class_name, class_index).
imgs (list): List of (image path, class_index) tuples
"""
"""
Speciality:
when self.mode=='test', __getitem__ return img & img_path
"""

# 初始化,继承参数
def __init__(self, root, transform=None, target_transform=None,
loader=default_loader, mode=None):
# TODO
# 1. Initialize file path or list of file names.
# 找到root的文件和索引
classes, class_to_idx = find_classes(root)
# 保存路径下图片文件路径和索引至imgs
imgs = make_dataset(root, class_to_idx)
if len(imgs) == 0:
raise(RuntimeError("Found 0 images in subfolders of: " + root + "\n"
"Supported image extensions are: " + ",".join(IMG_EXTENSIONS)))
if mode not in ['train', 'test']:
raise(RuntimeError('Mode should be train or test'))

self.root = root
self.imgs = imgs
self.classes = classes
self.class_to_idx = class_to_idx
self.transform = transform
self.target_transform = target_transform
self.loader = loader
self.mode = mode

def __getitem__(self, index):
"""
Args:
index (int): Index
Returns:
tuple: (image, target) where target is class_index of the target class.
"""
# TODO
# 1. Read one data from file (e.g. using numpy.fromfile, PIL.Image.open).
# 2. Preprocess the data (e.g. torchvision.Transform).
# 3. Return a data pair (e.g. image and label).
#这里需要注意的是,第一步:read one data,是一个data

path, target = self.imgs[index]
# 这里返回的是图片路径,而需要的是图片格式
img = self.loader(path) # 将图片路径加载成所需图片格式
if self.transform is not None:
img = self.transform(img)

if self.mode == 'train':
if self.target_transform is not None:
target = self.target_transform(target)
return img, target
elif self.mode == 'test':
return img, path.split('/')[-1]


def __len__(self):
# return the total size of your dataset.
return len(self.imgs)

2.2.3 数据增强

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
train_transform = transforms.Compose([
transforms.Resize(size=(img_size,img_size)),
transforms.RandomCrop(size=(crop_size,crop_size)), # 通过随机裁剪进行数据增强
transforms.RandomRotation(10),
transforms.RandomHorizontalFlip(), # 通过随机水平翻转进行数据增强
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225],data_format='CHW')
])

test_transform = transforms.Compose([
transforms.Resize(size=(crop_size,crop_size)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225],data_format='CHW')
])


train_dataset = ImageFolder("/home/aistudio/work/train/",transform = train_transform,mode='train')
valid_dataset = ImageFolder("/home/aistudio/work/valid/",transform = test_transform,mode='train')
test_dataset = ImageFolder("/home/aistudio/work/test_folder/",transform = test_transform,mode='test')

# 用 DataLoader 实现数据加载
train_loader = paddle.io.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
valid_loader = paddle.io.DataLoader(valid_dataset, batch_size=batch_size, shuffle=True)
test_loader = paddle.io.DataLoader(test_dataset, batch_size=batch_size, shuffle=False)

由于训练样本非常少,模型极易过拟合。为此,我加入多种数据增强手段:

  • resize后随机裁剪
  • 随机旋转
  • 随机水平翻转
  • 归一化
    • 上面代码中的mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]ImageNet数据集在三通道的均值和方差。

2.2.4 训练

不使用高阶API,而是手写训练过程。

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
def train(model, optim, epoch, train_loss, train_acc):
pbar = tqdm(train_loader)

for batch_id, data in enumerate(pbar):

x_data = data[0] # 训练数据
y_data = data[1] # 训练数据标签
predicts = model(x_data) # 预测结果

y_data = paddle.unsqueeze(y_data, axis=1)
predicts = paddle.unsqueeze(predicts, axis=1)

# 计算损失 等价于 prepare 中loss的设置
loss = loss_fn(predicts, y_data)
train_loss.append(loss)

# 计算准确率 等价于 prepare 中metrics的设置
acc = paddle.metric.accuracy(predicts, y_data)
train_acc.append(acc)

# 下面的反向传播、打印训练信息、更新参数、梯度清零都被封装到 Model.fit() 中
# 反向传播
loss.backward()

pbar.set_description(f"Training | Epoch: {epoch} | Loss: {loss.item():.4f} | ACC: {acc.item():.4f}")

# 更新参数
optim.step()
# 梯度清零
optim.clear_grad()

好处是可以把每个batch的损失函数与精度记录下来,所绘制的图像更能反映训练情况。

且通过pbar打印进度条,比较美观:

1655721228(1).png

2.2.5 验证

同上,手写验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def velidate(model, epoch, val_loss, val_acc):
val_pbar = tqdm(train_loader)
# 不加这行会爆内存
with paddle.no_grad():
for batch_id, data in enumerate(val_pbar):
x_data = data[0] # 训练数据
y_data = data[1] # 训练数据标签
predicts = model(x_data) # 预测结果

y_data = paddle.unsqueeze(y_data, axis=1)
predicts = paddle.unsqueeze(predicts, axis=1)

# 计算损失 等价于 prepare 中loss的设置
loss = loss_fn(predicts, y_data)
val_loss.append(loss)

# 计算准确率 等价于 prepare 中metrics的设置
acc = paddle.metric.accuracy(predicts, y_data)
val_acc.append(acc)
val_pbar.set_description(f"Validate | Epoch: {epoch} | Loss: {loss.item():.4f} | ACC: {acc.item():.4f}")

【一个小细节】:

一开始的时候,我总是可以训练,但会在验证时提示cuda out of memory。即使我使用内存32G的GPU也还是爆内存。

查阅资料发现,这是我在验证时没有设置with torch.no_grad导致的。被with torch.no_grad()包住的代码,不用跟踪反向梯度计算,大大节省了内存。

2.2.6 测试

同上,手写测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def test():
global results
test_pbar = tqdm(test_loader)
for batch_id, data in enumerate(test_pbar):
x_data = data[0] # 训练数据
y_data = data[1] # 文件名
# 预测结果
pred0 = np.argmax(model0(x_data), axis=1)
pred1 = np.argmax(model1(x_data), axis=1)
pred2 = np.argmax(model2(x_data), axis=1)
# vote
results += [[y_data[i], (pred0[i]+pred1[i]+pred2[i] > 2)] for i in range(len(pred0))]


with open('result.csv', 'w') as f:
for x in results:
f.write(x[0]+','+str(1-x[1])+'\n')

对三个模型的结果进行投票。这里巧妙地使用(pred0[i]+pred1[i]+pred2[i] > 2)这样简洁的代码,实现了vote。

2.3 性能提升技巧

  1. 分别训练三个模型,并进行投票,所得到的预测结果更为可靠。
  2. 使用多种图像增强方法,一定程度上避免过拟合。
  3. 设置较小的epochs轮数,一定程度上避免过拟合。
  4. 选用Adam优化器,引入动量防止陷入局部最优或鞍点的同时,自动调节学习率。
  5. 多次尝试。

三、结果分析

3.1 训练损失与验证损失

3.1.1 mobilenet_v2

output_19_1.png

3.1.2 resnet18

output_20_1.png

3.1.3 resnet34

output_22_1.png

3.1.4 分析

上三图中,loss在一开始就比较小,是由于我使用了预训练模型。

除去最初的上升,训练与验证的loss基本上都是在震荡中逐步下降。这表明超参数、优化器等选择比较合适,模型在训练过程中学习到有利于这个任务的参数。

3.2 训练精度与验证精度

3.2.1 mobilenet_v2

output_19_2.png

3.2.2 resnet18

output_20_2.png

3.2.3 resnet34

output_22_2.png

3.2.4 分析

上三图中,accuracy在震荡中从0.5逐步接近1。

除去最初的上升,训练与验证的accuracy基本上都是在震荡中逐步下降。这表明超参数、优化器等选择比较合适,模型在训练过程中学习到有利于这个任务的参数。

3.3 得分及排名

得分97.7755,排名第三(截至2022.6.20)。

image.png

四、实验体会和建议

4.1 实验体会

本次实验,老师与助教的讲解与所提供的资料,帮助我快速上手国产深度学习框架paddlepaddle

在最后一堂课的分享交流环节,各位同学无私的分享,让我受到了很多启发:如warm-up学习率、momentum优化器等。两位“励志”同学的故事,也令我深受感动,啼笑皆非。

4.2 建议

pros:

  1. 采取在飞桨实验平台开放比赛、公平竞争的方式,十分新颖,能让同学们实时看到自己的测试得分与排名。建议保持。
  2. 与百度的合作,让同学们可以免费使用V100等先进GPU,不用再为cuda out of memory而发愁。

cons:

  1. 建议在实验发布时告知评分机制。
  2. 飞桨实验平台不兼容tensorflowpytorch等深度学习主流框架,不过这也可以理解。

五、参考资料

  1. https://zhuanlan.zhihu.com/p/32230623
  2. https://www.paddlepaddle.org.cn/
  3. https://github.com/PaddlePaddle/PaddleClas/blob/release/2.3/docs/zh_CN/algorithm_introduction/ImageNet_models.md
  4. https://zhuanlan.zhihu.com/p/35709485
  5. https://github.com/pytorch/pytorch
  6. https://blog.csdn.net/xiaoxifei/article/details/84377204

六、完整代码

1
2
# 先解压数据
!unzip -o -q -d /home/aistudio/work/ /home/aistudio/data/data140198/data.zip
1
2
!mkdir /home/aistudio/work/test_folder/
!mv /home/aistudio/work/test/ /home/aistudio/work/test_folder/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import numpy as np
import paddle
# python第三方图像处理库
from PIL import Image
# python的绘图库 pyplot:matplotlib的绘图框架
import matplotlib.pyplot as plt
%matplotlib inline
# 处理文件和目录
import os
from paddle.io import random_split
from paddle.io import Dataset, DataLoader
from paddle.vision.transforms import Compose, Normalize
from paddle.vision import transforms
import paddle
import paddle.nn as nn
import paddle.nn.functional as F
from paddle.metric import Accuracy
import random
import tqdm
from tqdm import tqdm, trange
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/matplotlib/__init__.py:107: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
  from collections import MutableMapping
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/matplotlib/rcsetup.py:20: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
  from collections import Iterable, Mapping
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/matplotlib/colors.py:53: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
  from collections import Sized

DataLoader

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# 图片扩展(图片格式)
IMG_EXTENSIONS = [
'.jpg', '.JPG', '.jpeg', '.JPEG',
'.png', '.PNG', '.ppm', '.PPM', '.bmp', '.BMP', '.webp'
]

# 判断是不是图片文件
def is_image_file(filename):
# 只要文件以IMG_EXTENSIONS结尾,就是图片
# 注意any的使用
return any(filename.endswith(extension) for extension in IMG_EXTENSIONS)


# 打开路径下的图片,并转化为RGB模式
def pil_loader(path):
# open path as file to avoid ResourceWarning (https://github.com/python-pillow/Pillow/issues/835)
# with as : 安全方面,可替换:try,finally
# 'r':以读方式打开文件,可读取文件信息
# 'b':以二进制模式打开文件,而不是文本
with open(path, 'rb') as f:
with Image.open(f) as img:
# convert:,用于图像不同模式图像之间的转换,这里转换为‘RGB’
return img.convert('RGB')

def default_loader(path):
return pil_loader(path)

def find_classes(dir):
'''
返回dir下的类别名,classes:所有的类别,class_to_idx:将文件中str的类别名转化为int类别
classes为目录下所有文件夹名字的集合
'''
# os.listdir:以列表的形式显示当前目录下的所有文件名和目录名,但不会区分文件和目录。
# os.path.isdir:判定对象是否是目录,是则返回True,否则返回False
# os.path.join:连接目录和文件名

classes = [d for d in os.listdir(dir) if os.path.isdir(os.path.join(dir, d))]
# sort:排序
classes.sort()
# 将文件名中得到的类别转化为数字class_to_idx['3'] = 3
class_to_idx = {classes[i]: i for i in range(len(classes))}
return classes, class_to_idx
# class_to_idx :{'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}

# 如果文件是图片文件,则保留它的路径,和索引至images(path,class_to_idx)
def make_dataset(dir, class_to_idx):
# 返回(图片的路径,图片的类别)
# 打开文件夹,一个个索引
images = []
# os.path.expanduser(path):把path中包含的"~"和"~user"转换成用户目录
dir = os.path.expanduser(dir)
for target in sorted(os.listdir(dir)):
d = os.path.join(dir, target)
if not os.path.isdir(d):
continue

# os.walk:遍历目录下所有内容,产生三元组
# (dirpath, dirnames, filenames)【文件夹路径, 文件夹名字, 文件名】
for root, _, fnames in sorted(os.walk(d)):
for fname in sorted(fnames):
if is_image_file(fname):
path = os.path.join(root, fname) # 图片的路径
item = (path, class_to_idx[target]) # (图片的路径,图片类别)
images.append(item)
random.shuffle(images)
return images


class ImageFolder(Dataset):
"""A generic data loader where the images are arranged in this way: ::
root/dog/xxx.png
root/dog/xxy.png
root/dog/xxz.png
root/cat/123.png
root/cat/nsdf3.png
root/cat/asd932_.png
Args:
root (string): Root directory path.
transform (callable, optional): A function/transform that takes in an PIL image
and returns a transformed version. E.g, ``transforms.RandomCrop``
target_transform (callable, optional): A function/transform that takes in the
target and transforms it.
loader (callable, optional): A function to load an image given its path.
Attributes:
classes (list): List of the class names.
class_to_idx (dict): Dict with items (class_name, class_index).
imgs (list): List of (image path, class_index) tuples
"""
"""
Speciality:
when self.mode=='test', __getitem__ return img & img_path
"""

# 初始化,继承参数
def __init__(self, root, transform=None, target_transform=None,
loader=default_loader, mode=None):
# TODO
# 1. Initialize file path or list of file names.
# 找到root的文件和索引
classes, class_to_idx = find_classes(root)
# 保存路径下图片文件路径和索引至imgs
imgs = make_dataset(root, class_to_idx)
if len(imgs) == 0:
raise(RuntimeError("Found 0 images in subfolders of: " + root + "\n"
"Supported image extensions are: " + ",".join(IMG_EXTENSIONS)))
if mode not in ['train', 'test']:
raise(RuntimeError('Mode should be train or test'))

self.root = root
self.imgs = imgs
self.classes = classes
self.class_to_idx = class_to_idx
self.transform = transform
self.target_transform = target_transform
self.loader = loader
self.mode = mode

def __getitem__(self, index):
"""
Args:
index (int): Index
Returns:
tuple: (image, target) where target is class_index of the target class.
"""
# TODO
# 1. Read one data from file (e.g. using numpy.fromfile, PIL.Image.open).
# 2. Preprocess the data (e.g. torchvision.Transform).
# 3. Return a data pair (e.g. image and label).
#这里需要注意的是,第一步:read one data,是一个data

path, target = self.imgs[index]
# 这里返回的是图片路径,而需要的是图片格式
img = self.loader(path) # 将图片路径加载成所需图片格式
if self.transform is not None:
img = self.transform(img)

if self.mode == 'train':
if self.target_transform is not None:
target = self.target_transform(target)
return img, target
# modified
elif self.mode == 'test':
return img, path.split('/')[-1]



def __len__(self):
# return the total size of your dataset.
return len(self.imgs)

Model

1
2
3
4
5
6
7
8
9
10
11
12
13
# import paddle
# from paddle.vision.models import resnet34

# # build model
# model = resnet34(pretrained=True)

# # build model and load imagenet pretrained weight
# # model = resnet34(pretrained=True)

# x = paddle.rand([1, 3, 224, 224])
# out = model(x)

# print(out.shape)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from paddle.vision.models import mobilenet_v2, resnet18, resnet34

def build_model(mode = 0, num_classes = 2):
if mode==0:
model = mobilenet_v2(pretrained=True)
model.classifier = nn.Sequential(
nn.Dropout(p=0.2),
nn.Linear(in_features=1280, out_features=num_classes)
)
elif mode==1:
model = resnet18(pretrained=True)
model.fc = nn.Linear(in_features=512, out_features=num_classes)
elif mode==2:
model = resnet34(pretrained=True)
model.fc = nn.Linear(in_features=512, out_features=num_classes)
else:
raise(RuntimeError('Mode should be in [0,1,2].'))

return model
1
2
3
4
test = [[ 3.29275370e-01, -1.15783066e-01],
[ 7.25766659e-01, 2.16264486e-01],
[ 1.21175408e-01, 3.12835097e-01]]
np.argmax(test, axis=1)
array([0, 0, 1])
1
2
3
4
model0 = build_model(mode=0)
x = paddle.rand([1, 3, 224, 224])
out = model0(x)
print(out.shape)
W0620 17:28:38.303269 11498 device_context.cc:447] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 11.2, Runtime API Version: 10.1
W0620 17:28:38.312353 11498 device_context.cc:465] device: 0, cuDNN Version: 7.6.


[1, 2]


/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/nn/layer/norm.py:653: UserWarning: When training, we now always track global mean and variance.
  "When training, we now always track global mean and variance.")
1
2
3
4
model1 = build_model(mode=1)
x = paddle.rand([1, 3, 224, 224])
out = model1(x)
print(out.shape)
[1, 2]
1
2
3
4
5
model2 = build_model(mode=2)
x = paddle.rand([1, 3, 224, 224])
out = model2(x)
print(out.shape)
# paddle.summary(model2, input=x)
[1, 2]

超参数

1
2
3
4
5
6
7
8
9
10
11
12
# 参数设置
batch_size = 64 # 设置每个batch的样本数
epochs = 5 # 设置迭代次数
img_size, crop_size = 512, 448
# img_size, crop_size = 256, 224

# 设置优化器
optim0 = paddle.optimizer.Adam(parameters=model0.parameters())
optim1 = paddle.optimizer.Adam(parameters=model1.parameters())
optim2 = paddle.optimizer.Adam(parameters=model2.parameters())
# 设置损失函数
loss_fn = paddle.nn.CrossEntropyLoss()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
train_transform = transforms.Compose([
transforms.Resize(size=(img_size,img_size)),
transforms.RandomCrop(size=(crop_size,crop_size)), # 通过随机裁剪进行数据增强
transforms.RandomRotation(10),
transforms.RandomHorizontalFlip(), # 通过随机水平翻转进行数据增强
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225],data_format='CHW')
])

test_transform = transforms.Compose([
transforms.Resize(size=(crop_size,crop_size)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225],data_format='CHW')
])


train_dataset = ImageFolder("/home/aistudio/work/train/",transform = train_transform,mode='train')
valid_dataset = ImageFolder("/home/aistudio/work/valid/",transform = test_transform,mode='train')
test_dataset = ImageFolder("/home/aistudio/work/test_folder/",transform = test_transform,mode='test')

# 用 DataLoader 实现数据加载
train_loader = paddle.io.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
valid_loader = paddle.io.DataLoader(valid_dataset, batch_size=batch_size, shuffle=True)
test_loader = paddle.io.DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
1
2
3
4
5
print('[INFO]样本分布')
print('训练样本:', len(train_dataset))
print('有缺陷样本:', len(os.listdir('/home/aistudio/work/train/defective')), '无缺陷样本:', len(os.listdir('/home/aistudio/work/train/no_defective')))
print('验证样本:', len(valid_dataset))
print('测试样本:', len(test_dataset))
[INFO]样本分布
训练样本: 480
有缺陷样本: 248 无缺陷样本: 232
验证样本: 160
测试样本: 159
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
46
47
48
49
50
51
def velidate(model, epoch, val_loss, val_acc):
val_pbar = tqdm(train_loader)
# 不加这行会爆内存
with paddle.no_grad():
for batch_id, data in enumerate(val_pbar):
x_data = data[0] # 训练数据
y_data = data[1] # 训练数据标签
predicts = model(x_data) # 预测结果

y_data = paddle.unsqueeze(y_data, axis=1)
predicts = paddle.unsqueeze(predicts, axis=1)

# 计算损失 等价于 prepare 中loss的设置
loss = loss_fn(predicts, y_data)
val_loss.append(loss)

# 计算准确率 等价于 prepare 中metrics的设置
acc = paddle.metric.accuracy(predicts, y_data)
val_acc.append(acc)
val_pbar.set_description(f"Validate | Epoch: {epoch} | Loss: {loss.item():.4f} | ACC: {acc.item():.4f}")

def train(model, optim, epoch, train_loss, train_acc):
pbar = tqdm(train_loader)

for batch_id, data in enumerate(pbar):

x_data = data[0] # 训练数据
y_data = data[1] # 训练数据标签
predicts = model(x_data) # 预测结果

y_data = paddle.unsqueeze(y_data, axis=1)
predicts = paddle.unsqueeze(predicts, axis=1)

# 计算损失 等价于 prepare 中loss的设置
loss = loss_fn(predicts, y_data)
train_loss.append(loss)

# 计算准确率 等价于 prepare 中metrics的设置
acc = paddle.metric.accuracy(predicts, y_data)
train_acc.append(acc)

# 下面的反向传播、打印训练信息、更新参数、梯度清零都被封装到 Model.fit() 中
# 反向传播
loss.backward()

pbar.set_description(f"Training | Epoch: {epoch} | Loss: {loss.item():.4f} | ACC: {acc.item():.4f}")

# 更新参数
optim.step()
# 梯度清零
optim.clear_grad()
1
2
# model_dict=paddle.load('best_model.pdparams')
# model.set_state_dict(model_dict)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def plotLossAcc(train_loss, val_loss, train_acc, val_acc, title):
# plot train loss & val loss
X = np.arange(len(train_loss))
plt.plot(X, train_loss, label='train loss')
plt.plot(X, val_loss, label = 'val loss')
plt.xlabel('epochs')
plt.ylabel('loss')
plt.legend()
plt.savefig(title+'_loss')
plt.show()

# plot train accuracy & val accuracy
X = np.arange(len(train_acc))
plt.plot(X, train_acc, label='train acc')
plt.plot(X, val_acc, label = 'val acc')
plt.xlabel('epochs')
plt.ylim(0,1)
plt.ylabel('accuracy')
plt.legend()
plt.savefig(title+'_accuracy')
plt.show()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# mobilenet_v2
# model0_dict = paddle.load('mobilenetv2_best_model.pdparams')
# model0.set_state_dict(model0_dict)
epochs = 5

train_loss = []
val_loss = []
train_acc = []
val_acc = []

for epoch in range(epochs):
train(model0, optim0, epoch, train_loss, train_acc)
# if (epoch+1) % 5 == 0 or epoch==epochs-1:
velidate(model0, epoch, val_loss, val_acc)

plotLossAcc(train_loss, val_loss, train_acc, val_acc, title='mobilenet_v2')

# velidate(model0, epochs, val_loss, val_acc)
paddle.save(model0.state_dict(), 'mobilenetv2_best_model.pdparams')
  0%|          | 0/8 [00:00<?, ?it/s]/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/tensor/creation.py:130: DeprecationWarning: `np.object` is a deprecated alias for the builtin `object`. To silence this warning, use `object` by itself. Doing this will not modify any behavior and is safe. 
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  if data.dtype == np.object:
Training | Epoch: 0 | Loss: 0.2871 | ACC: 0.9375: 100%|██████████| 8/8 [01:19<00:00,  9.89s/it]
Validate | Epoch: 0 | Loss: 0.2611 | ACC: 0.9062: 100%|██████████| 8/8 [01:17<00:00,  9.67s/it]
Training | Epoch: 1 | Loss: 0.2584 | ACC: 0.9375: 100%|██████████| 8/8 [01:20<00:00, 10.04s/it]
Validate | Epoch: 1 | Loss: 0.0350 | ACC: 1.0000: 100%|██████████| 8/8 [01:19<00:00,  9.99s/it]
Training | Epoch: 2 | Loss: 0.3539 | ACC: 0.9062: 100%|██████████| 8/8 [01:19<00:00,  9.91s/it]
Validate | Epoch: 2 | Loss: 0.0136 | ACC: 1.0000: 100%|██████████| 8/8 [01:19<00:00,  9.89s/it]
Training | Epoch: 3 | Loss: 0.1782 | ACC: 0.9688: 100%|██████████| 8/8 [01:20<00:00, 10.05s/it]
Validate | Epoch: 3 | Loss: 0.0575 | ACC: 1.0000: 100%|██████████| 8/8 [01:20<00:00, 10.11s/it]
Training | Epoch: 4 | Loss: 0.0120 | ACC: 1.0000: 100%|██████████| 8/8 [01:20<00:00, 10.09s/it]
Validate | Epoch: 4 | Loss: 0.1596 | ACC: 0.9375: 100%|██████████| 8/8 [01:17<00:00,  9.63s/it]
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/matplotlib/cbook/__init__.py:2349: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
  if isinstance(obj, collections.Iterator):
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/matplotlib/cbook/__init__.py:2366: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
  return list(data) if isinstance(data, collections.MappingView) else data

output_19_1.png

output_19_2.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# resnet18
# model1_dict = paddle.load('resnet18_best_model.pdparams')
# model1.set_state_dict(model1_dict)

epochs = 5

train_loss = []
val_loss = []
train_acc = []
val_acc = []

for epoch in range(epochs):
train(model1, optim1, epoch, train_loss, train_acc)
# if (epoch+1) % 5 == 0 or epoch==epochs-1:
velidate(model1, epoch, val_loss, val_acc)

plotLossAcc(train_loss, val_loss, train_acc, val_acc, title='resnet18')

# velidate(model1, epochs, val_loss, val_acc)
paddle.save(model1.state_dict(), 'resnet18_best_model.pdparams')
Training | Epoch: 0 | Loss: 0.1804 | ACC: 0.9375: 100%|██████████| 8/8 [01:19<00:00,  9.90s/it]
Validate | Epoch: 0 | Loss: 0.6224 | ACC: 0.8438: 100%|██████████| 8/8 [01:18<00:00,  9.80s/it]
Training | Epoch: 1 | Loss: 0.2298 | ACC: 0.9375: 100%|██████████| 8/8 [01:11<00:00,  8.98s/it]
Validate | Epoch: 1 | Loss: 0.4990 | ACC: 0.8438: 100%|██████████| 8/8 [01:18<00:00,  9.84s/it]
Training | Epoch: 2 | Loss: 0.4672 | ACC: 0.8750: 100%|██████████| 8/8 [01:19<00:00,  9.98s/it]
Validate | Epoch: 2 | Loss: 0.1952 | ACC: 0.9375: 100%|██████████| 8/8 [01:18<00:00,  9.78s/it]
Training | Epoch: 3 | Loss: 0.1788 | ACC: 0.9375: 100%|██████████| 8/8 [01:21<00:00, 10.14s/it]
Validate | Epoch: 3 | Loss: 0.5061 | ACC: 0.9375: 100%|██████████| 8/8 [01:20<00:00, 10.00s/it]
Training | Epoch: 4 | Loss: 0.0508 | ACC: 0.9688: 100%|██████████| 8/8 [01:20<00:00, 10.08s/it]
Validate | Epoch: 4 | Loss: 0.1306 | ACC: 0.9062: 100%|██████████| 8/8 [01:18<00:00,  9.81s/it]

output_20_1.png

output_20_2.png

1
2
3
4
5
6
7
8
9
10
# # resnet18
# model1_dict = paddle.load('resnet18_best_model.pdparams')
# model1.set_state_dict(model1_dict)
# epochs = 1
# for epoch in range(epochs):
# train(model1, optim1, epoch)
# if (epoch+1) % 5 == 0 and epoch!=epochs-1:
# velidate(model1, epoch)
# velidate(model1, epochs)
# paddle.save(model1.state_dict(), 'resnet18_best_model.pdparams')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# resnet34
# model2_dict = paddle.load('resnet34_best_model.pdparams')
# model2.set_state_dict(model2_dict)

epochs = 10

train_loss = []
val_loss = []
train_acc = []
val_acc = []

for epoch in range(epochs):
train(model2, optim2, epoch, train_loss, train_acc)
# if (epoch+1) % 5 == 0 or epoch==epochs-1:
velidate(model2, epoch, val_loss, val_acc)

plotLossAcc(train_loss, val_loss, train_acc, val_acc, title='resnet34')

# velidate(model2, epochs, val_loss, val_acc)
paddle.save(model0.state_dict(), 'resnet34_best_model.pdparams')
  0%|          | 0/8 [00:00<?, ?it/s]/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/tensor/creation.py:130: DeprecationWarning: `np.object` is a deprecated alias for the builtin `object`. To silence this warning, use `object` by itself. Doing this will not modify any behavior and is safe. 
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  if data.dtype == np.object:
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/nn/layer/norm.py:653: UserWarning: When training, we now always track global mean and variance.
  "When training, we now always track global mean and variance.")
Training | Epoch: 0 | Loss: 0.4711 | ACC: 0.8438: 100%|██████████| 8/8 [01:19<00:00, 10.00s/it]
Validate | Epoch: 0 | Loss: 0.6709 | ACC: 0.7500: 100%|██████████| 8/8 [01:12<00:00,  9.04s/it]
Training | Epoch: 1 | Loss: 0.2191 | ACC: 0.9688: 100%|██████████| 8/8 [01:13<00:00,  9.25s/it]
Validate | Epoch: 1 | Loss: 0.2335 | ACC: 0.9062: 100%|██████████| 8/8 [01:20<00:00, 10.01s/it]
Training | Epoch: 2 | Loss: 0.2510 | ACC: 0.8750: 100%|██████████| 8/8 [01:20<00:00, 10.01s/it]
Validate | Epoch: 2 | Loss: 0.1244 | ACC: 0.9688: 100%|██████████| 8/8 [01:18<00:00,  9.75s/it]
Training | Epoch: 3 | Loss: 0.1883 | ACC: 0.9688: 100%|██████████| 8/8 [01:13<00:00,  9.22s/it]
Validate | Epoch: 3 | Loss: 0.3558 | ACC: 0.8750: 100%|██████████| 8/8 [01:11<00:00,  8.98s/it]
Training | Epoch: 4 | Loss: 0.1757 | ACC: 0.9375: 100%|██████████| 8/8 [01:20<00:00, 10.03s/it]
Validate | Epoch: 4 | Loss: 0.1122 | ACC: 1.0000: 100%|██████████| 8/8 [01:20<00:00, 10.08s/it]
Training | Epoch: 5 | Loss: 0.1912 | ACC: 0.9688: 100%|██████████| 8/8 [01:19<00:00,  9.99s/it]
Validate | Epoch: 5 | Loss: 0.0612 | ACC: 0.9688: 100%|██████████| 8/8 [01:18<00:00,  9.78s/it]
Training | Epoch: 6 | Loss: 0.2404 | ACC: 0.9062: 100%|██████████| 8/8 [01:14<00:00,  9.30s/it]
Validate | Epoch: 6 | Loss: 0.0180 | ACC: 1.0000: 100%|██████████| 8/8 [01:11<00:00,  8.89s/it]
Training | Epoch: 7 | Loss: 0.2110 | ACC: 0.9062: 100%|██████████| 8/8 [01:20<00:00, 10.05s/it]
Validate | Epoch: 7 | Loss: 0.3300 | ACC: 0.9688: 100%|██████████| 8/8 [01:18<00:00,  9.78s/it]
Training | Epoch: 8 | Loss: 0.0429 | ACC: 1.0000: 100%|██████████| 8/8 [01:21<00:00, 10.17s/it]
Validate | Epoch: 8 | Loss: 0.1451 | ACC: 0.9375: 100%|██████████| 8/8 [01:11<00:00,  8.98s/it]
Training | Epoch: 9 | Loss: 0.1853 | ACC: 0.9375: 100%|██████████| 8/8 [01:20<00:00, 10.09s/it]
Validate | Epoch: 9 | Loss: 0.0643 | ACC: 0.9688: 100%|██████████| 8/8 [01:18<00:00,  9.79s/it]

output_22_1.png

output_22_2.png

1
# paddle.save(model.state_dict(), 'best_model.pdparams')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def test():
global results
test_pbar = tqdm(test_loader)
for batch_id, data in enumerate(test_pbar):
x_data = data[0] # 训练数据
y_data = data[1] # 文件名
# 预测结果
pred0 = np.argmax(model0(x_data), axis=1)
pred1 = np.argmax(model1(x_data), axis=1)
pred2 = np.argmax(model2(x_data), axis=1)

# results += [[y_data[i], (pred0[i]+pred1[i]+pred2[i] > 0), (pred0[i]+pred1[i]+pred2[i] > 1),
# (pred0[i]+pred1[i]+pred2[i] > 2)] for i in range(len(pred0))]
# results += [[y_data[i], pred0[i], pred1[i], pred2[i]] for i in range(len(pred0))]
results += [[y_data[i], (pred0[i]+pred1[i]+pred2[i] > 2)] for i in range(len(pred0))]


with open('result.csv', 'w') as f:
# for i, x in enumerate(results):
for x in results:
# f.write(x[0]+','+str(1-x[1])+','+str(1-x[2])+','+str(1-x[3])+'\n')
f.write(x[0]+','+str(1-x[1])+'\n')
1
2
results = []   
test()
  0%|          | 0/3 [00:00<?, ?it/s]/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/tensor/creation.py:130: DeprecationWarning: `np.object` is a deprecated alias for the builtin `object`. To silence this warning, use `object` by itself. Doing this will not modify any behavior and is safe. 
Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
  if data.dtype == np.object:
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/paddle/nn/layer/norm.py:653: UserWarning: When training, we now always track global mean and variance.
  "When training, we now always track global mean and variance.")
100%|██████████| 3/3 [00:26<00:00,  8.79s/it]
打赏
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2023 glisses
  • PV: UV:

请我喝杯咖啡吧~

支付宝
微信