0

0

漫卷的第一个神经网络,带你一起搭网络

P粉084495128

P粉084495128

发布时间:2025-07-23 09:37:19

|

201人浏览过

|

来源于php中文网

原创

本文展示了漫卷搭建首个神经网络manjuannet的过程,采用paddlepaddle框架,含两个基模块,通过双路径提取特征后拼接。介绍了数据集处理(含解压、检测损坏图像)、模型训练等,对比了与resnet34的参数量和flops,其参数量更少但flops高约40倍,在imagenet100上10轮验证准确率70.04%。

☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜

漫卷的第一个神经网络,带你一起搭网络 - php中文网

ManjuanNet,漫卷自己搭建的第一个神经网络

这里将展示神经网络的搭建过程和我遇到的一些问题以及一些思考;帮助大家和我一起学习搭建神经网络;

为什么要用paddlepaddle? 因为paddle可以直接在飞桨平台实践,省去了配环境的过程,直接运行起代码可以让我们快速入门,而配环境则会大量消耗我们的热情,一个急眼,也许就不想学了;
同时paddle和pytorch有着相似的接口,学会paddle,pytorch定是不在话下,而且paddle有非常重要的 中文文档 !

遇到了什么问题?
我自己搭的网络,参数比Resnet34少一些,但训练速度却非常慢,希望有大佬可以解答这个问题。

破案了,我的网络因为卷积计算比resnet34多很多,所以FLOPS高出约40倍。

数据集是直接引用其他飞桨用户上传的数据集,但是数据集里有一张图片坏掉了,后续添加了检测图像是否损坏的代码;

In [1]
import warnings
warnings.filterwarnings('ignore')# 忽略掉警告,由于环境原因,可能会出现各种干扰视野的警告,这里我们只关注搭建神经网络本身,所有忽略警告
   
In [2]
# 导包import paddleimport paddle.nn as nnimport paddle.nn.functional as Fimport paddle.optimizer as optimimport paddle.vision.transforms as Timport paddle.vision.datasets as Dfrom paddle.io import DataLoader
   

zipfile库和os库解压ImageNet100数据集

下面两个框:
第一个框是解压Imagenet100数据集的代码,用于测试我的神经网络在一个比较大的数据集上的性能,我已经训练了一个10个epoch的权重,验证集上的AC率达到70.04,文件中也保存了训练日志,名称均为imagenet100-10epoch。

第二个框是逐个图像检查是否损坏的代码;

两个框我均已经注释掉,需要运行请自主取消注释,同时在读取位置更改路径

使用工具
zipfile库是python中对压缩包操作的库
os库是python中进行系统操作的库

In [3]
import zipfileimport osdef unzip_file(zip_path):
    # 获取 ZIP 文件所在的目录
    extract_to = os.path.dirname(zip_path)    # 打开 ZIP 文件
    with zipfile.ZipFile(zip_path, 'r') as zip_ref:        # 解压所有文件到目标路径
        zip_ref.extractall(extract_to)    print(f"ZIP 文件 '{zip_path}' 已成功解压到 '{extract_to}'")# 示例使用zip_path = './data/data150555/MyImagenet.zip'  # ZIP 文件的路径unzip_file(zip_path)
       
'\nimport zipfile\nimport os\n\ndef unzip_file(zip_path):\n    # 获取 ZIP 文件所在的目录\n    extract_to = os.path.dirname(zip_path)\n\n    # 打开 ZIP 文件\n    with zipfile.ZipFile(zip_path, \'r\') as zip_ref:\n        # 解压所有文件到目标路径\n        zip_ref.extractall(extract_to)\n\n    print(f"ZIP 文件 \'{zip_path}\' 已成功解压到 \'{extract_to}\'")\n\n# 示例使用\nzip_path = \'./data/data150555/MyImagenet.zip\'  # ZIP 文件的路径\n\nunzip_file(zip_path)\n'
               
In [4]
import osfrom PIL import Imagedef check_and_delete_corrupted_images(root_folder):
    for root, dirs, files in os.walk(root_folder):        for file in files:
            file_path = os.path.join(root, file)            try:                # 尝试打开图片
                with Image.open(file_path) as img:
                    img.verify()  # 验证图片是否损坏
            except (IOError, SyntaxError) as e:                # 如果图片损坏,删除该图片
                print(f"Corrupted image found: {file_path}, deleting...")
                os.remove(file_path)# 指定根文件夹路径root_folder = './data/data150555/MyImagenet/train'check_and_delete_corrupted_images(root_folder)
       
'\nimport os\nfrom PIL import Image\n\ndef check_and_delete_corrupted_images(root_folder):\n    for root, dirs, files in os.walk(root_folder):\n        for file in files:\n            file_path = os.path.join(root, file)\n            try:\n                # 尝试打开图片\n                with Image.open(file_path) as img:\n                    img.verify()  # 验证图片是否损坏\n            except (IOError, SyntaxError) as e:\n                # 如果图片损坏,删除该图片\n                print(f"Corrupted image found: {file_path}, deleting...")\n                os.remove(file_path)\n\n# 指定根文件夹路径\nroot_folder = \'./data/data150555/MyImagenet/train\'\ncheck_and_delete_corrupted_images(root_folder)\n'
               

解压脑肿瘤分类数据集

默认使用这个数据集运行,如果想运行imagenet100,取消掉上面的注释即可

注意,由于版本问题,解压时可能出现奇怪的报错,如果该解压代码报错,请手动解压

这个数据集很奇怪的损坏掉了,晚点再解决

In [5]
'''
import zipfile
import os

def unzip_file(zip_path):
    # 获取 ZIP 文件所在的目录
    extract_to = os.path.dirname(zip_path)

    # 打开 ZIP 文件
    with zipfile.ZipFile(zip_path, 'r') as zip_ref:
        # 解压所有文件到目标路径
        zip_ref.extractall(extract_to)

    print(f"ZIP 文件 '{zip_path}' 已成功解压到 '{extract_to}'")

# 示例使用
zip_path_train = './data/data303405/Training.zip'  # ZIP 文件的路径
zip_path_test = './data/data303405/Testing.zip'  # ZIP 文件的路径
unzip_file(zip_path_train)
unzip_file(zip_path_test)
'''
       
'\nimport zipfile\nimport os\n\ndef unzip_file(zip_path):\n    # 获取 ZIP 文件所在的目录\n    extract_to = os.path.dirname(zip_path)\n\n    # 打开 ZIP 文件\n    with zipfile.ZipFile(zip_path, \'r\') as zip_ref:\n        # 解压所有文件到目标路径\n        zip_ref.extractall(extract_to)\n\n    print(f"ZIP 文件 \'{zip_path}\' 已成功解压到 \'{extract_to}\'")\n\n# 示例使用\nzip_path_train = \'./data/data303405/Training.zip\'  # ZIP 文件的路径\nzip_path_test = \'./data/data303405/Testing.zip\'  # ZIP 文件的路径\nunzip_file(zip_path_train)\nunzip_file(zip_path_test)\n'
               

定义我的网络结构

使用的工具:

paddlepaddlepaddlepaddle框架的nnnn类

在nn类中,封装了很多方法,具体的:
       

1.Conv系列卷积方法,有Conv1D、Conv2D、Conv3D三种卷积,分别是一维、二维、三维卷积,输入的参数基本相同,区别在于输入的数据格式,具体可以参考飞桨的官方开发文档:https://www.paddlepaddle.org.cn/tutorials/projectdetail/3493103
这俩我们具体使用的是Conv2D,二维卷积方法

2.BatchNormBatchNorm系列批量归一化方法,和卷积的方法类似,同样有BatchNorm1D、BatchNorm2D、BatchNorm3D三种,对应一维、二维、三维的批量归一化,同样的,这里使用二维批量归一化

艺映AI
艺映AI

艺映AI - 免费AI视频创作工具

下载

3.常用的激活函数:
           ReLUReLU:
paddle.nn.ReLU(): 的ReLU激活函数,对于输入的负值,输出为0;对于输入的非负值,输出与输入相同。
           SigmoidSigmoid:
paddle.nn.Sigmoid(): 输入映射到(0, 1)区间内,常用于二分类问题的输出层。
           TanhTanh:
paddle.nn.Tanh(): 将输入映射到(-1, 1)区间内,是另一种常见的激活函数。
           LeakyReLULeakyReLU:
paddle.nn.LeakyReLU(): 是ReLU的变体,允许小的负梯度当输入值为负时。这有助于解决“死亡ReLU”问题,其中神经元在训练过程中可能停止学习。
我们直接使用RelU进行网络搭建,也可以自行更换其他激活函数;

4.MaxPool2DMaxPool2D 池化方法:
二维的最大池化,取选定范围内的最大值作为这个区域的值,同样的,池化也有三种维度的方法,同时还有很多其他池化方法;
这里不仅使用了最大池化,还使用了平均池化;

5.SequentialSequential容器:
用于按顺序容纳多个网络层或模块。

paddlepaddlepaddlepaddle的flattenflatten方法

使用paddle.flatten()paddle.flatten()即可直接调用,将tensor展平为一维

第一个基模块

使用刚刚提到的paddlepaddlepaddlepaddle框架中的Conv2DConv2D、BatchNorm2DBatchNorm2D和ReLUReLU方法进行基模块的搭建;

这个基模块是resnet18的基模块改的,基本结构相同,只更改了卷积核大小和步长,将原来的两个3x3卷积改成了一个5x5,一个3x3

结构可以参考resnet18的基模块结构

In [6]
# 这里使用python的class来进行基模块的定义,这个基模块会在后面搭建模型时使用class BasicBlock1(nn.Layer):
    expansion = 1

    def __init__(self, in_channels1, out_channels, stride=1, downsample=None):
        # in_channels为输入大小, out_channels输出大小, stride为卷积步长, downsample为下采样
        super(BasicBlock1, self).__init__()# 继承一下自己
        self.conv1 = nn.Conv2D(in_channels1, out_channels, kernel_size=5, stride=stride, padding=2, bias_attr=False)        # Conv2D是paddle框架中封装好的对二维数据进行卷积的方法,和torch的封装结构一致,都封装在nn类中
        # 这是第一个卷积层,卷积核大小为5*5,步长为1,padding为2,不使用bias(偏置),大核无敌!
        self.bn1 = nn.BatchNorm2D(out_channels)        # 这是第一个BatchNorm层,对刚刚卷积后的数据进行批量归一化,out_channels为输出大小
        self.relu = nn.ReLU()        # 这是Relu激活函数,归一化后进入Relu激活函数,进行线性向非线性的变换
        self.conv2 = nn.Conv2D(out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias_attr=False)
        self.bn2 = nn.BatchNorm2D(out_channels)
        self.downsample = downsample
        self.stride = stride # 步长,这里可以不声明,在__init__的参数表中就有,这里是为了代码规范声明的。

    def forward(self, x):# 在这里定义前向传播
        identity = x
        
        out = self.conv1(x)# 先定义一个值做个卷积
        out = self.bn1(out)# 然后做个归一化
        out = self.relu(out)# 再经过一次激活函数

        out = self.conv2(out)# 接着卷积
        out = self.bn2(out)# 继续归一化

        if self.downsample is not None:# 在这里和进行对齐,避免无法进行残差链接
            identity = self.downsample(x)

        out += identity# 残差链接,这里直接将上次的计算结果作为残差链接上来
        out = self.relu(out)        return out
   

第二个基模块

这是第二个基模块,是我一拍脑袋设计的,可能有很多冗余;
结构为:先进行一次1x1的卷积,然后进行一次3x3的卷积,接着再进行一次1x1的卷积

In [7]
# 再定义一个模块,也用于搭建class BasicBlock2(nn.Layer):
    expansion = 1

    def __init__(self, in_channels2, out_channels, stride=1, downsample=None):
        # in_channels为输入大小, out_channels输出大小, stride为卷积步长, downsample为下采样
        super(BasicBlock2, self).__init__()# 继承一下自己
        self.conv1 = nn.Conv2D(in_channels2, out_channels, kernel_size=1, stride=stride, padding=0, bias_attr=False)# 这里不用padding,因为卷积核大小是1
        
        self.bn1 = nn.BatchNorm2D(out_channels)        # 这是第一个BatchNorm层,对刚刚卷积后的数据进行批量归一化,out_channels为输出大小
        self.relu = nn.ReLU()        # 这是Relu激活函数,归一化后进入Relu激活函数,进行线性向非线性的变换
        self.conv2 = nn.Conv2D(out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias_attr=False)
        self.bn2 = nn.BatchNorm2D(out_channels)

        self.conv3 = nn.Conv2D(out_channels, out_channels, kernel_size=1, stride=1, padding=0, bias_attr=False)
        self.bn3 = nn.BatchNorm2D(out_channels)
        self.downsample = downsample
        self.stride = stride # 步长,这里可以不声明,在__init__的参数表中就有,这里是为了代码规范声明的。

    def forward(self, x):# 在这里定义前向传播
        identity = x
        
        out = self.conv1(x)# 先定义一个值做个卷积
        out = self.bn1(out)# 然后做个归一化
        out = self.relu(out)# 再经过一次激活函数

        out = self.conv2(out)# 接着卷积
        out = self.bn2(out)# 继续归一化

        out = self.conv3(out)# 接着卷积
        out = self.bn3(out)# 继续归一化
        if self.downsample is not None:# 在这里和进行对齐,避免无法进行残差链接
            identity = self.downsample(x)
        
        out += identity# 残差链接,这里直接将上次的计算结果作为残差链接上来
        out = self.relu(out)        return out
   

这是我整个模型的搭建

我的模型有两个卷积路径,一条路径从少通道卷成多通道,一条路径从多通道卷成少通道,这样可以获取两种不同融合的特征,最终将两个路径得到的特征图拼接起来,经过一个全连接神经网络进行预测。

具体细节:
1.直接使用python原生的append方法,向列表中插入残差块;
2.手写了一个_make_layer方法,用于创建基模块组,这样可以同时创建多层;

In [8]
class manjuanNet(nn.Layer):
    def __init__(self, block1,block2, layer_configs, num_classes=1000):# block1和block2是可更换的基模块,这里我们用的是上面声明的BasicBlock1和BasicBlock2
        # layer_configs是一个列表,指定每个残差块组的块数量配置,也可以再这里加一个入参,额外设定新的表
        super(manjuanNet, self).__init__()
        self.in_channels1 = 64
        self.in_channels2 = 256
        self.conv1 = nn.Conv2D(3, 64, kernel_size=7, stride=2, padding=3, bias_attr=False)# 大核卷积,卷成64通道
        self.conv2 = nn.Conv2D(3,256, kernel_size=5,stride=1, padding=3, bias_attr=False)# 中核卷积,卷成256通道
        self.bn1 = nn.BatchNorm2D(64)
        self.bn2 = nn.BatchNorm2D(256)
        self.relu = nn.ReLU()
       
        self.maxpool = nn.MaxPool2D(kernel_size=3, stride=1, padding=1)        
        # 构建残差块组
        self.layer1 = self._make_layer(block1, 64, layer_configs[0])  # 第一个残差块组
        self.layer2 = self._make_layer(block1, 128, layer_configs[1], stride=2)  # 第二个残差块组,步长为2
        self.layer3 = self._make_layer(block1, 64, layer_configs[2], stride=1)  # 第三个残差块组,步长为2
        self.layer4 = self._make_layer(block1, 32, layer_configs[3], stride=1)  # 第四个残差块组,步长为2

        self.layer1s = self._make_layers(block2, 4, layer_configs[0])  # 第一个残差块组
        self.layer2s = self._make_layers(block2, 8, layer_configs[1]-1, stride=2)  # 第二个残差块组,步长为1
        self.layer3s = self._make_layers(block2, 16, layer_configs[2]-1, stride=1)  # 第三个残差块组,步长为3
        self.layer4s = self._make_layers(block2, 32, layer_configs[3], stride=1)  # 第四个残差块组,步长为3
        
        self.avgpool = nn.AdaptiveAvgPool2D((1, 1))
        self.fc = nn.Linear(32 * block1.expansion + 32*block2.expansion, num_classes)        #self.fc = nn.Linear(512 * block1.expansion , num_classes)
 
    def _make_layer(self, block, out_channels, num_blocks, stride=1):
        # 构建一个残差块组,包含多个残差块
        # num_blocks: 残差块的数量
        downsample = None
        # 判断是否需要下采样
        if stride != 1 or self.in_channels1 != out_channels * block.expansion:
            downsample = nn.Sequential(
                nn.Conv2D(self.in_channels1, out_channels * block.expansion, kernel_size=1, stride=stride, bias_attr=False),
                nn.BatchNorm2D(out_channels * block.expansion),
            )

        layers = []
        layers.append(block(self.in_channels1, out_channels, stride, downsample))
        self.in_channels1 = out_channels * block.expansion        # 添加剩余的残差块,这些块不需要进行下采样
        for _ in range(1, num_blocks):
            layers.append(block(self.in_channels1, out_channels)) 
        return nn.Sequential(*layers)    def _make_layers(self, block, out_channels, num_blocks, stride=1):
        # 构建一个残差块组,包含多个残差块
        # num_blocks: 残差块的数量
        downsample = None
        # 判断是否需要下采样
        if stride != 1 or self.in_channels2 != out_channels * block.expansion:
            downsample = nn.Sequential(
                nn.Conv2D(self.in_channels2, out_channels * block.expansion, kernel_size=1, stride=stride, bias_attr=False),
                nn.BatchNorm2D(out_channels * block.expansion),
            )

        layers = []
        layers.append(block(self.in_channels2, out_channels, stride, downsample))
        self.in_channels2 = out_channels * block.expansion        # 添加剩余的残差块,这些块不需要进行下采样
        for _ in range(1, num_blocks):
            layers.append(block(self.in_channels2, out_channels)) 
        return nn.Sequential(*layers)         
    def forward(self, x):
        y = x
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.maxpool(x)
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)
        x = self.avgpool(x)
        x = paddle.flatten(x, 1)# 这里双路径的实现方式,首先创建一个x的副本,然后分别对x和y操作,最后在直接使用concat方法拼接,然后链接一个全连接层输出
        y = self.conv2(y)
        y = self.bn2(y)
        y = self.relu(y)
        y = self.maxpool(y)
        y = self.layer1s(y)
        y = self.layer2s(y)
        y = self.layer3s(y)
        y = self.layer4s(y)
        y = self.avgpool(y)
        y = paddle.flatten(y,1)# 搭建网络其实很简单,只需要在__init__中声明层,在forward中调用即可
        res = paddle.concat([x,y],axis = 1)        #res = paddle.add([x,y],axis = 1)
        #res = x
        res = self.fc(res)        return res
   
In [9]
def ManjuanNet(num_classes = 10):
    return manjuanNet(BasicBlock1,BasicBlock2, [2, 2, 2, 2],num_classes=num_classes)# 在这里声明manjuanNet,列表是每层的块数量,num_classes是分类的类别数
   

这里出现了一些失误

在训练时未保存标签的映射字典,后续测试会出现特征不对齐的现象,现已更改代码,后续会再次训练;

In [10]
!pip install pyyamlimport yaml
       
Looking in indexes: https://mirror.baidu.com/pypi/simple/, https://mirrors.aliyun.com/pypi/simple/
Requirement already satisfied: pyyaml in /opt/conda/envs/python35-paddle120-env/lib/python3.10/site-packages (6.0.2)
       
In [11]
import osfrom PIL import Imageimport paddleimport paddle.nn as nnimport paddle.optimizer as optimimport paddle.vision.transforms as Tfrom paddle.io import DataLoader, Datasetfrom paddle.vision.models import resnet18# 自定义数据集类class CustomDataset(Dataset):
    def __init__(self, root_dir, transform=None):
        self.root_dir = root_dir
        self.transform = transform
        self.classes = sorted(os.listdir(root_dir))
        self.class_to_idx = {cls_name: i for i, cls_name in enumerate(self.classes)}        
        # 获取当前工作目录,即.ipynb文件所在的目录
        current_dir = os.getcwd()        
        # 将类标签映射保存为YAML文件,文件保存在.ipynb文件所在的目录
        yaml_path = os.path.join('./class_to_idx.yaml')        with open(yaml_path, 'w') as file:
            yaml.dump(self.class_to_idx, file, default_flow_style=False)
        
        self.images = []
        self.labels = []        for cls_name in self.classes:
            cls_dir = os.path.join(root_dir, cls_name)            for img_name in os.listdir(cls_dir):
                img_path = os.path.join(cls_dir, img_name)
                self.images.append(img_path)
                self.labels.append(self.class_to_idx[cls_name])    def __len__(self):
        return len(self.images)    def __getitem__(self, idx):
        img_path = self.images[idx]
        label = self.labels[idx]
        image = Image.open(img_path).convert('RGB')        if self.transform:
            image = self.transform(image)        return image, label# 数据预处理和加载transform = T.Compose([
    T.Resize((224, 224)),
    T.ToTensor(),
    T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])
   

在这里可以更换路径训练

我挂载了两个数据集,一个是比较小的,只有几千张图片的脑肿瘤分类数据集,一个是有十几万张的Imagenet100
默认运行使用脑肿瘤分类数据集

In [12]
# 这里是训练集和验证集的路径,按你的路径进行修改,这里的 “./”为从当前文件所处文件夹路径开始向下寻找,具体参考python的路径操作train_dataset = CustomDataset(root_dir='./data/data150555/MyImagenet/train', transform=transform)
val_dataset = CustomDataset(root_dir='./data/data150555/MyImagenet/val', transform=transform)

train_loader = DataLoader(train_dataset, batch_size=48, shuffle=True, drop_last=True)
val_loader = DataLoader(val_dataset, batch_size=48, shuffle=False)# 模型、损失函数和优化器device = paddle.set_device('gpu' if paddle.is_compiled_with_cuda() else 'cpu')
num_classes = len(train_dataset.classes)  # 根据数据集的类别数调整模型输出类别数model = ManjuanNet(num_classes=num_classes)#import paddle.visionw.models as models#model = models.resnet34()model = model.to(device)

criterion = nn.CrossEntropyLoss()
optimizer = optim.AdamW(parameters=model.parameters(), learning_rate=0.001,epsilon=1e-7)#使用Adamw优化器,初始学习率设置为0.001,也有很多其他优化器可以使用的,详细参考我都另一个项目或自行上网搜索
       
W0109 15:39:37.189237 60986 gpu_resources.cc:119] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 12.0, Runtime API Version: 11.8
W0109 15:39:37.191516 60986 gpu_resources.cc:164] device: 0, cuDNN Version: 8.9.
       
In [13]
model
       
manjuanNet(
  (conv1): Conv2D(3, 64, kernel_size=[7, 7], stride=[2, 2], padding=3, data_format=NCHW)
  (conv2): Conv2D(3, 256, kernel_size=[5, 5], padding=3, data_format=NCHW)
  (bn1): BatchNorm2D(num_features=64, momentum=0.9, epsilon=1e-05)
  (bn2): BatchNorm2D(num_features=256, momentum=0.9, epsilon=1e-05)
  (relu): ReLU()
  (maxpool): MaxPool2D(kernel_size=3, stride=1, padding=1)
  (layer1): Sequential(
    (0): BasicBlock1(
      (conv1): Conv2D(64, 64, kernel_size=[5, 5], padding=2, data_format=NCHW)
      (bn1): BatchNorm2D(num_features=64, momentum=0.9, epsilon=1e-05)
      (relu): ReLU()
      (conv2): Conv2D(64, 64, kernel_size=[3, 3], padding=1, data_format=NCHW)
      (bn2): BatchNorm2D(num_features=64, momentum=0.9, epsilon=1e-05)
    )
    (1): BasicBlock1(
      (conv1): Conv2D(64, 64, kernel_size=[5, 5], padding=2, data_format=NCHW)
      (bn1): BatchNorm2D(num_features=64, momentum=0.9, epsilon=1e-05)
      (relu): ReLU()
      (conv2): Conv2D(64, 64, kernel_size=[3, 3], padding=1, data_format=NCHW)
      (bn2): BatchNorm2D(num_features=64, momentum=0.9, epsilon=1e-05)
    )
  )
  (layer2): Sequential(
    (0): BasicBlock1(
      (conv1): Conv2D(64, 128, kernel_size=[5, 5], stride=[2, 2], padding=2, data_format=NCHW)
      (bn1): BatchNorm2D(num_features=128, momentum=0.9, epsilon=1e-05)
      (relu): ReLU()
      (conv2): Conv2D(128, 128, kernel_size=[3, 3], padding=1, data_format=NCHW)
      (bn2): BatchNorm2D(num_features=128, momentum=0.9, epsilon=1e-05)
      (downsample): Sequential(
        (0): Conv2D(64, 128, kernel_size=[1, 1], stride=[2, 2], data_format=NCHW)
        (1): BatchNorm2D(num_features=128, momentum=0.9, epsilon=1e-05)
      )
    )
    (1): BasicBlock1(
      (conv1): Conv2D(128, 128, kernel_size=[5, 5], padding=2, data_format=NCHW)
      (bn1): BatchNorm2D(num_features=128, momentum=0.9, epsilon=1e-05)
      (relu): ReLU()
      (conv2): Conv2D(128, 128, kernel_size=[3, 3], padding=1, data_format=NCHW)
      (bn2): BatchNorm2D(num_features=128, momentum=0.9, epsilon=1e-05)
    )
  )
  (layer3): Sequential(
    (0): BasicBlock1(
      (conv1): Conv2D(128, 64, kernel_size=[5, 5], padding=2, data_format=NCHW)
      (bn1): BatchNorm2D(num_features=64, momentum=0.9, epsilon=1e-05)
      (relu): ReLU()
      (conv2): Conv2D(64, 64, kernel_size=[3, 3], padding=1, data_format=NCHW)
      (bn2): BatchNorm2D(num_features=64, momentum=0.9, epsilon=1e-05)
      (downsample): Sequential(
        (0): Conv2D(128, 64, kernel_size=[1, 1], data_format=NCHW)
        (1): BatchNorm2D(num_features=64, momentum=0.9, epsilon=1e-05)
      )
    )
    (1): BasicBlock1(
      (conv1): Conv2D(64, 64, kernel_size=[5, 5], padding=2, data_format=NCHW)
      (bn1): BatchNorm2D(num_features=64, momentum=0.9, epsilon=1e-05)
      (relu): ReLU()
      (conv2): Conv2D(64, 64, kernel_size=[3, 3], padding=1, data_format=NCHW)
      (bn2): BatchNorm2D(num_features=64, momentum=0.9, epsilon=1e-05)
    )
  )
  (layer4): Sequential(
    (0): BasicBlock1(
      (conv1): Conv2D(64, 32, kernel_size=[5, 5], padding=2, data_format=NCHW)
      (bn1): BatchNorm2D(num_features=32, momentum=0.9, epsilon=1e-05)
      (relu): ReLU()
      (conv2): Conv2D(32, 32, kernel_size=[3, 3], padding=1, data_format=NCHW)
      (bn2): BatchNorm2D(num_features=32, momentum=0.9, epsilon=1e-05)
      (downsample): Sequential(
        (0): Conv2D(64, 32, kernel_size=[1, 1], data_format=NCHW)
        (1): BatchNorm2D(num_features=32, momentum=0.9, epsilon=1e-05)
      )
    )
    (1): BasicBlock1(
      (conv1): Conv2D(32, 32, kernel_size=[5, 5], padding=2, data_format=NCHW)
      (bn1): BatchNorm2D(num_features=32, momentum=0.9, epsilon=1e-05)
      (relu): ReLU()
      (conv2): Conv2D(32, 32, kernel_size=[3, 3], padding=1, data_format=NCHW)
      (bn2): BatchNorm2D(num_features=32, momentum=0.9, epsilon=1e-05)
    )
  )
  (layer1s): Sequential(
    (0): BasicBlock2(
      (conv1): Conv2D(256, 4, kernel_size=[1, 1], data_format=NCHW)
      (bn1): BatchNorm2D(num_features=4, momentum=0.9, epsilon=1e-05)
      (relu): ReLU()
      (conv2): Conv2D(4, 4, kernel_size=[3, 3], padding=1, data_format=NCHW)
      (bn2): BatchNorm2D(num_features=4, momentum=0.9, epsilon=1e-05)
      (conv3): Conv2D(4, 4, kernel_size=[1, 1], data_format=NCHW)
      (bn3): BatchNorm2D(num_features=4, momentum=0.9, epsilon=1e-05)
      (downsample): Sequential(
        (0): Conv2D(256, 4, kernel_size=[1, 1], data_format=NCHW)
        (1): BatchNorm2D(num_features=4, momentum=0.9, epsilon=1e-05)
      )
    )
    (1): BasicBlock2(
      (conv1): Conv2D(4, 4, kernel_size=[1, 1], data_format=NCHW)
      (bn1): BatchNorm2D(num_features=4, momentum=0.9, epsilon=1e-05)
      (relu): ReLU()
      (conv2): Conv2D(4, 4, kernel_size=[3, 3], padding=1, data_format=NCHW)
      (bn2): BatchNorm2D(num_features=4, momentum=0.9, epsilon=1e-05)
      (conv3): Conv2D(4, 4, kernel_size=[1, 1], data_format=NCHW)
      (bn3): BatchNorm2D(num_features=4, momentum=0.9, epsilon=1e-05)
    )
  )
  (layer2s): Sequential(
    (0): BasicBlock2(
      (conv1): Conv2D(4, 8, kernel_size=[1, 1], stride=[2, 2], data_format=NCHW)
      (bn1): BatchNorm2D(num_features=8, momentum=0.9, epsilon=1e-05)
      (relu): ReLU()
      (conv2): Conv2D(8, 8, kernel_size=[3, 3], padding=1, data_format=NCHW)
      (bn2): BatchNorm2D(num_features=8, momentum=0.9, epsilon=1e-05)
      (conv3): Conv2D(8, 8, kernel_size=[1, 1], data_format=NCHW)
      (bn3): BatchNorm2D(num_features=8, momentum=0.9, epsilon=1e-05)
      (downsample): Sequential(
        (0): Conv2D(4, 8, kernel_size=[1, 1], stride=[2, 2], data_format=NCHW)
        (1): BatchNorm2D(num_features=8, momentum=0.9, epsilon=1e-05)
      )
    )
  )
  (layer3s): Sequential(
    (0): BasicBlock2(
      (conv1): Conv2D(8, 16, kernel_size=[1, 1], data_format=NCHW)
      (bn1): BatchNorm2D(num_features=16, momentum=0.9, epsilon=1e-05)
      (relu): ReLU()
      (conv2): Conv2D(16, 16, kernel_size=[3, 3], padding=1, data_format=NCHW)
      (bn2): BatchNorm2D(num_features=16, momentum=0.9, epsilon=1e-05)
      (conv3): Conv2D(16, 16, kernel_size=[1, 1], data_format=NCHW)
      (bn3): BatchNorm2D(num_features=16, momentum=0.9, epsilon=1e-05)
      (downsample): Sequential(
        (0): Conv2D(8, 16, kernel_size=[1, 1], data_format=NCHW)
        (1): BatchNorm2D(num_features=16, momentum=0.9, epsilon=1e-05)
      )
    )
  )
  (layer4s): Sequential(
    (0): BasicBlock2(
      (conv1): Conv2D(16, 32, kernel_size=[1, 1], data_format=NCHW)
      (bn1): BatchNorm2D(num_features=32, momentum=0.9, epsilon=1e-05)
      (relu): ReLU()
      (conv2): Conv2D(32, 32, kernel_size=[3, 3], padding=1, data_format=NCHW)
      (bn2): BatchNorm2D(num_features=32, momentum=0.9, epsilon=1e-05)
      (conv3): Conv2D(32, 32, kernel_size=[1, 1], data_format=NCHW)
      (bn3): BatchNorm2D(num_features=32, momentum=0.9, epsilon=1e-05)
      (downsample): Sequential(
        (0): Conv2D(16, 32, kernel_size=[1, 1], data_format=NCHW)
        (1): BatchNorm2D(num_features=32, momentum=0.9, epsilon=1e-05)
      )
    )
    (1): BasicBlock2(
      (conv1): Conv2D(32, 32, kernel_size=[1, 1], data_format=NCHW)
      (bn1): BatchNorm2D(num_features=32, momentum=0.9, epsilon=1e-05)
      (relu): ReLU()
      (conv2): Conv2D(32, 32, kernel_size=[3, 3], padding=1, data_format=NCHW)
      (bn2): BatchNorm2D(num_features=32, momentum=0.9, epsilon=1e-05)
      (conv3): Conv2D(32, 32, kernel_size=[1, 1], data_format=NCHW)
      (bn3): BatchNorm2D(num_features=32, momentum=0.9, epsilon=1e-05)
    )
  )
  (avgpool): AdaptiveAvgPool2D(output_size=(1, 1))
  (fc): Linear(in_features=64, out_features=100, dtype=None)
)
               
In [14]
from datetime import datetime# 获取当前时间current_time = datetime.now()# 格式化输出当前时间formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S")# 打印当前时间print("当前时间:", formatted_time)
       
当前时间: 2025-01-09 15:39:39
       
In [ ]
# 训练循环num_epochs = 20 # 初始设置为20轮,可根据需求设置for epoch in range(num_epochs):
    model.train()    for batch_id, (data, label) in enumerate(train_loader):
        data, label = data.to(device), label.to(device)
        optimizer.clear_grad()
        output = model(data)
        loss = criterion(output, label)
        loss.backward()
        optimizer.step()        if batch_id % 50 == 0:   
            print(f'Epoch [{epoch+1}/{num_epochs}], Step [{batch_id}/{len(train_loader)}], Loss: {loss.item():.4f}')            # 获取当前时间
            current_time = datetime.now()            # 格式化输出当前时间
            formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S")            # 打印当前时间
            print("当前时间:", formatted_time)            #break
            # 打开一个文件用于写入,如果文件不存在则创建
            with open('training_log.txt', 'a') as f:
                f.write(f'Epoch [{epoch+1}/{num_epochs}], Step [{batch_id}/{len(train_loader)}], Loss: {loss.item():.4f}, Time:[{formatted_time}]\n')    # 验证
    model.eval()
    correct = 0
    total = 0
    with paddle.no_grad():        for data, label in val_loader:
            data, label = data.to(device), label.to(device)
            output = model(data) 
            _, predicted = paddle.topk(output, k=1)
            total += label.shape[0]
            correct += (predicted.squeeze() == label).astype('int32').sum().item()            #if batch_id % 100 == 0:
            #    break
    accuracy = correct / total
    paddle.save(model.state_dict(), 'imagenet100.pdparams')# 保存模型
    print(f'Validation Accuracy: {accuracy:.10f}')
       
Epoch [1/20], Step [0/2083], Loss: 4.7851
当前时间: 2025-01-09 15:39:42
Epoch [1/20], Step [50/2083], Loss: 4.5001
当前时间: 2025-01-09 15:40:14
Epoch [1/20], Step [100/2083], Loss: 4.4377
当前时间: 2025-01-09 15:40:48
Epoch [1/20], Step [150/2083], Loss: 4.3690
当前时间: 2025-01-09 15:41:21
Epoch [1/20], Step [200/2083], Loss: 4.3088
当前时间: 2025-01-09 15:41:54
Epoch [1/20], Step [250/2083], Loss: 4.2381
当前时间: 2025-01-09 15:42:26
Epoch [1/20], Step [300/2083], Loss: 4.3055
当前时间: 2025-01-09 15:42:58
       

下面两个框是对比参数量的代码

In [ ]
model = ManjuanNet(num_classes=10)def count_parameters(model):
    total_params = 0
    for param in model.parameters():
        total_params += param.numel()    return total_params# 计算并打印模型的参数量total_params = count_parameters(model)print(f"Total number of parameters: {total_params}")
   
In [ ]
import paddleimport paddle.vision.models as models# 创建 ResNet-34 模型实例model1 = models.resnet34()# 获取模型的参数量def count_parameters(model):
    total_params = 0
    for param in model.parameters():
        total_params += param.numel()    return total_params# 计算并打印模型的参数量total_params = count_parameters(model1)print(f"Total number of parameters in ResNet-34: {total_params}")
   

在这里计算和对比FLOPS

In [ ]
# 定义输入尺寸input_shape = (1, 3, 224, 224)  # 假设输入图像的尺寸为224x224,通道数为3# 计算FLOPSflops = paddle.flops(model, input_shape)print(f"FLOPS: {flops}")
   
In [ ]
# 定义输入尺寸input_shape = (1, 3, 224, 224)  # 假设输入图像的尺寸为224x224,通道数为3# 计算FLOPSflops = paddle.flops(model1, input_shape)print(f"FLOPS: {flops}")
   
In [ ]
<br/>
   

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

2

2026.03.10

Kotlin Android模块化架构与组件化开发实践
Kotlin Android模块化架构与组件化开发实践

本专题围绕 Kotlin 在 Android 应用开发中的架构实践展开,重点讲解模块化设计与组件化开发的实现思路。内容包括项目模块拆分策略、公共组件封装、依赖管理优化、路由通信机制以及大型项目的工程化管理方法。通过真实项目案例分析,帮助开发者构建结构清晰、易扩展且维护成本低的 Android 应用架构体系,提升团队协作效率与项目迭代速度。

24

2026.03.09

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

80

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

187

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

339

2026.03.04

AI安装教程大全
AI安装教程大全

2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

116

2026.03.04

Swift iOS架构设计与MVVM模式实战
Swift iOS架构设计与MVVM模式实战

本专题聚焦 Swift 在 iOS 应用架构设计中的实践,系统讲解 MVVM 模式的核心思想、数据绑定机制、模块拆分策略以及组件化开发方法。内容涵盖网络层封装、状态管理、依赖注入与性能优化技巧。通过完整项目案例,帮助开发者构建结构清晰、可维护性强的 iOS 应用架构体系。

180

2026.03.03

C++高性能网络编程与Reactor模型实践
C++高性能网络编程与Reactor模型实践

本专题围绕 C++ 在高性能网络服务开发中的应用展开,深入讲解 Socket 编程、多路复用机制、Reactor 模型设计原理以及线程池协作策略。内容涵盖 epoll 实现机制、内存管理优化、连接管理策略与高并发场景下的性能调优方法。通过构建高并发网络服务器实战案例,帮助开发者掌握 C++ 在底层系统与网络通信领域的核心技术。

31

2026.03.03

Golang 测试体系与代码质量保障:工程级可靠性建设
Golang 测试体系与代码质量保障:工程级可靠性建设

Go语言测试体系与代码质量保障聚焦于构建工程级可靠性系统。本专题深入解析Go的测试工具链(如go test)、单元测试、集成测试及端到端测试实践,结合代码覆盖率分析、静态代码扫描(如go vet)和动态分析工具,建立全链路质量监控机制。通过自动化测试框架、持续集成(CI)流水线配置及代码审查规范,实现测试用例管理、缺陷追踪与质量门禁控制,确保代码健壮性与可维护性,为高可靠性工程系统提供质量保障。

81

2026.02.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.5万人学习

Django 教程
Django 教程

共28课时 | 4.9万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号