第七章 区分鸟和飞机

本章主要内容:

1、构建前馈神经网络

2、使用Dataset和DataLoader加载数据

3、了解分类损失

1.cifar10数据集

1.1 数据集下载

###Cifar10数据集
##下载数据集
from torchvision import datasets
data_path = 'D:\\DeepLearning data\\data\\p1ch7'
cifar10 = datasets.CIFAR10(data_path, train=True, download=True)
cifar10_val = datasets.CIFAR10(data_path, train=False, download=True)

###查看cifar10的方法解析顺序
print(type(cifar10).__mro__)

1.2 Dataset类

%matplotlib inline
import torch
import numpy as np
from matplotlib import pyplot as plt
##Dataset类
##__len__()函数返回数据中的项数
print(len(cifar10))
##__getitem()__函数返回由样本和与之对应的标签组成的项

class_names = ['airplane','automobile','bird','cat','deer','dog','frog','horse','ship','truck']
fig = plt.figure(figsize=(8,3))
num_classes = 10
for i in range(num_classes):
    ax = fig.add_subplot(2, 5, 1+i, xticks=[], yticks=[])
    ax.set_title(class_names[i])
    img = next(img for img,label in cifar10 if label == i)
    plt.imshow(img)
plt.show()

输出:

绘制图像实例:

img, label = cifar10[99]
print(img, label, class_names[label])
plt.imshow(img)
plt.show()

1.3 Dataset变换

###Dateset变换
##引入之前介绍的torchvision.transforms模块
from torchvision import transforms
print(dir(transforms))##输出可用对象列表
##调用ToTensor变换
to_tensor = transforms.ToTensor()
img_t = to_tensor(img)
print(img_t.shape)

可以将变换直接作为参数传给dataset.CIFAR10:

###可以将变换直接作为参数传给dataset.CIFAR10:
tensor_cifar10 = datasets.CIFAR10(data_path, train=True, download=False, transform=transforms.ToTensor())
img_t,_ = tensor_cifar10[99]
print(type(img_t))
##查看形状
print(img_t.shape,img_t.dtype)
##ToTensor变换将数据变换为每个通道的32位浮点数,将值缩小为0.0-1.0,验证:
print(img_t.min(),img_t.max())
##验证变换轴顺序后的图片
plt.imshow(img_t.permute(1,2,0))##将坐标轴顺序从CxHxW变为HxWxC,以匹配Matplotlib的期望
plt.show()

 输出:

1.4 数据归一化

由于cifar10数据集较小,我们完全可以在内存中操作他,我们将数据集返回的所有张量沿着一个额外的维度进行堆叠:

##数据归一化
#由于cifar10数据集较小,我们完全可以在内存中操作他,我们将数据集返回的所有张量沿着一个额外的维度进行堆叠
imgs = torch.stack([img_t for img_t, _ in tensor_cifar10],dim=3)
print(imgs.shape)

输出:

计算每个信道的平均值:

view(3,-1)保留了3个通道,并将剩余的所有维度合并为一个维度,这里将3*32*32的图像转换为3*1024的向量,然后对每个通道的1024个元素取均值。

##计算每个信道的平均值
print(imgs.view(3,-1).mean(dim=1))
'''
view(3,-1)保留了3个通道,并将剩余的所有维度合并为一个维度,这里将3*32*32的图像转换为3*1024的向量,然后对每个通道的1024个元素取均值
'''
##同理计算标准差
print(imgs.view(3,-1).std(dim=1))
#进行Normalize变换
transforms.Normalize((0.4914, 0.4822, 0.4465),(0.2470, 0.2435, 0.2616))

输出:

将之加入到transform的变换中:

##加入到ToTensor变换中:
transformed_cifar10 = datasets.CIFAR10(data_path, train=True, download=False, 
                                       transform=transforms.Compose([
                                       transforms.ToTensor(),
                                       transforms.Normalize((0.4914, 0.4822, 0.4465),(0.2470, 0.2435, 0.2616))
                                       ]))
transformed_cifar10_val = datasets.CIFAR10(data_path, train=False, download=False, 
                                       transform=transforms.Compose([
                                       transforms.ToTensor(),
                                       transforms.Normalize((0.4914, 0.4822, 0.4465),(0.2470, 0.2435, 0.2616))
                                       ]))

输出处理过的图像:(注意,此时从数据集绘制的图像不能为我们提供实际图像的真实表示)

##注意,此时从数据集绘制的图像不能为我们提供实际图像的真实表示:
img_t, _ = transformed_cifar10[99]
print(img_t.shape)
print(type(img_t))
plt.imshow(img_t.permute(1,2,0))
plt.show()
# print(type(img_t))

输出:

 注:这里出现了一个warning:

先给出原因:

matplotlib.pyplot.imshow()函数在处理灰度图像时,自动将其值做归一化处理

而在处理彩色图像时则不会,而是将浮点值变换至[0,1],整数值变换到[0, 255]范围

解决:可以将数组中的数据类型都定义为uint8,就OK了

疑问:这里我转换了一下数据类型,warning是不见了,但是为什么图像也变了?你们会的一定要教教我!

img_t, _ = transformed_cifar10[99]
print(type(img_t)) ###Image类型

img = np.asarray(img_t,dtype='uint8')###np.ndarray uint8
print(img.dtype,type(img))
img = torch.from_numpy(img)
plt.imshow(img.permute(1,2,0))
###不知为何与之前的输出不同

 输出:

2 区分鸟和飞机

2.1 构建cifar2数据集

##区分鸟和飞机
#构建数据集
#这里在cifar10中过滤数据,重新映射标签
label_map = {0:0, 2:1}  ##个人理解是将原数据集标签进行映射(原数据集中0是airplane,2是bird,现在只有两类用0、1表示即可)
class_names = ['airplane','bird']
cifar2 = [(img, label_map[label])
          for img, label in transformed_cifar10 ###修改代码
          if label in [0,2]]
# print(type(cifar2))
cifar2_val = [(img, label_map[label]) 
              for img, label in transformed_cifar10_val 
              if label in [0,2]]
# print(type(cifar2[0]))

2.2 构建一个全连接的简单模型

#构建一个全连接模型
import torch.nn as nn
n_out = 2

model = nn.Sequential(
            nn.Linear(3072,512),
            nn.Tanh(),
            nn.Linear(512,n_out))

2.3 分类器用概率表示输出

手动实现softmax函数:

#手动实现softmax函数
def softmax(x):
    return torch.exp(x)/torch.exp(x).sum()
#验证
x = torch.tensor([1.0,2.0,3.0])
print(softmax(x))
print(softmax(x).sum())

nn模块中Softmax可以直接调用,但需要指定编码概率的维度:

改进网络:

##改进网络使之可以产生概率
model = nn.Sequential(
            nn.Linear(3072,512),
            nn.Tanh(),
            nn.Linear(512,n_out),
            nn.Softmax(dim=1))

尝试在模型训练之前运行:

##在训练模型之前尝试运行之
img, _ = cifar2[0]
# print(type(img))
plt.imshow(img.permute(1,2,0))   ###报错
plt.show()

注:这里书中给出的部分代码不全,可能会导致报错,本文是全的~

img, _ = cifar2[0]
# print(type(img))
plt.imshow(img.permute(1,2,0))  
plt.show()

输出:

为了调用模型,我们输入需要具有正确的维度,我们需要将3*32*32的图像变成一个一维张量,然后在第0个位置添加一个维度:

img_batch = img.view(-1).unsqueeze(0)
out = model(img_batch)
print(out)

沿着概率方向取最大值求其索引:

##沿着概率方向取最大值求其索引
_, index = torch.max(out, dim=1)
print(_)
print(index)

 输出:

然而,虽然模型将图像识别为鸟,但是这纯属运气。但起码验证了管道是否可以正常工作。

2.4 分类的损失

其实,nn.LogSoftmax()+nn.NLLLoss()就相当于nn.CrossEntropyLoss()

##分类的损失
#引入NLL损失
#修改模型将softmax改为logsoftmax
model = nn.Sequential(
            nn.Linear(3072,512),
            nn.Tanh(),
            nn.Linear(512,n_out),
            nn.LogSoftmax(dim=1))
#实例化NLL损失
loss = nn.NLLLoss()
#loss将批次的nn.LogSoftmax()的输出作为第1个参数,将类别索引的张量作为第2个参数
img, label = cifar2[0]
out = model(img.view(-1).unsqueeze(0))
print(loss(out, torch.tensor([label])))
'''
其实,nn.LogSoftmax()+nn.NLLLoss()就相当于nn.CrossEntropy()
'''

2.5 训练分类器

这里未使用小批量进行迭代:

##训练分类器
import torch
import torch.nn as nn
import torch.optim as optim
loss_list = []
epoch_list = []
model = nn.Sequential(
            nn.Linear(3072,512),
            nn.Tanh(),
            nn.Linear(512,2),
            nn.LogSoftmax(dim=1))
learning_rate = 1e-4
optimizer = optim.SGD(model.parameters(),lr=learning_rate)
loss_fn = nn.NLLLoss()
n_epochs = 100
for epoch in range(n_epochs):
    epoch_list.append(epoch)
    for img, label in cifar2:
        out = model(img.view(-1).unsqueeze(0))
        loss = loss_fn(out, torch.tensor([label]))
        
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    loss_list.append(loss.item())
    print("Epoch: %d, Loss: %f" % (epoch, float(loss)))
plt.plot(epoch_list,loss_list)
plt.xlabel('epoch')
plt.ylabel('loss')
plt.show()

这里cpu训练是较慢的。

输出:

把学习率调成1e-4又学了一遍,发现loss曲线还不如原来:

使用小批量进行迭代:

torch.utils.data模块有一个DataLoader类,助于打乱与组织数据。
DataLoader()构造函数至少接收一个数据集对象作为输入,以及batch_size和一个shuffle布尔值,该布尔值指示数据是否需要在每个迭代周期开始时重新进行打乱。

train_loader = torch.utils.data.DataLoader(cifar2, batch_size=64, shuffle=True)

 训练模型:

###使用小批量进行迭代
#torch.utils.data模块有一个DataLoader类,助于打乱与组织数据
#DataLoader()构造函数至少接收一个数据集对象作为输入,以及batch_size和一个shuffle布尔值,
#该布尔值指示数据是否需要在每个迭代周期开始时重新进行打乱
# train_loader = torch.utils.data.DataLoader(cifar2, batch_size=64, shuffle=True)
#DataLoader可以被迭代,因此可在内部循环中直接使用
loss_list = []
epoch_list = []
import torch
import torch.nn as nn
import torch.optim as optim
#准备数据
train_loader = torch.utils.data.DataLoader(cifar2, batch_size=64, shuffle=True)
#构建模型
model = nn.Sequential(
            nn.Linear(3072,512),
            nn.Tanh(),
            nn.Linear(512,2),
            nn.LogSoftmax(dim=1))
learning_rate = 1e-2
optimizer = optim.SGD(model.parameters(),lr=learning_rate)
loss_fn = nn.NLLLoss()
n_epochs = 100

for epoch in range(n_epochs):
    epoch_list.append(epoch)
    for imgs, labels in train_loader:
        batch_size = imgs.shape[0]
        outputs = model(imgs.view(batch_size,-1))
        loss = loss_fn(outputs, labels) 
        
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    loss_list.append(loss.item())
    print("Epoch: %d, Loss: %f" % (epoch, float(loss)))
plt.plot(epoch_list,loss_list)
plt.xlabel('epoch')
plt.ylabel('loss')
plt.show()

 输出:

看一下训练集与验证集上的准确率:

训练集:

##验证训练集上准确率
train_loader = torch.utils.data.DataLoader(cifar2, batch_size=64, shuffle=False)#注意参数的设置
correct = 0
total = 0

with torch.no_grad():
    for imgs, labels in train_loader:
        batch_size = imgs.shape[0]
        outputs = model(imgs.view(batch_size,-1))
        _, predicted = torch.max(outputs, dim=1)
        total += labels.shape[0]
        correct += int((predicted == labels).sum())
    print("Accuracy: %f" % (correct/total))  

验证集:

##验证模型验证集上准确率:
val_loader = torch.utils.data.DataLoader(cifar2_val, batch_size=64, shuffle=False)#注意参数的设置
correct = 0
total = 0

with torch.no_grad():
    for imgs, labels in val_loader:
        batch_size = imgs.shape[0]
        outputs = model(imgs.view(batch_size,-1))
        _, predicted = torch.max(outputs, dim=1)
        total += labels.shape[0]
        correct += int((predicted == labels).sum())
        
print("Accuracy: %f" % (correct/total))

很明显过拟合了。

利用pytorch的nn.model的parameters()方法,查看模型中参数个数。

增加模型的深度与容量同时使用nn.CrossEntropyLoss()

修改代码:

model = nn.Sequential(
                nn.Linear(3072,1024),
                nn.Tanh(),
                nn.Linear(1024,512),
                nn.Tanh(),
                nn.Linear(512,128),
                nn.Tanh(),
                nn.Linear(128,10))
loss_fn = nn.CrossEntropyLoss()

loss曲线:

两个准确率:

没什么改变。

参数个数:(查看方法同前)

3 练习题

1.使用TorchVision模块实现数据的随机剪裁

a.得到的图像与未剪裁的原始图像有何区别?

b.第2次请求相同的图像会发生什么?

c.使用随机剪裁的图像进行训练结果怎么样?

d.实际效果更好还是更差?

3.1 构建并处理数据集

###加载数据集
%matplotlib inline
import torch
import random
import numpy as np
from matplotlib import pyplot as plt
from torchvision import datasets
from torchvision import transforms

size2 = random.randint(20,50)
size3 = random.randint(30,50)

print(size2)
print(size3)
data_path = 'D:\\DeepLearning data\\data\\p1ch7'
cifar10 = datasets.CIFAR10(data_path, train=True, download=True)
cifar10_val = datasets.CIFAR10(data_path, train=False, download=True)
#不进行剪裁但是归一化
transformed1_cifar10 = datasets.CIFAR10(data_path, train=True, download=False, 
                                       transform=transforms.Compose([
                                       transforms.ToTensor(),
                                       transforms.Normalize((0.4914, 0.4822, 0.4465),(0.2470, 0.2435, 0.2616))
                                       ]))
transformed1_cifar10_val = datasets.CIFAR10(data_path, train=False, download=False, 
                                       transform=transforms.Compose([
                                       transforms.ToTensor(),
                                       transforms.Normalize((0.4914, 0.4822, 0.4465),(0.2470, 0.2435, 0.2616))
                                       ]))

#处理数据集+进行随机剪裁
transformed_cifar10 = datasets.CIFAR10(data_path, train=True, download=False, 
                                       transform=transforms.Compose([
                                       transforms.CenterCrop(size2),
                                       transforms.ToTensor(),
                                       transforms.Normalize((0.4914, 0.4822, 0.4465),(0.2470, 0.2435, 0.2616))
                                       ]))
transformed_cifar10_val = datasets.CIFAR10(data_path, train=False, download=False, 
                                       transform=transforms.Compose([
                                       transforms.CenterCrop(size2),
                                       transforms.ToTensor(),
                                       transforms.Normalize((0.4914, 0.4822, 0.4465),(0.2470, 0.2435, 0.2616))
                                       ]))



#构建cifar2数据集
#这里在cifar10中过滤数据,重新映射标签
label_map = {0:0, 2:1}  
class_names = ['airplane','bird']
cifar2 = [(img, label_map[label])
          for img, label in transformed_cifar10 ###修改代码
          if label in [0,2]]
# print(type(cifar2))
cifar2_val = [(img, label_map[label]) 
              for img, label in transformed_cifar10_val 
              if label in [0,2]]

输出:两个数字是随机剪裁图像的尺寸

对比一下原图像与剪裁后的图像:

##对比两种图像:
img_t,_ = transformed1_cifar10[99]
plt.imshow(img_t.permute(1,2,0))##将坐标轴顺序从CxHxW变为HxWxC,以匹配Matplotlib的期望
plt.show()
print(img_t.shape)
img,_ = transformed_cifar10[99]
plt.imshow(img.permute(1,2,0))
plt.show()
print(img.shape)

 输出:

就是尺寸不同而已。

3.2 训练模型

loss_list = []
epoch_list = []
import torch
import torch.nn as nn
import torch.optim as optim
model = nn.Sequential(
                nn.Linear(3*size2*size2,1024),
                nn.Tanh(),
                nn.Linear(1024,512),
                nn.Tanh(),
                nn.Linear(512,128),
                nn.Tanh(),
                nn.Linear(128,10))
loss_fn = nn.CrossEntropyLoss()
# print(model)
train_loader = torch.utils.data.DataLoader(cifar2, batch_size=64, shuffle=True)
learning_rate = 1e-2
optimizer = optim.SGD(model.parameters(),lr=learning_rate)
n_epochs = 100

for epoch in range(n_epochs):
    epoch_list.append(epoch)
    for imgs, labels in train_loader:
        batch_size = imgs.shape[0]
        outputs = model(imgs.view(batch_size,-1))
        loss = loss_fn(outputs, labels)       
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    loss_list.append(loss.item())
    print("Epoch: %d, Loss: %f" % (epoch, float(loss)))
plt.plot(epoch_list,loss_list)
plt.xlabel('epoch')
plt.ylabel('loss')
plt.show()

还是过拟合。size3是另一个剪裁尺寸,可以自己试一试

降低网络容量:

model = nn.Sequential(
                nn.Linear(3*size2*size2,512),
                nn.Tanh(),
                nn.Linear(512,128),
                nn.Tanh(),
                nn.Linear(128,10))

输出:

 好像降低网络复杂度效果也差不多:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值