深度学习(4):基于深层卷积网络实现车牌识别

2023-11-06

目的:基于深层卷积神经网络结合CTC损失函数对车牌进行识别,通过对车牌数据集进行训练获得识别模型,并验证模型性能和将模型进行应用。

一、原理

  1. 了解深层卷积神经网络构建方法和基本原理
  2. 熟悉目标识别相关算法的常规训练流程
  3. 掌握CTC损失函数的基本原理

CTC 的全称是Connectionist Temporal Classification. 这个方法主要是解决神经网络label 和output 不对齐的问题(Alignment problem)。参考博客:CTC loss原理详解大全_@you_123的博客-CSDN博客_ctcloss的原理

二、数据探索

1.安装库

  • scikit-image是基于scipy的一款图像处理包,它将图片作为numpy数组进行处理,正好与matlab一样,scikit-image是基于numpy,因此需要安装numpy和scipy,同时需要安装matplotlib进行图片的实现等。
  • OpenCV-Python是由OpenCV C++实现并封装的Python库。 OpenCV-Python使用Numpy,这是一个高度优化的数据操作库,具有MATLAB风格的语法。 所有OpenCV数组结构都转换为Numpy数组。这也使得与使用Numpy的其他库(如SciPy和Matplotlib)集成更容易。
  • torchvisionpytorch的一个图形库,它服务于PyTorch深度学习框架的,主要用来构建计算机视觉模型。参考torchvision详细介绍_Fighting_1997的博客-CSDN博客_torchvision
  • torchsummary能够查看模型的输入和输出的形状,可以更加清楚地输出模型的结构。
  •  torchviz模型可视化
#安装组件,安装完成需要重新启动一下kernel
!pip3 install scikit-image
!pip3 install opencv-python
!pip3 install torchvision
!pip3 install torchsummary
!pip3 install torchviz

2.导入库

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
from tqdm import tqdm

import numpy as np
import cv2
import matplotlib.pyplot as plt
import os
from skimage import io

%matplotlib inline

3.准备数据

#准备数据,从OSS中获取数据并解压到当前目录:

import oss2
access_key_id = os.getenv('OSS_TEST_ACCESS_KEY_ID', 'LTAI4G1MuHTUeNrKdQEPnbph')
access_key_secret = os.getenv('OSS_TEST_ACCESS_KEY_SECRET', 'm1ILSoVqcPUxFFDqer4tKDxDkoP1ji')
bucket_name = os.getenv('OSS_TEST_BUCKET', 'mldemo')
endpoint = os.getenv('OSS_TEST_ENDPOINT', 'https://oss-cn-shanghai.aliyuncs.com')
# 创建Bucket对象,所有Object相关的接口都可以通过Bucket对象来进行
bucket = oss2.Bucket(oss2.Auth(access_key_id, access_key_secret), endpoint, bucket_name)
# 下载到本地文件
bucket.get_object_to_file('data/c12/number_plate_data.zip', 'number_plate_data.zip')

!unzip -q -o number_plate_data.zip
!rm -rf __MACOSX
#训练集路径
data_dir_train = 'gen_res'
#验证集路径
data_dir_val = 'gen_res_val'

4.可视化

从训练集中随机提取18张照片进行可视化,代码如下:

import os
import numpy as np
import matplotlib.pyplot as plt
import random
# 随机选择18张照片
train_folder = data_dir_train+"/15/"

images = random.choices(os.listdir(train_folder), k=18)

fig = plt.figure(figsize=(20, 10))

# 6列
columns = 6
# 3行
rows = 3

# 依次可视化
for x, i in enumerate(images):
    path =  os.path.join(train_folder,i)
    img = plt.imread(path)
    fig.add_subplot(rows, columns, x+1)
    plt.imshow(img)
    
plt.show()

 

三、模型训练

1.构建深层卷积网络

  • Module类是nn模块里提供的一个模型构造类(专门用来构造模型的),是所有神经网络模块的爸爸,我们可以继承它来定义我们想要的模型。
  • forward定义模型的前向计算,即如何根据输入x计算返回所需要的模型输出
class Model(nn.Module):
    
    def __init__(self):
        super().__init__()
        
        self.conv0 = nn.Conv2d(3, 32, 3, stride=1, padding=1)
        self.bn0 = nn.BatchNorm2d(32)
        self.relu0 = nn.ReLU()
        self.pool0 = nn.MaxPool2d(2, stride=2)
        
        self.conv1 = nn.Conv2d(32, 64, 3, stride=1, padding=1)
        self.bn1 = nn.BatchNorm2d(64)
        self.relu1 = nn.ReLU()
        self.pool1 = nn.MaxPool2d(2, stride=2)
        
        self.conv2 = nn.Conv2d(64, 128, 3, stride=1, padding=1)
        self.bn2 = nn.BatchNorm2d(128)
        self.relu2 = nn.ReLU()
        self.pool2 = nn.MaxPool2d(2, stride=2)
        
        self.conv2d_1 = nn.Conv2d(128, 256, (1,5), stride=1)
        self.batch_normalization_1 = nn.BatchNorm2d(256)
        self.activation_1 = nn.ReLU()
        
        # --------------------
        
        self.conv2d_2 = nn.Conv2d(256, 256, (7,1), stride=1, padding=(3,0))
        self.batch_normalization_2 = nn.BatchNorm2d(256)
        self.activation_2 = nn.ReLU()
        
        self.conv2d_3 = nn.Conv2d(256, 256, (5,1), stride=1, padding=(2,0))
        self.batch_normalization_3 = nn.BatchNorm2d(256)
        self.activation_3 = nn.ReLU()
        
        self.conv2d_4 = nn.Conv2d(256, 256, (3,1), stride=1, padding=(1,0))
        self.batch_normalization_4 = nn.BatchNorm2d(256)
        self.activation_4 = nn.ReLU()
        
        self.conv2d_5 = nn.Conv2d(256, 256, (1,1), stride=1)
        self.batch_normalization_5 = nn.BatchNorm2d(256)
        self.activation_5 = nn.ReLU()
        
        # -----------------
        
        self.conv_1024_11 = nn.Conv2d(1024, 1024, 1, stride=1)
        self.batch_normalization_6 = nn.BatchNorm2d(1024)
        self.activation_6 = nn.ReLU()
        
        self.conv_class_11 = nn.Conv2d(1024, 84, 1, stride=1)
        
    def forward(self, x):
        x = self.pool0(self.relu0(self.bn0(self.conv0(x))))
        x = self.pool1(self.relu1(self.bn1(self.conv1(x))))
        x = self.pool2(self.relu2(self.bn2(self.conv2(x))))
        x = self.activation_1(self.batch_normalization_1(self.conv2d_1(x)))
        
        x2 = self.activation_2(self.batch_normalization_2(self.conv2d_2(x)))
        x3 = self.activation_3(self.batch_normalization_3(self.conv2d_3(x)))
        x4 = self.activation_4(self.batch_normalization_4(self.conv2d_4(x)))
        x5 = self.activation_5(self.batch_normalization_5(self.conv2d_5(x)))
        
        x = torch.cat([x2, x3, x4, x5], dim=1)
        
        x = self.activation_6(self.batch_normalization_6(self.conv_1024_11(x)))
        x = self.conv_class_11(x)
        x = torch.softmax(x, dim=1)
        
        return x

2.定义数据集 

#定义识别标签词典
vocabulary = ["京", "沪", "津", "渝", "冀", "晋", "蒙", "辽", "吉", "黑", "苏", "浙", "皖", "闽", "赣", "鲁", "豫", "鄂", "湘", "粤", "桂",
"琼", "川", "贵", "云", "藏", "陕", "甘", "青", "宁", "新", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A",
"B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X",
"Y", "Z","港","学","使","警","澳","挂","军","北","南","广","沈","兰","成","济","海","民","航","空", ""]
  • Python的魔法方法__getitem__ 可以让对象实现迭代功能,这样就可以使用for...in... 来迭代该对象了 
#车牌数据读取类
class CarnumberDataset(Dataset):

    def __init__(self, data_dir):
        files = []
        for d in os.listdir(data_dir):
            for d2 in os.listdir(os.path.join(data_dir, d)):
                files.append(os.path.join(data_dir, d, d2))
        self.files = files

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

    def __getitem__(self, idx):
        img_name = self.files[idx]
        image = io.imread(img_name)
        image = cv2.resize(image, (160,40)).transpose((2,1,0))
        
        text = os.path.basename(img_name).split('.')[0]
        carnumber = np.zeros((len(text)), dtype=np.long)
        for i, char in enumerate(text):
            carnumber[i] = vocabulary.index(char)

        return image, carnumber

3.读取数据

dataset_train = CarnumberDataset(data_dir_train)
#构建读取训练集加载器,其中读取过程中对数据进行随机化,每批大小为4个
dataloader_train = DataLoader(dataset_train, batch_size=4, shuffle=True, num_workers=0, drop_last=True)
dataset_val = CarnumberDataset(data_dir_val)
#构建验证集读取器,其中读取过程中对数据进行随机化,每批大小为4个
dataloader_val = DataLoader(dataset_val, batch_size=4, shuffle=True, num_workers=0, drop_last=True)

4.定义工具方法

#移除CTC中连续的重复字符
def remove_connective_duplicate(src):
    last = None
    out = []
    for c in src.tolist():
        if last is None or last != c:
            last = c
            out.append(c)
    
    return np.array(out)

#对模型输出结果进行词典解码
def decode(output):
    return ''.join(list(map(lambda i: vocabulary[i], output)))

#获得识别结果的概率值
def decode_prob(prob):
    return decode(remove_connective_duplicate(prob.argmax(axis=0)))

5.模型训练

#定义模型结构
model = Model()
#选择Adam作为学习率优化器
optimizer = optim.Adam(model.parameters(), lr=0.0001)
#选择CTC损失
criterion = nn.CTCLoss(blank=len(vocabulary)-1)
#输出模型结构
from torchviz import make_dot
from torchsummary import summary

summary(model,(3, 40, 160))

x = torch.zeros(1, 3, 40, 160, dtype=torch.float, requires_grad=False)
out = model(x)
make_dot(out, params=dict(model.named_parameters()), show_attrs=True, show_saved=True)

#模型训练
model = model.train()
#训练历史记录
loss_history = []
for epoch in range(10):
    pbar = tqdm(dataloader_train)
    for batch_image, batch_carnumber in pbar:
        batch_image =  batch_image.float()
        batch_output = model(batch_image).squeeze()
        loss = criterion(batch_output.permute(2,0,1).log(), 
                         batch_carnumber, 
                         torch.tensor([batch_output.shape[2]]*batch_output.shape[0]), 
                         torch.tensor([batch_carnumber.shape[1]]*batch_carnumber.shape[0]))

        pbar.set_description(f'epoch {epoch}, loss:{loss.item():.4f}')
        loss_history.append(loss.item())

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

#模型保存
torch.save(model, 'model-lastest.pth')

6.模型验证

#构建验证模型
model = model.eval()

#计算正确率指标
correct = 0
for sample in tqdm(dataset_val):
    sample_image = sample[0]
    sample_carnumber = sample[1]
    output = decode_prob(model(torch.tensor(sample_image[np.newaxis,...]).float())[0].squeeze().detach().numpy())
    truth = decode(sample_carnumber)
    
    if output == truth:
        correct += 1
#输出正确率结果
print(correct/len(dataset_val))

四、模型应用

1.加载模型

#加载模型
model = torch.load('model-lastest.pth',map_location="cpu")
for m in model.modules():
    if 'Conv' in str(type(m)):
        setattr(m, 'padding_mode', 'zeros')

2.定义模型使用方法 

  • eval():将字符串string对象转化为有效的表达式参与求值运算返回计算结果。
  • eval(expression,globals=None, locals=None)返回的是计算结果。
#定义模型使用方法
def eval(file):
    image = io.imread(file)
    #输入图片可视化
    plt.imshow(image)
    image = cv2.resize(image, (160,40)).transpose((2,1,0))
    output = model(torch.tensor(image[np.newaxis,...]).float())[0].squeeze().detach().numpy()
    return decode_prob(output)
eval('gen_res_val/29/宁C0B5GP.jpg')

eval('gen_res_val/21/琼DXQ66B.jpg')

 

eval('gen_res_val/20/桂FBK9CC.jpg')

本实验基于深层卷积算法实现车牌数据建模,通过将原始图片进行训练,获得识别模型,然后对测试车牌数据进行验证和应用。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

深度学习(4):基于深层卷积网络实现车牌识别 的相关文章

  • 如何查看Databricks中的所有数据库和表

    我想列出 Azure Databricks 中每个数据库中的所有表 所以我希望输出看起来像这样 Database Table name Database1 Table 1 Database1 Table 2 Database1 Table
  • Python:在列表理解本身中引用列表理解?

    这个想法刚刚出现在我的脑海中 假设您出于某种原因想要通过 Python 中的列表理解来获取列表的唯一元素 i if i in created comprehension else 0 for i in 1 2 1 2 3 1 2 0 0 3
  • 无法“安装”plpython3u - postgresql

    我正在尝试在 postgresql 中使用 python 语言 像这样的事情 create or replace function test a integer returns integer as if a 2 0 return even
  • 将 Matplotlib 误差线放置在不位于条形中心的位置

    我正在 Matplotlib 中生成带有错误栏的堆积条形图 不幸的是 某些层相对较小且数据多样 因此多个层的错误条可能重叠 从而使它们难以或无法读取 Example 有没有办法设置每个误差条的位置 即沿 x 轴移动它 以便重叠的线显示在彼此
  • 使用带有关键字参数的 map() 函数

    这是我尝试使用的循环map功能于 volume ids 1 2 3 4 5 ip 172 12 13 122 for volume id in volume ids my function volume id ip ip 我有办法做到这一点
  • 使用 matplotlib 绘制时间序列数据并仅在年初显示年份

    rcParams date autoformatter month b n Y 我正在使用 matpltolib 来绘制时间序列 如果我按上述方式设置 rcParams 则生成的图会在每个刻度处标记月份名称和年份 我怎样才能将其设置为仅在每
  • 如何在 Python 中检索 for 循环中的剩余项目?

    我有一个简单的 for 循环迭代项目列表 在某些时候 我知道它会破裂 我该如何退回剩余的物品 for i in a b c d e f g try some func i except return remaining items if s
  • 如何替换 pandas 数据框列中的重音符号

    我有一个数据框dataSwiss其中包含瑞士城市的信息 我想用普通字母替换带有重音符号的字母 这就是我正在做的 dataSwiss Municipality dataSwiss Municipality str encode utf 8 d
  • 根据列值突出显示数据框中的行?

    假设我有这样的数据框 col1 col2 col3 col4 0 A A 1 pass 2 1 A A 2 pass 4 2 A A 1 fail 4 3 A A 1 fail 5 4 A A 1 pass 3 5 A A 2 fail 2
  • 使用 Tkinter 显示 numpy 数组中的图像

    我对 Python 缺乏经验 第一次使用 Tkinter 制作一个 UI 显示我的数字分类程序与 mnist 数据集的结果 当图像来自 numpy 数组而不是我的 PC 上的文件路径时 我有一个关于在 Tkinter 中显示图像的问题 我为
  • 绘制方程

    我正在尝试创建一个函数 它将绘制我告诉它的任何公式 import numpy as np import matplotlib pyplot as plt def graph formula x range x np array x rang
  • 从 Flask 访问 Heroku 变量

    我已经使用以下命令在 Heroku 配置中设置了数据库变量 heroku config add server xxx xxx xxx xxx heroku config add user userName heroku config add
  • Pygame:有没有简单的方法可以找到按下的任何字母数字的字母/数字?

    我目前正在开发的游戏需要让人们以自己的名义在高分板上计时 我对如何处理按键有点熟悉 但我只处理过寻找特定的按键 有没有一种简单的方法可以按下任意键的字母 而不必执行以下操作 for event in pygame event get if
  • 对年龄列进行分组/分类

    我有一个数据框说df有一个柱子 Ages gt gt gt df Age 0 22 1 38 2 26 3 35 4 35 5 1 6 54 我想对这个年龄段进行分组并创建一个像这样的新专栏 If age gt 0 age lt 2 the
  • 如何在 Python 中追加到 JSON 文件?

    我有一个 JSON 文件 其中包含 67790 1 kwh 319 4 现在我创建一个字典a dict我需要将其附加到 JSON 文件中 我尝试了这段代码 with open DATA FILENAME a as f json obj js
  • 有人用过 Dabo 做过中型项目吗? [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 我们正处于一个新的 ERP 风格的客户端 服务器应用程序的开始阶段 该应用程序是作为 Python 富客户端开发的 我们目前正在评估 Dabo
  • Python:如何将列表列表的元素转换为无向图?

    我有一个程序 可以检索 PubMed 出版物列表 并希望构建一个共同作者图 这意味着对于每篇文章 我想将每个作者 如果尚未存在 添加为顶点 并添加无向边 或增加每个合著者之间的权重 我设法编写了第一个程序 该程序检索每个出版物的作者列表 并
  • Scrapy:如何使用元在方法之间传递项目

    我是 scrapy 和 python 的新手 我试图将 parse quotes 中的项目 item author 传递给下一个解析方法 parse bio 我尝试了 request meta 和 response meta 方法 如 sc
  • 导入错误:没有名为 site 的模块 - mac

    我已经有这个问题几个月了 每次我想获取一个新的 python 包并使用它时 我都会在终端中收到此错误 ImportError No module named site 我不知道为什么会出现这个错误 实际上 我无法使用任何新软件包 因为每次我
  • Python Selenium:如何在文本文件中打印网站上的值?

    我正在尝试编写一个脚本 该脚本将从 tulsaspca org 网站获取以下 6 个值并将其打印在 txt 文件中 最终输出应该是 905 4896 7105 23194 1004 42000 放置的动物 的 HTML span class

随机推荐

  • 迁移学习一、基本使用

    非完全原创 但遗失了原文链接 看到可更改 迁移学习 一 迁移学习方法介绍 1 微调网络的方法 微调网络的方法实现迁移学习 更改最后一层全连接 并且微调训练网络 2 将模型看成特征提取器 将模型看成特征提取器 如果一个模型的预训练模型非常的好
  • 实现model中的文件上传FTP(一)

    由于在django的model中配置了filefield或者imagefield配置了upload to参数只能将用户上传的文件上传到项目本地 就算重定向到项目外也只是直接读取文件系统 这样对未来的项目迁移或者资源管理都会造成很大的困扰 而
  • HJ2 计算某字符出现次数

    知识点字符串哈希 描述 写出一个程序 接受一个由字母 数字和空格组成的字符串 和一个字符 然后输出输入字符串中该字符的出现次数 不区分大小写字母 数据范围 1 n 1000 输入描述 第一行输入一个由字母和数字以及空格组成的字符串 第二行输
  • 设计模式梳理——访问者模式

    一 概述 访问者模式 Visitor 表示一个作用于某对象结构中的各元素的操作 它使你可以在不改变个元素的类的前提下作用于这些元素的新操作 二 UML图示 三 代码实现 我们都知道财务都是有账本的 这个账本就可以作为一个对象结构 而它其中的
  • springsecurity 提示:There is no PasswordEncoder mapped for the id "null"

    用户角色权限系统 springboot springsecurity mysql 提示如下错误信息 java lang IllegalArgumentException There is no PasswordEncoder mapped
  • 虚函数的原理

    引用 windows程序员面试指南 虚函数 虚函数通过虚函数表管理 特点 1 虚函数表属于类 类的所有对象共享这个类的虚函数表 2 虚函数指针属于对象 在每个对象内部的开头 指向同一虚函数表 继承特点 只继承一个父类无覆盖时 父类虚函数在子
  • 在64位电脑上 使用anaconda虚拟环境将Python程序打包成32位

    之前写啦一些打包博文 见链接 anaconda虚拟环境教程大全 使用pipenv建立虚拟环境解决python打包exe文件过大的问题 附打包带图标 多个py文件打包exe cmd执行python程序 文件夹直接进入cmd程序 python将
  • VirtualBox网络之桥接网卡

    如下图所示 VirtualBox支持的网络模式有多种 桥接网卡不同与其它的几种 首先体现在名字上 其它模式都带有 网络 字眼 说明当前的网卡需要接入一个由用户创建的网络 而桥接网卡则只是网卡 这种模式不需要创建网络 从效果上看 这种网卡与宿
  • Linux 下使用命令行查看PDF

    使用 evince命令查看 evince frames pdf
  • 低通、高通、带通、阻通滤波器

    目录 低通 高通 带通 阻通滤波器 低通 高通 带通 带阻滤波器的区别 通俗理解 1 低通滤波器 2 高通滤波器 3 带通滤波器 4 带阻滤波器 5 全通滤波器 低通 高通 带通 阻通滤波器 低通 高通 带通 带阻滤波器的区别 低通滤波器
  • STM32系列(HAL库)——F103C8T6通过NRF24L01(2.4G)无线模块进行双机通信

    本文参考 STM32L051C8T6 HAL库 nRF24L01 收发案例 硬件SPI通讯 1 软件准备 1 编程平台 Keil5 2 CubeMX 3 XCOM 串口调试助手 2 硬件准备 2套 1 F1的板子 本例使用经典F103C8T
  • ROS开发日记(1)——ROS基础知识

    ROS 即开源机器人操作系统 1 官方定义 ROS是面向机器人的开源的元操作系统 meta operating system 1 它能够提供类似传统操作系统的诸多功能 如硬件抽象 底层设备控制 常用功能实现 进程间消息传递和程序包管理等 此
  • java实现信息的增删改查功能的网页设计(1)

    仅供参考 不可转载 如遇其他情况概不负责 后果自负 切记 该项目运用的技术 spring springMVC ibatis 本网页只有一个页面 包过信息的增 删 改 查功能 只有部分代码 仅供参考 因该项目比较大 所以仅提供了一个页面内的增
  • 命名时取代基优先顺序_有机物命名:常见官能团的优先次序表

    一 官能团的优先次序 常见官能团的优先次序表类别序号官能团词头名称词尾名称 酸1 COOH羧基羧酸 2 SO3H磺基磺酸 羧 酸 衍 生 物3 COOR酯基羧酸酯 4 COX卤羰基酰卤 5 CONH2氨甲 酰基酰胺 腈6 CN氰基腈 醛7
  • MySQL——JDBC

    文章目录 1 数据库驱动 2 JDBC 3 第一个JDBC程序 4 步骤总结 5 JDBC对象解释 6 写工具类 7 JDBC操作事务 7 1 创建表 7 2 事务 7 3 总结 8 数据库连接池 8 1 DBCP 8 2 c3p0 1 数
  • 从游戏脚本语言说起,剖析Mono所搭建的脚本基础

    从游戏脚本语言说起 剖析Mono所搭建的脚本基础 0x00 前言 在日常的工作中 我偶尔能遇到这样的问题 为何游戏脚本在现在的游戏开发中变得不可或缺 那么这周我就写篇文章从游戏脚本聊起 分析一下游戏脚本因何出现 而mono又能提供怎样的脚本
  • 系统开发(上)-软件设计(三十二)

    信息系统安全 对称加密 软件设计 三十一 https blog csdn net ke1ying article details 129678350 瀑布模型SDLC 是结构化的开发 步奏 软件计划 gt 需求分析 gt 软件设计 gt 程
  • 【信号去噪】基于变分贝叶斯卡尔曼滤波器实现信号去噪附matlab代码

    作者简介 热爱科研的Matlab仿真开发者 修心和技术同步精进 matlab项目合作可私信 个人主页 Matlab科研工作室 个人信条 格物致知 更多Matlab仿真内容点击 智能优化算法 神经网络预测 雷达通信 无线传感器 电力系统 信号
  • Java使用get请求接收List集合数据(json)并导出报表

    Java使用get请求接收List集合数据 json 并导出报表 文章目录 Java使用get请求接收List集合数据 json 并导出报表 前言 一 实现分析 二 Maven依赖 基于EasyExcel实现 三 后台代码 四 使用Post
  • 深度学习(4):基于深层卷积网络实现车牌识别

    目的 基于深层卷积神经网络结合CTC损失函数对车牌进行识别 通过对车牌数据集进行训练获得识别模型 并验证模型性能和将模型进行应用 一 原理 了解深层卷积神经网络构建方法和基本原理 熟悉目标识别相关算法的常规训练流程 掌握CTC损失函数的基本