首页 > 其他分享 > 机器学习---花卉识别

机器学习---花卉识别

时间:2022-12-24 05:55:06浏览次数:37  
标签:target 模型 self --- 花卉 path model 识别 metric


一、选题的背景

因为我以前的专业是林学,平时需要上山出外业经常遇到一些不认识的花,那时候问老师或者自身学习记住了不少,但是渐渐的也忘记了,所以我选了花卉识别作为题目。在社会上可以帮助花卉爱好者认识更多的花,在经济上可以帮助农民快速识别不同的花卉品种,提高农业生产的效率和产量。

二、机器学习案例设计方案 1.本选题采用的机器学习案例(训练集与测试集)的来源描述 从网站下载数据集,构建神经网络,训练模型,数据从牛津大学花的数据集获取https://www.robots.ox.ac.uk/~vgg/data/flowers/17/

数据集数量多,本选题采用的训练集,训练集、验证码和测试集的比例按照6 2 2进行  首先获取数据集的类别名称并在目标目录下创建训练集、验证集和测试集,在每个文件夹下创建类别文件夹。然后遍历每一类,获取该类别的文件列表打乱顺序,并按照比例划分数据集,将图片复制到文件夹。

import os
import random
import shutil
from shutil import copy2
import os.path as osp
#数据集划分,以622比例划分
def data_set_split(src_data_folder, target_data_folder, train_scale=0.6, val_scale=0.2, test_scale=0.2):
    class_names = os.listdir(src_data_folder)  # 获取原始数据集的类别名称
    split_names = ['train', 'val', 'test']  # 在目标目录下创建文件夹
    for split_name in split_names:
        split_path = os.path.join(target_data_folder, split_name)  # 创建训练、验证、测试的文件夹
        if os.path.isdir(split_path):
            pass
        else:
            os.mkdir(split_path)
        # 然后在split_path的目录下创建类别文件夹
        for class_name in class_names:  # 创建类别文件夹
            class_split_path = os.path.join(split_path, class_name)  # 创建各类别的文件夹
            if os.path.isdir(class_split_path):
                pass
            else:
                os.mkdir(class_split_path)
    # 按照比例划分数据集,并进行数据图片的复制
    # 首先进行分类遍历
    for class_name in class_names:
        current_class_data_path = os.path.join(src_data_folder, class_name)  #原始数据文件夹路径
        current_all_data = os.listdir(current_class_data_path)  #获取该类别的文件列表
        current_data_length = len(current_all_data)  #计算该类别文件数量
        current_data_index_list = list(range(current_data_length))  #创建一个数字序列
        random.shuffle(current_data_index_list) #打乱顺序
        # 将目标文件夹路径、文件夹名称和类别名称组合在一起,获取训练集、验证集和测试集的文件夹路径
        train_folder = os.path.join(os.path.join(target_data_folder, 'train'), class_name)
        val_folder = os.path.join(os.path.join(target_data_folder, 'val'), class_name)
        test_folder = os.path.join(os.path.join(target_data_folder, 'test'), class_name)
        #计算 train_stop_flag 和 val_stop_flag 的值
        train_stop_flag = current_data_length * train_scale # 文件数量*0.6
        val_stop_flag = current_data_length * (train_scale + val_scale) # 文件数量*(0.6+0.2)

        current_idx = 0 # 记录每种文件的数量,判断文件用
        train_num = 0
        val_num = 0
        test_num = 0
        for i in current_data_index_list:
            src_img_path = os.path.join(current_class_data_path, current_all_data[i]) #获取图片的路径
            # 拷贝到对应的文件夹中
            if current_idx <= train_stop_flag: #如果 current_idx 小于等于 train_stop_flag,则将图片复制到训练集文件夹中
                copy2(src_img_path, train_folder)
                train_num = train_num + 1
            elif (current_idx > train_stop_flag) and (current_idx <= val_stop_flag):
                copy2(src_img_path, val_folder)
                val_num = val_num + 1
            else:
                copy2(src_img_path, test_folder)
                test_num = test_num + 1
            current_idx = current_idx + 1
        print("{}类划分完成,一共{}张图片".format(class_name,current_data_length))

if __name__ == '__main__':
    src_data_folder = r"E:\DemoProject\PycharmProjects\pythonProject\Demo\data\download"  # 数据集路径
    target_data_folder = src_data_folder + "_" + "split" #拼接后形成目标文件夹的路径
    if osp.isdir(target_data_folder):  #检查目标文件夹是否存在,如果存在则删除
        print("文件夹已存在,已删除")
        shutil.rmtree(target_data_folder) #删除文件夹
    os.mkdir(target_data_folder)  #创建新的文件夹
    print("文件夹创建成功")
    data_set_split(src_data_folder, target_data_folder) #划分数据集
    print("数据集划分完成,请在{}目录下查看".format(target_data_folder))
2.采用的机器学习框架描述

选用的是PyTorch框架,PyTorch 提供了高度的灵活性和可扩展性,可以满足各种不同的机器学习应用需求。可以自动计算模型的参数的梯度,提供了数据并行的机制,可以在多个 GPU 上并行训练模型,支持多种深度学习任务,包括图像分类、语音识别、自然语言处理等。

三、机器学习的实现步骤

实验步骤主要为1.准备数据 2.数据预处理 3.定义模型 4.调节超参数 5.训练并保存模型 6.模型测试 

1.准备数据

本次选用11个花卉数据集进行学习,太多了电脑跑不动

 

(1) 输出每个分组(训练 / 测试)中分别包含多少张图像

#输出每个分组(训练 / 测试)中分别包含多少张图像
train_path = "E:/DemoProject/PycharmProjects/pythonProject/Demo/data/flower_split/train/"
test_path = "E:/DemoProject/PycharmProjects/pythonProject/Demo/data/flower_split/test/"
image_list=['双荚槐','向日葵','夹竹桃','月季花','水仙花','紫罗兰','菊花','蒲公英','郁金香','银杏']
for i in range(0,len(image_list)): # 遍历每个类别名称
    train_sum += (len(os.listdir(train_path + image_list[i])))
    test_sum += (len(os.listdir(test_path + image_list[i])))
    print("===================================================================")
    print("{}{}:{}".format(image_list[i],'训练集的数量为', train_sum))
    print("{}{}:{}".format(image_list[i],'测试集的数量为', test_sum))
    train_sum = 0
    test_sum = 0

 

 

 

2.数据预处理

(1)统一图片格式

使用 transforms 模块中的函数来转换,对图像进行尺寸调整、随机水平翻转、随机旋转、随机自适应对比度增强、转换为张量和图像标准化。这些转换将作为两个数据字典的值返回,分别对应训练集和验证集。

def Uniform_picture(img_size=224):
    # 创建用于训练和验证的两组图像转换
    data_transforms = {
        'train': transforms.Compose([
            transforms.Resize((img_size, img_size)),  # 对图像尺寸进行调整,让模型输入大小统一为224*224
            transforms.RandomHorizontalFlip(p=0.2),  # 随机水平翻转概率为0.2
            transforms.RandomRotation((-5, 5)),  # 随机旋转,旋转角度在 -5 到 5 度之间
            transforms.RandomAutocontrast(p=0.2),  # 随机自适应对比度增强概率为0.2
            transforms.ToTensor(),  # 图像转换为tensor格式,方便模型对的处理和计算,可以方便的在GPU上进行计算
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])  # 图像标准化,使得模型的训练更加稳定,3个值对应RGP三个通道
        ]),
        'val': transforms.Compose([
            transforms.Resize((img_size, img_size)),
            transforms.ToTensor(),
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
        ]),
    }
    return data_transforms

3.神经网络模型

在初始化模型时会加载一个预训练模型,并将其全连接层的输出特征数量修改为类别数目,使用预训练模型的优点是可以在较小的数据集上训练模型,并且可以获得较好的效果。

# 定义神经网络模型
class SELFMODEL(nn.Module):
    # 初始化模型构造器,三个参数分别是模型的名称、输出特征数量和是否使用预训练的权值
    def __init__(self, model_name=params['model'], out_features=params['num_classes'],pretrained=True):
        super().__init__()  # 初始化基类
        self.model = timm.create_model(model_name, pretrained=pretrained)  # 从预训练的库中加载模型(params)
        self.model.fc = nn.Linear(self.model.fc.in_features, out_features)  # 将全连接层的输出特征数量修改为类别数
    def forward(self, x):  # 卷积神经网络的前向传播
        x = self.model(x)
        return x

 

4.定义超参数

超参数对模型的训练有着重要的影响,机器学习中超参数决定了模型的性能,包括模型结构、学习率、训练轮数等。

params = {。
    'model': 'resnet50d',  # 选择预训练模型,resnet50d卷积神经网络主要用于图像分类和识别等
    "img_size": 224,  # 图片输入大小
    "train_dir": osp.join(data_path, "train"),  # 训练集路径
    "val_dir": osp.join(data_path, "val"),  # 验证集路径
    'device': device,  # 设备
    'lr': 0.001,  # 设置学习率为0.001,学习率越低过程越慢,但更稳定
    'batch_size': 4,  # 单次传递给程序用以训练的数据个数
    'num_workers': 0,  # 进程数
    'epochs': 12,  # 训练轮数
    "save_dir": "../checkpoints/",  #模型保存路径
    "pretrained": True,  # 使用预训练模型
     "num_classes": len(os.listdir(osp.join(data_path, "train"))),  # 类别数目, 自适应获取类别数目
    'weight_decay': 0.00001  # 学习率衰减,设置为 0.00001,用于防止模型过度拟合
}

 

5.训练模型和保存模型

(1)训练流程和验证流程

该方法训练数据集的数据和标签,并将数据和标签加载到设备上,输入到模型中进行前向传播,计算模型预测和真实标签的损失,然后调整神经网络权值参数,更新优化器,并通过更新进度条来显示训练的进度
目的是为了让训练过程更加有序,便于程序的编写和调试。
# 定义训练流程(训练数据的加载器,定义的模型,损失函数,优化器,训练的轮数)
def train(train_loader, model, criterion, optimizer, epoch, params):
    metric_monitor = MetricMonitor()  # 设置指标监视器
    model.train()  # 模型设置为训练模型,可以更新模型的参数
    stream = tqdm(train_loader)# 设置进度条
    for i, (images, target) in enumerate(stream, start=1):  # 开始训练,遍历训练数据集中的数据和标签
        images = images.to(params['device'], non_blocking=True)  # 将数据和标签加载到设备上输入到模型中
        target = target.to(params['device'], non_blocking=True)
        output = model(images)  # 数据放入模型进行前向传播
        loss = criterion(output, target.long())  # 计算模型预测和真实标签的损失
        Probability = torch.softmax(output, dim=1)  # 计算output中每个类别的概率
        Probability = torch.argmax(Probability, dim=1).cpu()  # 找到概率最大的类别
        target = target.cpu() # 将target从GPU设备转移到CPU上
        f1_macro = f1_score(target, Probability, average='macro')  # 计算f1分数
        recall_macro = recall_score(target, Probability, average="macro", zero_division=0)  # 计算recall分数
        acc = accuracy_score(target, Probability)  # 计算准确率分数
        metric_monitor.update('loss', loss.item())  # 更新损失监视器
        metric_monitor.update('F1', f1_macro)  # 更新f1监视器
        metric_monitor.update('Recall', recall_macro)  # 更新召回率
        metric_monitor.update('acc', acc)  # 更新准确率
        optimizer.zero_grad()  # 清空学习率
        loss.backward()  # 损失反向传播,调整神经网络权值参数,可以让输出更接近预期
        optimizer.step()  # 更新优化器
        # lr = adjust_learning_rate(optimizer, epoch, params, i, nBatch)  # 调整学习率
        stream.set_description(  # 更新进度条
            "Epoch: {epoch}. Train.{metric_monitor}".format(epoch=epoch,metric_monitor=metric_monitor) #MetricMonitor存储训练过程中的各种指标
        )
    return metric_monitor.metrics['acc']["avg"], metric_monitor.metrics['loss']["avg"]  # 返回平均准确度和平均损失
这是模型验证流程,在验证流程中,首先会将模型设置为验证模式,然后遍历验证数据集中的数据和标签。在每次遍历中,会将图片和标签放到设备上进行前向传播,
并计算模型的预测和真实标签的损失值,以及f1分数、recall分数和准确率。
# 定义模型验证流程 def validate(val_loader, model, criterion, epoch, params): metric_monitor = MetricMonitor() # 跟踪不同指标的值 model.eval() # 模型设置为验证格式,验证模式下模型不会更新权值 stream = tqdm(val_loader) # 设置进度条 with torch.no_grad(): for i, (images, target) in enumerate(stream, start=1): # 将每次迭代的元素解压缩到变量images和target images = images.to(params['device'], non_blocking=True) # 读取图片放到设备上 target = target.to(params['device'], non_blocking=True) # 同上,第二个参数表示传输在后台进行,可以加速传输的过程 output = model(images) # 前向传播 loss = criterion(output, target.long()) # 计算损失 f1_macro = calculate_f1_macro(output, target) # 计算f1分数 recall_macro = calculate_recall_macro(output, target) # 计算recall分数 acc = accuracy(output, target) # 计算acc metric_monitor.update('loss', loss.item()) metric_monitor.update('F1', f1_macro) metric_monitor.update("Recall", recall_macro) metric_monitor.update('acc', acc) # 设置进度条的显示内容 stream.set_description("Epoch: {epoch}. val. {metric_monitor}".format(epoch=epoch,metric_monitor=metric_monitor)) return metric_monitor.metrics['acc']["avg"], metric_monitor.metrics['loss']["avg"]

 

(2)准确率计算

# 计算acc
def accuracy(output, target):
    y_pred = torch.softmax(output, dim=1)  #模型的输出中每个类别的概率值
    y_pred = torch.argmax(y_pred, dim=1).cpu()  #找到每个样本概率最大的类别
    target = target.cpu()
    return accuracy_score(target, y_pred)  #预测类别和真实类别的准确率,返回分类准确率的值

(3)F1值计算

如果只考虑精确度或者只考虑召回率都不能够作为评价一个模型好坏的指标,所以使用F1值来调和两者。

# 计算F1
def calculate_f1_macro(output, target):
    y_pred = torch.softmax(output, dim=1)  #模型的输出中每个类别的概率
    y_pred = torch.argmax(y_pred, dim=1).cpu()  #找到概率最大的类别
    target = target.cpu()
    return f1_score(target, y_pred, average='macro') #计算平均F1分数

(4)计算召回率

把正确样本召回来,有可能本来是正确的样本给否定了

# 计算recall
def calculate_recall_macro(output, target):
    y_pred = torch.softmax(output, dim=1)
    y_pred = torch.argmax(y_pred, dim=1).cpu()
    target = target.cpu()
    return recall_score(target, y_pred,average="macro", zero_division=0)  #计算宏召回率,对每个类别分别计算召回率

(5)开始训练并保存模型

 

 

    # 开始训练,循环指定参数中的 epoch 次数
    for epoch in range(1, params['epochs'] + 1):
        # 训练模型一个 epoch 并获取准确率和损失
        acc, loss = train(train_loader, model, criterion, optimizer, epoch, params)
        val_acc, val_loss = validate(val_loader, model, criterion, epoch, params)
        # 将准确率和损失值添加到相应的列表中
        accs.append(acc)
        losss.append(loss)
        val_accs.append(val_acc)
        val_losss.append(val_loss)
        save_path = osp.join(save_dir, f"acc{acc:.5f}_weights.h5")
        torch.save(model.state_dict(), save_path)# 保存文件
        best_acc = val_acc #将当前模型的权重保存到save_dir目录下的文件中

    # 显示训练和验证集的损失和准确率折线图
    show_loss_acc(accs, losss, val_accs, val_losss, save_dir)
    print("训练已完成,模型和训练日志保存在: {}".format(save_dir))

 

 

(6)训练集和验证集上的损失和准确率的折线图

 

 在这张图上外面可以看到随着训练次数越多准确率也就越高,随着训练次数越多丢失率也越来越低,但是到达第17轮作用就开始平稳了,有可能是训练的数据不够多或者是模型的学习率过大以及模型复杂度过高

6.模型测试

利用切分好的训练集测试训练完成的模型

(1)随机种子

通常需要对模型进行多次训练,并且希望在每次训练中使用相同的数据集。使用随机种子可以确保在每次训练中使用相同的数据集,因为随机数生成器会按照相同的方式生成随机数。

mpl.rcParams["font.sans-serif"] = ["SimHei"]  # 设置显示的中文字体
# 固定随机种子,保证实验结果是可以复现的
seed = 42
os.environ['PYTHONHASHSEED'] = str(seed)  # 为了禁止hash随机化,使得实验可复现
np.random.seed(seed)  # 设置 NumPy 的随机数生成器的种子
torch.manual_seed(seed)  # 设置 PyTorch CPU 版本的随机数生成器的种子
torch.backends.cudnn.deterministic = True  # 每次返回的卷积算法将是默认算法
torch.backends.cudnn.benchmark = True  # 让内置的 cuDNN 的 auto-tuner 自动寻找最适合当前配置的高效算法

(2)定义数据集路径

# 数据集根目录
data_path = r"C:\Users\Administrator\Desktop\flower_split"
model_path = r"C:\Users\Administrator\Desktop\acc0.99471_weights.h5"
model_name = 'resnet50d'
# 数据集训练时输入模型的大小
img_size = 224

(3)超参数设置

params = {
    'model': model_name,  # 选择预训练模型
    "img_size": img_size,  # 图片输入的大小
    "test_dir": osp.join(data_path, "test"),  # 测试集子目录
    'batch_size': 4,  # 单次传递给程序用以训练的数据个数
    'num_workers': 0,  # 进程数
    "num_classes": len(os.listdir(osp.join(data_path, "train"))),  # 类别数目, 自适应获取类别数目
}

(4)验证输入的数据集

用于在验证模式下对输入的图像数据集进行推理,并计算模型的 F1 分数、召回率和准确度。使用 softmax 和 argmax 函数将模型的输出转化为概率和预测标签,将真实标签和预测标签添加到列表中,并使用三个函数进行计算

def test(val_loader, model, params, class_names):
    metric_monitor = MetricMonitor()  # 验证流程
    model.eval()  # 模型设置为验证格式
    stream = tqdm(val_loader)  # 设置进度条
    # 对模型进行推理
    test_real_labels = []
    test_pre_labels = []
    with torch.no_grad():  # 禁用自动求导,加速推理过程
        for i, (images, target) in enumerate(stream, start=1): # 验证数据集中的所有图像和标签
            output = model(images)  # 向前传播
            target_numpy = target.cpu().numpy()  # 将标签从 GPU 复制到 CPU 上并转化为 numpy 数组
            y_pred = torch.softmax(output, dim=1)# 使用 softmax 函数将模型的输出转化为概率
            # 使用 argmax 函数获取概率最大的标签,并将其从 GPU 复制到 CPU 上并转化为 numpy 数组
            y_pred = torch.argmax(y_pred, dim=1).cpu().numpy()
            # 将真实标签和预测标签添加到列表中
            test_real_labels.extend(target_numpy)
            test_pre_labels.extend(y_pred)
            f1_macro = calculate_f1_macro(output, target)  # 计算f1分数
            recall_macro = calculate_recall_macro(output, target)  # 计算recall分数
            acc = accuracy(output, target)  # 计算acc

            #f1_macro多分类问题,不受数据不平衡影响,容易受到识别性高(高recall、高precision)的类别影响
            metric_monitor.update('F1', f1_macro)
            metric_monitor.update("Recall", recall_macro)
            metric_monitor.update('Accuracy', acc)

 

(5)加载模型和测试

首先通过 DataLoader 加载测试数据集。然后加载模型,将模型的权重加载到模型中,并将模型设置为测试模式。最后,将准确率、F1 值和召回率打印到控制台

 # 按照批次将测试数据集加载到内存中
    test_loader = DataLoader(
        test_dataset, batch_size=params['batch_size'], shuffle=True, num_workers=params['num_workers'], pin_memory=True,
    )
    # 加载模型,实例化自定义模型类,并将预训练的模型的参数设置为False
    model = SELFMODEL(model_name=params['model'], out_features=params['num_classes'],pretrained=False)
    weights = torch.load(model_path) # 加载模型的权重
    model.load_state_dict(weights) # 将权重应用到模型上
    model.eval()  # 将模型设置为测试模式
    acc, f1, recall = test(test_loader, model, params, class_names)  # 指标上的测试结果,返回准确率、F1值和召回率
    print("测试结果:")
    print(f"acc: {acc},recall: {recall}")

(6)

 7.识别界面

(1)界面参数

#界面参数设置
        super().__init__()
        self.setWindowTitle('花卉识别系统')
        self.resize(500, 500)  # 设置窗口大小
        self.output_size = 300  # 设置上传图片的输出大小
        self.origin_shape = ()  # 设置图片原始大小,图片上传会变形,所以记录一下
        self.model_path = r"C:\Users\Administrator\Desktop\22epochs_acc0.99844.h5"  #模型路径
        self.classes_names = ['双荚槐', '向日葵', '夹竹桃', '月季花','水仙花','牵牛花','紫罗兰','菊花','蒲公英','郁金香','银杏']  #类名
        self.img_size = 224  #输入图片的大小
        self.model_name = "resnet50d"  #模型名称
        self.num_classes = len(self.classes_names)  #类别数目

(2)加载并初始化模型

创建一个模型,并加载一个已经训练好的模型的权重文件,将模型设置为评估模式,并将加载的模型赋值给 self.model。然后加载数据处理并获取验证集使用的数据转换器进行数据预处理,最后初始化 UI

        # 创建一个 SELFMODEL 类型的模型,并命名为 self.model_name,输出类别数为 self.num_classes,不使用预训练的参数
        model = SELFMODEL(model_name=self.model_name, out_features=self.num_classes, pretrained=False)
        weights = torch.load(self.model_path,map_location=torch.device('cpu')) # 加载模型权重,并将其放在 CPU 上
        model.load_state_dict(weights) # 将加载的权重赋值给模型
        model.eval()   # 将模型设置为评估模式,用于优化训练而添加的网络层会被关闭,使得评估时不会发生偏移
        self.model = model  # 将加载的模型赋值给 self.model
        data_transforms = Uniform_picture(img_size=self.img_size) # 加载数据处理
        self.valid_transforms = data_transforms['val']  # 获取验证集使用的数据转换器,对训练集、验证集和测试集进行数据预处理
        self.initUI()  # 初始化 UI

(3)创建部件

在这里创建了两个小部件,并在里面添加了两个标签。然后创建两个按钮并创建点击事件,创建文字提示标签。最后,将按钮、标签和小部件添加到布局中,并将布局添加到小部件中,最后将小部件添加到窗口的选项卡中。

    def initUI(self):
        font_main = QFont('楷体', 14)
        img_detection_widget = QWidget()  #创建一个小部件
        img_detection_layout = QVBoxLayout()  #设置为垂直布局
        mid_img_widget = QWidget()  #创建一个小部件
        #  创建了两个img标签
        self.left_img = QLabel()
        self.right_img = QLabel()

        #  创建两个按钮
        up_img_button = QPushButton("上传图片")
        det_img_button = QPushButton("识别")
        up_img_button.clicked.connect(self.upload_img) #创建点击事件连接函数
        det_img_button.clicked.connect(self.detect_img)
        # 设置button字体
        up_img_button.setFont(font_main)
        det_img_button.setFont(font_main)
        #上传图片和开始检测按钮的样式
        self.rrr = QLabel("等待上传")
        self.rrr.setFont(font_main)  #设置字体

        # 将按钮、标签和小部件添加到布局中
        img_detection_layout.addWidget(mid_img_widget, alignment=Qt.AlignCenter)
        img_detection_layout.addWidget(self.rrr)
        img_detection_layout.addWidget(up_img_button)
        img_detection_layout.addWidget(det_img_button)
        img_detection_widget.setLayout(img_detection_layout)# 布局添加到小部件中
        self.addTab(img_detection_widget, '')  # 小部件添加到窗口的选项卡

 

(4)图片上传

点击按钮时,会弹出一个文件选择对话框,可以选一张图片,然后将选择的图片复制到临时文件夹中,然后保存原图的尺寸,对选择的图片统一的尺寸, 把选择的图片展示到控件上

        #上传图片
    def upload_img(self):
        # 选择图片进行读取
        fileName, fileType = QFileDialog.getOpenFileName(self, '*.jpg *.png *.jpeg')
        if fileName:
            # 将选择的文件复制到临时保存路径
            suffix = fileName.split(".")[-1]
            save_path = osp.join("images/tmp", "tmp_upload." + suffix)
            shutil.copy(fileName, save_path)
            # 对选择的图片进行缩放,使其符合统一的尺寸
            im0 = cv2.imread(save_path)
            resize_scale = self.output_size / im0.shape[0]
            im0 = cv2.resize(im0, (0, 0), fx=resize_scale, fy=resize_scale)
            cv2.imwrite("images/tmp/upload_show_result.jpg", im0)
            self.img2predict = fileName
            # 保存原图的尺寸
            self.origin_shape = (im0.shape[1], im0.shape[0])
            # 将图片显示在图片控件中
            self.left_img.setPixmap(QPixmap("images/tmp/upload_show_result.jpg"))
            #上传图片后图片重置
            self.right_img.setPixmap(QPixmap())
            self.rrr.setText("等待识别")

(5)图片检测

对输入的图片进行检测,首先读取图片文件,然后使用预处理函数将图片进行预处理,然后将图片的维度扩展到第一维,

使图像的维度符合模型的输入要求。然后使用训练好的模型进行预测,最后获取预测的类别名称并显示在文本控件中。

    # 检测图片
    def detect_img(self):
        # 读取待检测的图片文件,首先要知道图片名
        source = self.img2predict
        img = Image.open(source)# 打开图片文件
        img = self.valid_transforms(img)  # 对图片进行预处理
        img = img.unsqueeze(0)  # 在第一维处添加一个维度,使图像的维度符合模型的输入要求
        output = self.model(img) # 使用训练好的模型进行预测
        label_id = torch.argmax(output).item() # 获取最大值所在的索引
        predict_name = self.classes_names[label_id] # 获取预测的类别名称
        self.rrr.setText("识别结果为:{}".format(predict_name))  # 将预测的结果显示在文本控件中



四、总结 1.通过对本案例的机器学习过程实现,得到哪些结论? 感觉学到了很多,花的时间真的很久,从安装框架开始就遇到了配置问题,数据的预处理,比如初始loss高,测试精确率低,在学习中遇到不会的就去网上查询,不会的方法就查询方法的用法,再看看别人怎么实现的,然后给代码都加上注释,成功治好了以前不喜欢加注释的不好的习惯。   2.自己在完成此设计过程中,得到哪些收获?以及要改进的建议?

感觉对机器学习越来越感兴趣了,让我了解了分类识别项目流程,边做边学,收获了很多,如图片的预处理,F1值和召回率的理解,数据集切割,以及了解了一些方法的使用方式,在以后的学习道路有很大的帮助。

改进的建议是做一些优化,收集更多的数据集,因为电脑性能不太好所以数据集的体量中规中矩,

 

全代码:

train.py

import timm
from matplotlib import pyplot as plt
from torch.utils.data import DataLoader
import os.path as osp
import os
from pylab import mpl
from torchvision import datasets
import torch
from tqdm import tqdm
import torch.nn as nn
from sklearn.metrics import f1_score, accuracy_score, recall_score
from torchutils import MetricMonitor, calculate_f1_macro, calculate_recall_macro, accuracy, \
    Uniform_picture

mpl.rcParams["font.sans-serif"] = ["SimHei"]  # 设置显示的中文字体
if torch.cuda.is_available():
    device = torch.device('cuda:0')
else:
    device = torch.device('cpu')
print(f'Using device: {device}')
train_sum=0
test_sum=0

#输出每个分组(训练 / 测试)中分别包含多少张图像
train_path = "E:/DemoProject/PycharmProjects/pythonProject/Demo/data/flower_split/train/"
test_path = "E:/DemoProject/PycharmProjects/pythonProject/Demo/data/flower_split/test/"
image_list=['双荚槐','向日葵','夹竹桃','月季花','水仙花','紫罗兰','菊花','蒲公英','郁金香','银杏']
for i in range(0,len(image_list)): # 遍历每个类别名称
    train_sum += (len(os.listdir(train_path + image_list[i])))
    test_sum += (len(os.listdir(test_path + image_list[i])))
    print("===================================================================")
    print("{}{}:{}".format(image_list[i],'训练集的数量为', train_sum))
    print("{}{}:{}".format(image_list[i],'测试集的数量为', test_sum))
    train_sum = 0
    test_sum = 0

data_path = r"E:\DemoProject\PycharmProjects\pythonProject\Demo\data\flower_split" #数据集路径
# 超参数(模型的配置参数)设置
params = {
    'model': 'resnet50d',  # 选择预训练模型
    "img_size": 224,  # 图片输入大小
    "train_dir": osp.join(data_path, "train"),  # 训练集路径
    "val_dir": osp.join(data_path, "val"),  # 验证集路径
    'device': device,  # 设备
    'lr': 0.001,  # 设置学习率为0.001,学习率越低过程越慢,但更稳定
    'batch_size': 4,  # 单次传递给程序用以训练的数据个数
    'num_workers': 0,  # 进程数
    'epochs': 1,  # 训练轮数
    "save_dir": "../model/",  #模型保存路径
    "pretrained": True,  # 使用预训练模型
     "num_classes": len(os.listdir(osp.join(data_path, "train"))),  # 类别数目, 自适应获取类别数目
    'weight_decay': 0.00001  # 学习率衰减,设置为 0.00001,用于防止模型过度拟合
}

# 在机器学习中,预训练模型是一种已经训练好的模型,用于某一任务的模型。例如,在计算机视觉任务中,预训练模型可以是从ImageNet数据集上训练出来的模型,或者是从其他数据集上训练出来的模型。
# 使用预训练模型的好处在于,它们已经在大型数据集上被训练好了,可以为我们的任务所用。我们可以使用这些预训练模型的权值作为自己模型的初始权值,然后对模型进行微调,从而达到更好的泛化能力。
# 定义神经网络模型
class SELFMODEL(nn.Module):
    # 初始化模型构造器,三个参数分别是模型的名称、输出特征数量和是否使用预训练的权值
    def __init__(self, model_name=params['model'], out_features=params['num_classes'],pretrained=True):
        super().__init__()  # 初始化基类
        self.model = timm.create_model(model_name, pretrained=pretrained)  # 从预训练的库中加载模型(params)
        self.model.fc = nn.Linear(self.model.fc.in_features, out_features)  # 将全连接层的输出特征数量修改为类别数
    def forward(self, x):  # 卷积神经网络的前向传播
        x = self.model(x)
        return x

# 定义训练流程(训练数据的加载器,定义的模型,损失函数,优化器,训练的轮数)
def train(train_loader, model, criterion, optimizer, epoch, params):
    metric_monitor = MetricMonitor()  # 设置指标监视器
    model.train()  # 模型设置为训练模型,可以更新模型的参数
    # nBatch = len(train_loader)
    stream = tqdm(train_loader)# 设置进度条

    for i, (images, target) in enumerate(stream, start=1):  # 开始训练,遍历训练数据集中的数据和标签
        images = images.to(params['device'], non_blocking=True)  # 将数据和标签加载到设备上输入到模型中
        target = target.to(params['device'], non_blocking=True)
        output = model(images)  # 数据放入模型进行前向传播
        loss = criterion(output, target.long())  # 计算模型预测和真实标签的损失
        Probability = torch.softmax(output, dim=1)  # 计算output中每个类别的概率
        Probability = torch.argmax(Probability, dim=1).cpu()  # 找到概率最大的类别
        target = target.cpu() # 将target从GPU设备转移到CPU上
        f1_macro = f1_score(target, Probability, average='macro')  # 计算f1分数
        recall_macro = recall_score(target, Probability, average="macro", zero_division=0)  # 计算recall分数
        acc = accuracy_score(target, Probability)  # 计算准确率
        metric_monitor.update('loss', loss.item())  # 更新损失监视器
        metric_monitor.update('F1', f1_macro)  # 更新f1监视器
        metric_monitor.update('Recall', recall_macro)  # 更新召回率
        metric_monitor.update('acc', acc)  # 更新准确率
        optimizer.zero_grad()  # 清空学习率
        loss.backward()  # 损失反向传播,调整神经网络权值参数,可以让输出更接近预期
        optimizer.step()  # 更新优化器
        # lr = adjust_learning_rate(optimizer, epoch, params, i, nBatch)  # 调整学习率
        stream.set_description(  # 更新进度条
            "Epoch: {epoch}. Train.{metric_monitor}".format(epoch=epoch,metric_monitor=metric_monitor) #MetricMonitor存储训练过程中的各种指标
        )
    return metric_monitor.metrics['acc']["avg"], metric_monitor.metrics['loss']["avg"]  # 返回平均准确度和平均损失

# 定义模型验证流程
def validate(val_loader, model, criterion, epoch, params):
    metric_monitor = MetricMonitor()  # 跟踪不同指标的值
    model.eval()  # 模型设置为验证格式,验证模式下模型不会更新权值
    stream = tqdm(val_loader)  # 设置进度条
    with torch.no_grad():
        for i, (images, target) in enumerate(stream, start=1): # 将每次迭代的元素解压缩到变量images和target
            images = images.to(params['device'], non_blocking=True)  # 读取图片放到设备上
            target = target.to(params['device'], non_blocking=True)  # 同上,第二个参数表示传输在后台进行,可以加速传输的过程
            output = model(images)  # 前向传播
            loss = criterion(output, target.long())  # 计算损失
            f1_macro = calculate_f1_macro(output, target)  # 计算f1分数
            recall_macro = calculate_recall_macro(output, target)  # 计算recall分数
            acc = accuracy(output, target)  # 计算acc
            metric_monitor.update('loss', loss.item())
            metric_monitor.update('F1', f1_macro)
            metric_monitor.update("Recall", recall_macro)
            metric_monitor.update('acc', acc)
            # 设置进度条的显示内容
            stream.set_description("Epoch: {epoch}. val. {metric_monitor}".format(epoch=epoch,metric_monitor=metric_monitor))
    return metric_monitor.metrics['acc']["avg"], metric_monitor.metrics['loss']["avg"]

# 展示训练过程的曲线图
def show_loss_acc(acc, loss, val_acc, val_loss, sava_dir):
    # 从history中提取模型训练集和验证集准确率信息和误差信息
    plt.figure(figsize=(8, 8))  # 创建一个新图表,并设置大小为 (8, 8)
    plt.subplot(2, 1, 1)  # 将图表分成 2 行 1 列的网格,并选择第一个子图
    plt.plot(acc, label='Training Accuracy')  # 绘制训练精度的曲线
    plt.plot(val_acc, label='Validation Accuracy')  # 绘制验证精度的曲线

    plt.legend(loc='lower right')  # 添加图例
    plt.ylabel('Accuracy')  # 设置 y 轴的标签
    plt.ylim([min(plt.ylim()), 1])  # 设置 y 轴的范围
    plt.title('训练和验证的准确性')  # 设置图表的标题

    plt.subplot(2, 1, 2)  # 将图表分成 2 行 1 列的网格,并选择第二个子图
    plt.plot(loss, label='Train_Loss')  # 绘制训练损失的曲线
    plt.plot(val_loss, label='Val_Loss')  # 绘制验证损失的曲线
    plt.legend(loc='upper right')  # 添加图例
    plt.ylabel('Cross Entropy')  # 设置 y 轴的标签
    plt.title('训练和验证的损失')
    plt.xlabel('epoch')  # 设置 x 轴的标签

    # 保存图片在savedir目录下。
    save_path = osp.join(save_dir, "train_result.png")
    # 保存图表到文件,分辨率设置为100
    plt.savefig(save_path, dpi=100)


if __name__ == '__main__':
    accs = []
    losss = []
    val_accs = []
    val_losss = []
    data_transforms = Uniform_picture(img_size=params["img_size"])  # 获取图像预处理方式
    train_transforms = data_transforms['train']  # 训练集数据处理方式
    valid_transforms = data_transforms['val']  # 验证集数据集处理方式
    train_dataset = datasets.ImageFolder(params["train_dir"], train_transforms)  # 加载训练集
    valid_dataset = datasets.ImageFolder(params["val_dir"], valid_transforms)  # 加载验证集
    save_dir = osp.join(params['save_dir'], params['model']+"_pretrained_" + str(params["img_size"]))  # 设置模型保存路径
    if not osp.isdir(save_dir):
        # 如果保存路径不存在的话就创建
        os.makedirs(save_dir)
        print("save dir {} created".format(save_dir))

    train_loader = DataLoader(
        # 按照批次加载训练集
        train_dataset, batch_size=params['batch_size'], shuffle=True,
        # 创建一个数据加载器
        num_workers=params['num_workers'], pin_memory=True,
    )
    val_loader = DataLoader(  # 按照批次加载验证集
        valid_dataset, batch_size=params['batch_size'], shuffle=False,num_workers=params['num_workers'], pin_memory=True,
    )
    print(train_dataset.classes)

    model = SELFMODEL(model_name=params['model'], out_features=params['num_classes'],pretrained=params['pretrained']) # 加载模型
    model = model.to(params['device'])  # 模型部署到设备上
    criterion = nn.CrossEntropyLoss().to(params['device'])  # 设置损失函数
    optimizer = torch.optim.AdamW(model.parameters(), lr=params['lr'], weight_decay=params['weight_decay'])  # 设置优化器

    # 开始训练,循环指定参数中的 epoch 次数
    for epoch in range(1, params['epochs'] + 1):
        # 训练模型一个 epoch 并获取准确率和损失
        acc, loss = train(train_loader, model, criterion, optimizer, epoch, params)
        val_acc, val_loss = validate(val_loader, model, criterion, epoch, params)
        # 将准确率和损失值添加到相应的列表中
        accs.append(acc)
        losss.append(loss)
        val_accs.append(val_acc)
        val_losss.append(val_loss)
        save_path = osp.join(save_dir, f"acc{acc:.5f}_weights.h5")
        torch.save(model.state_dict(), save_path)# 保存文件
        best_acc = val_acc #将当前模型的权重保存到save_dir目录下的文件中
    # 显示训练和验证集的损失和准确率折线图
    show_loss_acc(accs, losss, val_accs, val_losss, save_dir)
    print("训练已完成,模型和训练日志保存在: {}".format(save_dir))
View Code

test.py

import numpy as np
from torch.utils.data import DataLoader
from tqdm import tqdm
from torchutils import *
from torchvision import datasets, models, transforms
import os.path as osp
import os
from train import SELFMODEL
from pylab import mpl

mpl.rcParams["font.sans-serif"] = ["SimHei"]  # 设置显示的中文字体
# 固定随机种子,保证实验结果是可以复现的
seed = 42
os.environ['PYTHONHASHSEED'] = str(seed)  # 为了禁止hash随机化,使得实验可复现
np.random.seed(seed)  # 设置 NumPy 的随机数生成器的种子
torch.manual_seed(seed)  # 设置 PyTorch CPU 版本的随机数生成器的种子
torch.cuda.manual_seed(seed)  # 设置 PyTorch GPU 版本的随机数生成器的种子
torch.backends.cudnn.deterministic = True  # 每次返回的卷积算法将是默认算法
torch.backends.cudnn.benchmark = True  # 让内置的 cuDNN 的 auto-tuner 自动寻找最适合当前配置的高效算法
# 数据集根目录
data_path = r"C:\Users\Administrator\Desktop\flower_split"
model_path = r"C:\Users\Administrator\Desktop\acc0.99471_weights.h5"
model_name = 'resnet50d'
# 数据集训练时输入模型的大小
img_size = 224

# 超参数(模型的配置参数)设置
params = {
    'model': model_name,  # 选择预训练模型
    "img_size": img_size,  # 图片输入的大小
    "test_dir": osp.join(data_path, "test"),  # 测试集子目录
    'batch_size': 4,  # 单次传递给程序用以训练的数据个数
    'num_workers': 0,  # 进程数
    "num_classes": len(os.listdir(osp.join(data_path, "train"))),  # 类别数目, 自适应获取类别数目
}

#对输入的图像数据集进行验证
def test(val_loader, model, params, class_names):
    metric_monitor = MetricMonitor()  # 验证流程
    model.eval()  # 模型设置为验证格式
    stream = tqdm(val_loader)  # 设置进度条
    # 对模型分开进行推理
    test_real_labels = []
    test_pre_labels = []
    with torch.no_grad():  # 禁用自动求导,加速推理过程
        for i, (images, target) in enumerate(stream, start=1): # 验证数据集中的所有图像和标签
            output = model(images)  # 向前传播
            target_numpy = target.cpu().numpy()  # 将标签从 GPU 复制到 CPU 上并转化为 numpy 数组
            y_pred = torch.softmax(output, dim=1)# 使用 softmax 函数将模型的输出转化为概率
            # 使用 argmax 函数获取概率最大的标签,并将其从 GPU 复制到 CPU 上并转化为 numpy 数组
            y_pred = torch.argmax(y_pred, dim=1).cpu().numpy()
            # 将真实标签和预测标签添加到列表中
            test_real_labels.extend(target_numpy)
            test_pre_labels.extend(y_pred)
            f1_macro = calculate_f1_macro(output, target)  # 计算f1分数
            recall_macro = calculate_recall_macro(output, target)  # 计算recall分数
            acc = accuracy(output, target)  # 计算acc

            #f1_macro多分类问题,不受数据不平衡影响,容易受到识别性高(高recall、高precision)的类别影响
            metric_monitor.update('F1', f1_macro)
            metric_monitor.update("Recall", recall_macro)
            metric_monitor.update('Accuracy', acc)
            # 设置进度条的描述文本
            stream.set_description(
                "mode: {epoch}.{metric_monitor}".format(epoch="test",metric_monitor=metric_monitor)
            )
    # 返回准确率、F1 值、召回率的平均值
    return metric_monitor.metrics['Accuracy']["avg"], \
           metric_monitor.metrics['F1']["avg"], \
           metric_monitor.metrics['Recall']["avg"]

if __name__ == '__main__':
    data_transforms = Uniform_picture(img_size=params["img_size"])  # 获取图像预处理方式
    valid_transforms = data_transforms['val']  # 验证集数据集处理方式
    test_dataset = datasets.ImageFolder(params["test_dir"], valid_transforms)  # 加载测试数据集
    class_names = test_dataset.classes  # 获取类别名称
    print(class_names)

    # 按照批次将测试数据集加载到内存中
    test_loader = DataLoader(
        test_dataset, batch_size=params['batch_size'], shuffle=True, num_workers=params['num_workers'], pin_memory=True,
    )
    # 加载模型,实例化自定义模型类,并将预训练的模型的参数设置为False
    model = SELFMODEL(model_name=params['model'], out_features=params['num_classes'],pretrained=False)
    weights = torch.load(model_path) # 加载模型的权重
    model.load_state_dict(weights) # 将权重应用到模型上
    model.eval()  # 将模型设置为测试模式
    acc, f1, recall = test(test_loader, model, params, class_names)  # 指标上的测试结果,返回准确率、F1值和召回率
    print("测试结果:")
    print(f"acc: {acc},recall: {recall}")
View Code

torchutils.py

import torch
from collections import defaultdict
# Metric 测试准确率需要的包
from sklearn.metrics import f1_score, accuracy_score, recall_score
from torchvision import datasets, models, transforms

# 数据增强,统一图片的格式。
def Uniform_picture(img_size=224):
    # 创建用于训练和验证的两组图像转换
    data_transforms = {
        'train': transforms.Compose([
            transforms.Resize((img_size, img_size)),  # 对图像尺寸进行调整,让模型输入大小统一为224*224
            transforms.RandomHorizontalFlip(p=0.2),  # 随机水平翻转概率为0.2
            transforms.RandomRotation((-5, 5)),  # 随机旋转,旋转角度在 -5 到 5 度之间
            transforms.RandomAutocontrast(p=0.2),  # 随机自适应对比度增强概率为0.2
            transforms.ToTensor(),  # 图像转换为tensor格式,方便模型对的处理和计算,可以方便的在GPU上进行计算
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])  # 图像标准化,使得模型的训练更加稳定,3个值对应RGP三个通道
        ]),
        'val': transforms.Compose([
            transforms.Resize((img_size, img_size)),
            transforms.ToTensor(),
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
        ]),
    }
    return data_transforms

# 计算acc
def accuracy(output, target):
    y_pred = torch.softmax(output, dim=1)  #输出中每个类别的最大值
    y_pred = torch.argmax(y_pred, dim=1).cpu()  #找到最大值的位置
    target = target.cpu()
    return accuracy_score(target, y_pred)  #计算分类准确率


# 计算F1
def calculate_f1_macro(output, target):
    y_pred = torch.softmax(output, dim=1)  #计算output中每个类别的概率
    y_pred = torch.argmax(y_pred, dim=1).cpu()  #找到概率最大的类别
    target = target.cpu()
    return f1_score(target, y_pred, average='macro') #计算平均F1分数


# 计算recall
def calculate_recall_macro(output, target):
    y_pred = torch.softmax(output, dim=1)
    y_pred = torch.argmax(y_pred, dim=1).cpu()
    target = target.cpu()
    return recall_score(target, y_pred,average="macro", zero_division=0)  #计算宏召回率,对每个类别分别计算召回率

# 训练的时候输出信息使用,计算训练过程中的指标平均值
class MetricMonitor:
    def __init__(self, float_precision=3):
        self.float_precision = float_precision
        self.reset() # 重置计数器

    def reset(self):
        self.metrics = defaultdict(lambda: {"val": 0, "count": 0, "avg": 0})

    def update(self, metric_name, val):
        metric = self.metrics[metric_name] #指定指标的名称
        metric["val"] += val
        metric["count"] += 1
        metric["avg"] = metric["val"] / metric["count"]

    def __str__(self):  #格式化为字符串
        return " | ".join(
            ["{metric_name}: {avg:.{float_precision}f}".format(metric_name=metric_name, avg=metric["avg"],float_precision=self.float_precision)
                for (metric_name, metric) in self.metrics.items()
            ]
        )
View Code

 

 

 

标签:target,模型,self,---,花卉,path,model,识别,metric
From: https://www.cnblogs.com/jyx123/p/jyx123.html

相关文章

  • 机器学习——机动车图片识别
    一、选题的背景随着城市化建设不断发展,我国对交通建设的需求也不断增长,成为了世界上在交通领域基础设施建设最快的国家之一,但车辆管控问题、道路交通问题、车辆违章问题......
  • 《LEARNING HOMOPHILIC INCENTIVES IN SEQUENTIAL SOCIAL DILEMMAS》 2022-ICLR
    在序贯社会困境中学习同质性激励总结:由于在类似harvest这种困境中,带有惩罚功能的智能体惩罚他人会付出代价,由此可能引发第二层级的搭便车行为,即都不想进行惩罚动作付出小......
  • week1-homework
    week01-homework1,图文并茂解释开源许可证GPL、BSD、MIT、Mozilla、Apache和LGPL的区别?参考文档:各大开源协议的比较_Robin_zero的博客-CSDN博客_开源协议对比参考文档:细......
  • 学点数模-1
    一、模拟退火算法说起退火算法说是模拟金属淬火之后退火的情况的,不过这里也无所谓。我们直接来解释一下这个退火算法是怎么进行的。在说之前,要看看爬山算法。爬山算法:......
  • 每天一点基础K8S--K8S中的存储类storage Class
    存储类storageClass1、官网链接https://kubernetes.io/zh-cn/docs/concepts/storage/storage-classes/2、storageClass资源#在前面持久卷的实验中看到,对于静态绑定的p......
  • buuoj-pwn-pwnable_bf
    buuoj-pwn-pwnable_bf总结bss段上存储libc地址的地方有很多,最值得注意的就是stdin、stdoutbrainfuck的认识(虽然这题没用[、]),如下:题目分析简单一看就知道本......
  • 力扣-303-区域和检索-数组不可变
    前缀和入门模板题我想着“前缀和”嘛,那就整一个“前缀和”出来,但是好像空间效率特别差感觉有点空间换时间的意思classNumArray{private: vector<int>prefixSum;pu......
  • 9) TestProgressLeader(raft_test.go:0-350)
    【TestProgressLeader】测试leader的progress.match1)写5条消息2)生成ready(模拟本地盘写)3)advance(ready)  leader的match变为6,next=7 【TestProgressResumeByHear......
  • 机器学习——鞋类别识别
    机器学习——鞋类别识别一、选题背景随着计算机硬件的不断发展,人工智能再次走进研究人员的实现,通过构造一定智能的人工系统,让机器去完成以往需要人类智力才能胜任的......
  • 【S-V信道】基于毫米波的5G通信S-V信道模型仿真
    1.软件版本MATLAB2021a2.本算法理论知识由于大气中存在一定含量的极化氧分子和水汽(如图3.1所示),因此毫米波在传输过程中容易被大气吸收,从而导致信号衰减,影响通信质量[1......