[图像识别]pytorch实现手写英文字母识别:

pytorch搭建CNN实现手写英文字母识别:

更新 2024/2/19:

链接:Pytorch从0实现图像分类pipeline

● 对代码的重构和一些细节上的调整
● 对并对功能类似的部分进行了模块化封装

Part1.数据集选取:

The Chars74K dataset:

http://www.ee.surrey.ac.uk/CVSSP/demos/chars74k/

resize后的The Chars74K EnglishImg部分数据集展示,尺寸为28*28:
在这里插入图片描述

EMNIST dataset:

https://rds.westernsydney.edu.au/Institutes/MARCS/BENS/EMNIST/emnist-gzip.zip

EMNIST数据集一共包含6个不同的数据类别,由于我们只会用到英文字母手写体,所以只需要emnist-letters part:
在这里插入图片描述

其中test表示测试集,train表示训练集,images表示测试集,labels表示标签。其中测试集共20800张,训练集共124800张,图像尺寸均为28*28。

由于网上下载的数据集统一封装为.idx3-ubyte格式,我的做法是将其解析为图片,再用解析的图片训练。

解析后的Emnist_letters部分数据集展示:
在这里插入图片描述

解析代码如下(转载自其他博客,时间久远,忘了网址TvT):

from PIL import Image
import struct

#图片:
def read_image(filename):
    f = open(filename, 'rb')
    index = 0
    buf = f.read()
    f.close()

    magic, images, rows, columns = struct.unpack_from('>IIII' , buf , index)
    index += struct.calcsize('>IIII')

    for i in range(images):
    #for i in range(2000):
        image = Image.new('L', (columns, rows))

        for x in range(rows):
            for y in range(columns):
                image.putpixel((y, x), int(struct.unpack_from('>B', buf, index)[0]))
                index += struct.calcsize('>B')
                #print ('save' + str(i) + 'image')
                
                image1 = image.transpose(Image.FLIP_LEFT_RIGHT)
                image2 = image1.rotate(90) 

                image2.save('train/' + str(i) + '.png')

#标签:
def read_label(filename, saveFilename):
  f = open(filename, 'rb')
  index = 0
  buf = f.read()

  f.close()

  magic, labels = struct.unpack_from('>II' , buf , index)
  index += struct.calcsize('>II')
  
  labelArr = [0] * labels
  #labelArr = [0] * 2000


  for x in range(labels):
  #for x in range(2000):
    labelArr[x] = int(struct.unpack_from('>B', buf, index)[0])
    index += struct.calcsize('>B')

    save = open(saveFilename, 'w')

    save.write(','.join(map(lambda x: str(x), labelArr)))
    save.write('\n')

    save.close()
    #print ('save labels success')


if __name__ == '__main__':
  
  imagePath = 'gzip/emnist-letters-train-images-idx3-ubyte'
  labelPath = 'gzip/emnist-letters-train-labels-idx1-ubyte'
  labelSavTransPath = 'train/label.txt'

  #读取数据集:
  read_image(imagePath)
  #读取标签,并解析为txt文档:
  read_label(labelPat, labelSavTransPath)

解析后的标签信息(1-26分别对应英文字母A-Z,不区分大小写):
在这里插入图片描述

同时,我们需要将解析后的数据集标签根据一并解析的txt文本里的标签信息进行一一标注,由于我采用的是pytorch里的ImageFolder类进行数据读取,这个类有一个方法能够根据数据所在的不同文件夹对数据进行分类,因此我们只需将不同的字母存放在不同的文件夹下就OK了:
在这里插入图片描述

分类代码如下:

import os
import cv2

labels = open("train_label.txt","r")  
label = labels.read().split(',')
print(len(label))


#path = 'test/test/'
path =  'train/'
for cnt in range(124800):
	image_path = (path+str(cnt)+'.png')
	img = cv2.imread(image_path)
    #根据图片对应的标签分类到对应的文件夹下:
	cv2.imwrite('Train_png/'+label[cnt]+'/'+str(cnt)+'.png',img)
	cnt += 1

最后,我采用的最终数据集是将EMnistThe Chars74K融合,多样的数据集能够使训练出的模型具有更好的泛化效果。

Part2.数据预处理:

首先定义超参数:

EPOCH = 2         #训练批次
BATCH_SIZE = 100  #训练的最小规模(一次反向传播更新权重)
LR = 1e-3         #学习率

使用torchvision.datasets下的ImageFolder类构造数据集:

(当然还可以通过重写data.Dataset自定义pytorch数据集类,其他博客也有相关教程)

#数据集要作为一整个文件夹读入:

#构造训练集:
train_data = ImageFolder(root="./Emnist_letters_png/Train_png", transform=transform)
#shuffle代表是否在构建批次时随机选取数据:
train_loader = torch.utils.data.DataLoader(dataset = train_data, batch_size=BATCH_SIZE, shuffle=True)

#构造数据集:
test_data = ImageFolder(root="./Emnist_letters_png/Test_png", transform=transform)
#之所以要将test_data转换为loader是因为网络不支持原始的ImageFolder类数据,到时候直接使用批训练,便是tensor类。因此batch_size为全部testdata(test_data.__len__())
test_loader = torch.utils.data.DataLoader(dataset = test_data, batch_size=test_data.__len__())

其中:

torch.utils.data.DataLoader是PyTorch中数据读取的一个重要接口,能够将自定义的Dataset封装成一个Batch Size大小的Tensor,用于后面的训练。

transform作为对数据集的自定义预处理函数:

# 数据预处理 转为tensor 以及 标准化:
transform = T.Compose([
     #转为灰度图像:
     T.Grayscale(num_output_channels=1),
     #将图片转换为Tensor,归一化至(0,1):
     T.ToTensor(),
     #T.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])

Part3.网络搭建:

本次训练字母识别使用的网络类似于LeNet-5的结构:两层卷积网络(卷积+池化)+三层全连接层:

注:若损失函数选用交叉熵损失,就不需要添加Softmax层,因为交叉熵损失的计算过程就包含了计算Softmax(Softmax+负对数损失)

class CNN(nn.Module):
    
    def __init__(self):
        super(CNN, self).__init__()
        #Sequential()把不同的函数组合成一个模块使用:
        #定义网络框架:
        self.Conv1 = nn.Sequential(
            #卷积层1(卷积核=16)
            nn.Conv2d(
 				in_channels = 1,   #输入图像的通道数,即输入高度为1
 				out_channels = 16, #定义16个卷积核,,即输出高度为16
 				kernel_size = 5,   #卷积核size为(5,5)
 				stride = 1,        #步长
 				padding = 2,       #边界填充为0 (如步长为1时,若要保证输出尺寸像和原尺寸一致,计算公式为:padding = (kernel_size-1)/2)
 			),
 			#激活函数层
            nn.ReLU(),
            #最大池化层
            nn.MaxPool2d(kernel_size = 2)
        )
        self.Conv2 = nn.Sequential(
            #卷积层2
            nn.Conv2d(16, 32, 5, 1, 2),
            nn.Dropout(p=0.2),
            #激活函数层
            nn.ReLU(),
            #最大池化层
            nn.MaxPool2d(kernel_size = 2)
        )
        #最后接上三层全连接(将图像变为1维)
        #为什么是32*7*7:(1,28,28)->(16,28,28)(conv1)->(16,14,14)(pool1)->(32,14,14)(conv2)->(32,7,7)(pool2)->output
        self.Linear = nn.Sequential(
            nn.Linear(32*7*7,400),
            #Dropout按概率p随机舍去部分神经元
            nn.Dropout(p=0.2),
            nn.ReLU(),
            nn.Linear(400,80),
            nn.ReLU(),
            nn.Linear(80,label_num),
         )
    #前向传播:
    def forward(self, input):
        input = self.Conv1(input)
        input = self.Conv2(input)       #view可理解为resize
        #input.size() = [100, 32, 7, 7], 100是每批次的数量,32是厚度,图片尺寸为7*7
        #当某一维是-1时,会自动计算他的大小(原则是总数据量不变):
        input = input.view(input.size(0), -1) #(batch=100, 1568), 最终效果便是将二维图片压缩为一维(数据量不变)
        #最后接上一个全连接层,输出为10:[100,1568]*[1568,10]=[100,10]
        output = self.Linear(input)
        return output


cnn = CNN()
#print(cnn)
#定义优化器(Adam优化算法,能够计算自适应性学习率)
optimizer = torch.optim.Adam(cnn.parameters(), lr = LR)
#定义损失函数(因为是分类问题,所以使用交叉熵损失)
loss_func = nn.CrossEntropyLoss()

print(cnn)显示网络层结构:

CNN(
  (Conv1): Sequential(
    (0): Conv2d(1, 16, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
    (1): ReLU()
    (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (Conv2): Sequential(
    (0): Conv2d(16, 32, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
    (1): Dropout(p=0.2, inplace=False)
    (2): ReLU()
    (3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (Linear): Sequential(
    (0): Linear(in_features=1568, out_features=400, bias=True)
    (1): Dropout(p=0.2, inplace=False)
    (2): ReLU()
    (3): Linear(in_features=400, out_features=80, bias=True)
    (4): ReLU()
    (5): Linear(in_features=80, out_features=26, bias=True)
  )
)

Part4.训练与模型保存:

在训练过程中,我们根据定义的Epoch进行循环,每次Epoch分为若干个step,一般step=Total/Epoch

#为了可视化网络在测试集上的效果,我们需要将测试集传入网络测试,但一直苦恼于网络不接受ImageFolder类只接受Tensor类数据,这里使用一个小技巧:将测试集也封装为DataLoader类,batch设置为整个测试集大小,遍历时将图像与标签分别读取就OK了。

for epoch in range(EPOCH):
    #enumerate() 函数用于将一个可遍历的数据对象组合为一个索引序列。例:['A','B','C']->[(0,'A'),(1,'B'),(2,'C')],
    #这里是为了将索引传给step输出
    for step, (x, y) in enumerate(train_loader):
        output = cnn(x)
        loss = loss_func(output, y)
        loss.backward()
        optimizer.step()
        optimizer.zero_grad()

        if step % 100 == 0:
            #enumerate() 函数用于将一个可遍历的数据对象组合为一个索引序列。例:['A','B','C']->[(0,'A'),(1,'B'),(2,'C')]
            for (test_x, test_y) in test_loader:

                #print(test_y.size())
                #在所有数据集上预测精度:
                #预测结果 test_output.size() = [10000,10],其中每一列代表预测为每一个数的概率(softmax输出),而不是0或1
                test_output = cnn(test_x)
                #torch.max()则将预测结果转化对应的预测结果,即概率最大对应的数字:[10000,10]->[10000]
                pred_y = torch.max(test_output,1)[1].squeeze() #squeeze()默认是将a中所有为1的维度删掉
                #pred_size() = [10000]
                accuracy = sum(pred_y == test_y) / test_data.__len__()
                print('Eopch:', epoch, ' | train loss: %.6f' % loss.item(), ' | test accracy:%.5f' % accuracy,  ' | step: %d' % step)



                #为tensorboardX添加可视化日志:
                #1.添加训练集损失
                # SumWriter.add_scalar("train loss:",loss.item()/20, global_step = 20)

                # #计算测试集精度
                # test_output = cnn(test_x)
                # pred_y = torch.max(test_output,1)[1].squeeze()
                # accuracy = sum(pred_y == test_y) / test_data.__len__()
                # #2.添加测试集精度
                # SumWriter.add_scalar("test accuracy:",accuracy.item(),20)

                # #预处理当前batch:
                # b_x_im = utils.make_grid(x, nrow = 16)
                # #3.添加一个batch图像的可视化
                # SumWriter.add_image('train image sample:', b_x_im, 20)

                # #4.添加直方图可视化网络参数分布:
                # for name, param in cnn.named_parameters():
                #     SumWriter.add_histogram(name, param.data.numpy(), 20)

    scheduler.step()

#仅保存训练好的参数
torch.save(cnn.state_dict(), 'EMNIST_CNN.pkl')

使用tensorboard可视化训练过程:

损失与测试集准确率:
在这里插入图片描述
网络权重的直方图分布:
在这里插入图片描述

最终的训练精度达到了大约93%:

Eopch: 1  | train loss: 0.198971  | test accracy:0.93221  | step: 400
Eopch: 1  | train loss: 0.111288  | test accracy:0.93173  | step: 500
Eopch: 1  | train loss: 0.205008  | test accracy:0.93303  | step: 600
Eopch: 1  | train loss: 0.222448  | test accracy:0.93413  | step: 700
Eopch: 1  | train loss: 0.292033  | test accracy:0.93048  | step: 800
Eopch: 1  | train loss: 0.199204  | test accracy:0.93144  | step: 900
Eopch: 1  | train loss: 0.134805  | test accracy:0.93072  | step: 1000
[Finished in 874.9s]

Part5.模型读取并测试自己的数据:

读取权重,输入待预测图像:

#读取网络框架
cnn = CNN()
#读取权重:
cnn.load_state_dict(torch.load('EMNIST_CNN.pkl'))


#test_x:(10000行1列,每列元素为28*28矩阵) 
# 提供自己的数据进行测试:
my_img = plt.imread("Emnist_letters_png/My_jpg/8.jpg")
my_img = my_img[:,:,0] #转换为单通道
my_img = cv2.resize(my_img,(28,28))#转换为28*28尺寸
my_img = torch.from_numpy(my_img)#转换为张量
my_img = torch.unsqueeze(my_img, dim = 0)#添加一个维度
my_img = torch.unsqueeze(my_img, dim = 0)/255. #再添加一个维度并把灰度映射在(0,1之间)       
#print(my_img.size())#torch.Size([1, 1, 28, 28])卷积层需要4个维度的输入

可视化部分+输出预测结果:

#可视化部分:

#输入原图像:
plt.imshow(my_img.squeeze())
plt.show()



#Conv1:
cnt = 1
my_img = cnn.Conv1(my_img)
img = my_img.squeeze()
for i in img.squeeze():

    plt.axis('off')
    fig = plt.gcf()
    fig.set_size_inches(5,5)#输出width*height像素
    plt.margins(0,0)

    plt.imshow(i.detach().numpy())
    plt.subplot(4, 4, cnt)
    plt.axis('off')
    plt.imshow(i.detach().numpy())
    cnt += 1
plt.subplots_adjust(top=1,bottom=0,left=0,right=1,hspace=0,wspace=0)
plt.show()



#Conv2:
cnt = 1
my_img = cnn.Conv2(my_img)
img = my_img.squeeze()
for i in img.squeeze():

    plt.axis('off')
    fig = plt.gcf()
    fig.set_size_inches(5,5)#输出width*height像素
    plt.margins(0,0)

    plt.imshow(i.detach().numpy())
    plt.subplot(4, 8, cnt)
    plt.axis('off')
    plt.imshow(i.detach().numpy())
    cnt += 1
#plt.subplots_adjust(top=1,bottom=0,left=0,right=1,hspace=0,wspace=0)
plt.show()




#全连接层:
my_img = my_img.view(my_img.size(0), -1)
fig = plt.gcf()
fig.set_size_inches(10000,4)#输出width*height像素
plt.subplots_adjust(top=1,bottom=0,left=0,right=1,hspace=0,wspace=0)
plt.margins(0,0)


my_img = cnn.Linear[0](my_img)
plt.subplot(3, 1, 1)
plt.imshow(my_img.detach().numpy())

my_img = cnn.Linear[1](my_img)
my_img = cnn.Linear[2](my_img)
my_img = cnn.Linear[3](my_img)
plt.subplot(3, 1, 2)
plt.imshow(my_img.detach().numpy())

my_img = cnn.Linear[4](my_img)
my_img = cnn.Linear[5](my_img)
plt.subplot(3, 1, 3)
plt.imshow(my_img.detach().numpy())

plt.show()



#输出预测结果:
pred_y = int(torch.max(my_img,1)[1])
#chr()将数字转为对应的的ASCAII字符
print('\npredict character: %c or %c' % (chr(pred_y+65),chr(pred_y+97)))

INPUT(我的手写体):

在这里插入图片描述

卷积层1:

在这里插入图片描述

卷积层2:

在这里插入图片描述

全连接层:

在这里插入图片描述

OUTPUT:

predict character: G or g

Part6.完整代码:

import torch
import torch.nn as nn
from torchvision.datasets import ImageFolder
import torchvision.models as models
from torchvision import utils
import torchvision.transforms as T
import torch.utils.data as Data
from PIL import Image
import numpy as np
import torch.optim as optim
import os
import matplotlib.pyplot as plt
#使用tensorboardX进行可视化
from tensorboardX import SummaryWriter

SumWriter = SummaryWriter(log_dir = "./EMNIST_log")
#print(torch.cuda.is_available())


EPOCH = 2
BATCH_SIZE = 128
LR = 1e-4


# 预处理 转为tensor 以及 标准化
transform = T.Compose([
     #转为灰度图像:
     T.Grayscale(num_output_channels=1),
     #将图片转换为Tensor,归一化至(0,1):
     T.ToTensor(),
     #比如原来的tensor是三个维度的,值在0到1之间,经过以下变换之后就到了-1到1区间
     #T.Normalize([0.5], [0.5])
])


#数据集要作为一个文件夹读入:

#读取训练集:
train_data = ImageFolder(root="./Emnist_letters_png/Train_png", transform=transform)
train_loader = torch.utils.data.DataLoader(dataset = train_data, batch_size=BATCH_SIZE, shuffle=True)

#读取测试集:
test_data = ImageFolder(root="./Emnist_letters_png/Test_png", transform=transform)
#之所以要将test_data转换为loader是因为网络不支持原始的ImageFolder类数据,到时候直接使用批训练,便是tensor类。
#batch_size为全部10000张testdata,在全测试集上测试精度
test_loader = torch.utils.data.DataLoader(dataset = test_data, batch_size=test_data.__len__())
label_num = len(train_data.class_to_idx)


#数据可视化:
to_img = T.ToPILImage()
a=to_img(test_data[0][0]) #size=[1, 28, 28]
plt.imshow(a)
plt.axis('off')
plt.show()


# 图片的标签对应其在哪个文件夹下
#print(train_data.class_to_idx)#打印所有标签
#print(test_data.imgs)#打印所有图片对应的路径及标签








class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()

        self.Conv1 = nn.Sequential(
            #卷积层1
            nn.Conv2d(1, 16, 5, 1, 2),
            nn.BatchNorm2d(16),
            #激活函数层
            nn.ReLU(inplace=True),
            #最大池化层
            nn.MaxPool2d(kernel_size = 2)
        )
        self.Conv2 = nn.Sequential(
            #卷积层2
            nn.Conv2d(16, 32, 5, 1, 2),
            nn.BatchNorm2d(32),
            #激活函数层
            nn.ReLU(inplace=True),
            #最大池化层
            nn.MaxPool2d(kernel_size = 2)
        )
        #最后接上一个全连接层(将图像变为1维)
        #为什么是32*7*7:(1,28,28)->(16,28,28)(conv1)->(16,14,14)(pool1)->(32,14,14)(conv2)->(32,7,7)(pool2)->output
        self.Linear = nn.Sequential(
            nn.Linear(32*7*7,800),
            nn.Dropout(p = 0.5),
            nn.ReLU(inplace=True),
            nn.Linear(800,160),
            nn.Dropout(p = 0.5),
            nn.ReLU(inplace=True),
            nn.Linear(160,label_num),
         )

    def forward(self, input):
        input = self.Conv1(input)
        input = self.Conv2(input)       #view可理解为resize
        #input.size() = [100, 32, 7, 7], 100是每批次的数量,32是厚度,图片尺寸为7*7
        #当某一维是-1时,会自动计算他的大小(原则是总数据量不变):
        input = input.view(input.size(0), -1) #(batch=100, 1568), 最终效果便是将二维图片压缩为一维(数据量不变)
        #最后接上一个全连接层,输出为10:[100,1568]*[1568,10]=[100,10]
        output = self.Linear(input)
        return output





cnn = CNN()
cnn.load_state_dict(torch.load('EMNIST_CNN.pkl'))
cnn.train()


print(cnn)
#定义优化器
optimizer = torch.optim.Adam(cnn.parameters(), lr = LR)
#定义损失函数
loss_func = nn.CrossEntropyLoss()
#根据EPOCH自动更新学习率,2次EPOCH学习率减少为原来的一半:
#scheduler = torch.optim.lr_scheduler.StepLR(optimizer, 2, gamma = 0.6, last_epoch = -1)

for epoch in range(EPOCH):
    #enumerate() 函数用于将一个可遍历的数据对象组合为一个索引序列。例:['A','B','C']->[(0,'A'),(1,'B'),(2,'C')],
    #这里是为了将索引传给step输出
    for step, (x, y) in enumerate(train_loader):
        output = cnn(x)
        loss = loss_func(output, y)
        loss.backward()
        optimizer.step()
        optimizer.zero_grad()

        if step % 100 == 0:
            #enumerate() 函数用于将一个可遍历的数据对象组合为一个索引序列。例:['A','B','C']->[(0,'A'),(1,'B'),(2,'C')]
            for (test_x, test_y) in test_loader:

                #print(test_y.size())
                #在所有数据集上预测精度:
                #预测结果 test_output.size() = [10000,10],其中每一列代表预测为每一个数的概率(softmax输出),而不是0或1
                test_output = cnn(test_x)
                #torch.max()则将预测结果转化对应的预测结果,即概率最大对应的数字:[10000,10]->[10000]
                pred_y = torch.max(test_output,1)[1].squeeze() #squeeze()默认是将a中所有为1的维度删掉
                #pred_size() = [10000]
                accuracy = sum(pred_y == test_y) / test_data.__len__()
                print('Eopch:', epoch, ' | train loss: %.6f' % loss.item(), ' | test accracy:%.5f' % accuracy,  ' | step: %d' % step)



                #为tensorboardX添加可视化日志:
                #1.添加训练集损失
                # SumWriter.add_scalar("train loss:",loss.item()/20, global_step = 20)

                # #计算测试集精度
                # test_output = cnn(test_x)
                # pred_y = torch.max(test_output,1)[1].squeeze()
                # accuracy = sum(pred_y == test_y) / test_data.__len__()
                # #2.添加测试集精度
                # SumWriter.add_scalar("test accuracy:",accuracy.item(),20)

                # #预处理当前batch:
                # b_x_im = utils.make_grid(x, nrow = 16)
                # #3.添加一个batch图像的可视化
                # SumWriter.add_image('train image sample:', b_x_im, 20)

                # #4.添加直方图可视化网络参数分布:
                # for name, param in cnn.named_parameters():
                #     SumWriter.add_histogram(name, param.data.numpy(), 20)



    #scheduler.step()

#仅保存训练好的参数
torch.save(cnn.state_dict(), 'EMNIST_CNN.pkl')

可视化部分:

import torch
import torch.nn as nn
from torchvision.datasets import ImageFolder
import torchvision.models as models
from torchvision import utils
import torchvision.transforms as T
import torch.utils.data as Data
from PIL import Image
import numpy as np
import torch.optim as optim
import cv2
import matplotlib.pyplot as plt





class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()

        self.Conv1 = nn.Sequential(
            #卷积层1
            nn.Conv2d(1, 16, 5, 1, 2),
            #激活函数层
            nn.ReLU(),
            #最大池化层
            nn.MaxPool2d(kernel_size = 2)
        )
        self.Conv2 = nn.Sequential(
            #卷积层2
            nn.Conv2d(16, 32, 5, 1, 2),
            nn.Dropout(p=0.2),
            #激活函数层
            nn.ReLU(),
            #最大池化层
            nn.MaxPool2d(kernel_size = 2)
        )
        #最后接上一个全连接层(将图像变为1维)
        #为什么是32*7*7:(1,28,28)->(16,28,28)(conv1)->(16,14,14)(pool1)->(32,14,14)(conv2)->(32,7,7)(pool2)->output
        self.Linear = nn.Sequential(
            nn.Linear(32*7*7,400),
            nn.Dropout(p=0.2),
            nn.ReLU(),
            nn.Linear(400,80),
            nn.ReLU(),
            nn.Linear(80,26),
         )

    def forward(self, input):
        input = self.Conv1(input)
        input = self.Conv2(input)       #view可理解为resize
        #input.size() = [100, 32, 7, 7], 100是每批次的数量,32是厚度,图片尺寸为7*7
        #当某一维是-1时,会自动计算他的大小(原则是总数据量不变):
        input = input.view(input.size(0), -1) #(batch=100, 1568), 最终效果便是将二维图片压缩为一维(数据量不变)
        #最后接上一个全连接层,输出为10:[100,1568]*[1568,10]=[100,10]
        output = self.Linear(input)
        return output






#读取网络框架
cnn = CNN()
#读取权重:
cnn.load_state_dict(torch.load('EMNIST_CNN.pkl'))


#test_x:(10000行1列,每列元素为28*28矩阵) 
# 提供自己的数据进行测试:
my_img = plt.imread("Emnist_letters_png/My_jpg/g.jpg")
my_img = my_img[:,:,0] #转换为单通道
my_img = cv2.resize(my_img,(28,28))#转换为28*28尺寸
my_img = torch.from_numpy(my_img)#转换为张量
my_img = torch.unsqueeze(my_img, dim = 0)#添加一个维度
my_img = torch.unsqueeze(my_img, dim = 0)/255. #再添加一个维度并把灰度映射在(0,1之间)       
#print(my_img.size())#torch.Size([1, 1, 28, 28])卷积层需要4个维度的输入






#可视化部分:

#输入原图像:
plt.imshow(my_img.squeeze())
plt.show()



#Conv1:
cnt = 1
my_img = cnn.Conv1(my_img)
img = my_img.squeeze()
for i in img.squeeze():

    plt.axis('off')
    fig = plt.gcf()
    fig.set_size_inches(5,5)#输出width*height像素
    plt.margins(0,0)

    plt.imshow(i.detach().numpy())
    plt.subplot(4, 4, cnt)
    plt.axis('off')
    plt.imshow(i.detach().numpy())
    cnt += 1
plt.subplots_adjust(top=1,bottom=0,left=0,right=1,hspace=0,wspace=0)
plt.show()



#Conv2:
cnt = 1
my_img = cnn.Conv2(my_img)
img = my_img.squeeze()
for i in img.squeeze():

    plt.axis('off')
    fig = plt.gcf()
    fig.set_size_inches(5,5)#输出width*height像素
    plt.margins(0,0)

    plt.imshow(i.detach().numpy())
    plt.subplot(4, 8, cnt)
    plt.axis('off')
    plt.imshow(i.detach().numpy())
    cnt += 1
#plt.subplots_adjust(top=1,bottom=0,left=0,right=1,hspace=0,wspace=0)
plt.show()




#全连接层:
my_img = my_img.view(my_img.size(0), -1)
fig = plt.gcf()
fig.set_size_inches(10000,4)#输出width*height像素
plt.subplots_adjust(top=1,bottom=0,left=0,right=1,hspace=0,wspace=0)
plt.margins(0,0)


my_img = cnn.Linear[0](my_img)
plt.subplot(3, 1, 1)
plt.imshow(my_img.detach().numpy())

my_img = cnn.Linear[1](my_img)
my_img = cnn.Linear[2](my_img)
my_img = cnn.Linear[3](my_img)
plt.subplot(3, 1, 2)
plt.imshow(my_img.detach().numpy())

my_img = cnn.Linear[4](my_img)
my_img = cnn.Linear[5](my_img)
plt.subplot(3, 1, 3)
plt.imshow(my_img.detach().numpy())

plt.show()



#输出预测结果:
pred_y = int(torch.max(my_img,1)[1])
#chr()将数字转为对应的的ASCAII字符
print('\npredict character: %c or %c' % (chr(pred_y+65),chr(pred_y+97)))

数据集地址

本篇博客用到的数据集(包含解析后的emnist和chars74k)均已上传至github,若对您有帮助,欢迎点个star:
https://github.com/Scienthusiasts/emnist-chars74k_datasets/tree/master

好的,下面是一个基于Pytorch和GPU的手写英文字母识别系统的代码,包括详细注释和可视化性能结果: ```python import torch import torch.nn as nn import torch.optim as optim import torchvision import torchvision.transforms as transforms import matplotlib.pyplot as plt # 设置GPU加速 device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # 加载训练集和测试集,并进行数据预处理 transform = transforms.Compose( [transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,))]) trainset = torchvision.datasets.EMNIST(root='./data', split='letters', train=True, download=True, transform=transform) trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True, num_workers=2) testset = torchvision.datasets.EMNIST(root='./data', split='letters', train=False, download=True, transform=transform) testloader = torch.utils.data.DataLoader(testset, batch_size=64, shuffle=False, num_workers=2) # 定义神经网络模型 class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(1, 6, 5) self.pool1 = nn.MaxPool2d(2, 2) self.conv2 = nn.Conv2d(6, 16, 5) self.pool2 = nn.MaxPool2d(2, 2) self.fc1 = nn.Linear(16 * 4 * 4, 120) self.fc2 = nn.Linear(120, 84) self.fc3 = nn.Linear(84, 27) def forward(self, x): x = self.pool1(torch.relu(self.conv1(x))) x = self.pool2(torch.relu(self.conv2(x))) x = x.view(-1, 16 * 4 * 4) x = torch.relu(self.fc1(x)) x = torch.relu(self.fc2(x)) x = self.fc3(x) return x net = Net().to(device) # 定义损失函数和优化器 criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9) # 训练神经网络模型 for epoch in range(10): # 进行10次训练循环 running_loss = 0.0 for i, data in enumerate(trainloader, 0): inputs, labels = data[0].to(device), data[1].to(device) optimizer.zero_grad() outputs = net(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() if i % 200 == 199: # 每200个小批量数据打印一次损失函数值 print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 200)) running_loss = 0.0 print('Finished Training') # 测试神经网络模型 correct = 0 total = 0 with torch.no_grad(): for data in testloader: images, labels = data[0].to(device), data[1].to(device) 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)) # 输出每个类别的准确率 class_correct = list(0. for i in range(27)) class_total = list(0. for i in range(27)) with torch.no_grad(): for data in testloader: images, labels = data[0].to(device), data[1].to(device) outputs = net(images) _, predicted = torch.max(outputs, 1) c = (predicted == labels).squeeze() for i in range(64): label = labels[i] class_correct[label] += c[i].item() class_total[label] += 1 for i in range(27): print('Accuracy of %5s : %2d %%' % ( chr(i+65), 100 * class_correct[i] / class_total[i])) # 可视化神经网络模型的性能结果 dataiter = iter(testloader) images, labels = dataiter.next() outputs = net(images.to(device)) _, predicted = torch.max(outputs, 1) fig, axes = plt.subplots(nrows=5, ncols=10, figsize=(20,10)) fig.suptitle('Model Performance') for i, ax in enumerate(axes.flat): ax.imshow(images[i].squeeze(), cmap='gray') ax.set(title = f"true: {chr(labels[i]+65)}, pred: {chr(predicted[i].item()+65)}") ax.axis('off') plt.show() ``` 注释解释: 1. 导入必要的Pytorch库和Matplotlib库。 2. 设置GPU加速。 3. 加载训练集和测试集,并进行数据预处理。这里使用了EMNIST数据集,该数据集包含了手写字母和数字的图像数据,其中每个图像为28x28像素的灰度图像。 4. 定义神经网络模型。这里使用了一个简单的卷积神经网络,包括两个卷积层和三个全连接层。 5. 定义损失函数和优化器。这里使用了交叉熵损失函数和随机梯度下降优化器。 6. 训练神经网络模型。这里进行了10次训练循环,每次循环使用64个图像进行训练。在每个小批量数据之后,打印损失函数值。 7. 测试神经网络模型。这里使用测试集对神经网络模型进行测试,并计算其准确率。 8. 输出每个类别的准确率。这里计算了每个字母的准确率。 9. 可视化神经网络模型的性能结果。这里使用测试集中的一些图像进行可视化,展示神经网络模型的预测结果。 运行代码后,可以看到每个字母的准确率,以及神经网络模型对测试集中一些图像的预测结果。
评论 90
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值