入门Unet:自己总结方便后面回看

入门Unet网络学习

第一部分:网络的结构

Unet_part.py:

目的:实现Unet网络中需要用到的几个基础类:
大部分的解释在代码中:


import torch
import torch.nn.functional as F


class DoubleConv(torch.nn.Module):
    def __init__(self, in_ch, out_ch):
        super(DoubleConv, self).__init__()
        self.doubleconv = torch.nn.Sequential(
            torch.nn.Conv2d(in_ch, out_ch, kernel_size=3, padding=1),
            torch.nn.BatchNorm2d(out_ch),  # 归一化
            torch.nn.ReLU(inplace=True),
            torch.nn.Conv2d(out_ch, out_ch, kernel_size=3, padding=1),
            torch.nn.BatchNorm2d(out_ch),  # 归一化
            torch.nn.ReLU(inplace=True)

        )

    def forward(self, x):
        x = self.doubleconv(x)
        return x


class Down(torch.nn.Module):
    def __init__(self, in_ch, out_ch):
        super(Down, self).__init__()
        self.down = torch.nn.Sequential(
            torch.nn.MaxPool2d(2),
            DoubleConv(in_ch, out_ch)
        )

    def forward(self, x):
        x = self.down(x)
        return x


class Up(torch.nn.Module):
    # 构造函数里面输入的是事先定义的量
    def __init__(self, inputs, outputs, bilinear=True):
        super(Up, self).__init__()
        # 上采样的一种方法:双线性采样
        if bilinear:
            self.up = torch.nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True)
        else:
            self.up = torch.nn.ConvTranspose2d(inputs // 2, outputs // 2, 2, stride=2)
        self.dconv = DoubleConv(inputs, outputs)

    # forward(*x)里面x是传入的tensor
    def forward(self, x, y):  # forward(*input)是通过nn.Module 的__call__方法调用的,就相当于调用了模型就是直接调用它的forward函数,y=model(x),这个x就是直接传入到forward函数的x参数
        x = self.up(x)
        # Upsample-62        [-1, 128, 256, 256] 
        # 说明上采样后照片像素大小一样,但是为了确保下面需要进行对其
        diffx = torch.tensor([y.size()[2] - x.size()[2]])#其实可以不用tensor
        diffy = torch.tensor([y.size()[3] - x.size()[3]])
        x = F.pad(x, [diffx // 2, diffx - diffx // 2,
                      diffy // 2, diffy - diffy // 2])
        # pad的用法:对x进行0(默认)扩充,后面的列表表示从x的最后一个维度开始,列表的后面两个数分别表示对这个维度两头扩充的维数
        # 负数也有效不过会减小维度,所以这里明显是y大于x,(也就是说进过upsample的图片大小往往不能恢复到原大小)
        # " / "表示浮点数除法,返回浮点float结果;" // "表示整数除法
        x = torch.cat([y, x], dim=1)#通道数的合并,而且y和x必须是tensor
        #print(x.size())
        '''torch.Size([2, 1024, 64, 64])
        torch.Size([2, 512, 128, 128])
        torch.Size([2, 256, 256, 256])
        torch.Size([2, 128, 512, 512])'''
        x = self.dconv(x)
        return x


class OutConv(torch.nn.Module):
    def __init__(self, in_ch, out_ch):
        super(OutConv, self).__init__()
        self.outc = torch.nn.Conv2d(in_ch, out_ch, kernel_size=1)

    def forward(self, x):
        x = self.outc(x)
        return x

小问题:

1:对于tensor量,x.size()和x.shape返回的是一样
2:关于F.pad中参数列表中数据是int就行,意思就是diffx是int就行(test过没有差别)


Unet.py:

from Unet_parts import *
from torchsummary import summary  # 打印torch每层的结构参数


class Unet(torch.nn.Module):
    def __init__(self, in_channels, out_class):
        super(Unet, self).__init__()
        self.n_channels = in_channels#构造函数实现形参的传值
        self.n_classes = out_class

        self.dcon = DoubleConv(in_channels, 64)
        self.down1 = Down(64, 128)
        self.down2 = Down(128, 256)
        self.down3 = Down(256, 512)
        self.down4 = Down(512, 512)
        #输入的size如下,因为进行了dim=1的cat 相当于通道数合并
        # torch.Size([1, 1024, 64, 64]) torch.Size([1, 512, 128, 128])
        # torch.Size([1, 256, 256, 256])torch.Size([1, 128, 512, 512])
        self.up1 = Up(1024, 256)
        self.up2 = Up(512, 128)
        self.up3 = Up(256, 64)
        self.up4 = Up(128, 64)
        self.oconv = OutConv(64, out_class)

    def forward(self, x):
        x1 = self.dcon(x)
        x2 = self.down1(x1)
        x3 = self.down2(x2)
        x4 = self.down3(x3)
        x5 = self.down4(x4)


        x = self.up1(x5, x4)
        x = self.up2(x, x3)
        x = self.up3(x, x2)
        x = self.up4(x, x1)
        x = self.oconv(x)
        return x


if __name__ == '__main__':
    unet = Unet(1, 1)
    summary(unet, input_size=(1, 512, 512), device='cpu')
    #这里的效果是可以方便看到整个网络情况

summary效果图片:
summary
这里-1,意思是初始的in_channels还未赋值,这里只是检查网络的结构

第二部分:数据集的构成

dataset.py

from torch.utils.data import Dataset, DataLoader
import glob  
import os
import cv2
import random


class Dataset_Loader(Dataset):
    def __init__(self, data_path):
        # 初始化函数,读取所有data_path下的图片
        self.data_path = data_path
        self.imgs_path = glob.glob(os.path.join(data_path, 'image/*.png'))  # os用来合并路径
        # glob.glob()返回所有匹配的文件路径列表。

    def augment(self, image, flipCode):
        # 使用flip进行数据增强, flipCode为1水平翻转,0为垂直翻转,-1水平加垂直
        flip = cv2.flip(image, flipCode)
        return flip

    def __getitem__(self, index):
        # 根据index读取图片,根据图片路径读取具体图片数据内容
        image_path = self.imgs_path[index]
        # 获得label_path
        label_path = image_path.replace('image', 'label')
        # 读取图片和标签
        image = cv2.imread(image_path)  # 输入的需要的是带索引的路径
        label = cv2.imread(label_path)
        # 输出为(512, 512, 3)

        # 将数据转为单通道的图片
        image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        label = cv2.cvtColor(label, cv2.COLOR_BGR2GRAY)
        # 输出为(512, 512) 返回值:灰度图片的数组
        image = image.reshape(1, image.shape[0], image.shape[1])
        label = label.reshape(1, label.shape[0], label.shape[1])
        # (1, 512, 512)
        # 注意这里的输出都是数组类型还不是tensor
        #print(image.shape)
        # 处理标签,将像素值为255的改为1
        if label.max() > 1:
            label = label / 255
        # 随机进行数据增强,为2的不做处理
        flipcode = random.choice([-1, 0, 1, 2])
        if flipcode != 2:
            image = self.augment(image, flipcode)
            label = self.augment(label, flipcode)

        return image, label
        # 返回的是image的(1,1,255,255)的数组和label同样大小但进过等比缩小的数组

    def __len__(self):
        # 返回训练集大小
        return len(self.imgs_path)


if __name__ == "__main__":
    dataset_loader = Dataset_Loader("data/train/")  # 路径名不能有中文啊
    print("num:", len(dataset_loader))
    train_loader = DataLoader(dataset=dataset_loader, batch_size=2, shuffle=True)
    for image,label in train_loader:
        print(image.shape)
    # DataLoader返回的image是torch.Size([2, 1, 512, 512])
    # 而函数getitem中的image是(1, 512, 512)和(1, 512, 512)
    # 所以初始的数据集输入tensor第一位是batch_size,第二位是图像通道大小也是net中的in_channels(这里是灰度图)
    # 并且image也转为tensor了
   

这里对所有的输入输出都有注释,可以更清楚的了解数据在其中的处理

问题

1.对于图片进行数据增强的原理
2.还有一些cv中对于图像处理的常用操作

第三部分:训练和测试

train.py

from Unet import Unet
from dataset import Dataset_Loader
from torch import optim
import torch.nn as nn
import torch


def train_net(net, device, data_path, epochs=40, batch_size=1, lr=1e-5):
    # 加载训练集
    dataset_loader = Dataset_Loader(data_path)
    train_loader = torch.utils.data.DataLoader(dataset=dataset_loader, batch_size=batch_size, shuffle=True)

    # 定义优化方法
    optimizer = optim.RMSprop(net.parameters(), lr=lr, weight_decay=1e-8, momentum=0.5)
    criterion = nn.BCEWithLogitsLoss()

    # best_loss初始化为正无穷
    best_loss = float('inf')

    # 训练epoch次
    for epochs in range(epochs):

        for image, label in train_loader:
            #这里因为一共有30张图片,而且batch_size=1,所以train_loader中有30次训练
            optimizer.zero_grad()
            # 将数据拷贝到device中去
            image = image.to(device=device, dtype=torch.float32)
            label = label.to(device=device, dtype=torch.float32)

            pred = net(image)
            # print(pred.size()) torch.Size([1, 1, 512, 512])

            loss = criterion(pred, label)  # 返回的是什么
            # print(loss) tensor(0.6900, device='cuda:0', grad_fn=<BinaryCrossEntropyWithLogitsBackward>)
            print('Loss/train', epochs, loss.item())

            if loss < best_loss:
                best_loss = loss
                torch.save(net.state_dict(), 'best_model.path')
            # 虽然搞不懂,但是5.4和tensor([3])就是可以比较大小的,赋值过程中就进行了类型的转换了
            # 然后保存最优值

            loss.backward()
            optimizer.step()


if __name__ == "__main__":
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

    net = Unet(1, 1)

    net.to(device)

    data_path = "data/train"
    train_net(net, device, data_path)
    # 这里都是进行一些调试

问题

1.关于torch.save:
在这里插入图片描述
2./.item()将Tensor变量转换为python标量(int float等),其中t是一个Tensor变量,只能是标量,转换后dtype与Tensor的dtype一致。

test.py

从test这里把预测的权重带入原模型生成预测图片

import glob
import numpy as np
import torch
import os
import cv2
from Unet import Unet

if __name__ == "__main__":
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

    net = Unet(1, 1)  # 单通道图片且分类为1
    net.to(device)
    # 加载模型参数
    net.load_state_dict(torch.load('best_model.path', map_location=device))
    # 测试模型
    net.eval()
    # 读取所有图片路径
    test_path = glob.glob('data/test/*.png')

    for test_path in test_path:
        save_test_path = test_path.split('.')[0] + '_test.png'
        # split():拆分字符串。通过指定分隔符对字符串进行切片,并返回分割后的字符串列表。split:['data/test/0','png']
        # 这里就是以'.'为分割符,取其第0维,返回save_test_path:data/test/0.png'
        # os.path.split():将文件名和路径分割开。

        img = cv2.imread(test_path)  # (512, 512, 3) 输入的需要的是带索引的路径
        # 转为灰度图
        img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # (512, 512) 因为是黑白图片所以直接把rgb三值都一样,直接合并
        # 转为batch为1,通道为1,大小为512*512的数组
        img = img.reshape(1, 1, img.shape[0], img.shape[1])  # (1, 1, 512, 512)
        # 这里与dataset不同是因为这里没有经过DataLoader但是放入net的size需要一样

        img_tensor = torch.from_numpy(img)  # 数组与tensor([1, 1, 512, 512])的转换

        img_tensor = img_tensor.to(device=device, dtype=torch.float32)

        pred = net(img_tensor)
        # print(pred.shape)   torch.Size([1, 1, 512, 512])

        # print(pred.data.cpu()[0])  tensor([[[0, 1],
        #                                     [2, 3]]], dtype=torch.int32)  这里的0123是随便写的里面其实是512*512的矩阵
        # pred.data.cpu()[0]是torch.Size([1, 512, 512])
        pred = np.array(pred.data.cpu()[0])[0]
        # print(pred.shape) (512, 512) 并且转换为数组
        # pred.data.cpu()[0]表示读入cpu中

        pred[pred >= 0.5] = 225
        pred[pred < 0.5] = 0

        cv2.imwrite(save_test_path, pred)
        # cv2.imwrite(“图片要保存的相对路径”,os.path.splitext(filenames)[0]+os.splittext(filenames)[1],image)
        # 相对路径的只能以cv2.imwrite()所在当前文件夹开头,这里就是data

问题

.item()

将Tensor变量转换为python标量(int float等),其中t是一个Tensor变量,只能是标量,转换后dtype与Tensor的dtype一致。。

.cpu

将数据的处理设备从其他设备(如.cuda()拿到cpu上),不会改变变量类型,转换后仍然是Tensor变量。

.data

与item()相似 ,返回的是tensor

a = np.array(a.data.cpu()[0])[0]

1.首先a是一个放在GPU上的Variable,a.data是把Variable里的tensor取出来,可以看出与a的差别是:缺少了第一行(Variable containing)

2.a.cpu()和a.data.cpu()是分别把a和a.data放在cpu上,其他的没区别,另外:a.data.cpu(),a.cpu().data一样(这里的a必须是tensor)

3.a.data[0] | a.cpu().data[0] | a.data.cpu()[0]是一样的,都是取出第0维的tensor

4.连续取两次第0维就得到了一个512*512矩阵

数组的骚操作

在这里插入图片描述

CV2的操作

在这里插入图片描述
参考:https://blog.csdn.net/fu6543210/article/details/80835280

初步学习数据的可视化(tensorboardx)

在这里插入图片描述

from tensorboardX import SummaryWriter
################################
writer = SummaryWriter('data/log')
#################################
 # tensorboard --logdir=E:\code\Pytorch-Unet\data\log
 writer.add_scalar('Loss', loss.item(), i+epochs*30)
 writer.close()


  • 6
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值