深度学习——基于卷积神经网络实现食物图像分类【1】(datalodar处理方法)

1. 项目概述

在这个项目中,我们将使用PyTorch框架构建一个卷积神经网络(CNN)来实现食物图像分类任务。我们的数据集包含20种不同的食物类别,包括八宝粥、巴旦木、白萝卜、板栗等常见食物。本文将详细介绍从数据准备、模型构建到训练和评估的完整流程。

2. 环境准备

首先,我们需要导入必要的Python库:

import torch
import torchvision.models as models
from torch import nn
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
from PIL import Image
import numpy as np
import os

3. 数据准备与预处理

3.1 数据转换定义

我们定义了两组数据转换,分别用于训练集和验证集:

data_transforms = {
    'train': transforms.Compose([
        transforms.Resize([256,256]),   # 统一图像大小为256x256
        transforms.ToTensor(),          # 转换为PyTorch张量
    ]),
    'valid': transforms.Compose([
        transforms.Resize([256,256]),
        transforms.ToTensor(),
    ]),
}

3.2 数据文件准备

我们编写了一个函数来生成包含图像路径和标签的文本文件:

def train_test_file(root, dir):
    file_txt = open(dir+'.txt','w')
    path = os.path.join(root, dir)
    for roots, directories, files in os.walk(path):
        if len(directories) != 0:
            dirs = directories
        else:
            now_dir = roots.split('\\')
            for file in files:
                path_1 = os.path.join(roots, file)
                file_txt.write(path_1+' '+str(dirs.index(now_dir[-1]))+'\n')
    file_txt.close()

# 调用函数生成训练集和测试集文件
root = r'.\食物分类\food_dataset'
train_dir = 'train'
test_dir = 'test'
train_test_file(root, train_dir)
train_test_file(root, test_dir)
  • 生成的 txt 文件如下所示,包含图片的路径、类别和对应的标签

在这里插入图片描述

3.3 自定义数据集类

我们创建了一个继承自torch.utils.data.Dataset的自定义数据集类:

class food_dataset(Dataset):
    def __init__(self, file_path, transform=None):
        self.file_path = file_path
        self.imgs = []
        self.labels = []
        self.transform = transform
        with open(self.file_path) as f:
            samples = [x.strip().split(' ') for x in f.readlines()]
            for img_path, label in samples:
                self.imgs.append(img_path)
                self.labels.append(label)
    
    def __len__(self):
        return len(self.imgs)
    
    def __getitem__(self, idx):
        image = Image.open(self.imgs[idx])
        if self.transform:
            image = self.transform(image)
        label = self.labels[idx]
        label = torch.from_numpy(np.array(label, dtype=np.int64))
        return image, label

3.4 创建数据加载器

training_data = food_dataset(file_path='train.txt', transform=data_transforms['train'])
test_data = food_dataset(file_path='test.txt', transform=data_transforms['valid'])

train_dataloader = DataLoader(training_data, batch_size=16, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=16, shuffle=True)

4. 模型构建

我们定义了一个包含三个卷积层的CNN模型:

class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        # 第一个卷积块
        self.conv1 = nn.Sequential(
            nn.Conv2d(3, 16, 5, 1, 2),  # (16,256,256)
            nn.ReLU(),
            nn.MaxPool2d(2),             # (16,128,128)
        )
        # 第二个卷积块
        self.conv2 = nn.Sequential(
            nn.Conv2d(16, 32, 5, 1, 2),  # (32,128,128)
            nn.ReLU(),
            nn.MaxPool2d(2),            # (32,64,64)
        )
        # 第三个卷积块
        self.conv3 = nn.Sequential(
            nn.Conv2d(32, 64, 5, 1, 2),  # (64,64,64)
            nn.ReLU(),
            nn.MaxPool2d(2),             # (64,32,32)
        )
        # 全连接层
        self.out = nn.Linear(64*32*32, 20)
    
    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = self.conv3(x)
        x = x.view(x.size(0), -1)      # 展平操作
        output = self.out(x)
        return output

5. 训练与评估

5.1 设备选择

device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
print(f"Using {device} device")
model = CNN().to(device)

5.2 训练函数

def train(dataloader, model, loss_fn, optimizer):
    model.train()
    batch_size_num = 1
    for X, y in dataloader:
        X, y = X.to(device), y.to(device)
        pred = model(X)
        loss = loss_fn(pred, y)
        
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        
        if batch_size_num % 100 == 0:
            print(f"loss: {loss.item():>7f} [number:{batch_size_num}]")
        batch_size_num += 1

5.3 测试函数

labels = {0:"八宝粥", 1:"巴旦木", 2:"白萝卜", 3:"板栗", 4:"菠萝", 5:"草莓",
          6:"蛋", 7:"蛋挞", 8:"骨肉相连", 9:"瓜子", 10:"哈密瓜", 11:"汉堡",
          12:"胡萝卜", 13:"火龙果", 14:"鸡翅", 15:"青菜", 16:"生肉", 17:"圣女果",
          18:"薯条", 19:"炸鸡"}

def Test(dataloader, model, loss_fn):
    size = len(dataloader.dataset)
    num_batches = len(dataloader)
    model.eval()
    test_loss, correct = 0, 0
    
    with torch.no_grad():
        for X, y in dataloader:
            X, y = X.to(device), y.to(device)
            pred = model(X)
            test_loss += loss_fn(pred, y).item()
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()
    
    test_loss /= num_batches
    correct /= size
    result = zip(pred.argmax(1).tolist(), y.tolist())
    for i in result:
        print(f"当前测试的结果为:{labels[i[0]]}\t当前真实的结果为:{labels[i[1]]}")
    print(f"\n最终测试结果: \n 准确率:{(100*correct):.2f}%, 平均损失:{test_loss:.4f}")

5.4 模型训练与评估

loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

epochs = 10
for t in range(epochs):
    print(f"epoch {t+1}\n---------------")
    train(train_dataloader, model, loss_fn, optimizer)
print("Done!")
Test(test_dataloader, model, loss_fn)

6. 关键点解析

  1. 数据预处理

    • 统一图像大小(256x256)
    • 转换为PyTorch张量
    • 自动归一化到[0,1]范围
  2. 模型结构

    • 三个卷积块,每个块包含卷积层、ReLU激活和最大池化
    • 逐步增加通道数(16→32→64)
    • 逐步减小特征图尺寸(256→128→64→32)
    • 全连接层输出20个类别的概率分布
  3. 训练技巧

    • 使用Adam优化器,学习率设为0.001
    • 交叉熵损失函数
    • 批量大小为16
    • 训练10个epoch
  4. 评估方法

    • 计算整体准确率
    • 计算平均损失
    • 展示部分预测结果与真实标签的对比

7. 可能的改进方向

  1. 数据增强:在训练时添加随机翻转、旋转等增强技术,提高模型泛化能力
  2. 学习率调度:使用学习率衰减策略,如StepLR或ReduceLROnPlateau
  3. 模型优化:尝试更深的网络结构或预训练模型(如ResNet)
  4. 正则化:添加Dropout层或L2正则化防止过拟合
  5. 超参数调优:系统性地调整学习率、批量大小等超参数

8. 完整代码

import torch
import torchvision.models as models
from torch import nn
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
from PIL import Image
import numpy as np
import os

data_transforms = { #字典
    'train':
        transforms.Compose([            #对图片预处理的组合
        transforms.Resize([256,256]),   #对数据进行改变大小
        transforms.ToTensor(),          #数据转换为tensor
    ]),
    'valid':
        transforms.Compose([
        transforms.Resize([256,256]),
        transforms.ToTensor(),
    ]),
}

def train_test_file(root,dir):
    file_txt = open(dir+'small.txt','w')
    path = os.path.join(root,dir)
    for roots,directories,files in os.walk(path):
        if len(directories) !=0:
            dirs = directories
        else:
            now_dir = roots.split('\\')
            for file in files:
                path_1 = os.path.join(roots,file)
                print(path_1)
                file_txt.write(path_1+' '+str(dirs.index(now_dir[-1]))+'\n')

    file_txt.close()

root = r'.\食物分类\food_dataset'
train_dir = 'train'
test_dir = 'test'
train_test_file(root,train_dir)
train_test_file(root,test_dir)

#Dataset是用来处理数据的
class food_dataset(Dataset):        # food_dataset是自己创建的类名称,可以改为你需要的名称
    def __init__(self,file_path,transform=None):    #类的初始化,解析数据文件txt
        self.file_path = file_path
        self.imgs = []
        self.labels = []
        self.transform = transform
        with open(self.file_path) as f: #是把train.txt文件中的图片路径保存在self.imgs
            samples = [x.strip().split(' ') for x in f.readlines()]
            for img_path,label in samples:
                self.imgs.append(img_path)  #图像的路径
                self.labels.append(label)   #标签,还不是tensor

# 初始化:把图片目录加到self
    def __len__(self):  #类实例化对象后,可以使用len函数测量对象的个数
        return  len(self.imgs)

    #training_data[1]
    def __getitem__(self, idx):    #关键,可通过索引的形式获取每一个图片的数据及标签
        image = Image.open(self.imgs[idx])  #读取到图片数据,还不是tensor,BGR
        if self.transform:                  #将PIL图像数据转换为tensor
            image = self.transform(image)   #图像处理为256*256,转换为tensor

        label = self.labels[idx]    #label还不是tensor
        label = torch.from_numpy(np.array(label,dtype=np.int64))    #label也转换为tensor
        return image,label
#training_data包含了本次需要训练的全部数据集
training_data = food_dataset(file_path='train.txt', transform=data_transforms['train'])
test_data = food_dataset(file_path='test.txt', transform=data_transforms['valid'])

#training_data需要具备索引的功能,还要确保数据是tensor
train_dataloader = DataLoader(training_data,batch_size=16,shuffle=True)
test_dataloader = DataLoader(test_data,batch_size=16,shuffle=True)


'''判断当前设备是否支持GPU,其中mps是苹果m系列芯片的GPU'''
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
print(f"Using {device} device")   #字符串的格式化,CUDA驱动软件的功能:pytorch能够去执行cuda的命令
# 神经网络的模型也需要传入到GPU,1个batch_size的数据集也需要传入到GPU,才可以进行训练


''' 定义神经网络  类的继承这种方式'''
class CNN(nn.Module): #通过调用类的形式来使用神经网络,神经网络的模型,nn.mdoule
    def __init__(self): #输入大小:(3,256,256)
        super(CNN,self).__init__()  #初始化父类
        self.conv1 = nn.Sequential( #将多个层组合成一起,创建了一个容器,将多个网络组合在一起
            nn.Conv2d(              # 2d一般用于图像,3d用于视频数据(多一个时间维度),1d一般用于结构化的序列数据
                in_channels=3,      # 图像通道个数,1表示灰度图(确定了卷积核 组中的个数)
                out_channels=16,     # 要得到多少个特征图,卷积核的个数
                kernel_size=5,      # 卷积核大小 3×3
                stride=1,           # 步长
                padding=2,          # 一般希望卷积核处理后的结果大小与处理前的数据大小相同,效果会比较好
            ),                      # 输出的特征图为(16,256,256)
            nn.ReLU(),  # Relu层,不会改变特征图的大小
            nn.MaxPool2d(kernel_size=2),    # 进行池化操作(2×2操作),输出结果为(16,128,128)
        )
        self.conv2 = nn.Sequential(
            nn.Conv2d(16,32,5,1,2),  #输出(32,128,128)
            nn.ReLU(),  #Relu层  (32,128,128)
            nn.MaxPool2d(kernel_size=2),    #池化层,输出结果为(32,64,64)
        )
        self.conv3 = nn.Sequential(
            nn.Conv2d(32, 64, 5, 1, 2),  # 输出(64,64,64)
            nn.ReLU(),  # Relu层  (64,64,64)
            nn.MaxPool2d(kernel_size=2),  # 池化层,输出结果为(64,32,32)
        )
        self.out = nn.Linear(64*32*32,20)  # 全连接层得到的结果


    def forward(self,x):   #前向传播,你得告诉它 数据的流向 是神经网络层连接起来,函数名称不能改
        x = self.conv1(x)
        x = self.conv2(x)
        x = self.conv3(x)
        x = x.view(x.size(0),-1)    # flatten操作,结果为:(batch_size,32 * 64 * 64)
        output = self.out(x)
        return output
model = CNN().to(device) #把刚刚创建的模型传入到GPU
print(model)


def train(dataloader,model,loss_fn,optimizer):
    model.train() #告诉模型,我要开始训练,模型中w进行随机化操作,已经更新w,在训练过程中,w会被修改的
# pytorch提供2种方式来切换训练和测试的模式,分别是:model.train() 和 mdoel.eval()
# 一般用法是:在训练开始之前写上model.train(),在测试时写上model.eval()
    batch_size_num = 1
    for X,y in dataloader:              #其中batch为每一个数据的编号
        X,y = X.to(device),y.to(device) #把训练数据集和标签传入cpu或GPU
        pred = model.forward(X)         # .forward可以被省略,父类种已经对此功能进行了设置
        loss = loss_fn(pred,y)          # 通过交叉熵损失函数计算损失值loss
        # Backpropagation 进来一个batch的数据,计算一次梯度,更新一次网络
        optimizer.zero_grad()           # 梯度值清零
        loss.backward()                 # 反向传播计算得到每个参数的梯度值w
        optimizer.step()                # 根据梯度更新网络w参数

        loss_value = loss.item()        # 从tensor数据种提取数据出来,tensor获取损失值
        if batch_size_num %100 ==0:
            print(f"loss: {loss_value:>7f} [number:{batch_size_num}]")
        batch_size_num += 1



labels={0:"八宝粥",1:"巴旦木",2:"白萝卜",3:"板栗",4:"菠萝",5:"草莓",
           6:"蛋",7:"蛋挞",8:"骨肉相连",9:"瓜子",10:"哈密瓜",11:"汉堡",
           12:"胡萝卜",13:"火龙果",14:"鸡翅",15:"青菜",16:"生肉",17:"圣女果",
           18:"薯条",19:"炸鸡"}

def Test(dataloader,model,loss_fn):
    size = len(dataloader.dataset)
    num_batches = len(dataloader)  # 打包的数量
    model.eval()        #测试,w就不能再更新
    test_loss,correct =0,0

    with torch.no_grad():       #一个上下文管理器,关闭梯度计算。当你确认不会调用Tensor.backward()的时候
        for X,y in dataloader:
            X,y = X.to(device),y.to(device)
            pred = model(X) #等价于model.forward(X)

            test_loss += loss_fn(pred,y).item() #test_loss是会自动累加每一个批次的损失值
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()
            a = (pred.argmax(1) == y) #dim=1表示每一行中的最大值对应的索引号,dim=0表示每一列中的最大值对应的索引号
            b = (pred.argmax(1) == y).type(torch.float)
    test_loss /= num_batches #能来衡量模型测试的好坏
    correct /= size  #平均的正确率
    result = zip(pred.argmax(1).tolist(), y.tolist())
    for i in result:
        print(f"当前测试的结果为:{labels[i[0]]}\t当前真实的结果为:{labels[i[1]]}")
    print(f"\n最终测试结果: \n 准确率:{(100*correct):.2f}%, 平均损失:{test_loss:.4f}")

loss_fn = nn.CrossEntropyLoss()  #创建交叉熵损失函数对象,因为手写字识别一共有十种数字,输出会有10个结果

optimizer = torch.optim.Adam(model.parameters(),lr=0.001) #创建一个优化器,SGD为随机梯度下降算法
# # params:要训练的参数,一般我们传入的都是model.parameters()
# # lr:learning_rate学习率,也就是步长
#
# # loss表示模型训练后的输出结果与样本标签的差距。如果差距越小,就表示模型训练越好,越逼近真实的模型
# train(train_dataloader,model,loss_fn,optimizer) #训练1次完整的数据。多轮训练
# Test(test_dataloader,model,loss_fn)

epochs = 10
for t in range(epochs):
    print(f"epoch {t+1}\n---------------")
    train(train_dataloader,model,loss_fn,optimizer)
print("Done!")
Test(test_dataloader,model,loss_fn)

9. 总结

本文详细介绍了使用PyTorch实现食物图像分类的完整流程,包括数据准备、模型构建、训练和评估。通过这个项目,我们学习到了:

  1. 如何自定义数据集类来处理图像数据
  2. 如何构建一个有效的CNN模型
  3. PyTorch训练循环的基本结构
  4. 模型评估的方法和指标

这个项目可以作为计算机视觉任务的入门实践,为后续更复杂的图像分类任务打下基础。读者可以尝试修改模型结构或调整超参数,观察对模型性能的影响。

### 扣子智能体平台功能与使用说明 #### 平台概述 扣子Coze)是由字节跳动推出的一款面向终端用户的智能体开发平台[^3]。该平台支持用户通过零代码或低代码方式快速构建基于人工智能大模型的各种智能体应用,并能够将其部署至其他网站或者通过 API 集成到现有的系统中。 #### 快速搭建智能体 无论是具备还是缺乏编程基础的用户,都能够借助扣子平台迅速创建一个 AI 智能体。例如,可以参照一篇教程中的实例来学习如何打造一个解决日常生活问题的小助手[^1]。这不仅降低了技术门槛,还使得更多的人有机会参与到智能化工具的设计过程中去。 #### 插件系统的利用 为了进一步增强所建智能体的能力,在其技能配置环节可加入不同类型的插件。一旦添加成功,则可以在编写提示语句的时候直接调用这些插件,亦或是融入自动化流程里实现更复杂操作逻辑的目的[^2]。这种灵活运用外部资源的方法极大地拓宽了单个智能体所能覆盖的应用场景范围。 ```python # 示例:假设我们有一个简单的 Python 脚本用于模拟调用某个插件功能 def call_plugin(plugin_name, parameters): result = f"Plugin {plugin_name} called with params: {parameters}" return result example_call = call_plugin("weather", {"location": "Beijing"}) print(example_call) ``` 上述代码片段仅作为概念展示之用,实际情况下具体实现会依据官方文档指导完成。 #### 总结 综上所述,扣子智能体平台提供了便捷高效的途径让用户无需深厚编码背景即可打造出满足特定需求的AI解决方案;同时它开放性强允许接入第三方服务从而提升整体性能表现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值