Mnist手写数字识别
概要
使用torch的相关类实现神经网络各个部分,对手写数字数据集mnist_train_small.csv(需要可以私信qq1365157644发)进行手写数字识别训练。
具体内容
读取数据 生成对应的DataLoader
使用pandas读取数据集
mnist = pd.read_csv("sample_data/mnist_train_small.csv", header = None)
这个数据集总共20000张(1x28x28)的手写数字图片,在这个csv文件中,第一列表示的是各个样本的标签(0-9)其他784列是每个像素点的值(0-255)
可以通过matplootlib展示图片,这里展示的是第一个样本,从上图也可以看出第一个样本的标签为6。
plt.imshow(mnist.iloc[0,1:].values.reshape(28,28))
plt.title(mnist.iloc[0,0])
通过实现torch.utils.data.Dataset抽象类,为mnist生成专属的dataset
class Mnist(Dataset):
def __init__(self, mnist):
self.mnist = mnist
def __len__(self):
return self.mnist.shape[0]
def __getitem__(self,idx):
img = self.mnist.iloc[idx,1:].values.astype(np.float32)
label = self.mnist.iloc[idx,0]
return img, label
这里重写了__len__,__getitem__方法,第一个方法返回一共多少个样本,第二样本就是通过idx依次返回每个样本的feature和label。这里要注意:当为其生成dataloader时,这里的numpy类型的数据也就变成了tensor类型,在tensor后面的计算中需要的数据类型是torch.float(float32),在numpy中float数据类型有三种表示方法float,float32,float64(这只是我知道的)其中第一种和第三种在转换成tensor类型后会变成double。
m_train = Mnist(mnist.iloc[:15000,:])
m_test = Mnist(mnist.iloc[15000:,:])
mnistTrain = DataLoader(m_train, batch_size = 100, num_workers=2)
mnistTest = DataLoader(m_test, batch_size = 100, num_workers=2)
这里选取了前面3/4的数据作为你测试集,后面1/4作为测试集,DataLoader其中的主要参数,可以参考dataloader参数。
网络结构
class MnistNet(nn.Module):
def __init__(self):
super(MnistNet,self).__init__()
self.fc1 = nn.Linear(784, 50)
self.fc2 = nn.Linear(50, 10)
def forward(self,input):
x = self.fc1(input)
x = F.relu(x)
out = self.fc2(x)
return F.log_softmax(out, dim = 1)
需要继承nn.Module类,重写forward类,这里使用了ANN,第一层784x50,输出层50x10(因为有10各类别)。最后这个log_softmax,由于要实现多分类,这里的log_softmax就是先进行了普通的softmax操作,将10个输出映射到0-1,并且10个输出加起来等于1,这样就可以用概率很好的解释这个样本的预测标签(自己感觉),在将这10个结果取对数(因为方便后面直接使用nll_loss直接求损失)这里也可以直接返回结果不用log_softmax,后面选择损失函数时直接使用交叉熵损失(CrossEntropyLoss)也能达到一样的效果。
优化器
optimizer = Adam(model.parameters(), lr = 0.001)
优化器也是使用torch.optim现成的Adam(还没学)
损失函数
交叉熵损失,由于需要实现多分类不能直接使用sigmoid函数(它输出一个0-1的概率值反映而分类中正类的概率)。(还没学)
训练模型
def train(epoch):
model.train()
for idx, (input, target) in enumerate(mnistTrain):
optimizer.zero_grad()
output = model(input)
loss = F.nll_loss(output, target)
loss.backward()
optimizer.step()
print('Train Epoch: {} [{}/{} ({:.0f}%)]\t\tLoss: {:.6f}'.format((epoch+1), (idx+1)*len(input), len(mnistTrain.dataset), 100.*(idx+1)/len(mnistTrain), loss.item()))
训练模型时要注意需要每次将梯度置0,不然它会叠加,调用backward计算梯度,optimizer.step根据梯度优化参数。
评估模型
def test():
model.eval()
loss_list = []
acc_list = []
for idx, (input, target) in enumerate(mnistTest):
with torch.no_grad():
output = model(input)
cur_loss = F.nll_loss(output, target)
loss_list.append(cur_loss)
pred = output.max(dim = 1)[-1]
cur_acc = pred.eq(target).float().mean()
acc_list.append(cur_acc)
print("====================\naccuracy:{} loss:{}".format(np.mean(acc_list), np.mean(loss_list)))
评估模型不需要优化参数,所以优化器和backward方法都不需要调用,这里要算准确率,通过output找出最大值的位置(tensor.max()返回的就是最大值和最大值的位置),这个位置就是这次预测的预测标签再用eq方法与target对比,查看一个batch中有多少预测准确的(返回的是[True,Flase……])通过float转成0,1再用mean方法算平均值。
整体代码
import torch
import numpy as np
import pandas as pd
from torch.utils.data import Dataset
from torch.utils.data import DataLoader
from torch.nn import Linear
import torch.nn as nn
import torch.nn.functional as F
from torch.optim import Adam
mnist = pd.read_csv("sample_data/mnist_train_small.csv", header=None)
class Mnist(Dataset):
def __init__(self, mnist):
self.mnist = mnist
def __len__(self):
return self.mnist.shape[0]
def __getitem__(self,idx):
img = self.mnist.iloc[idx,1:].values.astype(np.float32)
label = self.mnist.iloc[idx,0]
return img, label
m_train = Mnist(mnist.iloc[:15000,:])
m_test = Mnist(mnist.iloc[15000:,:])
mnistTrain = DataLoader(m_train, batch_size = 100, num_workers=2)
mnistTest = DataLoader(m_test, batch_size = 100, num_workers=2)
class MnistNet(nn.Module):
def __init__(self):
super(MnistNet,self).__init__()
self.fc1 = nn.Linear(784, 50)
self.fc2 = nn.Linear(50, 10)
def forward(self,input):
x = self.fc1(input)
x = F.relu(x)
out = self.fc2(x)
return F.log_softmax(out, dim = 1)
model = MnistNet()
optimizer = Adam(model.parameters(), lr = 0.001)
def train(epoch):
model.train()
for idx, (input, target) in enumerate(mnistTrain):
optimizer.zero_grad()
output = model(input)
loss = F.nll_loss(output, target)
loss.backward()
optimizer.step()
print('Train Epoch: {} [{}/{} ({:.0f}%)]\t\tLoss: {:.6f}'.format((epoch+1), (idx+1)*len(input), len(mnistTrain.dataset), 100.*(idx+1)/len(mnistTrain), loss.item()))
def test():
model.eval()
loss_list = []
acc_list = []
for idx, (input, target) in enumerate(mnistTest):
with torch.no_grad():
output = model(input)
cur_loss = F.nll_loss(output, target)
loss_list.append(cur_loss)
pred = output.max(dim = 1)[-1]
cur_acc = pred.eq(target).float().mean()
acc_list.append(cur_acc)
print("====================\naccuracy:{} loss:{}".format(np.mean(acc_list), np.mean(loss_list)))
for i in range(3):
train(i)
test()
学到了哪些
了解了sigmoid函数,softmax函数,交叉熵损失(一点浅显的东西),拉了一遍神经网络模型构建训练的流程,学会了使用dataset和dataloader定义自己简单的数据集。
总结
曾试图想着看dataset和dataloader的源码,但是去看了下后发现自己好像没学过python一样,给我一种就像换了语言写的感觉,看不懂!可能目前水平不够,还没资格看懂。
通过这次手写数字识别,发现自己太懒了,特别是这两天(虽然才开始)又不想学了,总会逃避一些问题,没有把整个流程走完(保存模型,加载模型,怎么转成onnx应用等等)
没完成上次说的了解tensor的一些操作(明天单独补上!)
加油加油!坚持!