PyTorch 肺部感染识别2.0版本

110 篇文章 12 订阅

PyTorch 肺部感染识别2.0版本

转载自B站视频:https://www.bilibili.com/video/BV1wk4y1B77W/?spm_id_from=333.788

目录

一、完整代码

二、自己手敲代码

正文:

一、完整代码

# PyTorch肺部感染识别1.0版本 作者源代码
# 导入必要的库
import torch
import torch.nn as nn
import numpy as np
import torch.optim as optim
from torchvision import transforms, datasets, models, utils
from torchsummary import summary # 可视化训练过程
from torch.utils.data import DataLoader
import time
import matplotlib.pyplot as plt
import os
import seaborn as sns
import pandas as pd
from mlxtend.plotting import plot_confusion_matrix
from sklearn.metrics import confusion_matrix
from PIL import Image

# 分为为train, val, test定义transform
image_transforms = {
    'train': transforms.Compose([
        transforms.RandomResizedCrop(size=300, scale=(0.8, 1.1)),  # 功能:随机长宽比裁剪原始图片, 表示随机crop出来的图片会在的0.08倍至1.1倍之间
        transforms.RandomRotation(degrees=10),  # 功能:根据degrees随机旋转一定角度, 则表示在(-10,+10)度之间随机旋转
        transforms.ColorJitter(0.4, 0.4, 0.4),  # 功能:修改亮度、对比度和饱和度
        transforms.RandomHorizontalFlip(),  # 功能:水平翻转
        transforms.CenterCrop(size=256),  # 功能:根据给定的size从中心裁剪,size - 若为sequence,则为(h,w),若为int,则(size,size)
        transforms.ToTensor(),  # numpy --> tensor
        # 功能:对数据按通道进行标准化(RGB),即先减均值,再除以标准差
        transforms.Normalize([0.485, 0.456, 0.406],  # mean
                             [0.229, 0.224, 0.225])  # std
    ]),

    'val': transforms.Compose([
        transforms.Resize(300),
        transforms.CenterCrop(256),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406],  # mean
                             [0.229, 0.224, 0.225])  # std
    ]),

    'test': transforms.Compose([
        transforms.Resize(300),
        transforms.CenterCrop(256),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406],  # mean
                             [0.229, 0.224, 0.225])  # std
    ])
}


# 加载数据集
# 数据集所在目录路径
data_dir = './chest_xray/'
# train路径
train_dir = data_dir + 'train/'
# val路径
val_dir = data_dir + 'val/'
# test路径
test_dir = data_dir + 'test/'

# 从文件中读取数据
datasets = {
    'train' : datasets.ImageFolder(train_dir, transform=image_transforms['train']), # 读取train中的数据集,并transform
    'val' : datasets.ImageFolder(val_dir, transform=image_transforms['val']),  # 读取val中的数据集,并transform
    'test' : datasets.ImageFolder(test_dir, transform=image_transforms['test']) #  读取test中的数据集,并transform
}

# 定义BATCH_SIZE
BATCH_SIZE = 128 # 每批读取128张图片

# DataLoader : 创建iterator, 按批读取数据
dataloaders = {
    'train' : DataLoader(datasets['train'], batch_size=BATCH_SIZE, shuffle=True), # 训练集
    'val' : DataLoader(datasets['val'], batch_size=BATCH_SIZE, shuffle=True), # 验证集
    'test' : DataLoader(datasets['test'], batch_size=BATCH_SIZE, shuffle=True) # 测试集
}

# 创建label的键值对
LABEL = dict((v, k) for k, v in datasets['train'].class_to_idx.items())

LABEL

# train 简介
dataloaders['train'].dataset

dataloaders['train'].dataset.classes # train下的类别

dataloaders['train'].dataset.root # train的路径

# 肺部正常的图片
files_normal = os.listdir(os.path.join(str(dataloaders['train'].dataset.root), 'NORMAL'))
files_normal

# 肺部感染的图片
files_pneumonia = os.listdir(os.path.join(str(dataloaders['train'].dataset.root), 'PNEUMONIA'))
files_pneumonia

# val 简介
dataloaders['val'].dataset

# test 简介
dataloaders['test'].dataset

# 导入SummaryWriter
from torch.utils.tensorboard import SummaryWriter

# SummaryWriter() 向事件文件写入事件和概要

# 定义日志路径
log_path = 'logdir/'

# 定义函数:获取tensorboard writer
def tb_writer():
    timestr = time.strftime("%Y%m%d_%H%M%S")  # 时间格式
    writer = SummaryWriter(log_path + timestr)  # 写入日志
    return writer

writer = tb_writer()

# 第1种方法:显示部分图片集
images, labels = next(iter(dataloaders['train']))  # 获取到一批数据

# 定义图片显示方法
def imshow(img):
    img = img / 2 + 0.5  # 逆正则化
    np_img = img.numpy()  # tensor --> numpy
    plt.imshow(np.transpose(np_img, (1, 2, 0)))  # 改变通道顺序
    plt.show()

grid = utils.make_grid(images)  # make_grid的作用是将若干幅图像拼成一幅图像
imshow(grid)  # 展示图片

# 在summary中添加图片数据
writer.add_image('X-Ray grid', grid,
                 0)  # add_image(tag, img_tensor, global_step=None, walltime=None, dataformats='CHW')

writer.flush()  # 把事件文件写入到磁盘

# 获取一张图片tensor
dataloaders['train'].dataset[4] # 返回:tensor, label

# 第2种方法:显示一张图片
def show_sample(img, label):
    print("Label : ", dataloaders['train'].dataset.classes[label]) # 输出标签
    img = img.numpy().transpose((1, 2, 0)) # 改变shape顺序
    mean = np.array([0.485, 0.456, 0.406]) # 均值
    std = np.array([0.229, 0.224, 0.225]) # 标准差
    img = img * std + mean # 逆向复原
    img = np.clip(img, 0, 1) # np.clip() 将inp中的元素值限制在(0,1)之间,最小值为0,最大值为1。小于min的等于min,大于max等于max
    plt.imshow(img)
    plt.axis('off') # 关闭坐标轴

show_sample(*dataloaders['train'].dataset[4]) # 显示第5张图片


# 第3种方法:显示一张图片
def show_image(img):
    plt.figure(figsize=(8, 8))  # 显示大小
    plt.imshow(img)  # 显示图片
    plt.axis('off')  # 关闭坐标轴
    plt.show()


# 读取图片
one_img = Image.open(dataloaders['train'].dataset.root + 'NORMAL/IM-0239-0001.jpeg')

# 调用函数
show_image(one_img)


# 记录错误分类的图片
def misclassified_images(pred, writer, target, images, output, epoch, count=10):
    misclassified = (pred != target.data) # 判断是否一致
    for index, image_tensor in enumerate(images[misclassified][:count]):
        img_name = 'Epoch:{}-->Predict:{}-->Actual:{}'.format(epoch, LABEL[pred[misclassified].tolist()[index]],
                                                              LABEL[target.data[misclassified].tolist()[index]])
        writer.add_image(img_name, image_tensor, epoch)


# 自定义池化层

class AdaptiveConcatPool2d(nn.Module):
    def __init__(self, size=None):
        super(AdaptiveConcatPool2d,self).__init__()
        size = size or (1, 1) # kernel大小
        # 自适应算法能够自动帮助我们计算核的大小和每次移动的步长。
        self.avgPooling = nn.AdaptiveAvgPool2d(size) # 自适应平均池化
        self.maxPooling = nn.AdaptiveMaxPool2d(size) # 最大池化
    def forward(self, x):
        # 拼接avg和max
        return torch.cat([self.maxPooling(x), self.avgPooling(x)], dim=1)


# 迁移学习:获取预训练模型,并替换池化层和全连接层
def get_model():
    # 获取欲训练模型 restnet50
    model = models.resnet50(pretrained=True)
    # 冻结模型参数
    for param in model.parameters():
        param.requires_grad = False
    # 替换最后2层:池化层和全连接层
    # 池化层
    model.avgpool = AdaptiveConcatPool2d()
    # 全连接层
    model.fc = nn.Sequential(
        nn.Flatten(), # 拉平
        nn.BatchNorm1d(4096), # 加速神经网络的收敛过程,提高训练过程中的稳定性
        nn.Dropout(0.5), # 丢掉部分神经元
        nn.Linear(4096, 512), # 全连接层
        nn.ReLU(), # 激活函数
        nn.BatchNorm1d(512),
        nn.Dropout(0.5),
        nn.Linear(512, 2), # 2个输出
        nn.LogSoftmax(dim=1) # 损失函数:将input转换成概率分布的形式,输出2个概率
    )
    return model


# 定义训练函数
def train_val(model, device, train_loader, val_loader, optimizer, criterion, epoch, writer):
    model.train()
    total_loss = 0.0
    val_loss = 0.0
    val_acc = 0
    for batch_id, (images, labels) in enumerate(train_loader):
        # 部署到device上
        images, labels = images.to(device), labels.to(device)
        # 梯度置0
        optimizer.zero_grad()
        # 模型输出
        outputs = model(images)
        # 计算损失
        loss = criterion(outputs, labels)
        # 反向传播
        loss.backward()
        # 更新参数
        optimizer.step()
        # 累计损失
        total_loss += loss.item() * images.size(0)
    # 平均训练损失
    train_loss = total_loss / len(train_loader.dataset)
    # 写入到writer中
    writer.add_scalar('Training Loss', train_loss, epoch)
    # 写入到磁盘
    writer.flush()

    model.eval()
    with torch.no_grad():
        for images, labels in val_loader:
            images, labels = images.to(device), labels.to(device)
            outputs = model(images)  # 前向传播输出
            loss = criterion(outputs, labels)  # 损失
            val_loss += loss.item() * images.size(0)  # 累计损失
            _, pred = torch.max(outputs, dim=1)  # 获取最大概率的索引
            correct = pred.eq(labels.view_as(pred))  # 返回:tensor([ True,False,True,...,False])
            accuracy = torch.mean(correct.type(torch.FloatTensor))  # 准确率
            val_acc += accuracy.item() * images.size(0)  # 累计准确率
        # 平均验证损失
        val_loss = val_loss / len(val_loader.dataset)
        # 平均准确率
        val_acc = val_acc / len(val_loader.dataset)

    return train_loss, val_loss, val_acc


# 定义测试函数
def test(model, device, test_loader, criterion, epoch, writer):
    model.eval()
    total_loss = 0.0
    correct = 0.0 # 正确数
    with torch.no_grad():
        for batch_id, (images, labels) in enumerate(test_loader):
            images, labels = images.to(device), labels.to(device)
            # 输出
            outputs = model(images)
            # 损失
            loss = criterion(outputs, labels)
            # 累计损失
            total_loss += loss.item()
            # 获取预测概率最大值的索引
            _, predicted = torch.max(outputs, dim=1)
            # 累计正确预测的数
            correct += predicted.eq(labels.view_as(predicted)).sum().item()
            # 错误分类的图片
            misclassified_images(predicted, writer, labels, images, outputs, epoch)
        # 平均损失
        avg_loss = total_loss / len(test_loader.dataset)
        # 计算正确率
        accuracy = 100 * correct / len(test_loader.dataset)
        # 将test的结果写入write
        writer.add_scalar("Test Loss", total_loss, epoch)
        writer.add_scalar("Accuracy", accuracy, epoch)
        writer.flush()
        return total_loss, accuracy

# 定义训练流程

# 是否有GPU
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

print(device.type)

# 模型部署到device
model = get_model().to(device)

# 损失函数
criterion = nn.NLLLoss()

# 优化器
optimizer = optim.SGD(model.parameters(), lr=0.001)

# 定义训练流程函数
def train_epochs(model, device, dataloaders, criterion, optimizer, epochs, writer):
    # 输出信息
    print("{0:>15} | {1:>15} | {2:>15} | {3:>15} | {4:>15} | {5:>15}".format('Epoch', 'Train Loss', 'val_loss', 'val_acc', 'Test Loss', 'Test_acc'))
    # 初始最小的损失
    best_loss = np.inf
    # 开始训练、测试
    for epoch in range(epochs):
        # 训练,return: loss
        train_loss, val_loss, val_acc = train_val(model, device, dataloaders['train'], dataloaders['val'], optimizer, criterion, epoch, writer)
        # 测试,return: loss + accuracy
        test_loss, test_acc = test(model, device, dataloaders['test'], criterion, epoch, writer)
        # 判断损失是否最小
        if test_loss < best_loss:
            best_loss = test_loss # 保存最小损失
            # 保存模型
            torch.save(model.state_dict(), 'model.pth')
        # 输出结果
        print("{0:>15} | {1:>15} | {2:>15} | {3:>15} | {4:>15} | {5:>15}".format(epoch, train_loss, val_loss, val_acc, test_loss, test_acc))
        writer.flush()



# 调用函数
epochs=10
train_epochs(model, device, dataloaders, criterion, optimizer, epochs, writer)
writer.close()

def plot_confusion(cm):
    plt.figure()
    plot_confusion_matrix(cm, figsize=(12, 8), cmap=plt.cm.Blues) # 参数设置
    plt.xticks(range(2), ['Normal', 'Pneumonia'], fontsize=14)
    plt.yticks(range(2), ['Normal', 'Pneumonia'], fontsize=14)
    plt.xlabel('Predicted Label', fontsize=16)
    plt.ylabel('True Label', fontsize=16)
    plt.show()

def accuracy(outputs, labels):
    # 计算正确率
    _, preds = torch.max(outputs, dim=1)
    correct = torch.tensor(torch.sum(preds == labels).item() / len(preds))
    return correct


def metrics(outputs, labels):
    _, preds = torch.max(outputs, dim=1)
    # precision, recall, F1
    # 混淆矩阵
    cm = confusion_matrix(labels.cpu().numpy(), preds.cpu().numpy())
    # 绘制混淆矩阵
    plot_confusion(cm)
    # 获取tn, fp, fn, tp
    tn, fp, fn, tp = cm.ravel()
    # 精准率
    precision = tp / (tp + fp)
    # 召回率
    recall = tp / (tp + fn)
    # f1 score
    f1 = 2 * ((precision * recall) / (precision + recall))
    return precision, recall, f1

# 计算testloader
precisions = []
recalls = []
f1s = []
accuracies = []

with torch.no_grad():
    model.eval()
    for datas, labels in dataloaders['test']:
        datas, labels = datas.to(device), labels.to(device)
        # 预测输出
        outputs = model(datas)
        # 计算metrics
        precision, recall, f1 = metrics(outputs, labels)
        acc = accuracy(outputs, labels)
        # 保存结果
        precisions.append(precision)
        recalls.append(recall)
        f1s.append(f1)
        accuracies.append(acc.item())

['{:.2f}%'.format(pre*100) for pre in precisions]# 精准率 precision

['{:.2f}%'.format(r*100) for r in recalls]# 召回率 recall

['{:.2f}%'.format(f*100) for f in f1s]# f1

['{:.2f}%'.format(a*100) for a in accuracies]# 准确率 accuracy

二、自己手敲代码

# 1. 导入必要的库
import torch
import torch.nn as nn
import numpy as np
import torch.optim as optim
from torchvision import transforms, datasets, models, utils
from torchsummary import summary  # 可视化训练过程
from torch.utils.data import DataLoader
import time
import matplotlib.pyplot as plt
import os
import seaborn as sns
import pandas as pd
from mlxtend.plotting import plot_confusion_matrix
from sklearn.metrics import confusion_matrix
from PIL import Image
from datetime import datetime
import ssl

ssl._create_default_https_context = ssl._create_unverified_context




# 2. 分为train,val,test定义transform
image_transforms= {
    'train':transforms.Compose([
        transforms.RandomResizedCrop(size = 300, scale = (0.8, 1.1)),  #随机长度比例裁剪原始图片,表示随机crop出来的图片会在0.8倍和1.1倍之间
        transforms.RandomRotation(degrees = 10),  # 根据degrees随机旋转一定角度,则表示在(-10,+10)度之间随机旋转
        transforms.ColorJitter(0.4, 0.4, 0.4),  # 修改亮度、对比度、饱和度
        transforms.RandomHorizontalFlip(),  # 水平翻转
        transforms.CenterCrop(size = 256),  # 根据给定的size从中心裁剪,size 若为sequence则为(h,w),若为int,则(size,size)
        transforms.ToTensor(),  # numpy-----tensor
        # 对数据按通道进行标准化(RGB),即先减均值,再除以标准差
        transforms.Normalize([0.485, 0.456, 0.406],  # mean
                             [0.229, 0.224, 0.225])  # std
    ]),
    'val':transforms.Compose([
        transforms.Resize(300),
        transforms.CenterCrop(256),
        transforms.ToTensor(),  # numpy-----tensor
        # 对数据按通道进行标准化(RGB),即先减均值,再除以标准差
        transforms.Normalize([0.485, 0.456, 0.406],  # mean
                             [0.229, 0.224, 0.225])  # std
    ]),
    'test':transforms.Compose([
        transforms.Resize(300),
        transforms.CenterCrop(256),
        transforms.ToTensor(),  # numpy-----tensor
        # 对数据按通道进行标准化(RGB),即先减均值,再除以标准差
        transforms.Normalize([0.485, 0.456, 0.406],  # mean
                             [0.229, 0.224, 0.225])  # std
    ])
}


# 3. 加载数据集
# 数据集所在目录路径
data_dir = 'F:/Graduate students grade three/postgraduate studies/first year of postgraduate study/Project/PyTorch lung infection identification/one_version/chest_xray/'
# train路径
train_dir = data_dir + 'train/'
# val路径
val_dir = data_dir + 'val/'
# test路径
test_dir = data_dir + 'test/'

# 4. 从文件中读取数据
datas = {
    'train': datasets.ImageFolder(train_dir, transform = image_transforms['train']),  # 读取train中的数据集,并transform
    'val': datasets.ImageFolder(val_dir, transform = image_transforms['val']),  # 读取val中的数据集,并transform
    'test': datasets.ImageFolder(test_dir, transform = image_transforms['test']),  # 读取test中的数据集,并transform
}

# 5. 定义BATCH_SIZE
BATCH_SIZE = 16  # 每批读取16张图片

# 6. DataLoader: 创建iterator,按批读取数据
dataloaders = {
    'train': DataLoader(datas['train'], batch_size = BATCH_SIZE,shuffle = True),  # 训练集
    'val': DataLoader(datas['val'], batch_size = BATCH_SIZE,shuffle = True),  # 验证集
    'test': DataLoader(datas['test'], batch_size = BATCH_SIZE,shuffle = True),  # 测试集
}

# 7. 创建label的键值对
LABEL = dict((v, k) for k, v in datas['train'].class_to_idx.items())
print(LABEL)

# 8. train 简介
dataloaders['train'].dataset
print(dataloaders['train'].dataset)

# 9. train下的类别
dataloaders['train'].dataset.classes
print(dataloaders['train'].dataset.classes)

# 10. train路径
dataloaders['train'].dataset.root
print(dataloaders['train'].dataset.root)

# 11. 肺部正常图片
# listdir:把文件名字全部放在一个列表中,文件的名字就是列表的元素;os.path.join:路径的拼接,拼接str(dataloaders['train'].dataset.root)和NORMAL,最终组成一个完整的路径
file_normal = os.listdir(os.path.join(str(dataloaders['train'].dataset.root), 'NORMAL'))
print(file_normal)

# 12. 肺部感染图片
# listdir:把文件名字全部放在一个列表中,文件的名字就是列表的元素;os.path.join:路径的拼接,拼接str(dataloaders['train'].dataset.root)PNEUMONIA,最终组成一个完整的路径
file_pneumonia = os.listdir(os.path.join(str(dataloaders['train'].dataset.root), 'PNEUMONIA'))
print(file_pneumonia)

# 13. val简介 展示val的基本信息
dataloaders['val'].dataset
print(dataloaders['val'].dataset)

# 14. test简介 展示test的基本信息
dataloaders['test'].dataset
print(dataloaders['test'].dataset)

# 15. 导入SummaryWriter
from torch.utils.tensorboard import SummaryWriter
# SummaryWriter() 向事件写入事件和概要

# 16. 定义日志路径,logdir文件夹要事先在本地创建好
log_path = 'logdir/'

# 17. 定义函数:获取tensorboard writer
def tb_writer():
    now = datetime.now()  # current date and time
    timestr = date_time = now.strftime("%Y-%m-%d, %H:%M:%S")  # 时间格式,每份日志的时间
    # writer = SummaryWriter(log_path + timestr)  # 写入日志,在日志路径下面创建时间文件夹,把该时间点的日志,写在这个文件夹下面
    writer = SummaryWriter(log_path, timestr)  # 写入日志,在日志路径下面创建时间文件夹,把该时间点的日志,写在这个文件夹下面
    return writer


writer = tb_writer()

# 18. 第1种方法: 显示部分图片集
images, labels = next(iter(dataloaders['train']))  # 获取到一批数据,也就是上面设置的BATCH_SIZE张图片

# 定义图片显示方法;整个过程图片都是以张量的形式存在
def imshow(img):
    img = img /2 + 0.5  # 逆正则化
    np_img = img.numpy()  # tensor-----numpy
    plt.imshow(np.transpose(np_img, (1, 2, 0)))  # 改变通道顺序
    plt.show()

grid = utils.make_grid(images)  # make_grid的作用是将若干张图片拼成一张图片
imshow(grid)  # 显示图片

# 在summary中添加图片数据
# 把运行结果的图片保存在日志中
writer.add_image('X-ray grid', grid, 0)

writer.flush()  # 把事件文件写入到磁盘中


# 获取一张图片tensor
dataloaders['train'].dataset[4]  # 返回的是训练集第五张图片的张量数据和标签
print(dataloaders['train'].dataset[4])

# 19. 第2种方法:显示图片
# 这种方法使用的也是图片的张量
def show_sample(img, label):
    print('label : ', dataloaders['train'].dataset.classes[label])  # 输出标签
    img = img.numpy().transpose((1, 2, 0))  # 改变shape顺序
    mean = np.array([0.485, 0.456, 0.406])  # 均值
    std = np.array([0.229, 0.224, 0.225])  # 方差
    img = img * std + mean  # 逆向复原
    img =np.clip(img, 0, 1)  # np.clip()将inp中的元素值限制在(0,1)之间,最小值为0,最大值为1,小于min的等于min,大于max等于max
    plt.imshow(img)
    plt.axis('off')   # 关闭坐标轴

# 显示的是训练集中第5张图片
show_sample(*dataloaders['train'].dataset[4])

# 20. 第3种方法:显示一张图片
# 这种方法的限制是,文件保存的图片必须使安装jpg格式保存的
def show_image(img):
    plt.figure(figsize = (8, 8))  # 显示大小
    plt.imshow(img)  # 显示图片
    plt.axis('off')  # 关闭坐标轴,由于在此次显示中坐标轴没什么作用,所以选择不显示,如果需要也可以设置为显示’on‘
    plt.show()

# 读取图片
# 打开图片的函数,dataloaders['train'].dataset.root + 'NORMAL/IM-0239-001.jpeg'这里是一个路径拼接,前面是图片的位置,后面是图片的文件名
one_img = Image.open(dataloaders['train'].dataset.root + 'NORMAL/IM-0115-0001.jpeg')

# 调用函数
show_image(one_img)


# 21. 记录错误分离的图片
# epoch:那一轮预测错误
# count = 10:只展示前10张预测错误的图片
def misclassified_images(pred, writer, target, images, output, epoch, count = 10):
    misclassified = (pred != target.data)  # 判断是否一致
    for index, image_tensor in enumerate(images[misclassified][:count]):
        img_name = 'Epoch : {}---Predict: {} ---Actual : {}'.format(epoch, LABEL[pred[misclassified].tolist()[index]],
                                                                     LABEL[target.data[misclassified].tolist()[index]])
        writer.add_image(img_name, image_tensor, epoch)

# 22. 自定义池化层
class AdaptiveConcatPool2d(nn.Module):
    # size:修改池化层的实质就是修改卷积核的大小
    def __init__(self, size = None):
        super(AdaptiveConcatPool2d, self).__init__()
        size = size or (1, 1)  # 池化层的卷积核的大小,默认值为(1,1)
        # 自适应算法能够自动帮助我们计算核的大小和每次一到的步长
        self.avgpooling = nn.AdaptiveAvgPool2d(size)  # 自适应平均池化
        self.maxpooling = nn.AdaptiveMaxPool2d(size)  # 最大池化
    def forward(self, x):
        # torch.cat:将两个池化层拼接起来
        # 1 :按照列的方向进行拼接
        return torch.cat([self.maxpooling(x), self.avgpooling(x)], dim = 1)



# 23. 迁移学习:拿到一个成熟的模型,进行模型微调
def get_model():
    model = models.resnet50(pretrained=True)  # 获取预训练模型
    # 冻结预训练模型中的所有参数(由于我们使用的是resnet50网络,它里面的很多参数已经很好了不需要我们再训练了,但是由于这个模型并不是针对肺部图片的模型,所以我们需要对这个模型做一些微调,改变一些参数或者层,所以需要先把所有参数都冻结起来,需要改那个参数,直接拿出来改就行)
    for param in model.parameters():
        param.requires_grad = False
    # 微调模型:替换resnet最后的两层网络,返回一个新的网络模型。(本项目修改的是最后两层)
    # model.avgpool:获取到了resnet最后的池化层
    model.avgpool = AdaptiveConcatPool2d()  # 池化层替换
    # 改变全连接层
    model.fc = nn.Sequential(
        nn.Flatten(),  # 所有维度拉平
        nn.BatchNorm1d(4096),  # 256 * 6 * 6 ----------------4096   正则化处理
        nn.Dropout(0.5),  # 丢掉一些神经元
        nn.Linear(4096, 512),
        nn.ReLU(),  # 激活层
        nn.BatchNorm1d(512),  # 正则化处理
        nn.Dropout(0.5),  # 丢掉一些神经元
        nn.Linear(512, 2),  # 最后进行一个二分类,因为最后我们要判断这个肺部图片是正常的还是被感染的
        nn.LogSoftmax(dim = 1),  # 损失函数,dim = 1:维度为1
    )
    return model


# 24. 定义训练函数
# writer:将训练过程写进日志中,为后续了解训练过程做准备,比如写进每次训练的损失
# train_loader:训练数据集
# criterion: 损失函数
# optimizer:优化器
def train_val(model, device, train_loader, val_loader, optimizer, criterion, epoch, writer):
    model.train()
    total_loss = 0.0  # 总损失值初始化为0
    val_loss = 0.0
    val_acc = 0
    # 循环读取训练数据集, 更新模型参数
    # batch_id:获取正在运行的图片的批次,图片一个批次是8张肺部图片
    for batch_id, (images, labels) in enumerate(train_loader):
        images, labels = images.to(device), labels.to(device)
        optimizer.zero_grad()  # 梯度度初始化为0
        outputs = model(images)  # 训练后的输出
        loss =criterion(outputs, labels)  # 计算损失
        loss.backward()  # 反向传播
        optimizer.step()  # 更新参数
        total_loss += loss.item() + images.size(0)  # 累计训练损失
    # 平均损失
    train_loss = total_loss / len(train_loader.dataset)
    # 把每个批次的损失写进日志中,每个批次损失占总训练集的比列
    writer.add_scalar('Training Loss', total_loss / len(train_loader.dataset), epoch)
    writer.flush()  # 每写一次,都需要刷新日志

    model.eval()
    # 循环读取数据
    with torch.no_grad():  # 声明不进行梯度的更新和参数的更新
        for images, labels in val_loader:
            images, labels = images.to(device), labels.to(device)
            # 预测输出
            output = model(images)
            # 计算损失
            loss = criterion(output, labels)
            val_loss = val_loss + loss.item() * images.size(0)  # 累计损失
            # 获取预存结果中每一行数据概率最大的下标
            _, pred = torch.max(output, dim=1)  # 获取最大概率的索引
            # 判断预测是否正确,并把判断结果保存成tensor表,这个表示布尔类型的值(true和false),由于一次批处理128张图片,所以这里就有128个结果
            correct = pred.eq(labels.view_as(pred))  # 返回:tensor([True,False......])
            # 计算correct中true占128中的百分比,也就是正确率
            accuracy = torch.mean(correct.type(torch.FloatTensor))
            # 累计正确率
            val_acc = val_acc + accuracy.item() * images.size(0)
        # 平均验证损失
        val_loss /= len(val_loader.dataset)
        # 平均正确率
        val_acc = val_acc / len(val_loader.dataset)
    return train_loss, val_loss, val_acc



#  25. 定义测试函数
def test(model, device, test_loader, criterion, epoch, writer):
    model.eval()
    # 损失和正确个数
    total_loss = 0.0
    correct = 0.0
    # 循环读取数据
    with torch.no_grad():
        for batch_id, (images, labels) in enumerate(test_loader):
            images, labels = images.to(device), labels.to(device)
            # 预测输出
            outputs = model(images)
            # 计算损失
            loss = criterion(outputs, labels)
            # 累计损失
            total_loss = total_loss + loss.item()
            # 获取预存结果中每一行数据概率最大的下标
            _, predicted = torch.max(outputs, dim=1)
            # 累计预测正确的个数
            correct += predicted.eq(labels.view_as(predicted)).sum().item()
            # 错误分类的图片
            misclassified_images(predicted, writer, labels, images, outputs, epoch)
        # 平均损失
        avg_loss = total_loss / len(test_loader.dataset)
        # 计算正确率
        accuracy = 100 * correct / len(test_loader.dataset)
        # 写进日志
        writer.add_scalar('Test Loss', total_loss, epoch)
        writer.add_scalar('Accuracy', accuracy, epoch)
        # 刷新
        writer.flush()
        return total_loss, accuracy


# 26. 定义训练流程
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')  # 选择使用GPU运行程序还是CPU运行程序

model = get_model().to(device)

# 27. 损失函数
criterion = nn.NLLLoss()

# 28. 优化器
optimizer = optim.SGD(model.parameters(), lr = 0.001)

# 29. 定义训练流程函数
def train_epochs(model, device, dataloaders, criterion, optimizer, epochs, writer):
    """
    Returns:
        返回一个训练过后最好的模型
    """
    print("{0:>15} | {1:>15} | {2:>15} | {3:>15} |{4:>15} |{5:>15}".format('Epoch', 'Train Loss', 'val_loss', 'val_acc', 'Test Loss', 'Test_acc'))
    best_score = np.inf  # 假设最好的预测值,np.inf :表示正无穷
    # 开始循环读取数据进行训练和验证
    for epoch in range(epochs):

        train_loss, val_loss, val_acc = train_val(model, device, dataloaders['train'], dataloaders['val'],  optimizer, criterion, epoch, writer)

        test_loss, test_acc = test(model, device, dataloaders['test'], criterion, epoch, writer)

        if test_loss < best_score:
            best_score = test_loss  # 保留最小损失
            torch.save(model.state_dict(), 'model.pth')  # 保存模型 # state_dict变量存放训练过程中需要学习的权重和偏置系数

        print("{0:>15} | {1:>15} | {2:>15} | {3:>15} |{4:>15} |{5:>15}".format(epoch, train_loss, val_loss, val_acc, test_loss, test_acc))
        writer.flush()


# 30. 调用函数
epochs = 10
train_epochs(model, device, dataloaders, criterion, optimizer, epochs, writer)
writer.close()


# 31. 制作混淆矩阵
# cm:混淆矩阵的数值
def plot_confusion(cm):
    plt.figure()
    # figize = (12, 8):画布大小 cmap = plt.cm.Blues:颜色设置
    plot_confusion_matrix(cm, figize = (12, 8), cmap = plt.cm.Blues)  # 参数设置
   # range(2):横轴的范围,两个类别,fontsize:字体大小
    plt.xticks(range(2), ['Normal', 'Pneumonia'], fontsize = 14)
    plt.yticks(range(2), ['Normal', 'Pneumonia'], fontsize=14)
    plt.xlabel('Predicted Label', fontsize = 16)
    plt.ylabel('True Label', fontsize = 16)
    plt.show()


# 32. 计算正确率
def accuracy(outputs, labels):
    _, preds = torch.max(outputs, dim=1)
    correct = torch.tensor(torch.sum(preds == labels).item() / len(preds))
    return correct

# 33. 
def metrics(outputs, labels):
    _, preds = torch.max(outputs, dim =1)
    # precision, recall ,F1
    # 混淆矩阵
    cm = confusion_matrix(labels.cpu().numpy, preds.cpu().numpy())
    # 绘制混淆矩阵
    plot_confusion(cm)
    # 获取 tn, fp, fn, tp
    tn, fp, fn, tp = cm.ravel()
    # 精准率
    precision = tp / (tp + fp)
    # 召回率
    recall = tp / (tp + fn)
    # f1 score
    f1 = 2 * ((precision * recall) / (precision + recall))
    return precision, recall, f1


# 34. 计算testloader
precisions = []
recalls = []
f1s = []
accuracies = []

with torch.no_grad():
    model.eval()
    for datas, labels in dataloaders['test']:
        datas, labels =datas.to(device), labels.to(device)
        # 预测输出
        outputs = model(datas)
        # 计算metrics
        precision, recall, f1 = metrics(outputs, labels)
        acc = accuracy(outputs, labels)
        # 保存结果
        precisions.append(precision)
        recalls.append(recall)
        f1s.append(f1)
        accuracies.append(acc.item())

#  35. 精确率 precision
print(['{:.2f}%'.format(pre * 100) for pre in precisions])

# 36. 召回率 recall
print(['{:.2f}%'.format(r * 100) for r in recalls])

#  37. f1
print(['{:.2f}%'.format(f * 100) for f in f1s])

# 38. 准确率 accuracy
print(['{:.2f}%'.format(a * 100) for a in accuracies])













  • 0
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值