目录
一、 理论知识
1. 网络架构
各种结构中num_class===类别数+1
FCN-32S:
指的是下采样32倍,然后还原到原图尺寸。
- 全卷积指的就是将两条绿色线中间的部分(分类网络中的全连接)换为卷积网络;
- 之后通过上采样得到 ------------------(类别数+1)*H*W
相当于一个类别一个通道
FCN-16S:
FCN-8S:
毫无疑问,FCN-8S效果最好。
2. 损失计算
如下图:
假设2个类别+背景,左下角像素标签为1。
预测图中通道数==类别数+1,将每个通道的左下角元素做softmax回归;概率最大的通道数就是该像素的预测类别索引,相当于求每个像素的分类问题类别损失,然后求平均。
二、 代码实现
1. train.py
该文件下一些笔记如下:
1.1 warmup(参数调优)
流程:
刚开始模型对数据完全不了解,此时需要使用小学习率
对数据了解了一段时间之后,可以使用大学习率
快接近目标
时,使用小学习率进行探索
常见的warmup:
Constant Warmup:学习率从非常小的数值线性增加到预设值之后保持不变
Linner Warmup:学习率从非常小的数值线性增加到预设值之后,然后再线性减小
Cosine Warmup:学习率先从很小的数值线性增加到预设学习率,然后按照cos函数值衰减
代码示例:
####### 0
def create_lr_scheduler(optimizer,
num_step: int,
epochs: int,
warmup=True,
warmup_epochs=1,
warmup_factor=1e-3):
assert num_step > 0 and epochs > 0
if warmup is False:
warmup_epochs = 0
def f(x):
"""
根据step数返回一个学习率倍率因子,
注意在训练开始之前,pytorch会提前调用一次lr_scheduler.step()方法
"""
if warmup is True and x <= (warmup_epochs * num_step):
alpha = float(x) / (warmup_epochs * num_step)
# warmup过程中lr倍率因子从warmup_factor -> 1
return warmup_factor * (1 - alpha) + alpha
else:
# warmup后lr倍率因子从1 -> 0
# 参考deeplab_v2: Learning rate policy
return (1 - (x - warmup_epochs * num_step) / ((epochs - warmup_epochs) * num_step)) ** 0.9
return torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=f)
####### 1
optimizer = torch.optim.SGD(
params_to_optimize,
lr=args.lr, momentum=args.momentum, weight_decay=args.weight_decay
)
lr_scheduler = create_lr_scheduler(optimizer, len(train_loader), args.epochs, warmup=True)
####### 2
for image, target in metric_logger.log_every(data_loader, print_freq, header):
...
loss = ...
optimizer.zero_grad()
loss.backward()
optimizer.step()
lr_scheduler.step()
1.2 日志工具MetricLogger类
预先准备:
自行创建两个类—— class MetricLogger(object) 和 class SmoothedValue(object)
FCN项目下distributed_utils.py文件下的类和函数
使用示例:
2. 网络搭建
加载预训练权重时:
missing_keys, unexpected_keys = model.load_state_dict(weights_dict, strict=False)
strict=False
- True 时,代表有什么要什么,每一个键都有。
- False 时,有什么我要什么,没有的不勉强。
missing_keys, unexpected_keys 返回值:缺失的键,不期望的键。
3. 分割任务mask调色板及使用
常见的图片格式一般为rgb彩色三通道图或者单通道灰度图,但作为语义分割的label图(以voc数据集为例),其png图片实际格式是单通道的彩色图,这里叫伪彩图(或者也叫索引图)。由于将图片分割后是把像素点分类,不同类的分割结果用不同颜色表示(一个类别就是一个索引),需要定义一个类似于有序字典的东西来表示颜色
伪彩图:
先定义颜色结构,通过颜色的索引代表颜色。
举例:需要三种颜色,那就定义三种颜色[[255, 0, 0], [0, 255, 0],[0, 0, 255]]的结构储存起来,此时我们的伪彩色图片(只会出现上面三类颜色)当像素点的颜色为[255, 0, 0]时,只需要用0表示即可,这里的0就是颜色结构里的第一个颜色。同样地,1,2就分别表示另外两个颜色。
target都是单通道图;像素值是类别索引,通过调色板得到伪彩色图
网络训练时,不会用到;预测时会用到
- 首先得到调色板(也就是索引图):
存为json文件
import json
import numpy as np
from PIL import Image
# 读取mask标签(训练集中的任意一张target)
target = Image.open("F:/data/voc_data/VOCdevkit/VOC2012/SegmentationClass/2007_000032.png")
# 获取调色板
palette = target.getpalette()
palette = np.reshape(palette, (-1, 3)).tolist()
# 转换成字典子形式
pd = dict((i, color) for i, color in enumerate(palette))
# 写入json文件
json_str = json.dumps(pd)
with open("palette.json", "w") as f:
f.write(json_str)
- 预测时使用
############ 01 读取调色板.json文件
palette_path = "./palette.json"
with open(palette_path, "rb") as f:
pallette_dict = json.load(f)
pallette = []
for v in pallette_dict.values():
pallette += v
############ 02 网络预测
img_path = "/2007_000549.jpg" #一张图片
original_img = Image.open(img_path)
data_transform = ...
img = data_transform(original_img) # C,H,W
img = torch.unsqueeze(img, dim=0) # 扩维 B,C,H,W----1,C,H,W
model = ...
model.eval()
with torch.no_grad():
output = model(img.to(device)) # C=类别数+1 代表每个像素属于哪一类的概率
prediction = output['out'].argmax(1).squeeze(0) # 在C维度进行softmax取最大值,压缩为(H,W)
prediction = prediction.to("cpu").numpy().astype(np.uint8)
############ 03 调用调色板,转化单通道(H,W)为伪彩色图(H,W)
mask = Image.fromarray(prediction)
mask.putpalette(pallette)
mask.save("test_result.png") # 预测结果保存