<jupyter_start><jupyter_text>飞桨常规赛：遥感影像地块分割 - 4月第七名方案听过大佬分享遥感比赛心得后第一次参加。虽然只是4月第七，但是也还是（应要求）把我的做法以及踩的坑做一个分享。  0. 包准备- 既然是分割任务，主要还是用到PaddleSeg套件进行baseline的搭建。- 结合官方的提示，找到了AgentMaker大佬们维护的TTA库，实测这里使用TTA能够带来1.5分左右的增长。- 官方提示还提供了AgentMaker大佬们维护的RIFLE库，之前试了一下，应该是使用方法不对，没看到什么增长，就放那儿想着最后组合模型来用，结果，就没有结果了- -，我觉得大家还是可以试一下，毕竟官方推荐，肯定是有用的。<jupyter_code># ! pip -q install paddle-rifle  # 组合模型再来提升
! pip -q install patta
# ! git clone https://gitee.com/paddlepaddle/PaddleSeg.git  # 运行一次后可以注释，不用重复运行，因为进行过修改

import sys
sys.path.append('PaddleSeg')<jupyter_output><empty_output><jupyter_text>1. 数据准备 1.1 解压数据集- 这里的数据集产生于CCF遥感赛道的数据集，和CCF的比较类别更少了，而且像道路、水域那种长的联通的也没有了。我看过这个数据集，感觉凭我人看，好多图像中对应的标注我都压根看不出来- -。这里解压数据在新建的dataset中，并且删除了__MACOSX这个文件。- 训练数据集包含66,653张分辨率为2m/pixel，尺寸为256x256的JPG图片，标注为同样配置的PNG图片。![](https://ai-studio-static-online.cdn.bcebos.com/955d288c00594f7484fcfcc1609affb4432ee981566e4d498756dba0691f74b9)<jupyter_code># 此cell运行一次后可以注释，不用重复运行
# 创建文件夹
! mkdir dataset
# 解压数据
! unzip -oq data/data77571/train_and_label.zip -d dataset
! unzip -oq data/data77571/img_test.zip -d dataset
# 删除文件
! rm -rf dataset/__MACOSX<jupyter_output><empty_output><jupyter_text>1.2 生成数据列表- 这里按照95%训练集，5%验证集的比例，划分了数据集。<jupyter_code>import os
import random

def creat_txt(data_path, split_rate=0.9):
    # 训练集和验证集
    with open(os.path.join(data_path, 'train.txt'), 'w') as tf:
        with open(os.path.join(data_path, 'val.txt'), 'w') as vf: 
            imgs_name = os.listdir(os.path.join(data_path, 'img_train'))
            random.shuffle(imgs_name)  # 打乱顺序
            for idx, img_name in enumerate(imgs_name):
                img_path = os.path.join('img_train', img_name)
                lab_path = img_path.replace('img', 'lab').replace('jpg', 'png')
                if (idx % 20 + 1) <= (split_rate * 20):  # 这里是根据0.95来设定的
                    tf.write(img_path + ' ' + lab_path + '\n')
                else:
                    vf.write(img_path + ' ' + lab_path + '\n')
    # 测试集
    with open(os.path.join(data_path, 'test.txt'), 'w') as sf:
        imgs_name = os.listdir(os.path.join(data_path, 'img_testA'))
        for img_name in imgs_name:
            img_path = os.path.join('img_testA', img_name)
            sf.write(img_path + '\n')
    print('create finished!')

data_path = 'dataset'
creat_txt(data_path, 0.95)  # 0.95的划分比例<jupyter_output><empty_output><jupyter_text>1.3 构建数据读取器- PaddleSeg的基操，套路式的弄下来就行了，自己改下transforms。这里也没用到大佬说过的其他数据增强的库来自定义一点数据增强。<jupyter_code>from paddleseg.datasets import Dataset
import paddleseg.transforms as T

# 构建训练集
train_transforms = [
    T.RandomHorizontalFlip(),  # 水平翻转
    T.RandomVerticalFlip(),  # 垂直翻转
    T.RandomRotation(),  # 随机旋转
    T.Resize(target_size=(256, 256)),  # 修改大小
    T.Normalize()  # 归一化
]
train_dataset = Dataset(
    transforms=train_transforms,
    dataset_root='dataset',
    num_classes=4,
    mode='train',
    train_path='dataset/train.txt',
    separator=' ',
)

# 构建验证集
val_transforms = [
    T.Resize(target_size=(256, 256)),
    T.Normalize()
]
val_dataset = Dataset(
    transforms=val_transforms,
    dataset_root='dataset',
    num_classes=4,
    mode='val',
    val_path='dataset/val.txt',
    separator=' ',
)<jupyter_output><empty_output><jupyter_text>- 感觉测试一下还是很有必要的，对对数据形状，显示显示图像、标签什么的，有时错误往往就在这之中可以提前发现。还可以再看看验证集什么的。<jupyter_code># 测试
for img, lab in train_dataset:
    print(img.shape, lab.shape)
    break<jupyter_output>(3, 256, 256) (256, 256)<jupyter_text>2. 模型训练 2.1 训练准备- 这里我觉得很有意思，也表示一下自己踩的坑。之前恍惚记得大佬说过什么样本不均衡，网络不用选择太大的，多个小的模型投票什么的。结果这里数据都没分析就开始瞎搞，搞了一堆二分类的小网络了，每个类别正负样本的学习，最后再投票什么的。结果效果反而比不上这个直接怼个大模型跑完，不知道是不是我设置的问题。但是完了我在分析数据之后发现这个样本还还好吧，没有看到很不均衡的情况，可能这也是关键吧。![](https://ai-studio-static-online.cdn.bcebos.com/f0e3a950d19c440ca939b8047b811a0dd3a295ec9f0e4f98bf9d0efbe49e589d)- 学大佬的东西还是得学思想，分析数据从而针对性的设计，不是像我一样拿到什么所谓的技巧就跑。真正的技巧不是一成不变的。- 反思完了，说下这里的配置，也就是往大了怼，HRNet_W48作为backbone的OCRNet，损失函数混合了一堆，也不知道到底两个就好还是怎样。学习率衰减先用的Cosine，最后用的Poly。优化器永远只会Adam- -。<jupyter_code>import paddle
from paddleseg.models import OCRNet, HRNet_W48
from paddleseg.models.losses import BCELoss, CrossEntropyLoss, DiceLoss, LovaszSoftmaxLoss, MixedLoss

# 模型
url = 'ocrnet_output/last_model/model.pdparams'
model = OCRNet(num_classes=4, backbone=HRNet_W48(), backbone_indices=[0], pretrained=url)
# 训练参数
epochs = 10
batch_size = 16
iters = epochs * len(train_dataset) // batch_size
# 损失函数
ltp = [BCELoss(), CrossEntropyLoss(), DiceLoss(), LovaszSoftmaxLoss()]
lcf = [1] * 4
mixloss = MixedLoss(ltp, lcf)
losses = {}
losses['types'] = [mixloss] * 2
losses['coef'] = [1, 0.4]
# 学习率及优化器
# base_lr = 5e-5
# lr = paddle.optimizer.lr.CosineAnnealingDecay(base_lr, T_max=(iters // 3))
lr = paddle.optimizer.lr.PolynomialDecay(3e-4, 200, end_lr=3e-8)
optimizer = paddle.optimizer.Adam(lr, parameters=model.parameters(), weight_decay=paddle.regularizer.L2Decay(1e-9))<jupyter_output>2021-04-19 14:09:11 [INFO]	Loading pretrained model from ocrnet_output/last_model/model.pdparams
2021-04-19 14:09:14 [INFO]	There are 1583/1583 variables loaded into OCRNet.<jupyter_text>2.2 开始训练- 训练没啥好写的，就train上放哪儿就完了。这里反正保存10个模型。<jupyter_code>from paddleseg.core import train

train(
    model=model,
    train_dataset=train_dataset,
    val_dataset=val_dataset,
    optimizer=optimizer,
    save_dir='ocrnet_output',
    iters=iters,
    batch_size=batch_size,
    save_interval=int(iters/10),
    log_iters=10,
    num_workers=0,
    losses=losses,
    use_vdl=True)<jupyter_output><empty_output><jupyter_text>- 训练结果看着还是可以的，不过提交也就及格分，可能验证集有点少了。```[INFO]	[EVAL] Images=3332 mIoU=0.7324 Acc=0.8544 Kappa=0.8009 [INFO]	[EVAL] Class IoU: [0.7403 0.8304 0.7145 0.6443][INFO]	[EVAL] Class Acc: [0.8501 0.9046 0.8252 0.7965]```  3. 模型预测- 预测是自己做了一个函数来预测，给模型、图像名列表什么的就行。里面用到了TTA，针不戳，有1.5左右的涨分。tqdm可以来看进度，挺不错的，这也才是第一次用！要注意的就是transforms保持和前面验证要一致呢。还有一张一张的来的话需要扩充一个batch_size的维度，虽然就是1。<jupyter_code>import paddle
from paddleseg.models import OCRNet, HRNet_W48
import paddleseg.transforms as T
from paddleseg.core import infer
import os
from tqdm import tqdm
from PIL import Image
import numpy as np
import patta as tta

def nn_infer(model, imgs_path, is_tta=True):
    if not os.path.exists('result'):
        os.mkdir('result')
    # 预测结果
    transforms = T.Compose([
        T.Resize(target_size=(256, 256)),
        T.Normalize()
    ])
    # 循环预测和保存
    for img_path in tqdm(imgs_path):
        img, _ = transforms(img_path)  # 进行数据预处理
        img = paddle.to_tensor(img[np.newaxis, :])  # C,H,W -> 1,C,H,W
        # TTA
        if is_tta == True:
            tta_pres = paddle.zeros([1, 4, 256, 256])  # 图像大小256
            for tta_transform in tta.aliases.flip_transform():
                tta_img = tta_transform.augment_image(img)  # TTA_transforms
                tta_pre = infer.inference(model, tta_img)  # 预测
                deaug_pre = tta_transform.deaugment_mask(tta_pre)
                tta_pres += deaug_pre
            pre = tta_pres / 5.
        else:
            pre = infer.inference(model, img)  # 预测
        pred = paddle.argmax(pre, axis=1).numpy().reshape((256, 256)).astype('uint8')
        pil_img = Image.fromarray(pred)
        pil_img.save(os.path.join('result', img_path.split('/')[-1].replace('jpg', 'png')), 'png')

# 网络准备
model_path='ocrnet_output/last_model/model.pdparams'
model = OCRNet(num_classes=4, backbone=HRNet_W48(), backbone_indices=[0], pretrained=model_path)
model.eval()
# 预测文件
set_path = 'dataset'
list_file = 'dataset/test.txt'
imgs_path = []
with open(list_file, 'r') as f:
    datas_path = f.readlines()
    for data_path in datas_path:
        imgs_path.append(os.path.join(set_path, data_path.strip()))
# 预测
nn_infer(model, imgs_path, is_tta=True)<jupyter_output>2021-04-19 15:07:20 [INFO]	Loading pretrained model from ocrnet_output/last_model/model.pdparams
2021-04-19 15:07:22 [INFO]	There are 1583/1583 variables loaded into OCRNet.<jupyter_text>4. 后处理- 后处理这里我觉得也应该好好针对数据集和判分要求思考。之前大佬是针对CCF的数据，有道路、水体等等，要考虑连通性，因此感觉后处理会很棒。如果感觉不知道为什么，盲目处理，反而降分，比如这里就降分了，所以我都注释了，但我不删，留着看看自己瞎做的什么。总之后处理这里需要摸清要求和数据、仔细思考、多做实验！（比如除了这里的闭运算，我还在其他地方尝试了MRF后处理，反正就是处理了反而降个0.几分。）<jupyter_code># import os
# import cv2
# import numpy as np
# from tqdm import tqdm

# if not os.path.exists('result_ep'):
#     os.mkdir('result_ep')

# result = 'result'
# imgs_name = os.listdir(result)
# for img_name in tqdm(imgs_name):
#     img_path = os.path.join(result, img_name)
#     img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
#     # 后处理方法
#     # 1.闭运算
#     kernel = np.ones((5, 5), np.uint8)
#     img = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
#     # 保存
#     cv2.imwrite(img_path.replace('result', 'result_ep'), img)<jupyter_output><empty_output><jupyter_text>5. 查看- 这里是方便我人为检查一下分割的结果。因为分出来的0,1,2,3，看起来都是黑黑的图啊。所以这里我都乘了80来保存，就是0,80,160,240，差别比较大，这样我保存在另一个文件夹就可以下载下来浏览一下分割的效果，到底是哪儿不太如意~。<jupyter_code>import os
import cv2
import numpy as np
from tqdm import tqdm

if not os.path.exists('vis'):
    os.mkdir('vis')

result = 'result'
imgs_name = os.listdir(result)
for img_name in tqdm(imgs_name):
    img_path = os.path.join(result, img_name)
    img = cv2.imread(img_path) * 80
    cv2.imwrite(img_path.replace('result', 'vis'), img)<jupyter_output>100%|██████████| 4608/4608 [00:04<00:00, 970.16it/s]
{'fraction_non_alphanumeric': 0.12965340179717585, 'fraction_numerical': 0.04589216944801027, 'mean_word_length': 4.176633444075304, 'pattern_counts': {'":': 0, '<': 39, '<?xml version=': 0, '>': 38, 'https://': 3, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '10', 'dir': 'jupyter-structured-clean-dedup', 'id': '624019', 'license': 'Apache-2.0', 'n_tokens_mistral': 5037, 'n_tokens_neox': 5027, 'n_words': 548, 'path': '1911200.ipynb', 'repo_name': 'Pd-RegSea/pdrs-4-7', 'size': '9348'}