MNIST 這里就不多展開了,我們上幾期的文章都是使用此數據集進行的分享。
手寫字母識別
EMNIST數據集
Extended MNIST (EMNIST), 因為 MNIST 被大家熟知,所以這里就推出了 EMNIST ,一個在手寫字體分類任務中更有挑戰的 Benchmark 。此數據集當然也包含了手寫數字的數據集
在數據集接口上,此數據集的處理方式跟 MNIST 保持一致,也是為了方便已經熟悉 MNIST 的我們去使用,這里著重介紹一下 EMNIST 的分類方式。
分類方式
EMNIST 主要分為以下 6 類:
By_Class : 共 814255 張,62 類,與 NIST 相比重新劃分類訓練集與測試機的圖片數
By_Merge: 共 814255 張,47 類, 與 NIST 相比重新劃分類訓練集與測試機的圖片數
Balanced : 共 131600 張,47 類, 每一類都包含了相同的數據,每一類訓練集 2400 張,測試集 400 張
Digits :共 28000 張,10 類,每一類包含相同數量數據,每一類訓練集 24000 張,測試集 4000 張
Letters : 共 103600 張,37 類,每一類包含相同數據,每一類訓練集 2400 張,測試集 400 張
MNIST : 共 70000 張,10 類,每一類包含相同數量數據(注:這里雖然數目和分類都一樣,但是圖片的處理方式不一樣,EMNIST 的 MNIST 子集數字占的比重更大)
這里為什么后面的分類不是26+26?其主要原因是一些大小寫字母比較類似的字母就合并了,比如C等等
代碼實現手寫字母訓練神經網絡
由于EMNIST數據集與MNIST類似,我們直接使用MNIST的訓練代碼進行此神經網絡的訓練
import torch
import torch.nn as nn
import torch.utils.data as Data
import torchvision # 數據庫模塊
import matplotlib.pyplot as plt
# torch.manual_seed(1) # reproducible
EPOCH = 1 # 訓練整批數據次數,訓練次數越多,精度越高,為了演示,我們訓練5次
BATCH_SIZE = 50 # 每次訓練的數據集個數
LR = 0.001 # 學習效率
DOWNLOAD_MNIST = Ture # 如果你已經下載好了EMNIST數據就設置 False
# EMNIST 手寫字母 訓練集
train_data = torchvision.datasets.EMNIST(
root='./data',
train=True,
transform=torchvision.transforms.ToTensor(),
download = DOWNLOAD_MNIST,
split = 'letters'
)
# EMNIST 手寫字母 測試集
test_data = torchvision.datasets.EMNIST(
root='./data',
train=False,
transform=torchvision.transforms.ToTensor(),
download=False,
split = 'letters'
)
# 批訓練 50samples, 1 channel, 28x28 (50, 1, 28, 28)
train_loader = Data.DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)
# 每一步 loader 釋放50個數據用來學習
# 為了演示, 我們測試時提取2000個數據先
# shape from (2000, 28, 28) to (2000, 1, 28, 28), value in range(0,1)
test_x = torch.unsqueeze(test_data.data, dim=1).type(torch.FloatTensor)[:2000] / 255.
test_y = test_data.targets[:2000]
#test_x = test_x.cuda() # 若有cuda環境,取消注釋
#test_y = test_y.cuda() # 若有cuda環境,取消注釋
首先,我們下載EMNIST數據集,這里由于我們分享過手寫數字的部分,這里我們按照手寫字母的部分進行神經網絡的訓練,其split為letters,這里跟MNIST數據集不一樣的地方便是多了一個split標簽,備注我們需要那個分類的數據
torchvision.datasets.EMNIST(root: str, split: str, **kwargs: Any)
root ( string ) –
數據集所在EMNIST/processed/training.pt 和 EMNIST/processed/test.pt存在的根目錄。
split(字符串)
-該數據集具有6個不同的拆分:byclass,bymerge, balanced,letters,digits和mnist。此參數指定使用哪一個。
train ( bool , optional )
– 如果為 True,則從 中創建數據集training.pt,否則從test.pt.
download ( bool , optional )
– 如果為 true,則從 Internet 下載數據集并將其放在根目錄中。如果數據集已經下載,則不會再次下載。
transform ( callable , optional )
– 一個函數/轉換,它接收一個 PIL 圖像并返回一個轉換后的版本。例如,transforms.RandomCrop
target_transform ( callable , optional )
– 一個接收目標并對其進行轉換的函數/轉換。
可視化數據集
然后我們可視化一下此數據集,看看此數據集什么樣子
def get_mApping(num, with_type='letters'):
"""
根據 mapping,由傳入的 num 計算 UTF8 字符
"""
if with_type == 'byclass':
if num <= 9:
return chr(num + 48) # 數字
elif num <= 35:
return chr(num + 55) # 大寫字母
else:
return chr(num + 61) # 小寫字母
elif with_type == 'letters':
return chr(num + 64) + " / " + chr(num + 96) # 大寫/小寫字母
elif with_type == 'digits':
return chr(num + 96)
else:
return num
figure = plt.figure(figsize=(8, 8))
cols, rows = 3, 3
for i in range(1, cols * rows + 1):
sample_idx = torch.randint(len(train_data), size=(1,)).item()
img, label = train_data[sample_idx]
print(label)
figure.add_subplot(rows, cols, i)
plt.title(get_mapping(label))
plt.axis("off")
plt.imshow(img.squeeze(), cmap="gray")
plt.show()
字符編碼表(UTF-8)
字符編碼表
首先我們建立一個字符編碼表的規則,這是由于此數據集的label,是反饋一個字母的字符編碼,我們只有通過此字符編碼表才能對應起來神經網絡識別的是那個字母
通過觀察其數據集,此字母總是感覺怪,這是由于EMNIST數據集左右翻轉圖片后,又進行了圖片的逆時針旋轉90度,小編認為是進行圖片數據的特征增強,目前沒有一個文件來介紹為什么這樣做,大家可以討論
搭建神經網絡
神經網絡的搭建,我們直接使用MNIST神經網絡
# 定義神經網絡
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
self.conv1 = nn.Sequential( # input shape (1, 28, 28)
nn.Conv2d(
in_channels=1, # 輸入通道數
out_channels=16, # 輸出通道數
kernel_size=5, # 卷積核大小
stride=1, #卷積步數
padding=2, # 如果想要 con2d 出來的圖片長寬沒有變化,
# padding=(kernel_size-1)/2 當 stride=1
), # output shape (16, 28, 28)
nn.ReLU(), # activation
nn.MaxPool2d(kernel_size=2), # 在 2x2 空間里向下采樣, output shape (16, 14, 14)
)
self.conv2 = nn.Sequential( # input shape (16, 14, 14)
nn.Conv2d(16, 32, 5, 1, 2), # output shape (32, 14, 14)
nn.ReLU(), # activation
nn.MaxPool2d(2), # output shape (32, 7, 7)
)
self.out = nn.Linear(32 * 7 * 7, 37) # 全連接層,A/Z,a/z一共37個類
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
x = x.view(x.size(0), -1) # 展平多維的卷積圖成 (batch_size, 32 * 7 * 7)
output = self.out(x)
return output
以上代碼在介紹手寫數字時有詳細的介紹,大家可以參考文章開頭的文章鏈接,這里不再詳細介紹了,這里需要注意的是神經網絡有37個分類
神經網絡的訓練
cnn = CNN() # 創建CNN
# cnn = cnn.cuda() # 若有cuda環境,取消注釋
optimizer = torch.optim.Adam(cnn.parameters(), lr=LR) # optimize all cnn parameters
loss_func = nn.CrossEntropyLoss() # the target label is not one-hotted
for epoch in range(EPOCH):
for step, (b_x, b_y) in enumerate(train_loader): # 每一步 loader 釋放50個數據用來學習
#b_x = b_x.cuda() # 若有cuda環境,取消注釋
#b_y = b_y.cuda() # 若有cuda環境,取消注釋
output = cnn(b_x) # 輸入一張圖片進行神經網絡訓練
loss = loss_func(output, b_y) # 計算神經網絡的預測值與實際的誤差
optimizer.zero_grad() #將所有優化的torch.Tensors的梯度設置為零
loss.backward() # 反向傳播的梯度計算
optimizer.step() # 執行單個優化步驟
if step % 50 == 0: # 我們每50步來查看一下神經網絡訓練的結果
test_output = cnn(test_x)
pred_y = torch.max(test_output, 1)[1].data.squeeze()
# 若有cuda環境,使用84行,注釋82行
# pred_y = torch.max(test_output, 1)[1].cuda().data.squeeze()
accuracy = float((pred_y == test_y).sum()) / float(test_y.size(0))
print('Epoch: ', epoch, '| train loss: %.4f' % loss.data,
'| test accuracy: %.2f' % accuracy)
神經網絡的訓練部分也是往期代碼,這里建議大家訓練的EPOCH設置的大點,畢竟37個分類若只是訓練一兩次,其精度也很難保證
測試神經網絡與保存模型
# test 神經網絡
test_output = cnn(test_x[:10])
pred_y = torch.max(test_output, 1)[1].data.squeeze()
# 若有cuda環境,使用92行,注釋90行
#pred_y = torch.max(test_output, 1)[1].cuda().data.squeeze()
print(pred_y, 'prediction number')
print(test_y[:10], 'real number')
# save CNN
# 僅保存CNN參數,速度較快
torch.save(cnn.state_dict(), './model/CNN_letter.pk')
# 保存CNN整個結構
#torch.save(cnn(), './model/CNN.pkl')
訓練完成后,我們可以檢測一下神經網絡的訓練結果,這里需要注意的是,pred_y與test_y只是字母的編碼,并不是真實的字母,最后我們保存一下神經網絡的模型,方便下期進行手寫字母的識別篇