CNN深度学习代码实践(在最基础的CNN上的部分扩展知识解读)

目录

前言

一、准备自己的数据集并引入(使用ImageFolder、Dataloader 函数)

二、展示数据集的数据

 (一)查看图片shape,检查第一张图片

(二)查看一批图片(matplotlib库)

三、训练,测试代码部分

(一)训练

(二)测试

四、保存模型和加载模型


前言

        在学习CNN最基础的部分后要学习的部分实在很多而且很杂,故本人致力于记录下在学习CNN的道路上的各种也许是凌乱的知识点作为扩展,解释力求能让每个人看懂。

        注:文章并不是按照构建,运行神经网络的顺序书写的,如没有定义损失函数,优化器等。本文只是记录一些扩展的零散知识点。


一、准备自己的数据集并引入(使用ImageFolder、Dataloader 函数)

————————————————定义数据目录————————————————
train_dir = "../data/train"
test_dir = "../data/test"
————————————————定义预处理的参数————————————
# 将图像调整为224×224尺寸并归一化
mean = [0.485, 0.456, 0.406]
std = [0.229, 0.224, 0.225]
train_augs = transforms.Compose([
    transforms.RandomResizedCrop(size=224),
    transforms.RandomHorizontalFlip(),
    transforms.ToTensor(),
    transforms.Normalize(mean, std)
])
test_augs = transforms.Compose([
    transforms.Resize(size=256),
    transforms.CenterCrop(size=224),
    transforms.ToTensor(),
    transforms.Normalize(mean, std)
])
 
————————————————使用ImageFolder函数——————————
train_set = datasets.ImageFolder(train_dir, transform=train_augs)
test_set = datasets.ImageFolder(test_dir, transform=test_augs)
 
batch_size = 32
train_iter = DataLoader(train_set, batch_size=batch_size, shuffle=True)
test_iter = DataLoader(test_set, batch_size=batch_size)

(1)train_augs(数据增强(augmentations)

(2)

transforms.Normalize(mean, std)

将图像的每个通道(红、绿、蓝)减去均值并除以标准差,即进行标准化。这样可以使图像的分布更接近正态分布,有利于神经网络的收敛和优化。

其中mean和std是要自己计算的,一种简单的方法是:

1.遍历数据集中的所有图像,将每张图像转化为张量,并计算每个通道(红、绿、蓝)的均值和方差,然后将这些值累加起来。

2.将累加的均值和方差除以图像的总数,得到数据集的均值和方差。

下面是一个用PyTorch实现的例子,假设你已经定义好了一个数据集类和一个数据加载器:

# 初始化均值和方差
mean = torch.zeros(3)
std = torch.zeros(3)
# 遍历数据集for inputs in data_loader:
    # 计算每个通道的均值和方差
    mean += torch.mean(inputs, dim=[0, 2, 3])
    std += torch.std(inputs, dim=[0, 2, 3])
# 计算数据集的均值和方差
mean /= len(data_loader)
std /= len(data_loader)
# 打印结果print(mean)print(std)

dim=[0, 2, 3]的意思是沿着第0、2、3个维度进行计算,即批次、高度、宽度。因为输入的张量的形状是(batch_size, channels, height, width),我们想要计算每个通道的均值和方差,所以要忽略第1个维度,也就是通道维度。如果我们打印一下 mean 和 std 的形状,你会发现它们都是一个长度为 C 的向量,每个元素对应一个通道的均值或方差。

(3)

transforms.RandomHorizontalFlip():

随机地水平翻转图像,即左右对换。这也是一种数据增强的方法,可以提高神经网络的鲁棒性。

神经网络的鲁棒性是一个重要的性质,因为在实际应用中,输入往往会受到一些噪声、失真、遮挡等因素的影响,如果神经网络对这些因素过于敏感,那么它的性能和可靠性就会下降。

(4)

transforms.Resize()

是一个用于调整图像大小的函数。它可以接受一个整数或一个序列作为参数,表示目标大小。如果参数是一个整数,它会将图像的较短边缩放到这个数值,同时保持图像的长宽比。如果参数是一个序列,如(h, w),它会将图像的高度和宽度分别缩放到这个数值

(5)ImageFolder会将目录中的文件夹名自动转化成序列,当DataLoader载入时,标签自动就是整数序列了。比如说目录里的文件夹是郁金香,百合。生成的targets对应就是0, 1。

可以通过print(dataset.class_to_idx)这个代码查看标签对应关系。

如下:

print(f"标签对应关系为:{train_set.class_to_idx}")

输出结果:标签对应关系为:{'daisy': 0, 'dandelion': 1, 'roses': 2, 'sunflowers': 3, 'tulips': 4}

二、展示数据集的数据

 (一)查看图片shape,检查第一张图片

batch_size = 128

# Create data loaders.
train_dataloader = DataLoader(training_data, batch_size=batch_size)
test_dataloader = DataLoader(test_data, batch_size=batch_size)

for X, y in test_dataloader:
    print(f"Shape of X [N, C, H, W]: {X.shape}")
    print(f"Shape of y: {y.shape} {y.dtype}")
Break
# Display image and label.
train_features, train_labels = next(iter(train_dataloader))
print(f"Feature batch shape: {train_features.size()}")
print(f"Labels batch shape: {train_labels.size()}")
img = train_features[1].squeeze()
label = train_labels[1]
plt.imshow(img, cmap="gray")
plt.show()
print(f"Label: {label}")

 

 

(1)

next(iter(train_dataloader))

next()和iter()是Python中的两个内置函数,它们用于创建和遍历迭代器。迭代器是一个对象,它可以在一个可迭代的集合上进行迭代,返回集合中的下一个元素。next()用来获取下一批特征和标签。

当你使用iter()函数对数据加载器进行迭代时,它会返回一个迭代器对象。迭代器对象有一个__next__方法,它可以返回迭代器中的下一个元素。当你使用next函数对迭代器对象进行操作时,它会调用__next__方法,返回数据集中的下一批特征和标签。如果迭代器中没有更多的元素,它会抛出StopIteration异常。

所以,当你使用next(iter(iris_loader))时,你实际上是在获取数据集中的第一批特征和标签。如果你再次使用next(iter(iris_loader)),你会获取第二批特征和标签,以此类推

(2)squeeze()是PyTorch中的一个函数,它的作用是删除张量中维度为1的维度。例如,如果一个张量的形状是(2, 1, 3),那么使用squeeze()函数后,它的形状就变成了(2, 3)。你可以指定要删除的维度,也可以不指定,让它自动删除所有维度为1的维度。

代码中squeeze()函数删除train_features[1]中维度为1的维度。train_features[1]是一个张量。假设它的形状是(1, 28, 28),分别是通道数,高度,宽度。如果你使用squeeze函数,它的形状就变成了(28, 28)。这样做的目的可能是为了方便后续的处理或可视化 。  (因为有些函数或库可能要求输入的张量的形状是(C, H, W)而不是(1, C, H, W)。例如,如果你想使用matplotlib.pyplot.imshow函数来显示一个图像,它要求输入的数组的形状是(H, W)或(H, W, C)。如果你不使用squeeze函数,你就需要手动指定要显示的维度,例如imshow(train_features[1][0])。如果你使用了squeeze函数,你就可以直接使用imshow(train_features[1])。这样就更简洁和方便了)

(二)查看一批图片(matplotlib库)

# 展示随机16张图片
figure = plt.figure(figsize=(8, 8))
cols, rows = 4, 4
for i in range(1, cols * rows + 1):
    sample_idx = torch.randint(len(training_data), size=(1,)).item()
    img, label = training_data[sample_idx]
    figure.add_subplot(rows, cols, i)
    plt.title(training_data.classes[label])
    plt.axis("off")
    plt.imshow(img.squeeze(), cmap="gray")
plt.show()

 

(1)以下补充matplotlib.pyplot库的知识点

figure = plt.figure(figsize=(8, 8)):

创建一个8*8英寸的画布,命名为figure。画布大小规定了画布在屏幕中显示的大小

(2)

sample_idx = torch.randint(len(training_data), size=(1,)).item()

这一句代码是用torch库中的randint函数来生成一个随机整数。randint函数的第一个参数是上界,表示生成的整数不会超过这个值减1。第二个参数是size,表示生成的整数的形状。这里size是(1,),表示生成一个一维的张量,只有一个元素。如果size是(2,3),就表示生成一个二维的张量,有两行三列。

randint函数返回的是一个张量,但我们需要的是一个整数,所以我们用item()方法来获取张量中的唯一元素。item()方法只能用于只有一个元素的张量,否则会报错。

举个例子,假设training_data的长度是100,那么这一句代码就相当于:

sample_idx = torch.randint(100, size=(1,)):

生成一个0到99之间的随机整数,保存为一个一维张量,例如tensor([42])。

sample_idx = sample_idx.item()

获取张量中的元素,赋值给sample_idx

(3)

figure.add_subplot(rows, cols, i)

在画布上添加一个子图,位置是第i个,按照rows行cols列排列。

(4)

plt.title(training_data.classes[label])

:给子图添加一个标题,内容是标签对应的类别名称,从training_data.classes中获取。(label仅仅是名称对应的编号)

(5)

plt.imshow(img.squeeze(), cmap="gray")

:在子图上显示图片,使用灰度色彩映射。img.squeeze()是为了去掉图片张量中多余的维度。

三、训练,测试代码部分

def train(dataloader, model2, loss_fn, optimizer):
    size = len(dataloader.dataset)
    model2.train()
    for batch, (X, y) in enumerate(dataloader):
        X, y = X.to(device), y.to(device)

        # Compute prediction error
        pred = model2(X)
        loss = loss_fn(pred, y)

        # Backpropagation
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        if batch % 100 == 0:
            loss, current = loss.item(), batch * len(X)
            print(f"loss: {loss:>7f}  [{current:>5d}/{size:>5d}]")
def test(dataloader, model2, loss_fn):
    size = len(dataloader.dataset)
    num_batches = len(dataloader)
    model2.eval()
    test_loss, correct = 0, 0
    with torch.no_grad():
        for X, y in dataloader:
            X, y = X.to(device), y.to(device)
            pred = model2(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 Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")

epochs = 5
for t in range(epochs):
    print(f"Epoch {t+1}\n-------------------------------")
    train(train_dataloader, model, loss_fn, optimizer)
    test(test_dataloader, model, loss_fn)
print("Done!")

(一)训练

(1)关于dataloader.size

dataloader的size表示dataloader可以产生多少个小批量,也就是迭代的次数。数据集的大小表示数据集中有多少个样本,小批量的大小表示每个小批量中有多少个样本。dataloader的size等于数据集的大小除以小批量的大小,向上取整。也就是说,如果数据集的大小不能被小批量的大小整除,那么最后一个小批量会比其他的小批量小一些。

(2)dataloader.dataset表示dataloader所使用的数据集

所以size = len(dataloader.dataset)表示的是总训练集的大小,本例中为60000

(3)

model2.train()

:这一句是将模型设置为训练模式

这样一些层(如Dropout和BatchNorm)就会根据训练和评估的不同行为进行调整。例如,在训练模式下,BatchNorm会在每个新批次上更新一个移动平均值;而在评估模式下,这些更新会被冻结。这样可以提高模型的泛化能力和稳定性。

(4)enumerate(dataloader)这个函数的作用是将一个可遍历的数据对象(如数据加载器)组合为一个索引序列,同时列出数据和数据下标,一般用在for循环当中。

dataloader返回的每个批次(batch)是一个包含两个元素的元组(tuple),第一个元素是输入特征(X),第二个元素是目标标签(y)。为了将这两个元素分别赋值给X和y,我们需要用()来表示元组的解包(unpacking)。 这样,X就会得到输入特征,y就会得到目标标签。

(5)

 loss, current = loss.item(), batch * len(X)

len()返回值为第一个维度的大小,即batch_size, batch * len(X)即为已处理的数据量

(6)

print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]")

这个print的格式是使用了f-string,一种Python 3.6引入的新的字符串格式化方式。

f-string的语法是在字符串前加上f,然后在大括号 { }中填入表达式或变量,Python会将它们替换成对应的值。

在大括号 { }中,还可以使用一些格式描述符来控制输出的样式,比如对齐、宽度、精度等。

在这个例子中,loss:>7f表示将loss变量格式化为浮点数(f),并右对齐(>),宽度为7(7)。current:>5d和size:>5d表示将current和size变量格式化为整数(d),并右对齐(>),宽度为5(5)。

(二)测试

(1)

model2.eval()

:这一句是将模型设置为评估模式,这样一些层(如Dropout和BatchNorm)就会根据评估和训练的不同行为进行调整。

(2)

test_loss, correct = 0, 0

:这一句是初始化两个变量:test_loss用于累计测试损失,correct用于累计正确预测的数量。

(3)

with torch.no_grad()

::这一句是使用一个上下文管理器,表示在该代码块中不需要计算梯度,以节省内存和提高速度。

(4)

correct += (pred.argmax(1) == y).type(torch.float).sum().item()

:pred中储存了一个由 模型需要预测的类别数 和 batch_size 组成的二维张量

这一句是用argmax方法获取预测值中最大概率对应的类别,并与真实标签进行比较,得到一个布尔值的一维张量。然后用type方法将布尔值转换为浮点数(0或1),因为布尔值不能直接相加,接着用sum方法求和得到该批次中正确预测的数量。最后用.item()方法将张量(tensor)转换为标量(scalar),并累加到correct变量中。

四、保存模型和加载模型

torch.save(model.state_dict(), "model.pth")
print("Saved PyTorch Model State to model.pth")
model = NeuralNetwork()
model.load_state_dict(torch.load("model.pth"))

(1)

torch.save(model.state_dict(), "model.pth")

:这一句是将模型的state_dict保存到一个名为model.pth的文件中。state_dict是一个Python字典对象,它存储了模型的所有可学习参数(如权重和偏置)。

2

model = CNN()

这一句是创建一个新的神经网络对象,它的结构和保存时的模型结构一致,但是它的参数是随机初始化的,还没有经过训练

(3)

model.load_state_dict(torch.load(“model.pth”))

这一句是从model.pth文件中加载state_dict,并用它来更新模型的参数。state_dict是一个字典对象,它存储了模型的所有可学习参数(如权重和偏置)。

这样,通过这两句,我们就可以创建一个和保存时一样的模型,并用它进行推理或继续训练。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值