神经网络与深度学习——花卉分类

目录

项目背景和目的:

一. 导入相关库和相关模块

   1.导入PyTorch库及相关模块

    2.加载数据集

二.加载ResNet-18模型

三.对模型进行训练

使用PyTorch训练模型

 1.定义函数

 2.开始训练

可视化训练过程中的损失曲线。

 1.绘制可视化损失函数图像

2.输出的数据与图像

四.评估多类别分类模型的性能 

1.将模型设置为评估模式。

2.初始化用于存储预测结果和真实标签的空列表。

3.在测试数据加载器的每个批次

4.使用scikit-learn库计算性能指标

5.返回宏平均精确率、每个类别的精确率列表和整体准确度 

五.将训练好的模型进行分类预测,并将图像及其真实类别与预测类别进行对比显示 。

 分类预测 

进行对比显示

 显示对比结果:

六.前端页面实现 


项目背景和目的:

   互联网技术的飞速发展和智能硬件性能的不断提高,全球每时每刻都在产生海量的图像数据。这些图像数据作为一种重要的信息载体,包含着丰富的内容,对它们进行分类和分析具有重要的实际应用价值。本项目的在于利用模型对不同花卉进行识别,从而进一步将不同花卉进行分类。 

项目准备 

一. 导入相关库和相关模块

   1.导入PyTorch库及相关模块

  用于加载和预处理图像数据集,torch是PyTorch的核心库,提供了张量操作和神经网络等功能;transforms模块提供了图像预处理的工具;ImageFolder是PyTorch提供的一个方便的数据集类,可以根据目录结构自动加载标注好的图像数据;DataLoader是PyTorch的数据加载器,可以将数据集分批加载并打乱顺序,方便训练神经网络。

    2.加载数据集

import torch
from torchvision import transforms
from torchvision.datasets import ImageFolder
from torch.utils.data import DataLoader

# 定义图像转换器
transform = transforms.Compose([
    transforms.Resize((224, 224)),  # 调整图像大小
    transforms.ToTensor(),           # 将PIL图像转为Tensor(张量),并归一化至[0,1]
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])  # 对图像的每个通道(对于RGB图像即红、绿、蓝三个通道)进行标准化处理。
])

# 加载数据集
dataset = ImageFolder('E:/flower7595/flowers', transform=transform)

# 划分训练集和验证集(假设80%训练,20%验证)
train_size = int(0.8 * len(dataset))
val_size = len(dataset) - train_size
train_dataset, val_dataset = torch.utils.data.random_split(dataset, [train_size, val_size])

# 创建DataLoader(数据加载器)
batch_size = 32
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)#训练集数据加载器
val_loader = DataLoader(val_dataset, batch_size=batch_size)    #验证集数据加载器

二.加载ResNet-18模型


  ResNet-18模型:为了解决图像分类问题而设计,特别是在ImageNet数据集上的大规模图像分类任务。该模型能够学习从图像中提取有区分度的特征,并将这些特征用于分类任务。

  1. 设置pretrained参数为True,以便加载预训练的权重。
  2. 修改ResNet-18模型的最后一层全连接层,将其输出维度改为5,以适应特定任务的类别数。
import torch.nn as nn
from torchvision.models import resnet18

# 加载ResNet-18模型
model = resnet18(pretrained=True)

# 修改最后一层全连接层以匹配5个类别
num_features = model.fc.in_features
model.fc = nn.Linear(num_features, 5)

# 使用GPU进行加速(如果GPU不可用,则退回到CPU计算。)
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model = model.to(device)

三.对模型进行训练

使用PyTorch训练模型

 1.定义函数
import torch.optim as optim
import matplotlib.pyplot as plt
import torch
from torch import nn
import os

epoch_losses = [] # 用于存储每个epoch的平均损失

#定义损失函数为交叉熵损失,用于多类分类问题。
def define_loss_function():
    return nn.CrossEntropyLoss()

#定义优化器为随机梯度下降(SGD),并设置学习率和动量。
def define_optimizer(model):
    return optim.SGD(model.parameters(), lr=0.001, momentum=0.9)
 2.开始训练

 主要参数:
    model: 要训练的模型。
    criterion: 损失函数。
    optimizer: 优化器。
    train_loader: 训练数据加载器。
    train_dataset: 训练数据集。
    num_epochs: 训练轮数。


    返回:
    训练后的模型:
def train_model(model, criterion, optimizer, train_loader, train_dataset, num_epochs):
    model.train()
    for epoch in range(num_epochs):
        running_loss = 0.0    # 用于记录当前epoch的训练损失
        for inputs, labels in train_loader:
            inputs, labels = inputs.to(device), labels.to(device)

            optimizer.zero_grad() # 清空梯度
            outputs = model(inputs)  # 前向传播
            loss = criterion(outputs, labels)  # 计算损失

            # 异常处理:监控梯度并处理梯度爆炸
            torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1)

            loss.backward()  # 反向传播
            optimizer.step()  # 更新参数 
            running_loss += loss.item() * inputs.size(0)  # 累加损失

        epoch_loss = running_loss / len(train_dataset)  # 计算当前epoch的平均损失

        # 将当前epoch的平均损失添加到epoch_losses列表
        epoch_losses.append(epoch_loss)

        print(f'Epoch {epoch+1}/{num_epochs}, Loss: {epoch_loss:.4f}')

        

    return model

可视化训练过程中的损失曲线。

 1.绘制可视化损失函数图像

   这里设置的训练轮数为20次


#绘制损失曲线
def plot_training_loss(epoch_losses):
    plt.figure()
    plt.plot(range(1, len(epoch_losses) + 1), epoch_losses, label='Training Loss')
    plt.xlabel('Epochs')
    plt.ylabel('Loss')
    plt.title('Training Loss per Epoch')
    plt.legend()
    plt.show()


criterion = define_loss_function() # 定义损失函数
optimizer = define_optimizer(model) # 定义优化器
num_epochs = 20  # 设置训练轮数
model = train_model(model, criterion, optimizer, train_loader, train_dataset, num_epochs)

  
plot_training_loss(epoch_losses) # 绘制损失曲线

2.输出的数据与图像

   根据观察函数图像可以知道,当训练的轮次大概在14次,图像趋于平缓。这说明,模型可能已经收敛到一个局部最优解或者全局最优解。

四.评估多类别分类模型的性能 

  接受一个经过训练的模型和一个测试数据加载器作为输入,然后在测试数据集上运行模型以生成预测结果

1.将模型设置为评估模式。

2.初始化用于存储预测结果和真实标签的空列表。

3.在测试数据加载器的每个批次

4.使用scikit-learn库计算性能指标

 # 计算每个类别的精确率,然后取宏平均
    macro_avg_precision = precision_score(all_labels, all_preds, average='macro', zero_division=0)

    # 计算整体准确度
    overall_accuracy = accuracy_score(all_labels, all_preds)

    # 或者计算每个类别的精确率列表
    class_precisions = precision_score(all_labels, all_preds, average=None, zero_division=0)
    

    return macro_avg_precision, class_precisions, overall_accuracy

5.返回宏平均精确率、每个类别的精确率列表和整体准确度 

# 假设model和test_loader已经定义
macro_avg_precision, class_precisions, overall_accuracy = evaluate_multiclass(model, val_loader)
print(f"Macro-average Precision: {macro_avg_precision * 100:.2f}%")
print(f"Overall Accuracy: {overall_accuracy * 100:.2f}%")


# 打印每个类别的精确率
#for i, precision in enumerate(class_precisions):
#    print(f"Class {i} Precision: {precision * 100:.2f}%")

输出的结果:

五.将训练好的模型进行分类预测,并将图像及其真实类别与预测类别进行对比显示 。

 分类预测 

   从验证集中抽取10张图像进行分类,并通过matplotlib进行显示。函数首先将模型设置为评估模式,然后遍历验证集的前10个批次。对于每个批次,将图像数据转移到指定的计算设备上,并通过模型进行预测

import torch
from torchvision.transforms import ToPILImage
from matplotlib import pyplot as plt
import numpy as np


def classify_and_compare(val_loader, model, device):
    """
    从验证集中抽取10张图像进行分类,并对比显示图像及其真实类别与预测类别。
    
    参数:
    - val_loader: 验证集的数据加载器
    - model: 训练好的模型实例
    - device: 使用的计算设备 ('cuda' 或 'cpu')
    """
    model.eval()  # 设置模型为评估模式,确保预测的一致性和可重复性。
    image_count = 0
    #to_pil = ToPILImage()  # 用于将Tensor转换为PIL图片以便显示
    

进行对比显示

  遍历验证数据加载器中的每个批次,将图像和标签传输到指定设备(GPU或CPU),通过模型获取预测输出,然后找到预测概率最大的类别。

with torch.no_grad():
        for images, labels in val_loader:
            if image_count >= 10:
                break
            
            images = images.to(device)
            labels = labels.to(device)
            outputs = model(images)
            
            _, preds = torch.max(outputs, 1)
            
            for img, label, pred in zip(images, labels, preds):
                img = img.cpu()  # 将图像数据移回CPU以便显示
                #img = to_pil(img)
                img = np.transpose(img, (1, 2, 0))  # 将(3, 224, 224)调整为(224, 224, 3)
                
                # 显示图像及其实验结果
                plt.figure(figsize=(10, 5))
                plt.subplot(1, 2, 1)
                plt.imshow(img)
                plt.title(f"True: {label.item()}, Pred: {pred.item()}") #“True: 实际类别, Pred: 预测类别”
                
                image_count += 1
                
                if image_count >= 10:
                    plt.show()
                    break


classify_and_compare(val_loader, model, device)

 显示对比结果:

六.前端页面实现 

   这个Web应用允许用户上传一张图片,然后使用ResNet18模型预测图片中的花卉类别。

  1. 加载预训练的ResNet18模型,并修改其最后一层以适应5个类别的分类任务。
  2. 定义图像转换器,包括调整图像大小、转换为张量和归一化处理。
  3. 加载处理后的图像数据集。
  4. 定义一个函数predict_image,它接受一个图像路径作为输入,打开图像、转换为张量并进行预测,返回预测的花卉类别。
  5. 使用Flask框架定义路由和视图函数
  6. 程序入口:启动Flask应用。
#创建Flask应用
#在命令行中,导航到你的app.py文件所在目录,运行python app.py命令启动Flask应用:
#通过浏览器访问http://127.0.0.1:5000/,选择一个图片上传,然后查看预测结果和图片。



from flask import Flask, render_template, request, send_from_directory
import os
import torch
from PIL import Image
from torchvision.transforms import ToTensor
import torchvision.models as models
import torchvision.transforms.functional as TF

from torchvision import transforms
from torchvision.datasets import ImageFolder
from torch.utils.data import DataLoader
import torch.nn as nn
from torchvision.models import resnet18



# 定义图像转换器
transform = transforms.Compose([
    transforms.Resize((224, 224)),  # 调整图像大小
    transforms.ToTensor(),           # 将PIL图像转为Tensor(张量),并归一化至[0,1]
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])  # 对图像的每个通道(对于RGB图像即红、绿、蓝三个通道)进行标准化处理。
])

# 加载数据集
dataset = ImageFolder('E:/flower7595/flowers', transform=transform)

#类别映射

class_names = {
    0: 'daisy',
    1: 'dandelion',
    2: 'rose',
    3: 'sunflower',
    4: 'tulip',
}

def get_class_name(index):
    """根据索引获取类别名称"""
    return class_names.get(index, "unknown")

# 初始化Flask应用
app = Flask(__name__)
#model_path = 'resnet18_whole_model.pth'

# 加载模型
# 1. 定义(或导入)你的模型结构
model = resnet18()

# 修改最后一层全连接层以匹配5个类别
num_features = model.fc.in_features
model.fc = nn.Linear(num_features, 5)

# 2. 加载保存的模型参数
whole_model_path = 'resnet18_whole_model.pth'
state_dict = torch.load(whole_model_path)

# 3. 将状态字典加载到模型中
model.load_state_dict(state_dict)

# 4. 可选:将模型转移到GPU,如果可用
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)

# 现在模型已经加载完毕,可以用于评估或继续训练
print("Model loaded successfully.")



def predict_image(image_path):  #image_path :表示存储图像文件的位置和文件名的字符串
    """预测图片"""

    '''
    img = Image.open(image_path)
    img_tensor = ToTensor()(img).unsqueeze(0)
    outputs = model(img_tensor)
    _, predicted = torch.max(outputs.data, 1)
    return predicted.item()
    '''

    img = Image.open(image_path)
    img_tensor = ToTensor()(img).unsqueeze(0)
    outputs = model(img_tensor)
    _, predicted_index = torch.max(outputs.data, 1)
    predicted_class = get_class_name(predicted_index.item())  # 使用get_class_name函数获取类别名称
    return predicted_class
    
 # 主页面路由,支持GET和POST请求   
@app.route('/', methods=['GET', 'POST'])
def index():
    # 当请求方法为POST时,处理上传的文件
    if request.method == 'POST':
         # 获取上传的文件
        image_file = request.files['image']
        if image_file:
            # 临时保存上传的图片
            img_path = os.path.join('uploads', image_file.filename)
            image_file.save(img_path)
            
            # 预测
            prediction = predict_image(img_path)
            
            # 删除临时文件(根据需要)
            # os.remove(img_path)
            
            # 渲染结果页面,传递预测结果和图片名
            return render_template('result.html', image_name=image_file.filename, prediction=prediction)
    return render_template('index.html')

# 处理上传文件的路由
@app.route('/uploads/<filename>')
def uploaded_file(filename):
    # 从上传目录发送指定文件
    return send_from_directory(app.config['UPLOAD_FOLDER'], filename)

#程序入口
if __name__ == '__main__':
    app.config['UPLOAD_FOLDER'] = 'uploads'
    os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
    app.run(debug=True)

html的页面设计: 

<!doctype html>
<!-- 定义HTML文档的类型 -->
<html>
<head>
    <title>Classification Result</title>
    <!-- 设置页面的标题 -->
</head>
<body>
    <h1>Result</h1>
    <!-- 显示预测结果的标题 -->
    <p>Predicted Class: {{ prediction }}</p>
    <!-- 显示模型预测的类别 -->
    <img src="{{ url_for('uploaded_file', filename=image_name) }}" alt="Uploaded Image">
    <!-- 显示上传的图片,如果图片无法加载,则显示"Uploaded Image"作为替代文本 -->
</body>
</html>

<!doctype html>
<!-- 定义HTML文档的类型和版本 -->
<html>
<head>
    <title>Image Classifier</title>
    <!-- 页面标题,显示在浏览器标签上 -->
</head>
<body>
    <h1>Upload an Image</h1>
    <!-- 页面主要标题,指示用户可以上传图片 -->
    <form method=post enctype=multipart/form-data>
      <!-- 定义表单,用于用户上传图片,enctype指定表单数据的编码类型 -->
      <input type=file name=image>
      <!-- 文件输入控件,允许用户选择要上传的文件 -->
      <input type=submit value=Upload>
      <!-- 提交按钮,点击后将表单数据提交到服务器 -->
    </form>
</body>
</html>

前端页面

  • 10
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值