用 keras 建立超简单的汉字识别模型

之前看过很多 mnist 的识别模型,都是识别数字的,为啥不做一个汉字识别模型呢?因为汉字手写的库找不到啊。当时我还想自己从字库生成汉字用作识别(已经做出来了,导出字体图片再识别之)。 后来看了这篇文章这篇文章 : CASIA-HWDB 这个神奇的东西。原文是用 tensorflow 实现的,比较复杂,现在改成用 keras 去完成。

数据集下载

    $ wget http://www.nlpr.ia.ac.cn/databases/download/feature_data/HWDB1.1trn_gnt.zip
    # zip 解压没得说, 之后还要解压 alz 压缩文件
    $ wget http://www.nlpr.ia.ac.cn/databases/download/feature_data/HWDB1.1tst_gnt.zip

正好用新学的 keras 来尝试建模识别。

首先要将下载来的 gnt 文件解压。这部分我完全不懂,图像处理部分直接使用他们的代码了。

其中 3500.txt 是常用的 3500 个汉字,这个我用来跟另外一个根据字体生成汉字的脚本配合使用。

    import os
    import numpy as np
    import struct
    from PIL import Image


    data_dir = './hanwriting'
    train_data_dir = os.path.join(data_dir, 'HWDB1.1trn_gnt')
    test_data_dir = os.path.join(data_dir, 'HWDB1.1tst_gnt')

    # f = open('3500.txt', 'r', encoding="utf8")
    f = open('3500.txt', 'r')
    total_words = f.readlines()[0].decode("utf-8")
    print(total_words)

    def read_from_gnt_dir(gnt_dir=train_data_dir):
      def one_file(f):
        header_size = 10
        while True:
          header = np.fromfile(f, dtype='uint8', count=header_size)
          if not header.size:   
            break
          sample_size = header[0]   (header[1] << 8)   (header[2] << 16)   (header[3] << 24)
          tagcode = header[5]   (header[4] << 8)
          width = header[6]   (header[7] << 8)
          height = header[8]   (header[9] << 8)
          if header_size   width*height != sample_size:
            break
          image = np.fromfile(f, dtype='uint8', count=width*height).reshape((height, width))
          yield image, tagcode
      for file_name in os.listdir(gnt_dir):
        if file_name.endswith('.gnt'):
          file_path = os.path.join(gnt_dir, file_name)
          with open(file_path, 'rb') as f:
          for image, tagcode in one_file(f):
            yield image, tagcode
    char_set = set()
    for _, tagcode in read_from_gnt_dir(gnt_dir=train_data_dir):
    tagcode_unicode = struct.pack('>H', tagcode).decode('gb2312')
    char_set.add(tagcode_unicode)
    char_list = list(char_set)
    char_dict = dict(zip(sorted(char_list), range(len(char_list))))
    print len(char_dict)  
    import pickle
    f = open('char_dict', 'wb')
    pickle.dump(char_dict, f)
    f.close()
    train_counter = 0
    test_counter = 0

    for image, tagcode in read_from_gnt_dir(gnt_dir=train_data_dir):
      tagcode_unicode = struct.pack('>H', tagcode).decode('gb2312')
      if tagcode_unicode in total_words:
        im = Image.fromarray(image)
        dir_name = './data/train/'   '%0.5d'%char_dict[tagcode_unicode]
        if not os.path.exists(dir_name):
          os.mkdir(dir_name)
        im.convert('RGB').save(dir_name '/'   str(train_counter)   '.png')
        train_counter  = 1
    for image, tagcode in read_from_gnt_dir(gnt_dir=test_data_dir):
      tagcode_unicode = struct.pack('>H', tagcode).decode('gb2312')
      if tagcode_unicode in total_words:
        im = Image.fromarray(image)
        dir_name = './data/test/'   '%0.5d'%char_dict[tagcode_unicode]
        if not os.path.exists(dir_name):
          os.mkdir(dir_name)
        im.convert('RGB').save(dir_name '/'   str(test_counter)   '.png')
        test_counter  = 1

解压完会生成一个 train 和一个 test 的文件夹,里面分别用数字为文件夹名,里面都是一些别人手写的汉字的图片。

如果用 tensorflow 写的话,大概需要 300 行,需要处理图像(当然 tf 也会帮你处理大部分繁琐的操作),需要写批量加载,还有各种东西。

到了 keras,十分简单。总共的代码就 70 多行,连图像加载和偏移处理都是智能的。图片转换都给你包办了,简直贴心。

    from __future__ import print_function
    import os
    from keras.preprocessing.image import ImageDataGenerator
    from keras.layers import Input, Dense, Dropout, Convolution2D, MaxPooling2D, Flatten
    from keras.models import Model, load_model

    data_dir = './data'
    train_data_dir = os.path.join(data_dir, 'train')
    test_data_dir = os.path.join(data_dir, 'test')

    # dimensions of our images.
    img_width, img_height = 64, 64
    charset_size = 3751
    nb_validation_samples = 800
    nb_samples_per_epoch = 2000
    nb_nb_epoch = 20000;

    def train(model):
      train_datagen = ImageDataGenerator(
        rescale=1. / 255,
        rotation_range=0,
        width_shift_range=0.1,
        height_shift_range=0.1
      )
      test_datagen = ImageDataGenerator(rescale=1./255)

      train_generator = train_datagen.flow_from_directory(
        train_data_dir,
        target_size=(img_width, img_height),
        batch_size=1024,
        color_mode="grayscale",
        class_mode='categorical')
      validation_generator = test_datagen.flow_from_directory(
        test_data_dir,
        target_size=(img_width, img_height),
        batch_size=1024,
        color_mode="grayscale",
        class_mode='categorical')

      model.compile(loss='categorical_crossentropy',
        optimizer='rmsprop',
        metrics=['accuracy'])
      model.fit_generator(train_generator,
        samples_per_epoch=nb_samples_per_epoch,
        nb_epoch=nb_nb_epoch,
        validation_data=validation_generator,
        nb_val_samples=nb_validation_samples)

    def build_model(include_top=True, input_shape=(64, 64, 1), classes=charset_size):
      img_input = Input(shape=input_shape)
      x = Convolution2D(32, 3, 3, activation='relu', border_mode='same', name='block1_conv1')(img_input)
      x = Convolution2D(32, 3, 3, activation='relu', border_mode='same', name='block1_conv2')(x)
      x = MaxPooling2D((2, 2), strides=(2, 2), name='block1_pool')(x)
      x = Convolution2D(64, 3, 3, activation='relu', border_mode='same', name='block2_conv1')(x)
      x = Convolution2D(64, 3, 3, activation='relu', border_mode='same', name='block2_conv2')(x)
      x = MaxPooling2D((2, 2), strides=(2, 2), name='block2_pool')(x)

      if include_top:
        x = Flatten(name='flatten')(x)
        x = Dropout(0.05)(x)
        x = Dense(1024, activation='relu', name='fc2')(x)
        x = Dense(classes, activation='softmax', name='predictions')(x)

      model = Model(img_input, x, name='model')
      return model

    model = build_model()
    # model = load_model("./model.h5")
    train(model)
    # model.save("./model.h5")

可以看到生成模型的代码就 12 行,十分简洁。开头两套双卷积池化层,后面接一个 dropout 防过拟合,再接两个全链接层,最后一个 softmax 输出结果。 于是开我的 GTX1080 机器开跑,大约花了半天时间。

Epoch 20000/20000 1024/2000 [==============>...............] - ETA: 0s - loss: 0.2178 - acc: 0.9482 2048/2000 [==============================] - 2s - loss: 0.2118 - acc: 0.9478 - val_loss: 0.4246 - val_acc: 0.9102

在 20000 次 Epoch 后,准确率在 95%,验证的准确率在 91%左右,基本可以识别大部分库里的汉字了。

实际看来汉字识别是图像识别的一种,不过汉字数量比较多,很多手写的连人类都无法识别,估计难以达到 mnist 数据集的准确率。

最后可以看到,keras 是非常适合新手阶段去尝试的,代码也十分简洁。不过由于底层隐藏的比较深,如果深入研究的话容易会遇到瓶颈,而且包装太多,想对他做出修改优化也不是太容易。后期研究还是建议使用 tensorflow 和 pytorch。(个人在看 pytorch,比 tensorflow 要简洁不少,而且大部分 paper 都移植过去了,github 最近热门全是他,潜力无限)

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

陈佐琪的专栏

1 篇文章1 人订阅

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏AI科技评论

开发 | 如何为TensorFlow和PyTorch自动选择空闲GPU,解决抢卡争端

AI科技评论按:本文作者天清,原文载于其知乎专栏 世界那么大我想写代码,AI科技评论获授权发布。 项目地址:https://github.com/Quantum...

3838
来自专栏AI研习社

让系统自动选择空闲的GPU设备!帮你一次解决抢卡争端

项目地址:QuantumLiu / tf_gpu_manager 更新:支持pytorch 使用 git clone https://github.com/...

55511
来自专栏机器学习实践二三事

Caffe中均值文件的问题

关于均值文件 (1) 在Caffe中作classification时经常需要使用均值文件,但是caffe自己提供的脚本只能将图像数据转换为 binarypr...

2089
来自专栏ATYUN订阅号

将Keras权值保存为动画视频,更好地了解模型是如何学习的

将Keras权值矩阵保存为简短的动画视频,从而更好地理解你的神经网络模型是如何学习的。下面是第一个LSTM层的例子,以及一个经过一个学习周期训练的6级RNN模型...

3464
来自专栏人工智能头条

技术 | 机器学习中Python库的3个简单实践——你的图片将由你来创造

【导读】今天为大家介绍机器学习、深度学习中一些优秀、有意思的 Python 库,以及这些库的 Code 实践教程。涉及到的理论与学术内容会附上相应的论文与博客,...

1524
来自专栏和蔼的张星的图像处理专栏

LCT代码跑起来先文章思路总结

论文才刚开始看,但是代码先跑了一下看结果,有一点小坑,记录下: 首先去论文的github上去下载代码:点这里 readme里其实写了怎么搞:

2933
来自专栏专知

【干货】主题模型如何帮助法律部门提取PDF摘要及可视化(附代码)

【导读】本文是Oguejiofor Chibueze于1月25日发布的一篇实用向博文,详细介绍了如何将主题模型应用于法律部门。文章中,作者分析了律师在浏览大量的...

3537
来自专栏AI研习社

手把手教你搭建能够实现 Prisma 风格迁移效果的 iOS 酷炫应用(附代码)

随着 2012 年深度神经网络在 ImageNetchallenge 比赛上以 AlexNet 模型获胜,深度神经网络开创了空前的高潮。AI 工程师已经将深度学...

1453
来自专栏AI研习社

发掘 ARM GPU 的全部深度学习性能,TVM 优化带来高达 2 倍性能提升

本文是由来自上海交通大学 Apex 实验室的本科生 Lianmin Zheng 发表于 TVM 的一篇博客,文中阐述了如何使用 TVM 优化移动端上的 ARM...

93510
来自专栏ATYUN订阅号

计算机视觉项目:用dlib进行单目标跟踪

本教程将教你如何使用dlib和Python执行目标跟踪(object tracking)。阅读今天的博客文章后,你将能够使用dlib实时跟踪视频中的目标。

3372

扫码关注云+社区