PyTorch學習之 圖像分類器
學習網站
http://pytorch123.com/SecondSection/neural_networks/
訓練一個圖像分類器
通過前面的章節,我們已經知道怎樣定義一個神經網絡,以及計算其損失函數,並且更新網絡的權重
現在,我們將要學習怎樣去處理數據。
一般來說,當處理圖像,文本,音頻,視頻這些數據時,可以使用標準的python包來下載這些數據,
並轉換成numpy數組格式。然後,將這些數組轉換成 “torch.Tensor”格式
- 對於圖像,可以使用Pillow,OpenCV包
- 對於音頻,可以使用 scipy, librosa包
- 對於文本,可以使用Python或者Cyphon直接加載,或者使用NLTK和SpaCy
對於視覺,PytorCh中創建了一個“torchvision”包,裏面包含一些常見的數據集,例如
Imagenet, CIFAR10, MNIST等,以及一些圖像轉換模塊:torchvision.datasets, torch.utils.data.DataLoader
下面會使用CIFAR10數據集作爲例子,進行圖像分類:
CIFAR10: ‘airplane’, ‘automobile’, ‘bird’, ‘cat’, ‘deer’,‘dog’, ‘frog’, ‘horse’, ‘ship’, ‘truck’.
尺寸:3*32*32
圖像分類一般分爲以下5個步驟
- 使用torchvision加載並且歸一化CIFAR10的訓練和測試數據集
- 定義一個卷積神經網絡
- 定義一個損失函數
- 在訓練樣本數據上訓練網絡
- 在測試樣本數據上測試網絡
1. 下載並歸一化CIFAR10數據集
import torch
import torchvision
import torchvision.transforms as transforms
########################################################################
# torchvision加載的數據都是PILImage的數據類型,在[0, 1]之間
# 對上述類型的數據集進行歸一化爲[-1, 1]範圍的tensors
# 歸一化方法: (X-mean)/std
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) # mean, std
# 檢驗是否已經存在,若不存在,則下載數據集
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform)
# 數據加載器,結合了數據集和取樣器,並且可以提供多個線程處理數據集。
# 在訓練模型時使用到此函數,用來把訓練數據分成多個小組,此函數每次拋出一組數據。
# 直至把所有的數據都拋出。就是做一個數據的初始化。
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,
shuffle=True, num_workers=0)
testset = torchvision.datasets.CIFAR10(root='./data', train=False,
download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4,
shuffle=False, num_workers=0)
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
顯示數據中的一些圖像
########################################################################
# 顯示數據集中的一些圖像
import matplotlib.pyplot as plt
import numpy as np
def imshow(img):
img = img / 2 + 0.5 # unnormalize, 因爲前面是將圖像進行了歸一化,即 x = (X-0.5)/0.5
npimg = img.numpy()
image = np.transpose(npimg, (1, 2, 0))
plt.imshow(image) # 1 是和第二個軸交換,2,是和第2個軸交換,0是和第一個軸交換image[Height, Width, Dim]
plt.show()
# get some random training images
dataiter = iter(trainloader) # 使得 trainloader 變成迭代器
images, labels = dataiter.next()
# show images
imshow(torchvision.utils.make_grid(images)) # 將若干圖像拼成一幅圖像
# print labels
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
2. 定義一個卷積神經網絡
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5) # 輸出 6*28*28
self.pool = nn.MaxPool2d(2, 2) # 6*14*14
self.conv2 = nn.Conv2d(6, 16, 5) # 16*10*10
self.fc1 = nn.Linear(16 * 5 * 5, 120) # conv2經過 pooling 後,變成 5*5 map, 所以 16*5*5個全連接神經元
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
x = self.pool(F.relu(self.conv1(x))) # 卷積 -> Relu -> Pool
x = self.pool(F.relu(self.conv2(x)))
x = x.view(-1, 16 * 5 * 5) # view函數將張量x變形成一維的向量形式,作爲全連接的輸入
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
net = Net()
3. 定義損失函數與優化器
import torch.optim as optim
criterion = nn.CrossEntropyLoss() # 損失函數
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9) # 優化器 SGD with momentum
4. 訓練網絡
for epoch in range(2): # 訓練集訓練次數
running_loss = 0.0
# enumerate()用於可迭代\可遍歷的數據對象組合爲一個索引序列,
# 同時列出數據和數據下標.上面代碼的0表示從索引從0開始,
for i, data in enumerate(trainloader, 0):
# 獲得輸入
inputs, labels = data
# 初始化參數梯度
optimizer.zero_grad()
# 前饋 + 後饋 + 優化
outputs = net(inputs)
loss = criterion(outputs, labels) # labels 會進行二值化,即[1 0 0 0 0 0 0 0 0]
loss.backward() # 梯度反向傳播
optimizer.step() # 更新參數空間
# print statistics
running_loss += loss.item()
if i % 2000 == 1999: # print every 2000 mini-batches
print('[%d, %5d] loss: %.3f' %
(epoch + 1, i + 1, running_loss / 2000))
running_loss = 0.0
print('Finished Training')
5. 在數據集上測試網絡結構
上面已經在訓練集上進行了2次完整的訓練循環,但是我們需要檢查網絡是否真正的學到了一些什麼東西。測試的方式是,將網絡輸出的結果與數據集的ground-truth進行對比.
- 首先,顯示一些圖像
dataiter = iter(testloader)
images, labels = dataiter.next()
# 輸出圖像
imshow(torchvision.utils.make_grid(images))
print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))
print :
GroundTruth: cat ship ship plane
- 查看網絡輸出結果
outputs = net(images)
outputs
tensor([[-1.3145, -2.4341, -0.7362, 6.8300, 0.5993, 2.2841, -0.9894, -0.9424,
1.3211, -3.0649],
[ 4.2055, 8.5567, -2.8397, -2.3198, -3.1733, -4.6069, -8.4125, -2.9534,
10.5395, 5.7375],
[ 1.3612, 1.1350, 0.3872, -0.3729, -0.1908, -1.1665, -3.7862, -0.3712,
3.3340, -0.1305],
outputs 是10種類別分別預測出來的能量值,即某一類的能量值越高,其被認爲是該種類的概率越大。因此,我們需要獲得outputs中類被能量的最大值所對應的種類。
_, predicted = torch.max(outputs, 1) # predicted 對應的種類
print('Predicted: ', ' '.join('%5s' % classes[predicted[j]]
for j in range(4)))
Predicted: cat ship ship ship
tensor([3, 8, 8, 8])
從上面的輸出結果看,檢測結果似乎還是不錯的。
下面,我們看一下訓練的網絡在整個數據集上的表現。
correct = 0 #預測正確的數據
total = 0 #總共的數據
with torch.no_grad(): # 因爲是進行測試,所以不需要進行梯度傳播
for data in testloader:
images, labels = data
outputs = net(images) #輸出結果
_, predicted = torch.max(outputs.data, 1) #選擇數值最大的一類作爲其預測結果
total += labels.size(0)
correct += (predicted == labels).sum().item() # 預測值與標籤相同則預測正確
print('Accuracy of the network on the 10000 test images: %d %%' % (
100 * correct / total))
Accuracy of the network on the 10000 test images: 55 %
因爲隨機預測的概率是10%(10類中預測一類),所以55%看起來要比隨機預測好很多,似乎學到了一些東西。
下面,我們來進一步看一下,哪些類的預測比較好一些,哪些表現的不好。
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
with torch.no_grad():
for data in testloader:
images, labels = data
outputs = net(images)
_, predicted = torch.max(outputs, 1)
c = (predicted == labels).squeeze() # 將shape中爲1的維度去掉
for i in range(4):
label = labels[i]
class_correct[label] += c[i].item() # 正確預測累計
class_total[label] += 1 # 每一類的總數
for i in range(10):
print('Accuracy of %5s : %2d %%' % (
classes[i], 100 * class_correct[i] / class_total[i])) # 每一類的準確率
Accuracy of plane : 52 %
Accuracy of car : 70 %
Accuracy of bird : 44 %
Accuracy of cat : 28 %
Accuracy of deer : 54 %
Accuracy of dog : 41 %
Accuracy of frog : 66 %
Accuracy of horse : 60 %
Accuracy of ship : 65 %
Accuracy of truck : 68 %
6. 在GPU上訓練數據
在GPU上訓練神經網絡,就像將一個Tensor轉移到GPU上一樣。
首先來定義我們的device作爲第一個可見的cuda device。
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# 如果程序運行在CUDA機器上,下面會輸出一個device的id
print(device)
cuda:0
設定好之後,這些方法就會遞歸的遍歷所有模塊,並把他們的參數和buffers轉換爲cuda的tensors
下面的語句是必不可少的:
net.to(device)
同時,也必須每一個步驟都要向gpu中發送inputs和targets.
inputs, labels = inputs.to(device), labels.to(device)
當網絡非常小的時候,感覺不帶速度的變化,可以把卷積1的輸出改爲128,卷積2的輸入改爲128,觀察效果。改爲128後,訓練2次後的準確率爲
Accuracy of the network on the 10000 test images: 60 %
更多的特徵圖的結果似乎要比6個特徵圖要好一些,下面是每個類的輸出結果
Accuracy of plane : 73 %
Accuracy of car : 82 %
Accuracy of bird : 27 %
Accuracy of cat : 32 %
Accuracy of deer : 53 %
Accuracy of dog : 55 %
Accuracy of frog : 75 %
Accuracy of horse : 76 %
Accuracy of ship : 70 %
Accuracy of truck : 54 %
備註:
改成GPU運行方式後會出現如下報錯:
TypeError: can't convert CUDA tensor to numpy. Use Tensor.cpu() to copy the tensor to host memory first.
解決方法:
將 npimg = img.numpy() 改爲
npimg = img.cpu().numpy()