ResNet
一般的 CNN
网络,层数太深时,会出现梯度消失或梯度爆炸的情况,以及退化问题。而引入残差模块之后,就可以解决上述问题,使得网络层数很深,并且不会有退化问题。最常用的是 ResNet34
和 ResNet50
。
1)Batch Normalization, BN
“批量标准化”。就是使每个通道的特征矩阵,满足均值为 0 0 0 ,方差为 1 1 1 的正态分布。
跟
transforms.Normalize
的作用是类似的。transforms.Normalize
文章链接:https://blog.csdn.net/qq_51409113/article/details/144155914
pytorch
官网中nn.BatchNorm2d()
的链接:BatchNorm2d — PyTorch 2.5 文档
可以加快网络训练,以及提升准确率。使用原因:即使原来的输入已经调整成符合均值为
0
0
0 ,方差为
1
1
1 的正态分布了,但是通过卷积层之后的输出就不一定继续满足了。所以在每个卷积层之后,都会进行 BN
操作。
①为什么可以加快网络训练,以及提升准确率??
BN
通过归一化处理,将每一层的输入数据的均值接近0
,方差接近1
,这有助于减少内部协变量偏移(Internal Covariate Shift
),即每一层输入分布的变化。这种稳定性使得网络参数的更新更加平滑,从而允许使用更大的学习率,进而加速训练过程。BN
通过归一化处理,使得每一层的输入值都落在激活函数的敏感区域内,从而避免了梯度消失的问题。同时,由于BN引入了可学习的参数 γ \gamma γ 和 β \beta β ,这些参数可以通过训练来调整输入值的分布,使得网络在保持稳定性的同时,也能适应不同的数据分布。这种灵活性有助于减少梯度爆炸的风险。BN
通过在每个mini-batch
中对输入数据进行归一化处理时,由于每个mini-batch
的数据都是从总体样本中随机抽样的,因此不同mini-batch
的均值和方差会有所不同,这就为网络的学习过程中增加了随机噪音。这种随机噪音在一定程度上对模型起到了正则化的效果,有助于减少过拟合的风险。(类似于Dropout
的效果,这有助于防止过拟合,从而提升模型的泛化能力)
②公式:
y = x − E [ x ] V a r [ x ] + ϵ ⋅ γ + β y=\frac{x-E[x]}{\sqrt{Var[x]+\epsilon}}·\gamma+\beta y=Var[x]+ϵx−E[x]⋅γ+β
其中
x
x
x 为输入的 batch_size
个图像的特征矩阵;
E
[
x
]
E[x]
E[x] 为输入特征矩阵的均值;
V
a
r
[
x
]
Var[x]
Var[x] 为输入特征矩阵的方差;
ϵ
\epsilon
ϵ 为一个很小的数,用来防止分母为零的;
γ
\gamma
γ 是系数;
β
\beta
β 是偏置。
γ
\gamma
γ 和
β
\beta
β 是可学习的参量,但一般默认
γ
=
1
\gamma=1
γ=1 和
β
=
0
\beta=0
β=0 。
ϵ
\epsilon
ϵ 通常也默认为
0
0
0 。所以默认下的公式为:
y
=
x
−
E
[
x
]
V
a
r
[
x
]
y=\frac{x-E[x]}{\sqrt{Var[x]}}
y=Var[x]x−E[x]
③函数调用:
bn1 = torch.nn.BatchNorm2d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, device=None, dtype=None)
num_features
:输入通道数(该函数的输出通道数 = 输入通道数);eps
:就是 ϵ \epsilon ϵ 的值;- 其余的基本上不用管。
- 示例:
bn1 = torch.nn.BatchNorm2d(64)
。 - 函数的输入格式为:
(N,C,H,W)
,输出格式和输入格式相同
④注意点:
batch size
尽可能设置大点,设置小后表现可能很糟糕,设置的越大求的均值和方差越接近整个训练集的均值和方差。- 建议将
bn
层放在卷积层(Conv
)和激活层(例如Relu
)之间(顺序:Conv -> bn -> Relu
),且卷积层不要使用偏置bias
,因为没有用。
⑤补充:
详细介绍可以参考 CSDN 博客链接(里面有公式和代码):https://blog.csdn.net/qq_37541097/article/details/104434557
2)残差块 (ResNet Block)
残差块有两种
-
恒等映射残差块:
使用场景:残差块的输入和输出的
shape
是相同的,可以直接相加。 -
卷积映射残差块。
使用场景:残差块的输入和输出的
shape
是不相同的,不可以直接相加。- 下采样卷积映射残差块:改变通道数、改变尺寸(
stride>1
)。 - 非下采样卷积映射残差块:只改变通道数(
stride=1
)。
- 下采样卷积映射残差块:改变通道数、改变尺寸(
当残差块中只有两个 3×3
的卷积层时,叫做基础残差块 basic block
,通常用于 ResNet18
和 ResNet34
。
而 ResNet50
以上版本,基本都是三个卷积层了,并且通常是首尾为 1×1
卷积层,中间为 3×3
卷积层。因为输出的形状,又被叫做 “瓶颈残差块” 。
①恒等映射残差块
实线
此残差块主要包括:卷积层、非线性激活层(ReLU
激活函数)、BN
层、恒等映射。
恒等映射 identity mapping
又可以叫做捷径映射
【注意】其中的卷积层一般有两种:①高宽减半卷积层(步幅2)、②高宽不变的卷积层(步幅1)。
【注意】相加的两个部分,其 shape
要相等。
②卷积映射残差块
虚线
这种残差块,一般是用于大残差结构的连接处,其输入和输出不是同一个 shape
的情况,不能直接相加(做恒等映射)。因此需要使用一个卷积层,使其 shape
相同,方便相加。虚线处的卷积层,采用 1×1 Conv
。
【注意】其中的卷积层一般有两种:①高宽减半卷积层(步幅2)、②高宽不变的卷积层(步幅1)。
【注意】在ResNet
网络中,通常将包含多个连续且相同的残差块的结构叫做一个大残差结构,例如ResNet34
结构:
【注意】残差块中的层是可变化的,可以根据自己的想法来进行调整、增删。
3)18/34/50/101/152层ResNet一览
【注意点】
ResNet
网络模型的输入图像的像素大小默认是224×224
;在训练和测试时最好要将数据集的尺寸变成224×224
!!!
4)ResNet34
①对应的残差块
- 恒等映射残差块:
- 下采样卷积映射残差块:
②模型结构图
其中的 /2
表示的是尺寸减半,此时一般 stride=2
。
③模型pytorch代码
文件名为
NN_models.py
from torch import nn
# 18、34版本残差块
'''
@:param
input_channels: 输入通道数
num_channels: 输出通道数
use_1x1conv: 是否使用 1x1conv (使用就说明是卷积映射残差块, 要变换尺寸)
strides: 步长
'''
class Residual_primary(nn.Module):
def __init__(self, input_channels, num_channels, use_1x1conv=False, strides=1):
# 默认 use_1x1conv=False, strides=1
super(Residual_primary, self).__init__()
self.conv1 = nn.Conv2d(input_channels, num_channels, kernel_size=3, padding=1, stride=strides)
self.bn1 = nn.BatchNorm2d(num_channels) # BN层
self.relu1 = nn.ReLU(inplace=True) # Inplace ReLU to save memory
self.conv2 = nn.Conv2d(num_channels, num_channels, kernel_size=3, padding=1)
self.bn2 = nn.BatchNorm2d(num_channels)
# 如果用了 1x1conv 就说明是卷积映射残差块, 要变换尺寸
if use_1x1conv:
self.conv3 = nn.Conv2d(input_channels, num_channels, kernel_size=1, stride=strides)
self.bn3 = nn.BatchNorm2d(num_channels)
else:
self.conv3 = None
def forward(self, X):
Y = self.conv1(X)
Y = self.bn1(Y)
Y = self.relu1(Y)
Y = self.conv2(Y)
Y = self.bn2(Y)
if self.conv3:
X = self.conv3(X)
X = self.bn3(X) # If using 1x1 conv, also apply BN
Y += X
Y = nn.ReLU(inplace=True)(Y) # Optionally, you can move this ReLU outside the Residual class
return Y
# 初始卷积层和池化层
b1 = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False), # Removed bias since we have BN, 输入通道为3表示彩色图像
nn.BatchNorm2d(64),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)
# 大残差结构
'''
@:param
input_channels: 输入通道数
num_channels: 输出通道数
num_residuals: 残差块的个数
first_block: 是否是第一个大残差结构
@:return
nn.Sequential(*blk): 大残差结构
'''
def resnet_block(in_channels, out_channels, num_residuals, first_block=False):
blk = []
for i in range(num_residuals):
stride = 2 if i == 0 and not first_block else 1 # 从第二个大残差结构开始, 结构中的第一个残差块一般都会尺寸减半, 即 stride=2
use_1x1conv = i == 0 and not first_block # use_1x1conv = False/True, 从第二个大残差结构开始, 结构中的第一个残差块都是卷积映射残差块
if i == 0:
blk.append(Residual_primary(in_channels, out_channels, use_1x1conv=use_1x1conv, strides=stride))
else:
blk.append(Residual_primary(out_channels, out_channels, strides=stride))
return nn.Sequential(*blk)
# ResNet-18
def resnet18(num_classes, in_channels=3):
# net = nn.Sequential(b1)
net = nn.Sequential(
nn.Conv2d(in_channels, 64, kernel_size=7, stride=2, padding=3, bias=False),
nn.BatchNorm2d(64),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)
net.add_module("resnet_block1", resnet_block(64, 64, 2, first_block=True))
net.add_module("resnet_block2", resnet_block(64, 128, 2))
net.add_module("resnet_block3", resnet_block(128, 256, 2))
net.add_module("resnet_block4", resnet_block(256, 512, 2))
net.add_module("global_avg_pool", nn.AdaptiveAvgPool2d((1, 1)))
net.add_module("fc", nn.Sequential(nn.Flatten(), nn.Linear(512, num_classes)))
# Optionally, initialize weights here (e.g., nn.init.kaiming_normal_(net[0].conv1.weight, mode='fan_out', nonlinearity='relu'))
return net
# ResNet-34
def resnet34(num_classes, in_channels=3):
# 也可以使用语句 net = nn.Sequential(b1) 来代替下方
net = nn.Sequential(
nn.Conv2d(in_channels, 64, kernel_size=7, stride=2, padding=3, bias=False),
nn.BatchNorm2d(64),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)
net.add_module("resnet_block1", resnet_block(64, 64, 3, first_block=True))
net.add_module("resnet_block2", resnet_block(64, 128, 4))
net.add_module("resnet_block3", resnet_block(128, 256, 6))
net.add_module("resnet_block4", resnet_block(256, 512, 3))
net.add_module("global_avg_pool", nn.AdaptiveAvgPool2d((1, 1)))
net.add_module("fc", nn.Sequential(nn.Flatten(), nn.Linear(512, num_classes)))
# Optionally, initialize weights here
return net
④测试代码
import torch
from torch import nn
import torchvision
from torchvision import transforms,datasets
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from NN_models import *
from PIL import Image
import time
# 检查CUDA是否可用,并设置设备为 GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
dataclass_transform = transforms.Compose([
transforms.ToTensor(),
transforms.Resize(224),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) # 标准化
])
# 加载本地训练数据集
train_data_dir = 'E:\\4_Data_sets\\respiratory waveform\\train' # 数据集根目录路径,自定义
train_image_datasets = datasets.ImageFolder(train_data_dir, transform=dataclass_transform)
train_dataloader = DataLoader(train_image_datasets, batch_size=7, shuffle=True)
train_classes = train_image_datasets.classes # 获取类别名称
# 加载本地验证数据集
test_data_dir = 'E:\\4_Data_sets\\respiratory waveform\\val' # 数据集根目录路径,自定义
test_image_datasets = datasets.ImageFolder(test_data_dir, transform=dataclass_transform)
test_dataloader = DataLoader(test_image_datasets, batch_size=7, shuffle=True)
test_classes = test_image_datasets.classes # 获取类别名称
# 创建网络模型
resnet34_Instance = resnet34(3, 3).to(device)
# 定义损失函数
loss = nn.CrossEntropyLoss()
# 定义优化器
learning_rate = 0.01
optimizer = torch.optim.SGD(resnet34_Instance.parameters(), lr=learning_rate, momentum=0.9)
# 开始训练
total_train_step = 0
first_train_step = 0
total_test_step = 0
epoch_sum = 15 # 迭代次数
# 添加tensorboard
writer = SummaryWriter('logs')
start_time = time.time()
last_epoch_time = time.time() # 记录开始训练的时间
for i in range(epoch_sum):
print("----------------第 {} 轮训练开始了----------------:".format(i + 1))
# 训练步骤开始
for data in train_dataloader:
imgs, labels = data
imgs, labels = imgs.to(device), labels.to(device) # 将数据和目标移动到GPU
outputs = resnet34_Instance(imgs)
loss_real = loss(outputs, labels) # 这里的损失变量 loss_real,千万别和损失函数 loss 相同,否则会报错!
optimizer.zero_grad()
loss_real.backward()
optimizer.step() # 更新模型参数
total_train_step += 1
# 表示第一轮训练结束,取每一轮的第一个batch_size来看看训练效果,下面的33,由训练数据集总数÷batch_size
if total_train_step % 33 == 0:
first_train_step += 1
print("训练次数为:{}, loss为:{}".format(total_train_step, loss_real)) # 此训练次数非训练轮次
# 每轮测试结束之后,计算训练的准确率,就拿第一个batch_size来看看准确率
outputs_B = torch.argmax(outputs, dim=1)
outputs_C = (labels == outputs_B).sum() # True默认为1,False默认为0
accuracy = (outputs_C.item() / len(outputs_B)) * 100
accuracy = round(accuracy, 2) # 2表示保留两位小数(四舍五入)
print(f"训练正确率为:{accuracy}%")
writer.add_scalar('first_batch_size', loss_real.item(), first_train_step)
writer.add_scalar('total_batch_size', loss_real.item(), total_train_step)
# 每训练一轮,就使用测试集看看训练效果
total_test_loss = 0
with torch.no_grad():
for data in test_dataloader:
imgs, labels = data
imgs, labels = imgs.to(device), labels.to(device)
outputs = resnet34_Instance(imgs)
loss_fake = loss(outputs, labels)
total_test_loss += loss_fake.item()
print("整体测试集上的LOSS为:{}".format(total_test_loss))
one_epoch_time = time.time() # 记录训练一次的时间
one_cost_time = one_epoch_time - last_epoch_time
print(f"训练此轮需要的时间为:{one_cost_time}")
last_epoch_time = one_epoch_time
one_epoch_time = 0
end_time = time.time()
total_time = end_time - start_time
print(f"训练总计需要的时间为:{total_time}")
writer.close()
训练结果:
----------------第 1 轮训练开始了----------------:
训练次数为:33, loss为:5.042407512664795
训练正确率为:57.14%
整体测试集上的LOSS为:52.15475556533784
训练此轮需要的时间为:3.630617618560791
----------------第 2 轮训练开始了----------------:
训练次数为:66, loss为:0.6747639775276184
训练正确率为:42.86%
整体测试集上的LOSS为:11.183943532407284
训练此轮需要的时间为:3.354886293411255
----------------第 3 轮训练开始了----------------:
......
----------------第 15 轮训练开始了----------------:
训练次数为:495, loss为:1.8968732357025146
训练正确率为:71.43%
整体测试集上的LOSS为:0.9958151796163293
训练此轮需要的时间为:2.9634268283843994
训练总计需要的时间为:47.2208309173584
5)ResNet50
①对应的残差块
-
下采样卷积映射残差块
第一个大残差结构的第一个是非下采样卷积映射残差块,这里就不做赘述,可以直接看后面的整体结构图。
ResNet50
中也有恒等映射残差块,但是这里也不再次展示了。这些残差块又叫做“瓶颈残差块”,因为中间的
3×3
卷积层的输出维度比两端小,故而得名。第一个卷积层的作用是:使用
1x1
的卷积核,主要作用是降低特征图的维度(即通道数);第二个卷积层的作用是:使用
3x3
的卷积核进行特征提取;第三个卷积层的作用是:再次使用
1x1
的卷积核,以便与卷积映射结果(shortcut connection)进行相加。
注意原论文中残差结构的主分支上第一个 1x1
卷积层的步距是 2
,第二个 3x3
卷积层步距是 1
。但在 pytorch
官方实现过程中是第一个 1x1
卷积层的步距是 1
,第二个 3x3
卷积层步距是 2
,这样能够在 imagenet
的上提升大概 0.5%
的准确率。
②模型结构图
其中 conv2_x
、conv3_x
这些就是大残差结构,其中包含了多个残差块,每个残差块里面都有三个卷积层。从 conv1
到conv5_x
,再加上初始卷积层和最后的全连接层,总共 50
层。模型结构图的详细展示如下,其中 s
表示 stride
的值,p
表示 padding
的值(非常无敌之全面工整) 。
③模型pytorch代码
# 50版本残差块
class Bottleneck(nn.Module):
def __init__(self, in_channels, out_channels, strides=1, downsamples=False):
super(Bottleneck, self).__init__()
self.conv1 = nn.Conv2d(in_channels, out_channels // 4, kernel_size=1, bias=False)
self.bn1 = nn.BatchNorm2d(out_channels // 4)
self.relu1 = nn.ReLU(inplace=True)
self.conv2 = nn.Conv2d(out_channels // 4, out_channels // 4, kernel_size=3, stride=strides, padding=1,
bias=False)
self.bn2 = nn.BatchNorm2d(out_channels // 4)
self.relu2 = nn.ReLU(inplace=True)
self.conv3 = nn.Conv2d(out_channels // 4, out_channels, kernel_size=1, bias=False)
self.bn3 = nn.BatchNorm2d(out_channels)
if downsamples:
self.conv4 = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=strides)
self.bn4 = nn.BatchNorm2d(out_channels)
else:
self.conv4 = None
def forward(self, x):
identity = x # 映射
out = self.conv1(x)
out = self.bn1(out)
out = self.relu1(out)
out = self.conv2(out)
out = self.bn2(out)
out = self.relu2(out)
out = self.conv3(out)
out = self.bn3(out)
if self.conv4:
identity = self.conv4(identity)
identity = self.bn4(identity)
out += identity
out = nn.ReLU(inplace=True)(out)
return out
# 大残差结构
def resnet_Bottleneck(in_channels, out_channels, num_residuals, first_block=False):
blk = []
for i in range(num_residuals):
stride = 2 if i == 0 and not first_block else 1 # 第一个残差结构中的第一个残差块,只改变通道数,不作下采样
downsample = i == 0 # downsample = False/True, 从第二个大残差结构开始, 结构中的第一个残差块都是下采样映射残差块
blk.append(Bottleneck(in_channels, out_channels, strides=stride, downsamples=downsample))
in_channels = out_channels
return nn.Sequential(*blk)
# ResNet-50
def resnet50(num_classes, in_channels=3):
net = nn.Sequential(
nn.Conv2d(in_channels, 64, kernel_size=7, stride=2, padding=3, bias=False),
nn.BatchNorm2d(64),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
net.add_module("layer1", resnet_Bottleneck(64, 256, 3, first_block=True))
net.add_module("layer2", resnet_Bottleneck(256, 512, 4))
net.add_module("layer3", resnet_Bottleneck(512, 1024, 6))
net.add_module("layer4", resnet_Bottleneck(1024, 2048, 3))
net.add_module("global_avg_pool", nn.AdaptiveAvgPool2d((1, 1)))
net.add_module("fc", nn.Sequential(nn.Flatten(), nn.Linear(2048, num_classes)))
return net
④测试代码
上述模型源码保存在了
NN_models.py
文件中。
import torch
from torch import nn
import torchvision
from torchvision import transforms,datasets
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from NN_models import *
from PIL import Image
import time
# 检查CUDA是否可用,并设置设备为 GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
dataclass_transform = transforms.Compose([
transforms.ToTensor(),
transforms.Resize(224),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) # 标准化
])
# 加载本地训练数据集, 自定义
train_data_dir = 'E:\\4_Data_sets\\respiratory waveform\\train' # 数据集根目录路径
train_image_datasets = datasets.ImageFolder(train_data_dir, transform=dataclass_transform)
train_dataloader = DataLoader(train_image_datasets, batch_size=7, shuffle=True)
train_classes = train_image_datasets.classes # 获取类别名称
# 加载本地验证数据集, 自定义
test_data_dir = 'E:\\4_Data_sets\\respiratory waveform\\val' # 数据集根目录路径
test_image_datasets = datasets.ImageFolder(test_data_dir, transform=dataclass_transform)
test_dataloader = DataLoader(test_image_datasets, batch_size=7, shuffle=True)
test_classes = test_image_datasets.classes # 获取类别名称
# 创建网络模型
resnet50_Instance = resnet50(3, 3).to(device)
# 定义损失函数
loss = nn.CrossEntropyLoss()
# 定义优化器
learning_rate = 0.01
optimizer = torch.optim.SGD(resnet50_Instance.parameters(), lr=learning_rate, momentum=0.9)
# 开始训练
total_train_step = 0
first_train_step = 0
total_test_step = 0
epoch_sum = 15 # 迭代次数
# 添加tensorboard
writer = SummaryWriter('logs')
start_time = time.time()
last_epoch_time = time.time() # 记录开始训练的时间
for i in range(epoch_sum):
print("----------------第 {} 轮训练开始了----------------:".format(i + 1))
# 训练步骤开始
for data in train_dataloader:
imgs, labels = data
imgs, labels = imgs.to(device), labels.to(device) # 将数据和目标移动到GPU
outputs = resnet50_Instance(imgs)
loss_real = loss(outputs, labels) # 这里的损失变量 loss_real,千万别和损失函数 loss 相同,否则会报错!
optimizer.zero_grad()
loss_real.backward()
optimizer.step() # 更新模型参数
total_train_step += 1
# 表示第一轮训练结束,取每一轮的第一个batch_size来看看训练效果,下面的33,由训练数据集总数÷batch_size
if total_train_step % 33 == 0:
first_train_step += 1
print("训练次数为:{}, loss为:{}".format(total_train_step, loss_real)) # 此训练次数非训练轮次
# 每轮测试结束之后,计算训练的准确率,就拿第一个batch_size来看看准确率
outputs_B = torch.argmax(outputs, dim=1)
outputs_C = (labels == outputs_B).sum() # True默认为1,False默认为0
accuracy = (outputs_C.item() / len(outputs_B)) * 100
accuracy = round(accuracy, 2) # 2表示保留两位小数(四舍五入)
print(f"训练正确率为:{accuracy}%")
writer.add_scalar('first_batch_size', loss_real.item(), first_train_step)
writer.add_scalar('total_batch_size', loss_real.item(), total_train_step)
# 每训练一轮,就使用测试集看看训练效果
total_test_loss = 0
with torch.no_grad():
for data in test_dataloader:
imgs, labels = data
imgs, labels = imgs.to(device), labels.to(device)
outputs = resnet50_Instance(imgs)
loss_fake = loss(outputs, labels)
total_test_loss += loss_fake.item()
print("整体测试集上的LOSS为:{}".format(total_test_loss))
one_epoch_time = time.time() # 记录训练一次的时间
one_cost_time = one_epoch_time - last_epoch_time
print(f"训练此轮需要的时间为:{one_cost_time}")
last_epoch_time = one_epoch_time
one_epoch_time = 0
end_time = time.time()
total_time = end_time - start_time
print(f"训练总计需要的时间为:{total_time}")
writer.close()
训练结果:
----------------第 1 轮训练开始了----------------:
训练次数为:33, loss为:1.0290035009384155
训练正确率为:57.14%
整体测试集上的LOSS为:22.40016353689134
训练此轮需要的时间为:7.299371719360352
----------------第 2 轮训练开始了----------------:
训练次数为:66, loss为:19.585474014282227
训练正确率为:28.57%
整体测试集上的LOSS为:26.81494735251181
训练此轮需要的时间为:4.151764392852783
----------------第 3 轮训练开始了----------------:
......
----------------第 15 轮训练开始了----------------:
训练次数为:495, loss为:0.00018018828995991498
训练正确率为:100.0%
整体测试集上的LOSS为:1.971629376304918
训练此轮需要的时间为:4.134536266326904
训练总计需要的时间为:65.64659333229065
下一篇 |
---|
各类神经网络学习(二) |