项目案例:卷积神经网络实现食物图片分类代码详细解析

目标:

对文件夹中的食物图片使用卷积神经网络训练实现分类。

代码思路:

1、创建文件夹写入图片地址。2、定义处理原始图片获得张量的函数。3、定义卷积神经网络。4、定义测试函数与训练函数。5、选择损失函数和优化器。6、训练与测试。

代码设计:

1、创建文件夹写入图片地址:
python 复制代码
# 任务: 给图片自动生成train.txt,以及test.txt。对应的标签,就是用os默认的
import os  #

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):#os.list_dir()
        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')
                   #str(dirs.index(now_dir[-1]))作为图片的分类标签
    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、csv、json等格式
os.walk():

是Python 内置os模块的核心函数,作用是从指定的path路径开始,递归地遍历该路径下的所有目录和文件

遍历顺序:
split:

split(分隔符) 是 Python 字符串的内置函数,作用是:把一个字符串按指定的 "分隔符" 拆分成列表 。在代码中的效果:

执行结果:
2、把图片和标签转变为张量

代码:

python 复制代码
'''创建数据集的类'''
import torch
from torch.utils.data import Dataset,DataLoader  #用于处理数据集的
import numpy as np
from PIL import Image  #
from torchvision import transforms  #对数据进行处理工具 转换

data_transforms = { #机器学习的时候, 数据进行归一化?? 图片做归一化  ->0~1
    'train':
        transforms.Compose([
            transforms.Resize([300,300]),  #是图像变换大小  opencv  int8 0~
            transforms.RandomRotation(45),#随机旋转, -45到45度之间随机选
            transforms.CenterCrop(256),#从中心开始裁剪[256,256]
            transforms.RandomHorizontalFlip(p=0.5),#随机水平翻转 选择一个概率概率
            transforms.RandomVerticalFlip(p=0.5),#随机垂直翻转
            transforms.ColorJitter(brightness=0.2, contrast=0.1, saturation=0.1, hue=0.1),#参数1为亮度, 参数2为对比度, 参数3为饱和度, 参
            transforms.RandomGrayscale(p=0.1),#概率转换成灰度率, 3通道就是R=G=B
            transforms.ToTensor(),
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])#标准化, 均值, 标准差,
        ]),
    'valid':#验证集 不需要对图像进行数据增强
        transforms.Compose([
            transforms.Resize([256,256]),  #
            transforms.ToTensor(),
            transforms.Normalize( [0.485, 0.456, 0.406],  [0.229, 0.224, 0.225])
        ])
}#数组增强

class food_dataset(Dataset):  # food_dataset是自己创建的类名称,可以改为你需要的名称 2用法
    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,train.txt文件中标签保存在 self.labels
            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.imgs.
    def __len__(self): #类实例化对象后,可以使用len函数测量对象的个数  ls=[12,3,4,4] len(training_data)
        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,转换为tenor
        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=64,shuffle=True)#64张图片为一个包,
test_dataloader = DataLoader(test_data,batch_size=64,shuffle=True)
data_transforms:

用字典的形式定义了训练集和验证集的形式:

子类class food_dataset:

创建子类继承Datasetj处理图片数据,初始化过程中通过samples = [x.strip().split(' ') for x in f.readlines()]和下面的循环结构获取存储图片位置的列表和标签的列表。.strip()去除字符串两头的空格以及换行符。.split(' ')以空格分隔字符串。执行效果:

Dataloader:

Dataloader打包批次时会自动生成索引,调用__getitem__将图片和标签转化为张量的形式。到这里我们就完成了对原始数据的处理,获得了可以训练的张量形式数据,接下来就是定义神经网络进行训练,和上一篇博文的方法是一样的。

3、定义神经网络

代码:

python 复制代码
import torch
import torch.nn as nn
''' 定义神经网络 '''
class CNN(nn.Module):#类的名称。 2用法
    def __init__(self):         # 输入大小
        super(CNN, self).__init__()#初始化父类
        self.conv1 = nn.Sequential( #将多个层组合成一起。创建了一个容器,将多个网络合在一起
            nn.Conv2d(        #2d一般用于图像,3d用于视频数据(多一个时间维度),1d一般用于结构化的序列数据28*28
                in_channels=3,  # 图像通道个数,1表示灰度图(确定了卷积核 组中的个数),
                out_channels=16, # 要得到多少个特征图,卷积核的个数
                kernel_size=5,  # 卷积核大小,5×5
                stride=1,       # 步长
                padding=2,      # 一般希望卷积核处理后的结果大小与处理前的数据大小相同,效果会比较好。那padding该如何设计呢?建议stride为1
            ),                  # 输出的特征图为16*128*128
            nn.ReLU(),  # relu层,不会改变特征图的大小
            nn.MaxPool2d(kernel_size=2),  # 进行池化操作(2x2 区域),输出结果为16*14*14
        )
        self.conv2 = nn.Sequential( # 输入16*14*14
            nn.Conv2d(in_channels=16, out_channels=32, kernel_size=5, stride=1, padding=2),  # 输出32*128*128
            nn.ReLU(),  # relu层
            nn.Conv2d(in_channels=32, out_channels=32, kernel_size=5, stride=1, padding=2),  # 输出32*128*128
            nn.ReLU(),
            nn.MaxPool2d(2), #32*64*64
        )
        self.conv3 = nn.Sequential(
            nn.Conv2d(in_channels=32, out_channels=128, kernel_size=5, stride=1, padding=2), #128*64*64
            nn.ReLU(),  # 输出
        )
        self.out = nn.Linear(128*64 *64, out_features=20)  # 全连接层得到的结果


    def forward(self, x):#这里必须要写 forward是来自于父类nn里面的函数 要继承父类的功能
        x = self.conv1(x)
        x = self.conv2(x)
        x = self.conv3(x)#输出(64,64, 7, 7)
        x = x.view(x.size(0), -1)  # flatten操作,结果为:(batch_size, 64 * 7 * 7)
        output = self.out(x)
        return output


device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = CNN().to(device)#类的初始化完成,就会创建一个对象,model
print(model)
4、定义训练函数和测试函数

代码:

python 复制代码
# 5. 定义训练函数
def train(dataloader, model, loss_fn, optimizer):
    model.train()  # 切换到训练模式
    batch_size_num = 1  # 统计batch数量

    for X, y in dataloader: # dataloader是函数形参,调用时传入train_dataloader/test_dataloader;迭代返回批次级数据:X为批次图片张量(shape[batch_size,1,28,28]),y为批次标签张量(shape[batch_size]),对应批次内所有样本的图片和标签
        # 数据移动到设备
        X, y = X.to(device), y.to(device)

        # 前向传播计算预测值
        pred = model(X)  # 可省略.forward,model(X)会自动调用forward
        loss = loss_fn(pred, y)  # 计算损失

        # 反向传播更新参数
        optimizer.zero_grad()  # 梯度清零
        loss.backward()  # 反向传播计算梯度(原代码中Loss大写,修正为loss)
        optimizer.step()  # 更新模型参数

        # 每100个batch打印一次损失
        loss_value = loss.item()
        if batch_size_num % 100 == 0:
            print(f"Loss: {loss_value:>7f}  [batch: {batch_size_num}]")
        batch_size_num += 1


# 6. 定义测试函数
def test(dataloader, model, loss_fn):
    size = len(dataloader.dataset)  # 测试集总样本数
    num_batches = len(dataloader)  # 测试集batch数量
    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
    print(f"Test result: \n Accuracy: {(100 * correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")
5、选择损失函数和优化器

代码:

python 复制代码
# 7. 初始化损失函数和优化器
loss_fn = nn.CrossEntropyLoss()  # 交叉熵损失(适用于分类任务)
# optimizer = torch.optim.SGD(model.parameters(), lr=0.01)  # SGD优化器
# 原代码的optimizer替换为Adam
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)  # Adam默认lr=0.001即可
6、训练和测试

代码:

python 复制代码
epochs = 10  # 训练10轮
for t in range(epochs):
    print(f"\n训练轮数 {t+1}/{epochs}")
    train(train_dataloader, model, loss_fn, optimizer)
    test(test_dataloader, model, loss_fn)  # 每轮训练后测试
print("训练完成!")

print("开始测试:")
test(test_dataloader, model, loss_fn)
7、运行结果

保存模型

我们不会每次使用模型都重新训练一次,这里给出两种保存模型的方法:仅保存模型的参数和保存完整的模型(包括卷积神经网络的定义)。

以保存完整模型为例,在测试函数中加入保存模型的语句:

python 复制代码
def test(dataloader, model, loss_fn):
    global best_acc  # 声明使用全局变量
    size = len(dataloader.dataset)
    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()
    # 计算准确率(0~1之间)
    correct /= size
    # 保存最优模型
    if correct > best_acc:
        best_acc = correct
        # 保存TorchScript完整模型
        script_model = torch.jit.script(model)
        torch.jit.save(script_model, "best12.pth")
        # 仅保存模型参数方法:torch.save(model.state_dict(), path)        (w,b)
        #     # print(model.state_dict().keys())  # 输出模型参数名称 cnn
        #     # torch.save(model.state_dict(), "best2025-30.pth")
    print(f"Test: Accuracy {(100*correct):>0.1f}%, Best Accuracy {(100*best_acc):>0.1f}%")

加载模型:

保存完整模型后,我们使用保存的模型,不需要构建神经网络,只需要完成数据的处理,使用保存的模型best12.pth的完整代码:

python 复制代码
# -*- coding: gbk -*-
# 后续代码...
import torch
from torch.utils.data import Dataset,DataLoader  #用于处理数据集的
import numpy as np
from PIL import Image  #
from torchvision import transforms  #对数据进行处理工具 转换
# 选择设备(与保存时一致或按需调整)
device = "cuda" if torch.cuda.is_available() else "cpu"

# 加载TorchScript模型
model = torch.jit.load("best12.pth", map_location=device)

# 设置模型为评估模式(用于预测,禁用训练相关层的行为)
model.eval()

class food_dataset(Dataset):  # food_dataset是自己创建的类名称,可以改为你需要的名称 2用法
    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,train.txt文件中标签保存在 self.labels
            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.imgs.
    def __len__(self): #类实例化对象后,可以使用len函数测量对象的个数  ls=[12,3,4,4] len(training_data)
        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,转换为tenor
        label = self.labels[idx]  #label还不是tensor
        label = torch.from_numpy(np.array(label,dtype = np.int64)) #label也转换为tensor,
        return image, label

data_transforms = { #机器学习的时候, 数据进行归一化?? 图片做归一化  ->0~1
    'train':
        transforms.Compose([
            transforms.Resize([300,300]),  #是图像变换大小  opencv  int8 0~
            transforms.RandomRotation(45),#随机旋转, -45到45度之间随机选
            transforms.CenterCrop(256),#从中心开始裁剪[256,256]
            transforms.RandomHorizontalFlip(p=0.5),#随机水平翻转 选择一个概率概率
            transforms.RandomVerticalFlip(p=0.5),#随机垂直翻转
            transforms.ColorJitter(brightness=0.2, contrast=0.1, saturation=0.1, hue=0.1),#参数1为亮度, 参数2为对比度, 参数3为饱和度, 参
            transforms.RandomGrayscale(p=0.1),#概率转换成灰度率, 3通道就是R=G=B
            transforms.ToTensor(),
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])#标准化, 均值, 标准差,
        ]),
    'valid':#验证集 不需要对图像进行数据增强
        transforms.Compose([
            transforms.Resize([256,256]),  #
            transforms.ToTensor(),
            transforms.Normalize( [0.485, 0.456, 0.406],  [0.229, 0.224, 0.225])
        ])
}

test_data = food_dataset(file_path = './test.txt',transform = data_transforms['valid'])
test_dataloader = DataLoader(test_data, batch_size=1, shuffle=True)
result = []#保存的预测的结果
Lables = []#真实结果
def test_true(dataloader, model):
    with torch.no_grad():  #一个上下文管理器,关闭梯度计算。当你确认不会调用Tensor.backward()的时候。这可以减少计算所用内存消耗。
        for X, y in dataloader:
            X, y = X.to(device), y.to(device)
            pred = model.forward(X)#预测之后的结果。
            result.append(pred.argmax(1).item())
            Lables.append(y.item())

test_true(test_dataloader,model)
print('预测值:\t',result)
print('真实值:\t',Lables)

运行结果:

准确率还是很低哈,我们今天主要保证代码能跑通。先跑通代码,再调优。

如果我们使用仅保存模型参数的方法,使用时需要写出与原始模型参数一样的神经网络,这里不再赘述,如想了解可以问豆包等。

欧克,今天我们也是完成了基于卷积神经网络对实物图片的分类,从图片数据整理转化为张量,到构建卷积神经网络,到最后保存模型。完整完美实现了我们的案例项目。

相关推荐
景联文科技2 小时前
景联文 × 麦迪:归一医疗数据枢纽,构建AI医疗新底座
大数据·人工智能·数据标注
wyg_0311132 小时前
机器问道:大模型RAG 解读凡人修仙传
人工智能·python·transformer
未来之窗软件服务2 小时前
幽冥大陆(七十九)Python 水果识别训练视频识别 —东方仙盟练气期
开发语言·人工智能·python·水果识别·仙盟创梦ide·东方仙盟
光影少年2 小时前
AI前端开发需要会哪些及未来发展?
前端·人工智能·前端框架
hqyjzsb2 小时前
2026年AI证书选择攻略:当“平台绑定”与“能力通用”冲突,如何破局?
大数据·c语言·人工智能·信息可视化·职场和发展·excel·学习方法
独自归家的兔2 小时前
基于 cosyvoice-v3-plus 的简单语音合成
人工智能·后端·语音复刻
民乐团扒谱机2 小时前
【微实验】Python——量子增强时频传递的精度量化
人工智能·python·aigc·量子力学·时空·参数敏感性·光量子
G***技2 小时前
杰和IB3-771:以RK3588赋能机场巡检机器人
人工智能·物联网
xinyaokeji3 小时前
认准高精度:基恩士 VL 扫描仪为三维测量优选之选
大数据·人工智能