【1】config.ini
#配置文件
[base]
batchsize=10
ckptpath='./dowoload/weights_19.pth'
train_path="F:/PytorchTes/torchdeeplearnmodel/unetdata/train"
val_path="F:/PytorchTes/torchdeeplearnmodel/unetdata/val"
【2】main.py
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
(1)参考文献:UNet网络简单实现
https://blog.youkuaiyun.com/jiangpeng59/article/details/80189889
(2)FCN和unet的区别
https://zhuanlan.zhihu.com/p/118540575
'''
import torch
import argparse
from torch.utils.data import DataLoader
from torch import nn, optim
from torchvision.transforms import transforms
from Unetmodel import Unet
from setdata import LiverDataset
from setdata import *
# 是否使用cuda
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
#定义输入数据的预处理模式,因为分为原始图片和研磨图像,所以也分为两种
#image转换为0~1的数据类型
x_transforms = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
])
# 此处提供的mask图像是单通道图像,所以mask只需要转换为tensor
y_transforms = transforms.ToTensor()
#model:模型 criterion:损失函数 optimizer:优化器
#dataload:数据 num_epochs:训练轮数
def train_model(model, criterion, optimizer, dataload, num_epochs=5):
for epoch in range(num_epochs):
#.format参考,https://blog.youkuaiyun.com/u012149181/article/details/78965472
print('Epoch {}/{}'.format(epoch, num_epochs - 1))
print('-' * 10)
dt_size = len(dataload.dataset)
epoch_loss = 0
step = 0
for x, y in dataload:
step += 1
#判断是否调用GPU
inputs = x.to(device)
labels = y.to(device)
# zero the parameter gradients
optimizer.zero_grad()
# forward
outputs = model(inputs)
loss = criterion(outputs, labels) #计算损失值
loss.backward()
optimizer.step()#更新所有的参数
#item()是得到一个元素张量里面的元素值
epoch_loss += loss.item()
print("%d/%d,train_loss:%0.3f" % (step, (dt_size - 1) // dataload.batch_size + 1, loss.item()))
print("epoch %d loss:%0.3f" % (epoch, epoch_loss/step))
#保存模型
torch.save(model.state_dict(), 'weights_%d.pth' % epoch)
return model
#训练模型函数
def train(batch_size,train_path):
#模型初始化
model = Unet(3, 1).to(device)
batch_size = batch_size
#定义损失函数
criterion = nn.BCEWithLogitsLoss()
#定义优化器
optimizer = optim.Adam(model.parameters())
#加载训练数据
liver_dataset = LiverDataset(train_path,transform=x_transforms,target_transform=y_transforms)
dataloaders = DataLoader(liver_dataset, batch_size=batch_size, shuffle=True, num_workers=4)
train_model(model, criterion, optimizer, dataloaders)
#模型的测试结果
def test(ckptpath,val_path):
#输入三通道,输出单通道
model = Unet(3, 1)
model.load_state_dict(torch.load(ckptpath,map_location='cpu'))
liver_dataset = LiverDataset(val_path, transform=x_transforms,target_transform=y_transforms)
#一次加载一张图像
dataloaders = DataLoader(liver_dataset, batch_size=1)
#eval函数是将字符串转化为list、dict、tuple,但是字符串里的字符必须是标准的格式,不然会出错
model.eval()
import matplotlib.pyplot as plt
plt.ion()# 打开交互模式
with torch.no_grad():
for x, _ in dataloaders:
y=model(x).sigmoid()
#a.squeeze(i) 压缩第i维,如果这一维维数是1,则这一维可有可无,便可以压缩
img_y=torch.squeeze(y).numpy()
plt.imshow(img_y)
plt.pause(0.01)
plt.show()
if __name__ == '__main__':
#载入配置模块并调用基础设置节点
import configparser
config = configparser.ConfigParser()
config.read("config.ini", encoding="utf-8")
config.sections() # 获取section节点
config.options('base') # 获取指定section 的options即该节点的所有键
batchsize=config.get("base", "batchsize") # 获取指定base下的batchsize
ckptpath=config.get("base", "ckptpath") # 获取指定base下的ckptpath
train_path=config.get("base", "train_path") # 获取指定base下的train_path
val_path=config.get("base", "val_path") # 获取指定base下的val_path
train(batchsize,train_path) # 训练
test(ckptpath,val_path) # 测试
【3】Read.md
参考文章:
https://codechina.csdn.net/mirrors/javispeng/u_net_liver/-/tree/master/data
https://blog.csdn.net/jiangpeng59/article/details/80189889
代码及权重下载地址:https://github.com/JavisPeng/u_net_liver
'''
%3d--可以指定宽度,不足的左边补空格
%-3d--左对齐
%03d---一种左边补0 的等宽格式,比如数字12,%03d出来就是: 012
'''
unet网络的输入是任意大小的,即输入图像的尺寸大小是不受限制的。(fcn输入大小为227*227)
网络对于输入的大小也是有要求的。为了使得输出的分割图无缝拼接,重要的是选择输入块的大小,以便所有的 2 × 2 的池化层都可以应用于偶数的 x 层和 y 层。一个比较好的方法是从最下的分辨率从反向推到,比如说在网络结构中,最小的是 32 × 32 32\times3232×32 ,沿着收缩路径的反向进行推导可知,输入图像的尺寸应该为 572 × 572 572\times572572×572。
关于unet网络的一些特性
【1】论文精读及分析:U-Net: Convolutional Networks for Biomedical Image Segmentation
https://blog.csdn.net/dugudaibo/article/details/82934731?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-3.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-3.control
(1) 提出了 U-net 这种网络结构。它同时具有捕捉上下文信息的收缩路径和允许精确定位的对称扩展路径;并且与 FCN 相比,U-net 的上采样(反卷积是上采样的一种方法)依然有大量的通道,这使得网络将上下文信息向更高层分辨率传播。
(FCN具有全连接部分,unet略去了全连接部分)
(2) Overlap-tile 策略。这种方法用于补全输入图像的上下文信息,可以解决由于现存不足造成的图像分块输入的问题。
(将图像切割成块输入)
(3) 使用随机弹性形变进行数据增强。
(当只有少量的训练样本,对于让网络学习到所需的不变性和鲁棒性来讲,数据增强是必要的。)
(4) 使用加权损失。预先计算权重图,一方面补偿了训练数据每类像素的不同频率,
另一方面使网络更注重学习相互接触的细胞间的边缘。
(这种方法补偿了训练数据每类像素的不同频率,并且使网络更注重学习相互接触的细胞间的边缘。)
【2】训练技巧----------------------------------------------------------------------------------------------
(1) 因为使用了 valid conv ,所以采用 Overlap - tile 策略补充图像,其中空白的部分用镜像的方法进行补充。
(2) 因为有池化层,因此要保证输入的图像在经过每一次池化的时候都要是边长偶数。这点与与一般的卷积神经网络不同,
因为一般的网络会使用 padding ,这样会保证卷积前后的大小不变,但是 valid conv 会使卷积后的尺寸变小,
所以要特别注意输入图像的尺寸。一个比较好的方法是从最小分辨率出发沿收缩路径的反方向进行计算,得到输入图像的尺寸。
(3) 预先计算权重图,以此计算后面的加权损失函数。
(4) 加权损失的权重中有一部分是经验值,因此对于不同的任务可以进行调整(只是理论上可以进行调整,并没有试验过)。
(5) 使用标准差为 sqrt(2/N),2/N的高斯分布来进行初始化,其中需要注意的是,对于不同的卷积层,N 的大小也是不同的。
(6) 在收缩路径的最后部加入了 dropout ,隐式地加强了数据增强。
【3】配置文件使用方法--------------------------------------------------------------------------------------
常用的配置文件后缀是.ini、.conf、.py,当然还有使用.json、.txt的,推荐使用常用的.ini、.py,配置文件的名字一般是config便于理解和使用。
.ini 文件是Initialization File的缩写,即初始化文件,是windows的系统配置文件所采用的存储格式,统管windows的各项配置;.py的配置文件,在python项目中是作为一个包导入,严格来说不是配置文件,而是扩展包。
下面将介绍两类配置文件的使用,一类是.ini、.txt,另一类是.py。
.ini、.txt配置文件使用方法是一致的,只是一个后缀的区别,这里以ini配置文件来介绍,这类配置文件我们使用内置configparser库来使用,它可以实现配置文件的写入、更新、删除、读取等操作非常方便,建议使用这种方式。
(1)参考文献
python中配置文件的使用方法
https://blog.csdn.net/weixin_41624982/article/details/88607382
【4】setdata.py
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
from torch.utils.data import Dataset
import PIL.Image as Image
import os
#创建一个列表,存放图像和研磨图像的图像路径
def make_dataset(root):
imgs=[]
n=len(os.listdir(root))//2
for i in range(n):
'''
%3d--可以指定宽度,不足的左边补空格
%-3d--左对齐
%03d---一种左边补0 的等宽格式,比如数字12,%03d出来就是: 012
'''
#img=root+00i.png
#mask=root+00i_mask.png
img=os.path.join(root,"%03d.png"%i)
mask=os.path.join(root,"%03d_mask.png"%i)
imgs.append((img,mask))
return imgs
class LiverDataset(Dataset):
def __init__(self, root, transform=None, target_transform=None):
imgs = make_dataset(root)
self.imgs = imgs
self.transform = transform #原始图像的预处理
self.target_transform = target_transform #研磨图像的预处理
def __getitem__(self, index):
x_path, y_path = self.imgs[index]
img_x = Image.open(x_path)
img_y = Image.open(y_path)
if self.transform is not None: #若设置了预处理
img_x = self.transform(img_x)
if self.target_transform is not None: #若设置了预处理
img_y = self.target_transform(img_y)
return img_x, img_y
def __len__(self):
return len(self.imgs)
【5】 Unetmodel.py
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@File : Unetmodel.py
@Time : 2021/03/23 20:09:25
@Author : Jian Song
@Contact : 1248975661@qq.com
@Desc : None
'''
# here put the import lib
import torch.nn as nn
import torch
from torch import autograd
from visualize import make_dot
from torchsummary import summary
import hiddenlayer as h1
import os
'''
文件介绍:定义了unet网络模型,
******pytorch定义网络只需要定义模型的具体参数,不需要将数据作为输入定义到网络中。
仅需要在使用时实例化这个网络,然后将数据输入。
******tensorflow定义网络时则需要将输入张量输入到模型中,即用占位符完成输入数据的输入。
'''
#把常用的2个卷积操作简单封装下
class DoubleConv(nn.Module):
#通过此处卷积,特征图的大小减4,但是通道数保持不变;
def __init__(self, in_ch, out_ch):
super(DoubleConv, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(in_ch, out_ch, 3, padding=1), #卷积层
nn.BatchNorm2d(out_ch), #归一化层
nn.ReLU(inplace=True), #激活层
nn.Conv2d(out_ch, out_ch, 3, padding=1), #卷积层
nn.BatchNorm2d(out_ch), #归一化层
nn.ReLU(inplace=True) #激活层
)
def forward(self, input):
return self.conv(input)
class Unet(nn.Module):
def __init__(self, in_ch, out_ch):
super(Unet, self).__init__()
#定义网络模型
#下采样-》编码
self.conv1 = DoubleConv(in_ch, 64) #参数为输入通道数和输出通道数
self.pool1 = nn.MaxPool2d(2)
self.conv2 = DoubleConv(64, 128)
self.pool2 = nn.MaxPool2d(2)
self.conv3 = DoubleConv(128, 256)
self.pool3 = nn.MaxPool2d(2)
self.conv4 = DoubleConv(256, 512)
self.pool4 = nn.MaxPool2d(2)
self.conv5 = DoubleConv(512, 1024)
# 逆卷积,也可以使用上采样(保证k=stride,stride即上采样倍数)
self.up6 = nn.ConvTranspose2d(1024, 512, 2, stride=2)#反卷积
self.conv6 = DoubleConv(1024, 512)
self.up7 = nn.ConvTranspose2d(512, 256, 2, stride=2)
self.conv7 = DoubleConv(512, 256)
self.up8 = nn.ConvTranspose2d(256, 128, 2, stride=2)
self.conv8 = DoubleConv(256, 128)
self.up9 = nn.ConvTranspose2d(128, 64, 2, stride=2)
self.conv9 = DoubleConv(128, 64)
self.conv10 = nn.Conv2d(64, out_ch, 1)
#定义网络前向传播过程
def forward(self, x):
c1 = self.conv1(x)
p1 = self.pool1(c1)
c2 = self.conv2(p1)
p2 = self.pool2(c2)
c3 = self.conv3(p2)
p3 = self.pool3(c3)
c4 = self.conv4(p3)
p4 = self.pool4(c4)
c5 = self.conv5(p4)
#上采样
up_6 = self.up6(c5)
#cat函数讲解:https://www.cnblogs.com/JeasonIsCoding/p/10162356.html
merge6 = torch.cat([up_6, c4], dim=1)#此处横着拼接,dim=1表示在行的后面添加上原有矩阵
c6 = self.conv6(merge6)
up_7 = self.up7(c6)
merge7 = torch.cat([up_7, c3], dim=1)
c7 = self.conv7(merge7)
up_8 = self.up8(c7)
merge8 = torch.cat([up_8, c2], dim=1)
c8 = self.conv8(merge8)
up_9 = self.up9(c8)
merge9 = torch.cat([up_9, c1], dim=1)
c9 = self.conv9(merge9)
c10 = self.conv10(c9)
out = nn.Sigmoid()(c10)
return out
#创建一个不存在的文件夹
def makefilepath(folder_path):
if not os.path.exists(folder_path):
os.makedirs(folder_path)
if __name__ == '__main__':
#[1]显示所有参数
myUnet=Unet(1,2)
print(myUnet)
print("[1] print success!")
print("-"*100)
#[2]hiddenlayer显示pdf文档
h1_graph=h1.build_graph(myUnet,torch.zeros([1,1,512,512]))
h1_graph.theme=h1.graph.THEMES["blue"].copy()
modelimage="./modelimage/"
makefilepath(modelimage)
h1_graph.save(modelimage+"unet_model_image.png",format="png")
print("[2] hiddenlayer show success!")
print("-"*100)
#[3]summary表格模式显示
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
myUnet1 = myUnet.to(device)
summary(myUnet1,input_size=(1,512,512))
print("[3] summary show success!")
print("-"*100)
#[4]模型PDF模型显示
y = myUnet (torch.zeros([1,1,512,512]))
g = make_dot(y)
g.view()
print("[4] make_dot show success!")
print("-"*100)
【6】 visualize.py
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@File : visualize.py
@Time : 2021/06/04 09:45:49
@Author : Jian Song
@Contact : 1248975661@qq.com
@Desc : None
'''
# here put the import lib
'''
该文档用于网络模型可视化显示
'''
from graphviz import Digraph
import torch
from torch.autograd import Variable
def make_dot(var, params=None):
""" Produces Graphviz representation of PyTorch autograd graph
Blue nodes are the Variables that require grad, orange are Tensors
saved for backward in torch.autograd.Function
Args:
var: output Variable
params: dict of (name, Variable) to add names to node that
require grad (TODO: make optional)
"""
if params is not None:
assert isinstance(params.values()[0], Variable)
param_map = {id(v): k for k, v in params.items()}
node_attr = dict(style='filled',
shape='box',
align='left',
fontsize='12',
ranksep='0.1',
height='0.2')
dot = Digraph(node_attr=node_attr, graph_attr=dict(size="12,12"))
seen = set()
def size_to_str(size):
return '('+(', ').join(['%d' % v for v in size])+')'
def add_nodes(var):
if var not in seen:
if torch.is_tensor(var):
dot.node(str(id(var)), size_to_str(var.size()), fillcolor='orange')
elif hasattr(var, 'variable'):
u = var.variable
name = param_map[id(u)] if params is not None else ''
node_name = '%s\n %s' % (name, size_to_str(u.size()))
dot.node(str(id(var)), node_name, fillcolor='lightblue')
else:
dot.node(str(id(var)), str(type(var).__name__))
seen.add(var)
if hasattr(var, 'next_functions'):
for u in var.next_functions:
if u[0] is not None:
dot.edge(str(id(u[0])), str(id(var)))
add_nodes(u[0])
if hasattr(var, 'saved_tensors'):
for t in var.saved_tensors:
dot.edge(str(id(t)), str(id(var)))
add_nodes(t)
add_nodes(var.grad_fn)
return dot
【7】完整代码下载
链接:https://pan.baidu.com/s/14AKr5H0-rwLMzDsgBoMT-w
提取码:7eap