深度学习入门指南:从零开始TinyMind汉字书法识别

深度学习入门指南:从零开始TinyMind汉字书法识别

这几天在刷这个新出的比赛,受举办方邀请谢了一篇文章,也转到CSDN来和大家分享下吧。话说TinyMind不是被CSDN收购了么,我这算不算把统一个发了两遍23333

  • 环境搭建
  • 数据导入
  • 启动网络

环境搭建:

对入门来说,最容易的还是在windows下进行开发。而且现在各种深度学习架构大都支持windows,因此如果只是入门深度学习,最好还是从windows开始。不过因为github上提交的代码全都运行在linux环境下,因此希望大家最终能转向linux下,话不多说,现在开始。

我们选用的深度学习架构是pytorch, 相比于tensorflow,pytorch更加简单易用,而且符合python的编程习惯,官网的支持也足够完善。

环境搭建步骤

  1. 安装Anaconda, 装python3.6版本的,至于为啥用python3这都2018年了,就别用上古版本了
  2. 安装pycharm,将pycharm的解释器改为anaconda安装目录下的python。当然用别的IDE也可以,但是我习惯用pycahrm了,如果大家用别的IDE这步另当别论
  3. 安装深度学习架构pytorch, 到了最重要的步骤了,如果没有英伟达显卡,或者显卡不支持请忽略1-3步
    1. 安装英伟达显卡驱动
    2. 安装CUDA
    3. 安装Cuddn
    4. 安装pytorch gpu版 (没有显卡的装cpu版)具体方法参见知乎这篇文章 https://zhuanlan.zhihu.com/p/26871672 选择自己对应的版本、系统、cuda版本,按照命令直接装就可以

数据导入

数据下载在TinyMind的比赛网站 http://www.tinymind.cn/competitions/41
下载解压后是两部分,分别是train和test1,其中train是训练集,test1是用来提交评分的测试集
为了导入图片数据,需要调用opencv,没装opencv的话就先装opencv

conda install -c https://conda.binstar.org/menpo opencv
import os
import numpy as np
import torch
import torch.utils.data as data
import cv2
from PIL import Image
from tqdm import tqdm

trainpath = 'E:\\Code\\TMD1st\\train\\' #这是我的储存路径,windows下的路径是需要用\\隔开的,linux是反斜杠/
testpath = 'E:\\Code\\TMD1st\\test1\\'
words = os.listdir(trainpath)   # 按时间排序 从早到晚
category_number = len(words) # 一共有多少个字

img_size = (256, 256) #将图片大小统一设定为这个值

def loadOneWord(order):
    path = trainpath + words[order] + '\\'
    files = os.listdir(path)
    datas = []
    for file in files:
        file = path + file
        img = np.asarray(Image.open(file))
        img = cv2.resize(img, img_size)
        datas.append(img)
    datas = np.array(datas)
    labels = np.zeros([len(datas), len(words)], dtype=np.uint8)
    labels[:, order] = 1
    return datas, labels

def transData():    #将所有数据转存,以后就不用每次都从原始数据读取了
    num = len(words)
    datas = np.array([], dtype=np.uint8)
    datas.shape = -1, 256, 256
    labels = np.array([], dtype=np.uint8)
    labels.shape = -1, 100
    for k in range(num):
        data, label = loadOneWord(k)

        datas = np.append(datas, data, axis=0)
        labels = np.append(labels, label, axis=0)
        print('loading', k)

    np.save('data.npy', datas) #将数据和标签分别存为data和label
    np.save('label.npy', labels)

将转存完的结果读出来看一下

if __name__ == '__main__':
    datas = np.load('data.npy')
    labels = np.load('label.npy')
    index = np.arange(0, len(datas), 1, dtype=np.int)
    print(datas.shape, labels.shape)

(40000, 256, 256) (40000, 100)

我是将40000个图像的label按照one-hot编码存的,这么干其实浪费空间,但是反正也没几兆,就懒得改了,index那一行就是专为将ong-hot转label

这才将数据转存,为了训练时给pytorch使用,最方便的方法是使用pytorch做好的loader工具,为此需要实现自己的data.Dataset。只需继承data.Dataset,并且重写getitemlen两个方法就可以。

class TrainSet(data.Dataset):
    def __init__(self, eval=False):
        datas = np.load('data.npy') #装载
        labels = np.load('label.npy')
        index = np.arange(0, len(datas), 1, dtype=np.int) #换one-hot为label
        np.random.seed(123)
        np.random.shuffle(index)
        if eval:    #如果eval为真,就取10%作为验证集,设定随机数种子是为了每次取出来的都是固定的10%,以免将验证集用于训练
            index = index[:int(len(datas) * 0.1)]
        else:
            index = index[int(len(datas) * 0.1):]
        self.data = datas[index]
        self.label = labels[index]
        np.random.seed()

    def __getitem__(self, index):
        return torch.from_numpy(self.data[index]), \
               torch.from_numpy(self.label[index])

    def __len__(self):
        return len(self.data)

完成dataset后只要使用torch.utils.data.DataLoader就可以自动划分batch。

启动网络

无论网络结构如何,用网络进行训练的整个过程是相同的

import torch
import torch.optim as optim
from torch.autograd import Variable
import torch.nn as nn
import data
import torch.nn.functional as F

n_epoch, batch_size = 25, 8 # 设置遍历次数及每个batch的大小

trainset = data.TrainSet(eval=False) #实例化上面定义的数据集对象
trainloader = torch.utils.data.DataLoader(trainset, batch_size=batch_size, shuffle=True) #用trainset实例化loader
evalset = data.TrainSet(eval=True)  #验证集
evalloader = torch.utils.data.DataLoader(evalset, batch_size=batch_size, shuffle=True)

net = Net() # 实例化模型
if torch.cuda.is_available():   # 将模型移到GPU上
    net.cuda()
criterion = nn.CrossEntropyLoss()   #损失函数使用交叉熵
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=1e-1, weight_decay=1e-4) #优化器使用SGD 学习率1e-3

def train(epoch):
    net.train() # 经模型切换到训练模式
    correct = 0
    sum = 0

    for batch_index, (datas, labels) in enumerate(trainloader, 0):  #从loader装载数据
        labels = labels.max(1)[1]
        datas = Variable(datas).float()
        datas = datas.view(-1, 1, 256, 256)
        labels = Variable(labels).long()
        if torch.cuda.is_available():   #数据转移到GPU
            datas = datas.cuda()
            labels = labels.cuda()
        optimizer.zero_grad()   # 每次前项计算之前,将优化器梯度清零
        outputs = net(datas)    # 前项计算
        loss = criterion(outputs, labels) # 根据结果和label计算损失函数
        loss.backward() # 做反向传播
        optimizer.step() # 用优化器进行一次更新
        pred_choice = outputs.data.max(1)[1]    # 前向输出计算最大的一个作为最可能的输出
        correct += pred_choice.eq(labels.data).cpu().sum() # 统计正确个数
        sum += len(labels)  # 总数

        # 输出每次计算的信息
        print('batch_index: [%d/%d]' % (batch_index, len(trainloader)),
              'Train epoch: [%d]' % (epoch),
              # 'acc:%.4f p:%.4f r:%.4f F1:%.4f' % (acc, p, r, F1),
              'correct/sum:%d/%d, %.4f' % (correct, sum, correct / sum))

def eval(epoch):    # 用验证集做类似过程,只是不计算梯度、不更新参数
    net.eval()
    correct = 0
    sum = 0
    for batch_index, (datas, labels) in enumerate(evalloader, 0):
        labels = labels.max(1)[1]
        datas = Variable(datas).cuda().float()
        datas = datas.view(-1, 1, 256, 256)
        labels = Variable(labels).cuda().long()
        # optimizer.zero_grad()
        outputs = net(datas)
        # loss = criterion(outputs, labels)
        # loss.backward()
        # optimizer.step()

        pred_choice = outputs.data.max(1)[1]
        correct += pred_choice.eq(labels.data).cpu().sum()
        sum += len(labels)
        print('batch_index: [%d/%d]' % (batch_index, len(evalloader)),
              'Eval epoch: [%d]' % (epoch),
              # 'acc:%.4f p:%.4f r:%.4f F1:%.4f' % (acc, p, r, F1),
              'correct/sum:%d/%d, %.4f' % (correct, sum, correct / sum))

if __name__ == '__main__':
    for epoch in range(n_epoch):
        train(epoch)
        eval(epoch)

如此,我们就完成了从原始数据制作dataset送入loader并且启动网络的所有代码。
等等,我们忘记了最重要的部分,我们没有定义网络的结构。
Net这里,这是一个继承自nn.Moudule的类,只要在这个类中定义网络的前向计算即可,反向计算会由pytorch自动实现。

为了简答起见,我们只举一个简单的例子,这个网络是随便写的,完全没有任何合理性的考虑,但至少能开始训练了

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        # 定义卷积层1
        self.conv1 = nn.Conv2d(1, 64, kernel_size=7, stride=4,  
                               padding=3, dilation=1, groups=1, bias=True)
        # 最大池化层
        self.pool = nn.MaxPool2d(2, 2)
        #卷积层2
        self.conv2 = nn.Conv2d(64, 64, kernel_size=3, stride=1, padding=1)
        #全连接层
        self.fc1 = nn.Linear(64 * 16 * 16, 4096)
        self.fc2 = nn.Linear(4096, 1024)
        self.fc3 = nn.Linear(1024, 100)

    def forward(self, x):       # 重写forward函数,定义前向计算
        x = self.pool(F.relu(self.conv1(x)))    # 卷积->ReLU->池化
        x = self.pool(F.relu(self.conv2(x)))    # 卷积->ReLU->池化
        x = x.view(-1, 64 * 16 * 16)            # 将平面拉成一维向量
        x = F.relu(self.fc1(x))                 # 全连接->ReLU
        x = F.relu(self.fc2(x))                 # 全连接->ReLU
        x = self.fc3(x)                         # 全连接产生输出
        return x

这样,代码就完整了,运行开始以后我么你就恩能够看到训练正确率从0慢慢的向上爬。当然,这个网络是随意写的,性能肯定极其的差,但至少举了一个栗子。

我们都知道,深度学习也叫炼丹。所以接下来的活便是研究拜读各个大牛级炼丹师的的炼丹秘籍(论文),学习人家先进的炼丹手法(trick),把我们的栗子给炼成金丹。

五年炼丹,三年悟道,炼丹一道,非大毅力大智慧者不可成。吾等当昼夜苦修,方有机缘窥得一丝丹道真谛,与诸君共勉。

该代码的github地址https://github.com/Link2Link/TinyMind-start-with-0

如有疑问,请在github里提问

你可能感兴趣的:(pytorch相关)