本章主要内容:
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))
输出:
好像降低网络复杂度效果也差不多: