InceptionV4 Pytorch 实现图片分类

一、目录结构

训练过程:

  1. 在训练集和测试集分类目录中放好待训练的分类图片(f1,f2,f3)
  2. 运行模型训练代码,生成模型参数文件
  3. 运行分类测试文件,设置待验证的图片路径,调用模型文件得出分类结果

二、模型构建代码

python 复制代码
import torch
import torch.nn as nn
import torch.nn.functional as F

class BasicConv2d(nn.Module):
    def __init__(self, in_channels, out_channels, **kwargs):
        super(BasicConv2d, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, **kwargs)
        self.bn = nn.BatchNorm2d(out_channels)

    def forward(self, x):
        x = self.conv(x)
        x = self.bn(x)
        return F.relu(x)


class InceptionA(nn.Module):
    def __init__(self, in_channels, out_channels):
        super(InceptionA, self).__init__()
        # branch1: avgpool --> conv1*1(96)
        self.b1_1 = nn.AvgPool2d(kernel_size=3, padding=1, stride=1)
        self.b1_2 = BasicConv2d(in_channels, 96, kernel_size=1)

        # branch2: conv1*1(96)
        self.b2 = BasicConv2d(in_channels, 96, kernel_size=1)

        # branch3: conv1*1(64) --> conv3*3(96)
        self.b3_1 = BasicConv2d(in_channels, 64, kernel_size=1)
        self.b3_2 = BasicConv2d(64, 96, kernel_size=3, padding=1)

        # branch4: conv1*1(64) --> conv3*3(96) --> conv3*3(96)
        self.b4_1 = BasicConv2d(in_channels, 64, kernel_size=1)
        self.b4_2 = BasicConv2d(64, 96, kernel_size=3, padding=1)
        self.b4_3 = BasicConv2d(96, 96, kernel_size=3, padding=1)

    def forward(self, x):
        y1 = self.b1_2(self.b1_1(x))
        y2 = self.b2(x)
        y3 = self.b3_2(self.b3_1(x))
        y4 = self.b4_3(self.b4_2(self.b4_1(x)))

        outputsA = [y1, y2, y3, y4]
        return torch.cat(outputsA, 1)


class InceptionB(nn.Module):
    def __init__(self, in_channels, out_channels):
        super(InceptionB, self).__init__()
        # branch1: avgpool --> conv1*1(128)
        self.b1_1 = nn.AvgPool2d(kernel_size=3, padding=1, stride=1)
        self.b1_2 = BasicConv2d(in_channels, 128, kernel_size=1)

        # branch2: conv1*1(384)
        self.b2 = BasicConv2d(in_channels, 384, kernel_size=1)

        # branch3: conv1*1(192) --> conv1*7(224) --> conv1*7(256)
        self.b3_1 = BasicConv2d(in_channels, 192, kernel_size=1)
        self.b3_2 = BasicConv2d(192, 224, kernel_size=(1, 7), padding=(0, 3))
        self.b3_3 = BasicConv2d(224, 256, kernel_size=(1, 7), padding=(0, 3))

        # branch4: conv1*1(192) --> conv1*7(192) --> conv7*1(224) --> conv1*7(224) --> conv7*1(256)
        self.b4_1 = BasicConv2d(in_channels, 192, kernel_size=1, stride=1)
        self.b4_2 = BasicConv2d(192, 192, kernel_size=(1, 7), padding=(0, 3))
        self.b4_3 = BasicConv2d(192, 224, kernel_size=(7, 1), padding=(3, 0))
        self.b4_4 = BasicConv2d(224, 224, kernel_size=(1, 7), padding=(0, 3))
        self.b4_5 = BasicConv2d(224, 256, kernel_size=(7, 1), padding=(3, 0))

    def forward(self, x):
        y1 = self.b1_2(self.b1_1(x))
        y2 = self.b2(x)
        y3 = self.b3_3(self.b3_2(self.b3_1(x)))
        y4 = self.b4_5(self.b4_4(self.b4_3(self.b4_2(self.b4_1(x)))))

        outputsB = [y1, y2, y3, y4]
        return torch.cat(outputsB, 1)


class InceptionC(nn.Module):
    def __init__(self, in_channels, out_channels):
        super(InceptionC, self).__init__()
        # branch1: avgpool --> conv1*1(256)
        self.b1_1 = nn.AvgPool2d(kernel_size=3, padding=1, stride=1)
        self.b1_2 = BasicConv2d(in_channels, 256, kernel_size=1)

        # branch2: conv1*1(256)
        self.b2 = BasicConv2d(in_channels, 256, kernel_size=1)

        # branch3: conv1*1(384) --> conv1*3(256) & conv3*1(256)
        self.b3_1 = BasicConv2d(in_channels, 384, kernel_size=1)
        self.b3_2_1 = BasicConv2d(384, 256, kernel_size=(1, 3), padding=(0, 1))
        self.b3_2_2 = BasicConv2d(384, 256, kernel_size=(3, 1), padding=(1, 0))

        # branch4: conv1*1(384) --> conv1*3(448) --> conv3*1(512) --> conv3*1(256) & conv7*1(256)
        self.b4_1 = BasicConv2d(in_channels, 384, kernel_size=1, stride=1)
        self.b4_2 = BasicConv2d(384, 448, kernel_size=(1, 3), padding=(0, 1))
        self.b4_3 = BasicConv2d(448, 512, kernel_size=(3, 1), padding=(1, 0))
        self.b4_4_1 = BasicConv2d(512, 256, kernel_size=(3, 1), padding=(1, 0))
        self.b4_4_2 = BasicConv2d(512, 256, kernel_size=(1, 3), padding=(0, 1))

    def forward(self, x):
        y1 = self.b1_2(self.b1_1(x))
        y2 = self.b2(x)
        y3_1 = self.b3_2_1(self.b3_1(x))
        y3_2 = self.b3_2_2(self.b3_1(x))
        y4_1 = self.b4_4_1(self.b4_3(self.b4_2(self.b4_1(x))))
        y4_2 = self.b4_4_2(self.b4_3(self.b4_2(self.b4_1(x))))

        outputsC = [y1, y2, y3_1, y3_2, y4_1, y4_2]
        return torch.cat(outputsC, 1)


class ReductionA(nn.Module):
    def __init__(self, in_channels, out_channels, k, l, m, n):
        super(ReductionA, self).__init__()
        # branch1: maxpool3*3(stride2 valid)
        self.b1 = nn.MaxPool2d(kernel_size=3, stride=2)

        # branch2: conv3*3(n stride2 valid)
        self.b2 = BasicConv2d(in_channels, n, kernel_size=3, stride=2)

        # branch3: conv1*1(k) --> conv3*3(l) --> conv3*3(m stride2 valid)
        self.b3_1 = BasicConv2d(in_channels, k, kernel_size=1)
        self.b3_2 = BasicConv2d(k, l, kernel_size=3, padding=1)
        self.b3_3 = BasicConv2d(l, m, kernel_size=3, stride=2)

    def forward(self, x):
        y1 = self.b1(x)
        y2 = self.b2(x)
        y3 = self.b3_3(self.b3_2(self.b3_1(x)))

        outputsRedA = [y1, y2, y3]
        return torch.cat(outputsRedA, 1)


class ReductionB(nn.Module):
    def __init__(self, in_channels, out_channels):
        super(ReductionB, self).__init__()
        # branch1: maxpool3*3(stride2 valid)
        self.b1 = nn.MaxPool2d(kernel_size=3, stride=2)

        # branch2: conv1*1(192) --> conv3*3(192 stride2 valid)
        self.b2_1 = BasicConv2d(in_channels, 192, kernel_size=1)
        self.b2_2 = BasicConv2d(192, 192, kernel_size=3, stride=2)

        # branch3: conv1*1(256) --> conv1*7(256) --> conv7*1(320) --> conv3*3(320 stride2 valid)
        self.b3_1 = BasicConv2d(in_channels, 256, kernel_size=1)
        self.b3_2 = BasicConv2d(256, 256, kernel_size=(1, 7), padding=(0, 3))
        self.b3_3 = BasicConv2d(256, 320, kernel_size=(7, 1), padding=(3, 0))
        self.b3_4 = BasicConv2d(320, 320, kernel_size=3, stride=2)

    def forward(self, x):
        y1 = self.b1(x)
        y2 = self.b2_2(self.b2_1((x)))
        y3 = self.b3_4(self.b3_3(self.b3_2(self.b3_1(x))))

        outputsRedB = [y1, y2, y3]
        return torch.cat(outputsRedB, 1)


class Stem(nn.Module):
    def __init__(self, in_channels, out_channels):
        super(Stem, self).__init__()
        # conv3*3(32 stride2 valid)
        self.conv1 = BasicConv2d(in_channels, 32, kernel_size=3, stride=2)
        # conv3*3(32 valid)
        self.conv2 = BasicConv2d(32, 32, kernel_size=3)
        # conv3*3(64)
        self.conv3 = BasicConv2d(32, 64, kernel_size=3, padding=1)
        # maxpool3*3(stride2 valid) & conv3*3(96 stride2 valid)
        self.maxpool4 = nn.MaxPool2d(kernel_size=3, stride=2)
        self.conv4 = BasicConv2d(64, 96, kernel_size=3, stride=2)

        # conv1*1(64) --> conv3*3(96 valid)
        self.conv5_1_1 = BasicConv2d(160, 64, kernel_size=1)
        self.conv5_1_2 = BasicConv2d(64, 96, kernel_size=3)
        # conv1*1(64) --> conv7*1(64) --> conv1*7(64) --> conv3*3(96 valid)
        self.conv5_2_1 = BasicConv2d(160, 64, kernel_size=1)
        self.conv5_2_2 = BasicConv2d(64, 64, kernel_size=(7, 1), padding=(3, 0))
        self.conv5_2_3 = BasicConv2d(64, 64, kernel_size=(1, 7), padding=(0, 3))
        self.conv5_2_4 = BasicConv2d(64, 96, kernel_size=3)

        # conv3*3(192 valid)
        self.conv6 = BasicConv2d(192, 192, kernel_size=3, stride=2)
        # maxpool3*3(stride2 valid)
        self.maxpool6 = nn.MaxPool2d(kernel_size=3, stride=2)

    def forward(self, x):
        y1_1 = self.maxpool4(self.conv3(self.conv2(self.conv1(x))))
        y1_2 = self.conv4(self.conv3(self.conv2(self.conv1(x))))
        y1 = torch.cat([y1_1, y1_2], 1)

        y2_1 = self.conv5_1_2(self.conv5_1_1(y1))
        y2_2 = self.conv5_2_4(self.conv5_2_3(self.conv5_2_2(self.conv5_2_1(y1))))
        y2 = torch.cat([y2_1, y2_2], 1)

        y3_1 = self.conv6(y2)
        y3_2 = self.maxpool6(y2)
        y3 = torch.cat([y3_1, y3_2], 1)

        return y3


class MyInceptionV4(nn.Module):
    def __init__(self, num_classes):
        super(MyInceptionV4, self).__init__()
        self.stem = Stem(3, 384)
        self.icpA = InceptionA(384, 384)
        self.redA = ReductionA(384, 1024, 192, 224, 256, 384)
        self.icpB = InceptionB(1024, 1024)
        self.redB = ReductionB(1024, 1536)
        self.icpC = InceptionC(1536, 1536)
        self.avgpool = nn.AvgPool2d(kernel_size=8)
        self.dropout = nn.Dropout(p=0.8)
        self.linear = nn.Linear(1536, out_features=num_classes)

    def forward(self, x):
        # Stem Module
        out = self.stem(x)
        # InceptionA Module * 4
        out = self.icpA(self.icpA(self.icpA(self.icpA(out))))
        # ReductionA Module
        out = self.redA(out)
        # InceptionB Module * 7
        out = self.icpB(self.icpB(self.icpB(self.icpB(self.icpB(self.icpB(self.icpB(out)))))))
        # ReductionB Module
        out = self.redB(out)
        # InceptionC Module * 3
        out = self.icpC(self.icpC(self.icpC(out)))
        # Average Pooling
        out = self.avgpool(out)
        out = out.view(out.size(0), -1)
        # Dropout
        out = self.dropout(out)
        # Linear(Softmax)
        out = self.linear(out)

        return out

# def test():
#     x = torch.randn(20, 3, 299, 299)
#     net = MyInceptionV4(num_classes=5)
#     y = net(x)
#     print(y.size())
# test()

三、模型训练代码

python 复制代码
import time
import torch
from torch import nn
import os
from MyInceptionV4 import MyInceptionV4 as Model
from torchvision.datasets import ImageFolder
from torch.utils.data import DataLoader
import torchvision.transforms as transforms
from tqdm import tqdm



os.environ["PYTORCH_CUDA_ALLOC_CONF"]="expandable_segments:True,max_split_size_mb:64"


def WriteData(fname, *args):
    with open(fname, 'a+') as f:
        for data in args:
            f.write(str(data)+"\t")
        f.write("\n")

def train(dataloader, model, loss_fn, optimizer, device):
    model.train()
    size = len(dataloader.dataset)
    avg_loss = 0

    # 从数据加载器中读取batch(一次读取多少张,即批次数),X(图片数据),y(图片真实标签)
    time_start = time.time()
    for batch,(X, y) in enumerate(dataloader): #固定格式:batchL第几批数据,不是批次大小,(X,y):数值用括号
        # 将数据存储到显卡
        X, y = X.to(device), y.to(device)
        # 得到预测的结果pred
        out = model(X)
        loss = loss_fn(out, y)
        avg_loss += loss # 一个batch的数据
        #反向传播,更新模型参数
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        # 每次训练10次,输出一次当前信息
        if batch % 10 == 0:
            loss, current = loss.item(), batch * len(X)
            print(f"Current Batch Training Loss:{loss:>5f} [{current:>5d}/{size:>5d}]")

    # 当一个epoth玩了后返回平均 loss
    avg_loss /= size
    avg_loss = avg_loss.detach().cpu().numpy()

    time_end = time.time()
    print(f"train time:{(time_end - time_start):>0.2f} Avg Loss ={avg_loss:>5f}")
    return avg_loss

def validate(dataloader, model, loss_fn, device):
    size = len(dataloader.dataset)
    # 将模型转为验证模式
    model.eval()
    # 初始化 test_loss 和 correct 用来统计每次的误差
    test_loss, correct = 0, 0
    # 测试时模型参数不用跟新,所以 no_gard()
    # 非训练,推理期用到
    with torch.no_grad():
        # 加载数据加载器,得到里面的X(图片数据) 和 y(真实标签)
        for X, y in tqdm(dataloader):
            # 将数据转到GPU
            X, y = X.to(device), y.to(device)
            # 将图片传入到模型当中就得到预测的值pred
            pred = model(X)
            # 计算预测值pred和真实值y的差距
            test_loss += loss_fn(pred, y).item()
            # 统计预测正确的个数(针对分类)
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()
    test_loss /= size
    correct /= size
    print(f"correct = {correct}, Test Error: \n Accuracy: {(100 * correct):>0.5f}%, Avg loss:{test_loss:>0.5f} \n")
    return correct, test_loss

if __name__=='__main__':
    '''
    加载数据集
    '''
    train_root = "dataset/dataset_train"
    test_root = "dataset/dataset_test"

    train_tf = transforms.Compose([
        transforms.Resize((299, 299)),
        transforms.RandomVerticalFlip(), #对图片进行随机的水平翻转
        transforms.ToTensor() # 把图片改为Tenser格式
    ])

    test_tf = transforms.Compose([
        transforms.Resize((299, 299)),
        transforms.ToTensor()  # 把图片改为Tenser格式
    ])

    batch_size = 32
    train_data = ImageFolder(root=train_root, transform=train_tf)
    train_loader = DataLoader(dataset=train_data, batch_size=batch_size, pin_memory=True, num_workers=4, shuffle=True)

    test_data = ImageFolder(root=test_root, transform=test_tf)
    test_loader = DataLoader(dataset=test_data, batch_size=batch_size, pin_memory=True, num_workers=4, shuffle=True)

    # 如果显卡可用,则用显卡训练
    device = "cuda" if torch.cuda.is_available() else "cpu"
    # device = "cpu"
    print(f"Using {device} device")

    if hasattr(torch.cuda, 'empty_cache'):
        torch.cuda.empty_cache()
    model = Model(num_classes=5)
    model = model.to(device)

    # 定义损失函数,计算相差多少,交叉熵
    loss_fn = nn.CrossEntropyLoss()

    # 定义优化器,用来训练时候优化模型参数,随机梯度下降法
    learning_rate = 1e-4
    optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

    epochs = 40
    loss_ = 10
    save_root = "Model_result/My_Inception_v4/"

    if not os.path.exists(save_root):
        os.makedirs(save_root)

    for t in range(epochs):
        print(f"Epoth {t+1}\n--------------------------------")
        avg_loss = train(train_loader, model, loss_fn, optimizer, device)

        val_accuracy, val_loss = validate(test_loader, model, loss_fn, device)
        # 写入数据
        WriteData(save_root + "My_Inception_v4.txt",
                  "epoch", t,
                  "train_loss", avg_loss,
                  "val_loss", val_loss,
                  "val_accuracy", val_accuracy)
        if t % 5 == 0:
            torch.save(model.state_dict(), save_root+"My_Inception_v4_epoch" + str(t) + "_loss_" + str(avg_loss) + ".pth")

        torch.save(model.state_dict(), save_root + "My_Inception_v4_last.pth")

        if val_loss < loss_:
            loss_ = val_loss
            torch.save(model.state_dict(), save_root + "My_Inception_v4_best.pth")

四、分类测试代码

python 复制代码
'''
单图测试
'''

import torch
from MyInceptionV4 import MyInceptionV4
from PIL import Image
import torchvision.transforms as transforms
import os


if __name__=='__main__':
    img_path = r"dataset/dataset_train/f3/image_00581.jpg"

    test_tf = transforms.Compose([
        transforms.Resize(((299, 299))),
        transforms.ToTensor()
    ])

    # 如果显卡可用,则用显卡训练
    device = "cuda" if torch.cuda.is_available() else "cpu"
    print(f"Using {device} device")

    model = MyInceptionV4(num_classes=5)
    model = model.to(device)
    state_dict = torch.load(r"Model_result/My_Inception_v4/My_Inception_v4_best.pth")

    model.load_state_dict(state_dict)
    model.eval()
    with torch.no_grad():
        img = Image.open(img_path) #打开图片
        img = img.convert('RGB') #转换为RGB格式
        img = test_tf(img)
        img_tensor = torch.unsqueeze(img, 0) # C,H,W(通道,高,宽) 转为 N,C,H,W
        img_tensor = img_tensor.to(device)
        result = model(img_tensor)

        id = result.argmax(1).item()

        file_list = []
        for a, b, c in os.walk("dataset/dataset_train"):
            if len(b) != 0:
                file_list = b
                print("InveptionV4 对输入的图片预测的结果为:", file_list[id])
相关推荐
聚客AI1 小时前
🌟大模型为什么产生幻觉?预训练到推理的漏洞全揭秘
人工智能·llm·掘金·日新计划
Juchecar1 小时前
一文讲清 nn.Sequential 等容器类
人工智能
阿里云云原生2 小时前
如何快速看懂「祖传项目」?Qoder 强势推出新利器
人工智能
美团技术团队2 小时前
LongCat-Flash:如何使用 SGLang 部署美团 Agentic 模型
人工智能·算法
程序员小袁3 小时前
基于C-MTEB/CMedQAv2-rerankingv的Qwen3-1.7b模型微调-demo
人工智能
飞哥数智坊4 小时前
AI 编程一年多,我终于明白:比技巧更重要的,是熟练度
人工智能·ai编程
新智元5 小时前
收手吧 GPT-5-Codex,外面全是 AI 编程智能体!
人工智能·openai
IT_陈寒5 小时前
Java 性能优化:5个被低估的JVM参数让你的应用吞吐量提升50%
前端·人工智能·后端
阿里云云原生5 小时前
阿里云基础设施 AI Tech Day AI 原生,智构未来——AI 原生架构与企业实践专场
人工智能
Memene摸鱼日报6 小时前
「Memene 摸鱼日报 2025.9.16」OpenAI 推出 GPT-5-Codex 编程模型,xAI 发布 Grok 4 Fast
人工智能·aigc