You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在PyTorch实现的CNN食物分类任务中添加数据归一化?

食物分类CNN优化问题及解决方案

我是CNN初学者,正在基于食物分类任务学习CNN,以下是我的初始实现代码。在DATASET部分,我将训练集和验证集从numpy数组转换为tensor,此时tensor的形状为[9866, 128, 128, 3]。由于3通道需要放在第一维度,我使用transpose方法调整了维度顺序,之后用Data.TensorDataset拼接训练数据和训练标签,使用Data.DataLoader是为了按批次加载以提升训练速度。

import os
import numpy as np
import pandas as pd
import cv2
import torch
import torch.nn as nn
from torch.nn import functional as F
import torchvision.transforms as transforms
from torch.autograd import Variable
from torch import optim
import pandas as pd
from torch.utils.data import DataLoader, Dataset
import torch.utils.data as Data



'''Initialize Params'''
epochs = 3
learning_rate = 0.0001
momentum = 0.5
batch_size = 128


'''Load Data'''
def readFile(path,label):
    image_dir = sorted(os.listdir(path))
    # x stores photos
    x = np.zeros((len(image_dir),128,128,3),dtype=np.uint8)
    # y stores labels
    y = np.zeros((len(image_dir)), dtype=np.uint8)
    for i, file in enumerate(image_dir):
        img = cv2.imread(os.path.join(path, file))
        x[i, :, :] = cv2.resize(img,(128, 128))
        if label:
            y[i] = int(file.split("_")[0])
    if label:
        return x,y
    else:
        return x

train_x, train_y = readFile('./food/training',True)
val_x, val_y = readFile('./food/validation',True)
test_x = readFile('./food/testing',False)
# print("Reading data: ")
# print("Size of training data = {}".format(len(train_x)))
# print("Size of validation data = {}".format(len(val_x)))
# print("Size of Testing data = {}".format(len(test_x)))


'''DataSet'''
train_x = torch.tensor(train_x)
# print(train_x.shape)
train_x = train_x.transpose(1,3).float()
train_y = torch.tensor(train_y)
val_x = torch.tensor(val_x)
val_x = val_x.transpose(1, 3).float()
val_y = torch.tensor(val_y)


train_dataset = Data.TensorDataset(train_x,train_y)
val_dataset = Data.TensorDataset(val_x,val_y)

train_loader = Data.DataLoader(dataset=train_dataset,batch_size=batch_size,shuffle=True)
val_loader = Data.DataLoader(dataset=val_dataset,batch_size=batch_size,shuffle=True) 

目前我的模型训练集准确率仅为68%,希望进一步提升效果。我在网上搜索得知可以添加数据归一化操作,但只找到了如下实现方式:

transform = transforms.Compose([
    transforms.ToTensor(), # range [0, 255] -> [0.0,1.0]
    ]
)

我不清楚如何将其和Data.DataLoader结合使用。我还查到了另一种将numpy数据转换为dataloader的自定义Dataset实现,实现代码如下:

train_transform = transforms.Compose([
    transforms.ToPILImage(),
    transforms.RandomHorizontalFlip(), 
    transforms.RandomRotation(15), 
    transforms.ToTensor(), 
])

test_transform = transforms.Compose([
    transforms.ToPILImage(),                                    
    transforms.ToTensor(),
])


class ImgDataset(Dataset):
    def __init__(self, x, y=None, transform=None):
        self.x = x
        self.y = y
        if y is not None:
            self.y = torch.LongTensor(y)
        self.transform = transform
    def __len__(self):
        return len(self.x)
    def __getitem__(self, index):
        X = self.x[index]
        if self.transform is not None:
            X = self.transform(X)
        if self.y is not None:
            Y = self.y[index]
            return X, Y
        else:
            return X

train_set = ImgDataset(train_x, train_y, train_transform)
val_set = ImgDataset(val_x, val_y, test_transform)
train_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True)
val_loader = DataLoader(val_set, batch_size=batch_size, shuffle=False)

我的实现方式可能不够灵活,但我希望基于现有方案调整,恳请各位提供帮助,感谢解答。完整实现代码如下:

import os
import numpy as np
import pandas as pd
import cv2
import torch
import torch.nn as nn
from torch.nn import functional as F
import torchvision.transforms as transforms
from torch.autograd import Variable
from torch import optim
import pandas as pd
from torch.utils.data import DataLoader, Dataset
import torch.utils.data as Data



'''Initialize Params'''
epochs = 3
learning_rate = 0.0001
momentum = 0.5
batch_size = 128


transform = transforms.Compose([
    transforms.ToTensor(), # range [0, 255] -> [0.0,1.0]
    ]
)



'''Load Data'''
def readFile(path,label):
    image_dir = sorted(os.listdir(path))
    # x stores photos
    x = np.zeros((len(image_dir),128,128,3),dtype=np.uint8)
    # y stores labels
    y = np.zeros((len(image_dir)), dtype=np.uint8)
    for i, file in enumerate(image_dir):
        img = cv2.imread(os.path.join(path, file))
        x[i, :, :] = cv2.resize(img,(128, 128))
        if label:
            y[i] = int(file.split("_")[0])
    if label:
        return x,y
    else:
        return x

train_x, train_y = readFile('./food/training',True)
val_x, val_y = readFile('./food/validation',True)
test_x = readFile('./food/testing',False)
# print("Reading data: ")
# print("Size of training data = {}".format(len(train_x)))
# print("Size of validation data = {}".format(len(val_x)))
# print("Size of Testing data = {}".format(len(test_x)))


'''DataSet'''
train_x = torch.tensor(train_x)
# print(train_x.shape)
train_x = train_x.transpose(1,3).float()
train_y = torch.tensor(train_y)
val_x = torch.tensor(val_x)
val_x = val_x.transpose(1, 3).float()
val_y = torch.tensor(val_y)


train_dataset = Data.TensorDataset(train_x,train_y)
val_dataset = Data.TensorDataset(val_x,val_y)

train_loader = Data.DataLoader(dataset=train_dataset,batch_size=batch_size,shuffle=True)
val_loader = Data.DataLoader(dataset=val_dataset,batch_size=batch_size,shuffle=True) 


'''Create Model'''
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        # nn.Conv2d(input_channel, output_channel, kernel, stride)
        self.conv1 = nn.Conv2d(3,64,5,1,1) 
        nn.BatchNorm2d(64) 
        self.conv2 = nn.Conv2d(64,128,5,1,1)
        nn.BatchNorm2d(128)
        self.conv3 = nn.Conv2d(128,256,5,1,1)
        nn.BatchNorm2d(256)
        self.conv4 = nn.Conv2d(256,256,5,1,1)
        nn.BatchNorm2d(256)
        self.conv4_drop = nn.Dropout2d()
        self.fc1 = nn.Linear(6*6*256, 1024)  
        # self.fc1 = nn.Linear(512*4*4, 1024)
        self.fc2 = nn.Linear(1024, 512)
        self.fc3 = nn.Linear(512, 256)
        self.fc4 = nn.Linear(256, 11)


    def forward(self, x):
        # maxpooling 1
        x = self.conv1(x)
        x = F.relu(x) # 124*124*64
        x = F.max_pool2d(x, 2) # 62*62*20

        # maxpooling 2
        x = self.conv2(x)
        x = F.relu(x) # 58*58*128
        x = F.max_pool2d(x, 2) # 29*29*40

        # maxpooling 3
        x = self.conv3(x)
        x = F.relu(x) # 25*25*256
        x = F.max_pool2d(x, 2) # 12*12*100

        # maxpooling 4
        x = self.conv4(x)
        x = F.relu(x) # 8*8*256
        x = F.max_pool2d(x, 2) # 4*4*256

        x = x.view(-1,6*6*256)
        # print(x.shape)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = F.relu(self.fc3(x))
        x = self.fc4(x)
        # return F.log_softmax(x)
        return F.log_softmax(x)


'''Initialize the network'''
net = Net()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(), lr=learning_rate)


'''Train Model'''
if __name__ == '__main__':
    # print(train_x.shape)  torch.Size([9866, 128, 128, 3])
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    net.to(device)
    for epoch in range(epochs):
        for i,data in enumerate(train_loader):
            x,y = data
            # print(x)
            # print('--------------------')
            sum_loss = 0.0
            optimizer.zero_grad()  
            x,y = Variable(x).cuda(), Variable(y).cuda()
            outputs = net(x)  
            loss = criterion(outputs, y.long())  
            loss.backward()
            optimizer.step()  
            # print(loss.item())
            # sum_loss += loss.item()
            # if i % 500 == 99:
            #     print('[%d,%d] loss:%.03f' %
            #             (epoch + 1, i + 1, sum_loss / 500))
            #     sum_loss = 0.0


        '''Test Model with Validation'''
        net.eval() 
        correct = 0
        total = 0
        for val_data in val_loader:
            x,y = val_data
            x, y = Variable(x).cuda(), Variable(y).cuda()
            output_test = net(x)
            _, predicted = torch.max(output_test, 1)
            total += y.size(0)
            correct += (predicted == y).sum()
        print("correct1: ", correct)
        print("Test acc: {0}".format(correct.item() /len(val_loader)))

可直接落地的调整方案

1. 数据加载模块调整(支持归一化+数据增强,兼容你现有逻辑)

删除原来DataSet模块下手动转tensor、调整维度的代码,直接复用你找到的自定义Dataset类即可,transform会自动完成维度调整、[0,255]到[0,1]的归一化,还可以额外加数据增强提升泛化能力:

# 替换原有transform和DataSet模块代码
train_transform = transforms.Compose([
    transforms.ToPILImage(),
    transforms.RandomHorizontalFlip(), # 随机水平翻转
    transforms.RandomRotation(15), # 随机旋转15度以内
    transforms.ToTensor(), # 自动完成归一化+维度调整
    # 可选:追加ImageNet预训练分布归一化,适合后续微调预训练模型
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

test_transform = transforms.Compose([
    transforms.ToPILImage(),                                    
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

class ImgDataset(Dataset):
    def __init__(self, x, y=None, transform=None):
        self.x = x
        self.y = y
        if y is not None:
            self.y = torch.LongTensor(y)
        self.transform = transform
    def __len__(self):
        return len(self.x)
    def __getitem__(self, index):
        X = self.x[index]
        if self.transform is not None:
            X = self.transform(X)
        if self.y is not None:
            Y = self.y[index]
            return X, Y
        else:
            return X

# 直接用readFile返回的numpy数组创建数据集即可
train_set = ImgDataset(train_x, train_y, train_transform)
val_set = ImgDataset(val_x, val_y, test_transform)
train_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True)
val_loader = DataLoader(val_set, batch_size=batch_size, shuffle=False)

2. 现有模型和训练逻辑的bug修复(直接影响准确率)

  • 你定义的nn.BatchNorm2d没有赋值给类属性,相当于没有生效,需要改成self.bn1 = nn.BatchNorm2d(64)的形式并在forward中调用
  • 全连接层输入维度计算错误:四次池化后输出维度是44256,不是66256,维度不匹配会导致训练异常
  • 验证准确率计算错误:原有代码是除以loader的批次数量,应该除以验证集总样本数total
  • 训练轮次设置过小,3轮不足以让模型收敛,建议调到20-30轮,可以配合学习率衰减进一步提升效果
  • F.log_softmax需要指定dim参数,避免计算异常:F.log_softmax(x, dim=1)

修正后的模型代码如下:

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3,64,5,1,1) 
        self.bn1 = nn.BatchNorm2d(64) 
        self.conv2 = nn.Conv2d(64,128,5,1,1)
        self.bn2 = nn.BatchNorm2d(128)
        self.conv3 = nn.Conv2d(128,256,5,1,1)
        self.bn3 = nn.BatchNorm2d(256)
        self.conv4 = nn.Conv2d(256,256,5,1,1)
        self.bn4 = nn.BatchNorm2d(256)
        self.conv4_drop = nn.Dropout2d()
        self.fc1 = nn.Linear(4*4*256, 1024)  
        self.fc2 = nn.Linear(1024, 512)
        self.fc3 = nn.Linear(512, 256)
        self.fc4 = nn.Linear(256, 11)

    def forward(self, x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = F.relu(x)
        x = F.max_pool2d(x, 2)

        x = self.conv2(x)
        x = self.bn2(x)
        x = F.relu(x)
        x = F.max_pool2d(x, 2)

        x = self.conv3(x)
        x = self.bn3(x)
        x = F.relu(x)
        x = F.max_pool2d(x, 2)

        x = self.conv4(x)
        x = self.bn4(x)
        x = F.relu(x)
        x = F.max_pool2d(x, 2)

        x = x.view(-1,4*4*256)
        x = F.relu(self.fc1(x))
        x = F.dropout(x, training=self.training)
        x = F.relu(self.fc2(x))
        x = F.dropout(x, training=self.training)
        x = F.relu(self.fc3(x))
        x = self.fc4(x)
        return F.log
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.06 23:30:00