類神經網路應用在圖形辨識的時候,往往需要大量的資料來進行訓練;一方面,訓練完成所花的時間相當長;另一方面,也常常會在已訓練完成之後,再使用新的資料再「加強」訓練,用來擴展或改善類神經網路的辨識能力;因此,經常會將圖形資料「分批」來進行訓練。在圖形的辨識訓練教學案例上,紐約大學的手寫數字辨資料庫「MNIST」經常作為訓練的開放資料;透過「torchvision」也可以使用這個資料庫,作為訓練文字辨識類神經精神網路的使用。
「MNIST」問題提供了7,0000 個手寫字元的灰階圖形資料,每個圖形由「28*28」的像素所組成;其中60,000 為訓練資料而10,000組則為測試資料;在這裡,我們將所有的圖形共分成 10 組字元目標,分別由「0」到「9」。可以先透過「torchvision」下載資料後,將前10個圖形資料及對映的字元列出。
import torchvision
from torchvision import datasets
from torchvision.transforms import ToTensor
train_data=datasets.MNIST(root="mnistdata",train=True,download=True,
transform=ToTensor(),target_transform=None)
test_data = torchvision.datasets.MNIST(root = 'mnistdata', train = False,download=True,
transform=ToTensor())
print(train_data.train_data.size())
print(test_data.train_data.size())
#-----------
import matplotlib.pyplot as plt
fig=plt.figure(figsize=(9,9))
rows,cols=1,10
for i in range(rows*cols):
fig.add_subplot(rows,cols,i+1)
plt.imshow(train_data.train_data[i].numpy(),cmap='gray')
plt.title('%i' % train_data.train_labels[i])
plt.axis(False)
plt.show()
「MNIST」的資料可以使用多種方式來建立辨識機器,反饋類神經網路是其中之一。所以可以使用之前相同叢集類神經網路的方法來定義辨識模型;在這裡,參考紐約大學的作法,將中間層的節點數放大到 800。
import torch
from torch import nn
#-----------------------
# creat neural network class
class classNeural(nn.Module):
def __init__(self,n_input,n_hidden,n_output):
super().__init__()
self.n_input=n_input
self.n_hidden=n_hidden
self.n_output=n_output
#--------
self.layer1=nn.Linear(n_input,n_hidden)
self.layer2=nn.Linear(n_hidden,n_output)
self.active=nn.Sigmoid()
#--------
def forward(self,x):
x=self.active(self.layer1(x))
return self.layer2(x)
#------------
# set clustering neural networks
cluster_neurals=[]
for i in range(nType):
torch.manual_seed(13)
cluster_neurals.append(classNeural(28*28,800,1))
因為大量圖形資料的處理往往需要耗費相當多的時間。為了避免在執行過程當中,終端螢幕或 iPad 上沒有任何反應,不知道執行進度而造成人為錯誤;因此,我會安裝一個好用的進度指標小工具「tqdm」來防呆;「tqdm」指的是「taqaddum」也就是阿拉伯語的「進度」的意思,它是一個相當好用的小工具可以使用在觀察程式執行進度。 我們可以在終端機環境上先安裝這個程式庫, 方便在程式執行的時候觀察訓練的進度.
pip install tqdm
由於「MNIST」提供的圖形資料量相當地大,為了增加類神經網路調整權重值的速度,同時進一步加速訓練的收歛;在訓練時,我們可以不必一開始就全部作為訓練之用;可以將資料進行「分批」,再進行訓練。PyTorch 提供了「DataLoader」類別方法來協助訓練資料的分批作業;我們將所有的訓練資料每100筆作為一批。
from torch.utils.data import DataLoader
#-------------
trainDataLoader = DataLoader(train_data.data,batch_size=100,shuffle=False)
trainLabelLoader = DataLoader(train_data.targets,batch_size=100,shuffle=False)
#------------
batchTraindata=iter(trainDataLoader)
batchLabel=iter(trainLabelLoader)
#------------
nType=len(train_data.classes)
nBatch=len(batchTraindata)
print('batch count:', nBatch)
叢集類神經網路訓練的方法與之前的文章相同;所不同的是採用不過分批訓練作法,將每一批 (100筆資料) 先進行訓練,之後再換下一批資料來訓練;為了避免類神經網路過於「僵固」而不易訓練,每一批的訓練疊代次數會大量減少;在這裏我們每批訓練的疊代次數僅使用 5 次;再透過多批的資料訓練來趨近訓練目標。
from tqdm import tqdm
device=torch.device('cpu')
if(torch.cuda.is_available()):
device=torch.device('cuda')
#—————————————
nBatch=200
nEpoche=5
for ptBatch in tqdm(range(nBatch)):
Labels=next(batchLabel)
Images=next(batchTraindata)
#------------
Pixels=Images.flatten(start_dim=1,end_dim=2)
nData,nPixel=Pixels.shape
X_train=((Pixels-255/2.0)/(255)).to(device)
# set clustering targets
cluster_y_train=np.zeros((nType,nData,1))
for i in range(nData):
cluster_y_train[Labels[i],i,0]=1.0
cluster_Y_train=torch.tensor(cluster_y_train.astype('float32')).to(device)
#-------------
# training clustering neural network
for ptType in range(nType):
Y_train=cluster_Y_train[ptType]
imgNeural=cluster_neurals[ptType]
imgNeural.to(device)
optimizer=torch.optim.AdamW(imgNeural.parameters(),lr=0.01)
loss_fn=nn.MSELoss() # MSE
for epoche in range(nEpoche):
imgNeural.train()
Y_pred=imgNeural(X_train)
loss=loss_fn(Y_pred,Y_train)
optimizer.zero_grad()
loss.backward()
optimizer.step()
imgNeural.to('cpu')
cluster_neurals[ptType].load_state_dict(imgNeural.state_dict())
訓練完之後,可以同樣地使用前10 組圖形資料來看看訓練的結果。
def clusterPrediction(cluster_neurals,PixelInput,device):
nData,nPixel=PixelInput.shape
y_pred=np.zeros(nData).astype('int')
for ptType in range(nType):
imgNeural=cluster_neurals[ptType]
imgNeural.to(device).eval()
with torch.inference_mode():
Y_output=imgNeural(PixelInput)
y_output=np.round(Y_output.squeeze(dim=1).to('cpu').numpy()).astype('int')
ix=np.where(y_output==1)
y_pred[ix]=ptType
return y_pred
#———————
import random
fig=plt.figure(figsize=(9,9))
rows,cols=1,10
for ix in range(rows*cols):
#---------- handle input tensor
ImgInput=(train_data.data[ix]-255/2.0)/255
PixelInput=ImgInput.flatten().unsqueeze(dim=0).to(device)
#---------- neural network prediction
labelPred=clusterPrediction(cluster_neurals,PixelInput,device)
#---------- transfer output into label text
titlePred=train_data.classes[int(labelPred)]
titleTarget=train_data.classes[test_data.targets[ix]]
#---------- plot
fig.add_subplot(rows,cols,ix+1)
plt.imshow(ImgInput.to('cpu'),cmap='gray')
plt.title(titlePred[0])
plt.axis(False)
plt.show()
同樣地,也可以使用隨機的測試資料來觀察訓練的結果。
fig=plt.figure(figsize=(9,9))
rows,cols=1,10
random.seed(12)
for i in range(rows*cols):
ix=random.randint(0,500)
#---------- handle input tensor
ImgInput=(test_data.data[ix]-255/2.0)/255
PixelInput=ImgInput.flatten().unsqueeze(dim=0).to(device)
#---------- neural network prediction
labelPred=clusterPrediction(cluster_neurals,PixelInput,device)
#---------- transfer output into label text
titlePred=test_data.classes[int(labelPred)]
titleTarget=test_data.classes[test_data.targets[ix]]
#---------- plot
fig.add_subplot(rows,cols,i+1)
plt.imshow(ImgInput.to('cpu'),cmap='gray')
plt.title(titlePred[0])
plt.axis(False)
plt.show()
手寫圖形資料有相當程度的主觀性及不確定性;在實際應用上,車牌辨識或印刷體文字的圖形辨識相對一致而更容易執行,準確性也更高。另一方面,由於圖形辨識在實際應用時,會有不斷出現新的圖形資料;透過「分批」訓練的手法,可以在不需要重新訓練的前提下,不斷地更新類神經網路的辨識,達到「不斷學習」的效果。