个人技术总结——CRNN模型详解与实战

222200307叶宇滟 2024-12-10 18:12:51
这个作业属于哪个课程https://bbs.csdn.net/forums/2401_CS_SE_FZU
这个作业要求在哪里https://bbs.csdn.net/topics/619470310
这个作业的目标个人技术总结——CRNN模型详解与实战
其他参考文献

目录

  • 1.技术概述
  • 2.技术详述
  • 2.1 CV环境配置
  • 2.2 CRNN模型训练
  • 2.2.1 数据准备
  • 2.2.2 模型定义
  • 2.2.3 选择损失函数与优化器
  • 2.2.4 训练模型
  • 2.3 CRNN模型预测 / 使用
  • 2.3.1 数据预处理
  • 2.3.2 加载模型
  • 2.3.3 进行预测
  • 3.模型使用中遇到的问题和解决内容
  • 3.1 实际识别时字符准确度不高
  • 3.2 计算资源不足问题
  • 3.3 模型训练收敛缓慢
  • 4.总结
  • 5.参考文献

1.技术概述

CRNN结合了CNN和RNN,用于处理图像中的序列数据。CNN提取空间特征,RNN捕捉序列信息,常用于场景文本识别等。学习CRNN有助于解决图像中信息提取和文字识别,技术难点在于如何高效地结合CNN与RNN,并解决标签与输出序列长度不一致的问题。

img

2.技术详述

2.1 CV环境配置

大部分的CV项目或AI模型都需要安装多个技术框架来进行训练或预测,推荐使用Anaconda进行环境配置,最好减少pip安装(无法解决版本冲突),以下是该项目所需的主要依赖库及安装步骤:

  1. 安装Anaconda、创建并激活虚拟环境

    python -m venv crnn_env
    source crnn_env/bin/activate  # 在Linux/macOS上
    # 或者在Windows上:
    crnn_env\Scripts\activate
    
  2. 使用conda命令安装所需依赖库

    torch和torchvision的安装请参照pytorch的安装命令,因为这个和电脑配置以及cuda版本有关,版本不对就会出现很多有的没的问题

    • lmdb:用于高效地读取和存储数据。LMDB 是一种内存映射数据库,主要用于存储大规模数据集。

    • numpy:用于数组和矩阵操作,是许多科学计算的基础库。

    • Pillow:图像处理库,常用于图像的加载、保存和预处理。

    • six:用于编写兼容 Python 2 和 Python 3 的代码的工具库。

    • torch:PyTorch 主要库,用于构建和训练神经网络。

    • torchvision:PyTorch 官方库,提供了图像处理、数据集等工具,支持常见的 CV 操作和模型。

2.2 CRNN模型训练

训练一个深度学习模型通常包括以下步骤:准备数据集、定义模型结构、选择损失函数和优化器,最后进行训练

2.2.1 数据准备

在训练之前,首先需要准备和预处理数据。这包括以下步骤:

  • 数据集加载:从硬盘加载图像数据集,并分为训练集和验证集。

  • 数据增强:为了提高模型的泛化能力,可以使用数据增强技术(如旋转、缩放、裁剪等)。

  • 数据预处理:如归一化、调整大小等,使得数据符合模型的输入要求。

使用PyTorch的DataLoader加载数据:

from torch.utils.data import DataLoader
from torchvision import datasets, transforms

train_transforms = transforms.Compose([
    transforms.Resize((32, 100)),
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,)),
])

train_dataset = datasets.ImageFolder(root='train_data_path', transform=train_transforms)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)

2.2.2 模型定义

根据任务的需求,定义网络结构。在CRNN的场景下,使用卷积层(CNN)提取特征,然后使用LSTM或GRU等循环神经网络层进行序列建模。

以下是模型代码:

import torch.nn as nn

class BidirectionalLSTM(nn.Module):
    def __init__(self, nIn, nHidden, nOut):
        super(BidirectionalLSTM, self).__init__()
        self.rnn = nn.LSTM(nIn, nHidden, bidirectional=True)
        self.embedding = nn.Linear(nHidden * 2, nOut)

    def forward(self, input):
        recurrent, _ = self.rnn(input)
        T, b, h = recurrent.size()
        t_rec = recurrent.view(T * b, h)
        output = self.embedding(t_rec)  # [T * b, nOut]
        output = output.view(T, b, -1)
        return output

class CRNN(nn.Module):
    def __init__(self, imgH, nc, nclass, nh, n_rnn=2, leakyRelu=False):
        super(CRNN, self).__init__()
        assert imgH % 16 == 0, 'imgH has to be a multiple of 16'
        
        # 定义CNN部分
        ks = [3, 3, 3, 3, 3, 3, 2]
        ps = [1, 1, 1, 1, 1, 1, 0]
        ss = [1, 1, 1, 1, 1, 1, 1]
        nm = [64, 128, 256, 256, 512, 512, 512]
        
        cnn = nn.Sequential()
        def convRelu(i, batchNormalization=False):
            nIn = nc if i == 0 else nm[i - 1]
            nOut = nm[i]
            cnn.add_module('conv{0}'.format(i), nn.Conv2d(nIn, nOut, ks[i], ss[i], ps[i]))
            if batchNormalization:
                cnn.add_module('batchnorm{0}'.format(i), nn.BatchNorm2d(nOut))
            cnn.add_module('relu{0}'.format(i), nn.LeakyReLU(0.2, inplace=True) if leakyRelu else nn.ReLU(True))
        
        convRelu(0)
        cnn.add_module('pooling0', nn.MaxPool2d(2, 2))  # 64x16x64
        convRelu(1)
        cnn.add_module('pooling1', nn.MaxPool2d(2, 2))  # 128x8x32
        convRelu(2, True)
        convRelu(3)
        cnn.add_module('pooling2', nn.MaxPool2d((2, 2), (2, 1), (0, 1)))  # 256x4x16
        convRelu(4, True)
        convRelu(5)
        cnn.add_module('pooling3', nn.MaxPool2d((2, 2), (2, 1), (0, 1)))  # 512x2x16
        convRelu(6, True)  # 512x1x16
        
        self.cnn = cnn
        self.rnn = nn.Sequential(
            BidirectionalLSTM(512, nh, nh),
            BidirectionalLSTM(nh, nh, nclass)
        )

    def forward(self, input):
        conv = self.cnn(input)
        b, c, h, w = conv.size()
        assert h == 1, "the height of conv must be 1"
        conv = conv.squeeze(2)
        conv = conv.permute(2, 0, 1)  # [w, b, c]
        output = self.rnn(conv)
        return output

2.2.3 选择损失函数与优化器

对于序列标注任务,通常使用 CTC(Connectionist Temporal Classification)损失函数。优化器可以选择 AdamRMSprop

criterion = nn.CTCLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

2.2.4 训练模型

训练模型时,我们要根据每个批次的数据计算损失,进行反向传播,并更新模型参数。在训练同时,保存固定批次的模型权重。

训练代码如下:

for epoch in range(num_epochs):
    model.train()  # 训练模式
    for i, (images, labels) in enumerate(train_loader):
        images = images.cuda()
        labels = labels.cuda()
        
        # 前向传播
        outputs = model(images)
        
        # 计算损失
        loss = criterion(outputs, labels)
        
        # 反向传播
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        
        if i % 100 == 0:
            print(f"Epoch [{epoch}/{num_epochs}], Step [{i}/{len(train_loader)}], Loss: {loss.item()}")
            torch.save(model.state_dict(), f'crnn_model.pth_{i}')

2.3 CRNN模型预测 / 使用

2.3.1 数据预处理

在进行预测时,首先需要对输入图像进行处理。这包括调整图像的尺寸、归一化等,使其符合模型的输入要求。

以下为图像预处理代码:

import cv2
import numpy as np

def preprocess_image(image_path, imgH=32):
    # 读取图像并转换为灰度图
    image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
    
    # 将图像高度调整为imgH,宽度根据图像比例自适应调整
    image = cv2.resize(image, (int(image.shape[1] * imgH / image.shape[0]), imgH))

    # 归一化并增加通道维度
    image = image / 255.0  # 归一化
    image = np.expand_dims(image, axis=-1)  # 增加通道维度

    return image

2.3.2 加载模型

加载已保存的训练模型权重,并设置模型为评估模式

model = CRNN(32, 1, len(alphabet)+1, 256)
model.load_state_dict(torch.load('crnn_model.pth'))
model.eval()  # 设置为评估模式
if torch.cuda.is_available():
    model = model.cuda()

2.3.3 进行预测

将预处理后的图像输入到模型中,并解码预测结果。这里使用CTC解码器将模型的输出转换为文本。

def predict(image, model, converter):
    with torch.no_grad():
        preds = model(image)  # 模型预测
    preds_size = torch.IntTensor([preds.size(0)] * image.size(0))  # 每个批次的时间步
    _, preds = preds.max(2)  # 获取最大概率的字符索引
    preds = preds.squeeze(2).transpose(1, 0).contiguous().view(-1)  # 调整形状
    sim_preds = converter.decode(preds.data, preds_size.data, raw=False)  # 解码
    return sim_preds

predicted_text = predict(image, model, converter)
print("Predicted Text:", predicted_text)

3.模型使用中遇到的问题和解决内容

3.1 实际识别时字符准确度不高

问题描述:在使用 CRNN 进行中文字符识别时,可能会遇到模型对中文字符的识别准确度较低的问题,尤其是在文本较复杂、字符之间存在相似性或形态较为特殊时,模型的表现可能无法达到预期效果。

解决方案:针对这个问题,优化模型的首要策略是使用更适合中文文本识别的数据集进行训练。考虑到该项目主要应用于识别录取通知书中的印刷体文字(即主要为规范字体、印刷体),但原数据集可能存在不适配的情况,换用中文语料库(新闻 + 文言文)组合而成的**chinese_ocr数据集**针对这个问题,优化模型的首要策略是使用更适合中文文本识别的数据集进行训练。

3.2 计算资源不足问题

问题描述:在更换数据集后,由于新数据集内存较大,共约364万张图片,约10G左右,在使用GPU进行加速训练时,由于CUDA内存溢出导致训练无法进行。

训练方案:降低训练时批次大小,降低 batch_size,减少每次训练所需的内存,虽然这样可能会导致训练速度稍慢,但可以避免内存溢出。

3.3 模型训练收敛缓慢

问题描述:模型训练过程中,遇到训练收敛缓慢和精度无法提升问题。

解决方案

  • 学习率调整:使用学习率衰减策略(每经过一定的 epoch 降低学习率)来避免学习率过大导致训练不稳定,或者学习率过小导致收敛过慢。

  • 优化器选择:尝试不同的优化器(Adam、RMSprop、Adadelta),并调整其参数(学习率、权重衰减),以找到最适合的优化方案。

4.总结

CRNN能够高效地提取图像特征,并结合RNN的序列建模能力,处理文字识别中的时序依赖问题,适合用于手写识别、场景文本识别等任务。

CRNN作为一种强大的模型,在处理中文字符识别时展现了其巨大潜力。然而,随着文本的复杂性增加,模型仍然面临一些挑战。未来可以通过引入更深的网络结构、更高效的优化算法以及更加丰富的数据集来进一步提升模型的表现。同时,结合更多领域的前沿技术(如Transformer、Attention机制等),可以使得CRNN在各种实际应用场景下达到更高的识别准确率,推动OCR技术向更广泛的行业应用发展。

5.参考文献

  1. 『OCR_Recognition』CRNN
    作者:libo-coder
    链接:https://blog.csdn.net/libo1004/article/details/111595054

  2. CTC理论和实战
    作者:李理
    链接:https://fancyerii.github.io/books/ctc/

  3. LSTM 与 GRU 的比较:选择最佳的时间序列网络
    作者:陈光剑
    链接:https://juejin.cn/post/7315846670641659942

...全文
443 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

109

社区成员

发帖
与我相关
我的任务
社区描述
202401_CS_SE_FZU
软件工程 高校
社区管理员
  • FZU_SE_TeacherL
  • 言1837
  • 防震水泥
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧