pytorch学习笔记七————识别鸟和飞机1(一个粗糙的图像识别)
数据集(CIFAR-10)
CIFAR-10数据集含有60000张微小的(32*32)RGB图像,用一个整数对应10个级别中的1个,分别是:飞机0,汽车1,鸟2,猫3,鹿4,狗5,青蛙6,马7,船8,卡车9,虽然这些图像过于简单,但是可以满足我们的学习要求
下面代码是对数据集和验证集的数据下载:
from torchvision import datasets
from torchvision import transforms
data_path='data/'
cifar10 = datasets.CIFAR10(
data_path, train=True, download=True)
cifar10_val = datasets.CIFAR10(
data_path, train=False, download=True)
第1个参数是下载位置,第2个参数表示是数据集还是测试集,第3个参数表示找不到数据是否要开始下载
因为数据量很小,我们可以直接将其存在内存中
DataSet类
数据集是作为torch.utils.data.DataSet
类的子类进行返回,所以我们有必要理解一下这个类
len()与__getitem__()
函数内包含有__len__()
和__getitem__()
两个方法,因此我们可以按照习惯量取其长度和对其进行标准索引对元组和列表索引
例子如下:
from matplotlib import pyplot as plt
print(len(cifar10))
img,label=cifar10[99]
print(label)
plt.imshow(img)
plt.show()
DataSet变换
因为需要一种方法将PIL图像变换为pytorch图像,然后才能对他进行操作,因此引入了torchvision.transforms
模块
如下:
from torchvision import transforms
to_tensor=transforms.ToTensor()
img_t=to_tensor(img)
print(img_t.shape)
正如我们所期望的那样,我们可以直接将变换作为参数传递给dataset.CIFAR10
,此时数据集将返回一个张量
tensor_cifar10 = datasets.CIFAR10(data_path, train=True, download=False,
transform=transforms.ToTensor())
变化非常方便,因为我们可以用transforms.Compose()
将它们连接起来,然后在数据加载器中直接透明的进行数据归一化和数据增强操作
对每一个通道进行归一化,使其具有相同的分布,可以保证在相同的学习率下,通过梯度下降时下通道信息的混合和更新
为了使每个通道的均值为0,标准差为1,我们可以应用以下转换来计算数据集中每个通道的平均值和标准差:
v
n
[
c
]
=
v
[
c
]
−
m
e
a
n
[
c
]
s
t
d
e
v
[
c
]
v_n[c]=\frac{v[c]-mean[c]}{stdev[c]}
vn[c]=stdev[c]v[c]−mean[c]
这正是transforms.Normalize()
做的
计算出mean平均值和std标准差后可利用Normalize函数
如下:
print(imgs.view(3,-1).mean(dim=1))
print(imgs.view(3,-1).std(dim=1))
transforms.Normalize(mean=(0.4915, 0.4823, 0.4468), std=(0.2470, 0.2435, 0.2616))
并将其连接到ToTensor变换,即可完成了预处理工作
transformed_cifar10 = datasets.CIFAR10(
data_path, train=True, download=False,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.4915, 0.4823, 0.4468),
(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.4915, 0.4823, 0.4468),
(0.2470, 0.2435, 0.2616))
]))
区分鸟和飞机
既然我们已经获得相应的数据集,我们可以对数据集进行稍加处理将鸟和飞机从中过滤出来.如下:
label_map = {0: 0, 2: 1}
class_names = ['airplane', 'bird']
cifar2 = [(img, label_map[label])
for img, label in cifar10
if label in [0, 2]]
cifar2_val = [(img, label_map[label])
for img, label in cifar10_val
if label in [0, 2]]
这样我们就获得了鸟和飞机相应的数据
我们再来运用我们上次学到的全连接模型构架一个粗糙的神经网络来处理鸟和飞机
那么每个样本有多少个特征呢,
32
×
32
×
3
=
3072
32\times 32\times 3=3072
32×32×3=3072共3072个特征,因此我们构建一个具有3072个输入,输出为2的神经网络,[1,0]代表着飞机,[0,1]代表着鸟
import torch.nn as nn
n_out = 2
model = nn.Sequential(
nn.Linear(
3072,
512,
),
nn.Tanh(),
nn.Linear(
512,
n_out,
)
)
另外,我们对于分类器的输出还需要做一点研究,如果按照上述的理想情况得到结果可能不符合我们的预期,我们希望第1项是飞机的概率,第2项是鸟的概率根据概率处理问题回对我们的网络添加一点额外的约束:
- 输出的每个元素都必须在[0.0,1.0]的范围内
- 输出元素的总和必须为1
这听起来是一个很难用可微的方式对数字向量进行限制的问题,但是有一个非常有用的函数可以做到这一点,叫做SoftMax,其表达式如下
S o f t M a x ( x 1 , x 2 ) = ( e x 1 e x 1 + e x 2 , e x 2 e x 1 + e x 2 ) SoftMax(x_1,x_2)=(\frac{e^{x_1}}{e^{x_1}+e^{x_2}},\frac{e^{x_2}}{e^{x_1}+e^{x_2}}) SoftMax(x1,x2)=(ex1+ex2ex1,ex1+ex2ex2)
S o f t M a x ( x 1 , x 2 , x 3 ) = ( e x 1 e x 1 + e x 2 + e x 3 , e x 2 e x 1 + e x 2 + e x 3 , e x 3 e x 1 + e x 2 + e x 3 ) SoftMax(x_1,x_2,x_3)=(\frac{e^{x_1}}{e^{x_1}+e^{x_2}+e^{x_3}},\frac{e^{x_2}}{e^{x_1}+e^{x_2}+e^{x_3}},\frac{e^{x_3}}{e^{x_1}+e^{x_2}+e^{x_3}}) SoftMax(x1,x2,x3)=(ex1+ex2+ex3ex1,ex1+ex2+ex3ex2,ex1+ex2+ex3ex3)
以此类推
SoftMax是一个单调函数,因为输入中的较小值对应输出中的较小值,但是他并不是比率不变的,因为值之间的比率没有被保留,现在我们在末尾加上一个SoftMax()
即可产生概率了
model = nn.Sequential(
nn.Linear(3072, 512),
nn.Tanh(),
nn.Linear(512, 2),
nn.Softmax(dim=1))
为了调用模型,我们需要使输入具有正确的维度,因此需要调用unsqueeze()
此外我们可以用torch.max()
获得该维度上的最大元素以及该值出现的索引来获得最终的结果
img_batch=img.view(-1).unsqueeze(0)
out=model(img_batch)
print(out)
_,index=torch.max(out,dim=1)
print(index)
既然我们有了输出,所以接下来就是计算损失函数了,我们仍然可以用MSE,但是其实我们并不是对概率本身感兴趣,而是对概率的正确性感兴趣,MSE不太符合.
NLL(Negative Log Likelihood)负对数似然损失函数,当预测目标类别比概率较低时,NLL会增长到无穷大,而当预测目标概率大于0.5时,NLL下降速度非常慢
N
L
L
=
−
s
u
m
(
l
o
g
(
o
u
t
i
[
c
i
]
)
)
NLL=-sum(log(out_i[c_i]))
NLL=−sum(log(outi[ci]))
c
i
c_i
ci是样本的目标类别
但对概率取对数是一个相当麻烦的事情,所以我们将SoftMax函数改编成LogSoftMax,然后再利用NLL作为损失函数,torch里的NLL不会对输入进行对数运算,所以我们要提前做对数运算
model = nn.Sequential(
nn.Linear(3072, 512),
nn.Tanh(),
nn.Linear(512, 2),
nn.LogSoftmax(dim=1))
loss = nn.NLLLoss()
img, label = cifar2[0]
out = model(img.view(-1).unsqueeze(0))
loss(out, torch.tensor([label]))
好的,我们可以开始写训练循环了
import torch
import torch.nn as nn
import torch.optim as optim
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):
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()
print("Epoch: %d, Loss: %f" % (epoch, float(loss)))
这个模型的训练过程太有规律了,我们希望打乱下数据,torch.utils.data
模块中有一个DataLoader
类,有助于打乱和组织数据,帮助我们选取小批量,它可以被迭代
batch_size
选取输出的大小,shuffle
表示每一次是否要被打乱
train_loader = torch.utils.data.DataLoader(cifar2, batch_size=64,
shuffle=True)
由此修改后的代码为
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, 128),
nn.Tanh(),
nn.Linear(128, 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):
for imgs, labels in train_loader:
outputs = model(imgs.view(imgs.shape[0], -1))
loss = loss_fn(outputs, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print("Epoch: %d, Loss: %f" % (epoch, float(loss)))
但我们不知道是否这可以达到我们的目标,因此再写一个测评程序
训练集测试:
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:
outputs = model(imgs.view(imgs.shape[0], -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:
outputs = model(imgs.view(imgs.shape[0], -1))
_, predicted = torch.max(outputs, dim=1)
total += labels.shape[0]
correct += int((predicted == labels).sum())
print("Accuracy: %f" % (correct / total))
实际上nn.LogSoftMax()
和nn.NLLLoss()
的组合相当于nn.CrossEntropyLoss
,这两个基本一样,只不过后者没有显式的理解意义
完整代码
from torchvision import datasets, transforms
data_path = 'data/'
cifar10 = datasets.CIFAR10(
data_path, train=True, download=False,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.4915, 0.4823, 0.4468),
(0.2470, 0.2435, 0.2616))
]))
cifar10_val = datasets.CIFAR10(
data_path, train=False, download=False,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.4915, 0.4823, 0.4468),
(0.2470, 0.2435, 0.2616))
]))
label_map = {0: 0, 2: 1}
class_names = ['airplane', 'bird']
cifar2 = [(img, label_map[label])
for img, label in cifar10
if label in [0, 2]]
cifar2_val = [(img, label_map[label])
for img, label in cifar10_val
if label in [0, 2]]
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):
for imgs, labels in train_loader:
outputs = model(imgs.view(imgs.shape[0], -1))
loss = loss_fn(outputs, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print("Epoch: %d, Loss: %f" % (epoch, float(loss)))
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:
outputs = model(imgs.view(imgs.shape[0], -1))
_, predicted = torch.max(outputs, dim=1)
total += labels.shape[0]
correct += int((predicted == labels).sum())
print("Train 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:
outputs = model(imgs.view(imgs.shape[0], -1))
_, predicted = torch.max(outputs, dim=1)
total += labels.shape[0]
correct += int((predicted == labels).sum())
print("Val Accuracy: %f" % (correct / total))