PaddleOCR/notebook/notebook_ch/3.text_recognition/文本识别实践部分.ipynb

2538 lines
206 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"# 文本识别实战\n",
"\n",
"上一章理论部分介绍了文本识别领域的主要方法其中CRNN是较早被提出也是目前工业界应用较多的方法。本章将详细介绍如何基于PaddleOCR完成CRNN文本识别模型的搭建、训练、评估和预测。数据集采用 icdar 2015其中训练集有4468张测试集有2077张。\n",
"\n",
"\n",
"通过本章的学习,你可以掌握:\n",
"\n",
"1. 如何使用paddleocr whl 包快速完成文本识别预测\n",
"\n",
"2. CRNN的基本原理和网络结构\n",
"\n",
"3. 模型训练的必须步骤和调参方式\n",
"\n",
"4. 使用自定义的数据集训练网络\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"## 1. 快速体验\n",
"\n",
"### 1.1 安装相关的依赖及whl包\n",
"\n",
"首先确认安装了 paddle 以及 paddleocr如果已经安装过忽略该步骤。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple\n",
"Requirement already satisfied: paddlepaddle-gpu in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (2.1.2.post101)\n",
"Requirement already satisfied: protobuf>=3.1.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from paddlepaddle-gpu) (3.14.0)\n",
"Requirement already satisfied: numpy>=1.13; python_version >= \"3.5\" and platform_system != \"Windows\" in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from paddlepaddle-gpu) (1.20.3)\n",
"Requirement already satisfied: Pillow in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from paddlepaddle-gpu) (7.1.2)\n",
"Requirement already satisfied: six in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from paddlepaddle-gpu) (1.15.0)\n",
"Requirement already satisfied: requests>=2.20.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from paddlepaddle-gpu) (2.22.0)\n",
"Requirement already satisfied: astor in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from paddlepaddle-gpu) (0.8.1)\n",
"Requirement already satisfied: decorator in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from paddlepaddle-gpu) (4.4.2)\n",
"Requirement already satisfied: gast<=0.4.0,>=0.3.3; platform_system != \"Windows\" in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from paddlepaddle-gpu) (0.3.3)\n",
"Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from requests>=2.20.0->paddlepaddle-gpu) (1.25.6)\n",
"Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from requests>=2.20.0->paddlepaddle-gpu) (2019.9.11)\n",
"Requirement already satisfied: idna<2.9,>=2.5 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from requests>=2.20.0->paddlepaddle-gpu) (2.8)\n",
"Requirement already satisfied: chardet<3.1.0,>=3.0.2 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from requests>=2.20.0->paddlepaddle-gpu) (3.0.4)\n",
"Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple\n",
"Collecting pip\n",
"\u001b[?25l Downloading https://pypi.tuna.tsinghua.edu.cn/packages/a4/6d/6463d49a933f547439d6b5b98b46af8742cc03ae83543e4d7688c2420f8b/pip-21.3.1-py3-none-any.whl (1.7MB)\n",
"\u001b[K |████████████████████████████████| 1.7MB 8.4MB/s eta 0:00:01\n",
"\u001b[?25hInstalling collected packages: pip\n",
" Found existing installation: pip 19.2.3\n",
" Uninstalling pip-19.2.3:\n",
" Successfully uninstalled pip-19.2.3\n",
"Successfully installed pip-21.3.1\n",
"Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple\n",
"Collecting paddleocr\n",
" Downloading https://pypi.tuna.tsinghua.edu.cn/packages/e1/b6/5486e674ce096667dff247b58bf0fb789c2ce17a10e546c2686a2bb07aec/paddleocr-2.3.0.2-py3-none-any.whl (250 kB)\n",
" |████████████████████████████████| 250 kB 3.3 MB/s \n",
"\u001b[?25hCollecting lmdb\n",
" Downloading https://pypi.tuna.tsinghua.edu.cn/packages/2e/dd/ada2fd91cd7832979069c556607903f274470c3d3d2274e0a848908272e8/lmdb-1.2.1-cp37-cp37m-manylinux2010_x86_64.whl (299 kB)\n",
" |████████████████████████████████| 299 kB 12.8 MB/s \n",
"\u001b[?25hCollecting lxml\n",
" Downloading https://pypi.tuna.tsinghua.edu.cn/packages/7b/01/16a9b80c8ce4339294bb944f08e157dbfcfbb09ba9031bde4ddf7e3e5499/lxml-4.7.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl (6.4 MB)\n",
" |████████████████████████████████| 6.4 MB 52.4 MB/s \n",
"\u001b[?25hCollecting python-Levenshtein\n",
" Downloading https://pypi.tuna.tsinghua.edu.cn/packages/2a/dc/97f2b63ef0fa1fd78dcb7195aca577804f6b2b51e712516cc0e902a9a201/python-Levenshtein-0.12.2.tar.gz (50 kB)\n",
" |████████████████████████████████| 50 kB 1.6 MB/s \n",
"\u001b[?25h Preparing metadata (setup.py) ... \u001b[?25ldone\n",
"\u001b[?25hCollecting scikit-image\n",
" Downloading https://pypi.tuna.tsinghua.edu.cn/packages/9a/44/8f8c7f9c9de7fde70587a656d7df7d056e6f05192a74491f7bc074a724d0/scikit_image-0.19.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (13.3 MB)\n",
" |████████████████████████████████| 13.3 MB 56.1 MB/s \n",
"\u001b[?25hRequirement already satisfied: numpy in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from paddleocr) (1.20.3)\n",
"Collecting imgaug==0.4.0\n",
" Downloading https://pypi.tuna.tsinghua.edu.cn/packages/66/b1/af3142c4a85cba6da9f4ebb5ff4e21e2616309552caca5e8acefe9840622/imgaug-0.4.0-py2.py3-none-any.whl (948 kB)\n",
" |████████████████████████████████| 948 kB 62.9 MB/s \n",
"\u001b[?25hCollecting opencv-contrib-python==4.4.0.46\n",
" Downloading https://pypi.tuna.tsinghua.edu.cn/packages/08/51/1e0a206dd5c70fea91084e6f43979dc13e8eb175760cc7a105083ec3eb68/opencv_contrib_python-4.4.0.46-cp37-cp37m-manylinux2014_x86_64.whl (55.7 MB)\n",
" |████████████████████████████████| 55.7 MB 44 kB/s 0:01\n",
"\u001b[?25hCollecting premailer\n",
" Downloading https://pypi.tuna.tsinghua.edu.cn/packages/b1/07/4e8d94f94c7d41ca5ddf8a9695ad87b888104e2fd41a35546c1dc9ca74ac/premailer-3.10.0-py2.py3-none-any.whl (19 kB)\n",
"Collecting shapely\n",
" Downloading https://pypi.tuna.tsinghua.edu.cn/packages/ae/20/33ce377bd24d122a4d54e22ae2c445b9b1be8240edb50040b40add950cd9/Shapely-1.8.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl (1.1 MB)\n",
" |████████████████████████████████| 1.1 MB 14.5 MB/s \n",
"\u001b[?25hRequirement already satisfied: visualdl in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from paddleocr) (2.2.0)\n",
"Collecting fasttext==0.9.1\n",
" Downloading https://pypi.tuna.tsinghua.edu.cn/packages/10/61/2e01f1397ec533756c1d893c22d9d5ed3fce3a6e4af1976e0d86bb13ea97/fasttext-0.9.1.tar.gz (57 kB)\n",
" |████████████████████████████████| 57 kB 9.0 MB/s \n",
"\u001b[?25h Preparing metadata (setup.py) ... \u001b[?25ldone\n",
"\u001b[?25hRequirement already satisfied: cython in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from paddleocr) (0.29)\n",
"Requirement already satisfied: openpyxl in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from paddleocr) (3.0.5)\n",
"Collecting pyclipper\n",
" Downloading https://pypi.tuna.tsinghua.edu.cn/packages/c5/fa/2c294127e4f88967149a68ad5b3e43636e94e3721109572f8f17ab15b772/pyclipper-1.3.0.post2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl (603 kB)\n",
" |████████████████████████████████| 603 kB 7.6 MB/s \n",
"\u001b[?25hRequirement already satisfied: tqdm in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from paddleocr) (4.36.1)\n",
"Collecting pybind11>=2.2\n",
" Using cached https://pypi.tuna.tsinghua.edu.cn/packages/a8/3b/fc246e1d4c7547a7a07df830128e93c6215e9b93dcb118b2a47a70726153/pybind11-2.8.1-py2.py3-none-any.whl (208 kB)\n",
"Requirement already satisfied: setuptools>=0.7.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from fasttext==0.9.1->paddleocr) (56.2.0)\n",
"Requirement already satisfied: Pillow in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from imgaug==0.4.0->paddleocr) (7.1.2)\n",
"Requirement already satisfied: imageio in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from imgaug==0.4.0->paddleocr) (2.6.1)\n",
"Requirement already satisfied: scipy in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from imgaug==0.4.0->paddleocr) (1.6.3)\n",
"Requirement already satisfied: opencv-python in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from imgaug==0.4.0->paddleocr) (4.1.1.26)\n",
"Requirement already satisfied: matplotlib in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from imgaug==0.4.0->paddleocr) (2.2.3)\n",
"Requirement already satisfied: six in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from imgaug==0.4.0->paddleocr) (1.15.0)\n",
"Collecting PyWavelets>=1.1.1\n",
" Downloading https://pypi.tuna.tsinghua.edu.cn/packages/a1/9c/564511b6e1c4e1d835ed2d146670436036960d09339a8fa2921fe42dad08/PyWavelets-1.2.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl (6.1 MB)\n",
" |████████████████████████████████| 6.1 MB 3.8 MB/s \n",
"\u001b[?25hRequirement already satisfied: packaging>=20.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from scikit-image->paddleocr) (20.9)\n",
"Requirement already satisfied: networkx>=2.2 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from scikit-image->paddleocr) (2.4)\n",
"Collecting tifffile>=2019.7.26\n",
" Downloading https://pypi.tuna.tsinghua.edu.cn/packages/d8/38/85ae5ed77598ca90558c17a2f79ddaba33173b31cf8d8f545d34d9134f0d/tifffile-2021.11.2-py3-none-any.whl (178 kB)\n",
" |████████████████████████████████| 178 kB 7.1 MB/s \n",
"\u001b[?25hRequirement already satisfied: et-xmlfile in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from openpyxl->paddleocr) (1.0.1)\n",
"Requirement already satisfied: jdcal in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from openpyxl->paddleocr) (1.4.1)\n",
"Requirement already satisfied: requests in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from premailer->paddleocr) (2.22.0)\n",
"Collecting cssselect\n",
" Downloading https://pypi.tuna.tsinghua.edu.cn/packages/3b/d4/3b5c17f00cce85b9a1e6f91096e1cc8e8ede2e1be8e96b87ce1ed09e92c5/cssselect-1.1.0-py2.py3-none-any.whl (16 kB)\n",
"Collecting cssutils\n",
" Downloading https://pypi.tuna.tsinghua.edu.cn/packages/24/c4/9db28fe567612896d360ab28ad02ee8ae107d0e92a22db39affd3fba6212/cssutils-2.3.0-py3-none-any.whl (404 kB)\n",
" |████████████████████████████████| 404 kB 134 kB/s \n",
"\u001b[?25hRequirement already satisfied: cachetools in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from premailer->paddleocr) (4.0.0)\n",
"Requirement already satisfied: pre-commit in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from visualdl->paddleocr) (1.21.0)\n",
"Requirement already satisfied: Flask-Babel>=1.0.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from visualdl->paddleocr) (1.0.0)\n",
"Requirement already satisfied: flask>=1.1.1 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from visualdl->paddleocr) (1.1.1)\n",
"Requirement already satisfied: flake8>=3.7.9 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from visualdl->paddleocr) (3.8.2)\n",
"Requirement already satisfied: shellcheck-py in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from visualdl->paddleocr) (0.7.1.1)\n",
"Requirement already satisfied: pandas in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from visualdl->paddleocr) (1.1.5)\n",
"Requirement already satisfied: bce-python-sdk in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from visualdl->paddleocr) (0.8.53)\n",
"Requirement already satisfied: protobuf>=3.11.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from visualdl->paddleocr) (3.14.0)\n",
"Requirement already satisfied: pyflakes<2.3.0,>=2.2.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from flake8>=3.7.9->visualdl->paddleocr) (2.2.0)\n",
"Requirement already satisfied: pycodestyle<2.7.0,>=2.6.0a1 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from flake8>=3.7.9->visualdl->paddleocr) (2.6.0)\n",
"Requirement already satisfied: importlib-metadata in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from flake8>=3.7.9->visualdl->paddleocr) (0.23)\n",
"Requirement already satisfied: mccabe<0.7.0,>=0.6.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from flake8>=3.7.9->visualdl->paddleocr) (0.6.1)\n",
"Requirement already satisfied: itsdangerous>=0.24 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from flask>=1.1.1->visualdl->paddleocr) (1.1.0)\n",
"Requirement already satisfied: Jinja2>=2.10.1 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from flask>=1.1.1->visualdl->paddleocr) (2.11.0)\n",
"Requirement already satisfied: click>=5.1 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from flask>=1.1.1->visualdl->paddleocr) (7.0)\n",
"Requirement already satisfied: Werkzeug>=0.15 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from flask>=1.1.1->visualdl->paddleocr) (0.16.0)\n",
"Requirement already satisfied: pytz in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from Flask-Babel>=1.0.0->visualdl->paddleocr) (2019.3)\n",
"Requirement already satisfied: Babel>=2.3 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from Flask-Babel>=1.0.0->visualdl->paddleocr) (2.8.0)\n",
"Requirement already satisfied: decorator>=4.3.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from networkx>=2.2->scikit-image->paddleocr) (4.4.2)\n",
"Requirement already satisfied: pyparsing>=2.0.2 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from packaging>=20.0->scikit-image->paddleocr) (2.4.2)\n",
"Requirement already satisfied: pycryptodome>=3.8.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from bce-python-sdk->visualdl->paddleocr) (3.9.9)\n",
"Requirement already satisfied: future>=0.6.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from bce-python-sdk->visualdl->paddleocr) (0.18.0)\n",
"Requirement already satisfied: kiwisolver>=1.0.1 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from matplotlib->imgaug==0.4.0->paddleocr) (1.1.0)\n",
"Requirement already satisfied: python-dateutil>=2.1 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from matplotlib->imgaug==0.4.0->paddleocr) (2.8.0)\n",
"Requirement already satisfied: cycler>=0.10 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from matplotlib->imgaug==0.4.0->paddleocr) (0.10.0)\n",
"Requirement already satisfied: aspy.yaml in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from pre-commit->visualdl->paddleocr) (1.3.0)\n",
"Requirement already satisfied: virtualenv>=15.2 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from pre-commit->visualdl->paddleocr) (16.7.9)\n",
"Requirement already satisfied: nodeenv>=0.11.1 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from pre-commit->visualdl->paddleocr) (1.3.4)\n",
"Requirement already satisfied: pyyaml in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from pre-commit->visualdl->paddleocr) (5.1.2)\n",
"Requirement already satisfied: cfgv>=2.0.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from pre-commit->visualdl->paddleocr) (2.0.1)\n",
"Requirement already satisfied: identify>=1.0.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from pre-commit->visualdl->paddleocr) (1.4.10)\n",
"Requirement already satisfied: toml in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from pre-commit->visualdl->paddleocr) (0.10.0)\n",
"Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from requests->premailer->paddleocr) (1.25.6)\n",
"Requirement already satisfied: chardet<3.1.0,>=3.0.2 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from requests->premailer->paddleocr) (3.0.4)\n",
"Requirement already satisfied: idna<2.9,>=2.5 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from requests->premailer->paddleocr) (2.8)\n",
"Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from requests->premailer->paddleocr) (2019.9.11)\n",
"Requirement already satisfied: MarkupSafe>=0.23 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from Jinja2>=2.10.1->flask>=1.1.1->visualdl->paddleocr) (1.1.1)\n",
"Requirement already satisfied: zipp>=0.5 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from importlib-metadata->flake8>=3.7.9->visualdl->paddleocr) (3.6.0)\n",
"Building wheels for collected packages: fasttext, python-Levenshtein\n",
" Building wheel for fasttext (setup.py) ... \u001b[?25ldone\n",
"\u001b[?25h Created wheel for fasttext: filename=fasttext-0.9.1-cp37-cp37m-linux_x86_64.whl size=2584156 sha256=acb4d4fde73d31c7dfdd2ae3de0da25a558c34c672d4904e6a5c4279185fe5af\n",
" Stored in directory: /home/aistudio/.cache/pip/wheels/a1/cb/b3/a25a8ce16c1a4ff102c1e40d6eaa4dfc9d5695b92d57331b36\n",
" Building wheel for python-Levenshtein (setup.py) ... \u001b[?25ldone\n",
"\u001b[?25h Created wheel for python-Levenshtein: filename=python_Levenshtein-0.12.2-cp37-cp37m-linux_x86_64.whl size=171687 sha256=56b4a2de4349a05004121050df68b488ffd253dcc59187ca07b89b62d40c0218\n",
" Stored in directory: /home/aistudio/.cache/pip/wheels/38/b9/a4/3729726160fb103833de468adb5ce019b58543ae41d0b0e446\n",
"Successfully built fasttext python-Levenshtein\n",
"Installing collected packages: tifffile, PyWavelets, shapely, scikit-image, pybind11, lxml, cssutils, cssselect, python-Levenshtein, pyclipper, premailer, opencv-contrib-python, lmdb, imgaug, fasttext, paddleocr\n",
"Successfully installed PyWavelets-1.2.0 cssselect-1.1.0 cssutils-2.3.0 fasttext-0.9.1 imgaug-0.4.0 lmdb-1.2.1 lxml-4.7.1 opencv-contrib-python-4.4.0.46 paddleocr-2.3.0.2 premailer-3.10.0 pybind11-2.8.1 pyclipper-1.3.0.post2 python-Levenshtein-0.12.2 scikit-image-0.19.1 shapely-1.8.0 tifffile-2021.11.2\n"
]
}
],
"source": [
"# 安装 PaddlePaddle GPU 版本\n",
"!pip install paddlepaddle-gpu\n",
"# 安装 paddleocr whl包\n",
"! pip install -U pip\n",
"! pip install paddleocr"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"### 1.2 快速预测文字内容\n",
"\n",
"paddleocr whl包会自动下载ppocr轻量级模型作为默认模型\n",
"\n",
"下面展示如何使用whl包进行识别预测\n",
"\n",
"测试图片:\n",
"\n",
"![](https://ai-studio-static-online.cdn.bcebos.com/531d9b3aff45449893b33bcb5dd13971057fcb4038f045578b3abd99fa3a96f2)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[2021/12/23 20:28:44] root WARNING: version 2.1 not support cls models, use version 2.0 instead\n",
"download https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_det_infer.tar to /home/aistudio/.paddleocr/2.2.1/ocr/det/ch/ch_PP-OCRv2_det_infer/ch_PP-OCRv2_det_infer.tar\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/skimage/morphology/_skeletonize.py:241: DeprecationWarning: `np.bool` is a deprecated alias for the builtin `bool`. To silence this warning, use `bool` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.bool_` here.\n",
"Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations\n",
" 0, 1, 1, 0, 0, 1, 0, 0, 0], dtype=np.bool)\n",
"/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/skimage/morphology/_skeletonize.py:256: DeprecationWarning: `np.bool` is a deprecated alias for the builtin `bool`. To silence this warning, use `bool` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.bool_` here.\n",
"Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0], dtype=np.bool)\n",
" 0%| | 0.00/3.19M [00:00<?, ?iB/s]100%|██████████| 3.19M/3.19M [00:00<00:00, 7.80MiB/s]\n",
" 14%|█▎ | 1.20M/8.88M [00:00<00:00, 11.7MiB/s]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"download https://paddleocr.bj.bcebos.com/PP-OCRv2/chinese/ch_PP-OCRv2_rec_infer.tar to /home/aistudio/.paddleocr/2.2.1/ocr/rec/ch/ch_PP-OCRv2_rec_infer/ch_PP-OCRv2_rec_infer.tar\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 24%|██▍ | 2.15M/8.88M [00:00<00:00, 10.8MiB/s]100%|██████████| 8.88M/8.88M [00:01<00:00, 6.38MiB/s]\n",
" 17%|█▋ | 249k/1.45M [00:00<00:00, 2.42MiB/s]"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"download https://paddleocr.bj.bcebos.com/dygraph_v2.0/ch/ch_ppocr_mobile_v2.0_cls_infer.tar to /home/aistudio/.paddleocr/2.2.1/ocr/cls/ch_ppocr_mobile_v2.0_cls_infer/ch_ppocr_mobile_v2.0_cls_infer.tar\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
" 90%|█████████ | 1.31M/1.45M [00:00<00:00, 3.32MiB/s]100%|██████████| 1.45M/1.45M [00:00<00:00, 4.53MiB/s]\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Namespace(benchmark=False, cls_batch_num=6, cls_image_shape='3, 48, 192', cls_model_dir='/home/aistudio/.paddleocr/2.2.1/ocr/cls/ch_ppocr_mobile_v2.0_cls_infer', cls_thresh=0.9, cpu_threads=10, det=True, det_algorithm='DB', det_db_box_thresh=0.6, det_db_score_mode='fast', det_db_thresh=0.3, det_db_unclip_ratio=1.5, det_east_cover_thresh=0.1, det_east_nms_thresh=0.2, det_east_score_thresh=0.8, det_limit_side_len=960, det_limit_type='max', det_model_dir='/home/aistudio/.paddleocr/2.2.1/ocr/det/ch/ch_PP-OCRv2_det_infer', det_sast_nms_thresh=0.2, det_sast_polygon=False, det_sast_score_thresh=0.5, drop_score=0.5, e2e_algorithm='PGNet', e2e_char_dict_path='./ppocr/utils/ic15_dict.txt', e2e_limit_side_len=768, e2e_limit_type='max', e2e_model_dir=None, e2e_pgnet_mode='fast', e2e_pgnet_polygon=True, e2e_pgnet_score_thresh=0.5, e2e_pgnet_valid_set='totaltext', enable_mkldnn=False, gpu_mem=500, help='==SUPPRESS==', image_dir=None, ir_optim=True, label_list=['0', '180'], lang='ch', layout_path_model='lp://PubLayNet/ppyolov2_r50vd_dcn_365e_publaynet/config', max_batch_size=10, max_text_length=25, min_subgraph_size=15, output='./output/table', precision='fp32', process_id=0, rec=True, rec_algorithm='CRNN', rec_batch_num=6, rec_char_dict_path='/home/aistudio/PaddleOCR/ppocr/utils/ppocr_keys_v1.txt', rec_char_type='ch', rec_image_shape='3, 32, 320', rec_model_dir='/home/aistudio/.paddleocr/2.2.1/ocr/rec/ch/ch_PP-OCRv2_rec_infer', save_log_path='./log_output/', show_log=True, table_char_dict_path=None, table_char_type='en', table_max_len=488, table_model_dir=None, total_process_num=1, type='ocr', use_angle_cls=False, use_dilation=False, use_gpu=True, use_mp=False, use_pdserving=False, use_space_char=True, use_tensorrt=False, version='2.1', vis_font_path='./doc/fonts/simfang.ttf', warmup=True)\n",
"[2021/12/23 20:28:48] root WARNING: Since the angle classifier is not initialized, the angle classifier will not be uesd during the forward process\n",
"('SLOW', 0.9776376)\n"
]
}
],
"source": [
"from paddleocr import PaddleOCR\n",
"\n",
"ocr = PaddleOCR() # need to run only once to download and load model into memory\n",
"img_path = '/home/aistudio/work/word_19.png'\n",
"result = ocr.ocr(img_path, det=False)\n",
"for line in result:\n",
" print(line)"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"执行完上述代码块,将返回识别结果和识别置信度\n",
"\n",
"```\n",
"('SLOW', 0.9776376)\n",
"```\n",
"\n",
"至此,你掌握了如何使用 paddleocr whl 包进行预测。`./work/` 路径下有更多测试图片,可以尝试其他图片结果。"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"## 2. 预测原理详解\n",
"\n",
"第一节中 paddleocr 加载训练好的 CRNN 识别模型进行预测,本节将详细介绍 CRNN 的原理及流程。\n",
"\n",
"### 2.1 所属类别\n",
"\n",
"CRNN 是基于CTC的算法在理论部分介绍的分类图中处在如下位置。可以看出CRNN主要用于解决规则文本基于CTC的算法有较快的预测速度并且很好的适用长文本。因此CRNN是PPOCR选择的中文识别算法。\n",
"<center><img src=https://ai-studio-static-online.cdn.bcebos.com/0e74d46918e5423684e06df8e6eb992cf9f7c485d6c142fb98cc263714559898 width=\"600\"></center>\n",
"\n",
"\n",
"### 2.2 算法详解\n",
"\n",
"CRNN 的网络结构体系如下所示,从下往上分别为卷积层、递归层和转录层三部分:\n",
"\n",
"<center><img src=https://ai-studio-static-online.cdn.bcebos.com/f6fae3ff66bd413fa182d75782034a2af6aab1994fa148a08e6565f3fb75b18d width=\"600\"></center>\n",
"\n",
"1)backbone\n",
"\n",
"卷积网络作为底层的骨干网络,用于从输入图像中提取特征序列。由于 `conv`、`max-pooling`、`elementwise` 和激活函数都作用在局部区域上,所以它们是平移不变的。因此,特征映射的每一列对应于原始图像的一个矩形区域(称为感受野)并且这些矩形区域与它们在特征映射上对应的列从左到右的顺序相同。由于CNN需要将输入的图像缩放到固定的尺寸以满足其固定的输入维数因此它不适合长度变化很大的序列对象。为了更好的支持变长序列CRNN将backbone最后一层输出的特征向量送到了RNN层转换为序列特征。\n",
"\n",
"<center><img src=https://ai-studio-static-online.cdn.bcebos.com/6694818123724b0d92d05b63dc9dfb08c7ced6c47c3b4f4d9b110ae9ccfe941d width=\"600\"></center>\n",
"\n",
"2)neck: \n",
"\n",
"递归层,在卷积网络的基础上,构建递归网络,将图像特征转换为序列特征,预测每个帧的标签分布。\n",
"RNN具有很强的捕获序列上下文信息的能力。使用上下文线索进行基于图像的序列识别比单独处理每个像素更有效。以场景文本识别为例宽字符可能需要几个连续的帧来充分描述。此外有些歧义字符在观察其上下文时更容易区分。其次RNN可以将误差差分反向传播回卷积层使网络可以统一训练。第三RNN能够对任意长度的序列进行操作解决了文本图片变长的问题。CRNN使用双层LSTM作为递归层解决了长序列训练过程中的梯度消失和梯度爆炸问题。\n",
"\n",
"<center><img src=https://ai-studio-static-online.cdn.bcebos.com/41cdb7fb08fb4b55923b0baf66b783e46fd063223d05416fa952369ad20ac83c width=\"600\"></center>\n",
"\n",
"\n",
"3)head: \n",
"\n",
"转录层通过全连接网络和softmax激活函数将每帧的预测转换为最终的标签序列。最后使用 CTC Loss 在无需序列对齐的情况下完成CNN和RNN的联合训练。CTC 有一套特别的合并序列机制LSTM输出序列后需要在时序上分类得到预测结果。可能存在多个时间步对应同一个类别因此需要对相同结果进行合并。为避免合并本身存在的重复字符CTC 引入了一个 `blank` 字符插入在重复字符之间。\n",
"\n",
"<center><img src=https://ai-studio-static-online.cdn.bcebos.com/bea6be2f5d9d4ac791118737c3f2f140a2a261e0d8a540a3b0ef239b6bcb2c43 width=\"600\"></center>\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"### 2.3 代码实现\n",
"\n",
"整个网络结构非常简洁代码实现也相对简单可以跟随预测流程依次搭建模块。本节需要完成数据输入、backbone搭建、neck搭建、head搭建。\n",
"\n",
"**【数据输入】**\n",
"\n",
"数据送入网络前需要缩放到统一尺寸(3,32,320),并完成归一化处理。这里省略掉训练时需要的数据增强部分,以单张图为例展示预处理的必须步骤([源码位置](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.3/ppocr/data/imaug/rec_img_aug.py#L126)\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import cv2\n",
"import math\n",
"import numpy as np\n",
"\n",
"def resize_norm_img(img):\n",
" \"\"\"\n",
" 数据缩放和归一化\n",
" :param img: 输入图片\n",
" \"\"\"\n",
"\n",
" # 默认输入尺寸\n",
" imgC = 3\n",
" imgH = 32\n",
" imgW = 320\n",
"\n",
" # 图片的真实高宽\n",
" h, w = img.shape[:2]\n",
" # 图片真实长宽比\n",
" ratio = w / float(h)\n",
"\n",
" # 按比例缩放\n",
" if math.ceil(imgH * ratio) > imgW:\n",
" # 如大于默认宽度则宽度为imgW\n",
" resized_w = imgW\n",
" else:\n",
" # 如小于默认宽度则以图片真实宽为准\n",
" resized_w = int(math.ceil(imgH * ratio))\n",
" # 缩放\n",
" resized_image = cv2.resize(img, (resized_w, imgH))\n",
" resized_image = resized_image.astype('float32')\n",
" # 归一化\n",
" resized_image = resized_image.transpose((2, 0, 1)) / 255\n",
" resized_image -= 0.5\n",
" resized_image /= 0.5\n",
" # 对宽度不足的位置补0\n",
" padding_im = np.zeros((imgC, imgH, imgW), dtype=np.float32)\n",
" padding_im[:, :, 0:resized_w] = resized_image\n",
" # 转置 padding 后的图片用于可视化\n",
" draw_img = padding_im.transpose((1,2,0))\n",
" return padding_im, draw_img\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).\n"
]
},
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"import matplotlib.pyplot as plt\n",
"# 读图\n",
"raw_img = cv2.imread(\"/home/aistudio/work/word_1.png\")\n",
"plt.figure()\n",
"plt.subplot(2,1,1)\n",
"# 可视化原图\n",
"plt.imshow(raw_img)\n",
"# 缩放并归一化\n",
"padding_im, draw_img = resize_norm_img(raw_img)\n",
"plt.subplot(2,1,2)\n",
"# 可视化网络输入图\n",
"plt.imshow(draw_img)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"**【网络结构】**\n",
"\n",
"* backbone\n",
"\n",
"PaddleOCR 使用 MobileNetV3 作为骨干网络,组网顺序与网络结构一致,首先定义网络中的公共模块([源码位置](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.3/ppocr/modeling/backbones/rec_mobilenet_v3.py))ConvBNLayer、ResidualUnit、make_divisible"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import paddle\n",
"import paddle.nn as nn\n",
"import paddle.nn.functional as F\n",
"\n",
"class ConvBNLayer(nn.Layer):\n",
" def __init__(self,\n",
" in_channels,\n",
" out_channels,\n",
" kernel_size,\n",
" stride,\n",
" padding,\n",
" groups=1,\n",
" if_act=True,\n",
" act=None):\n",
" \"\"\"\n",
" 卷积BN层\n",
" :param in_channels: 输入通道数\n",
" :param out_channels: 输出通道数\n",
" :param kernel_size: 卷积核尺寸\n",
" :parma stride: 步长大小\n",
" :param padding: 填充大小\n",
" :param groups: 二维卷积层的组数\n",
" :param if_act: 是否添加激活函数\n",
" :param act: 激活函数\n",
" \"\"\"\n",
" super(ConvBNLayer, self).__init__()\n",
" self.if_act = if_act\n",
" self.act = act\n",
" self.conv = nn.Conv2D(\n",
" in_channels=in_channels,\n",
" out_channels=out_channels,\n",
" kernel_size=kernel_size,\n",
" stride=stride,\n",
" padding=padding,\n",
" groups=groups,\n",
" bias_attr=False)\n",
"\n",
" self.bn = nn.BatchNorm(num_channels=out_channels, act=None)\n",
"\n",
" def forward(self, x):\n",
" # conv层\n",
" x = self.conv(x)\n",
" # batchnorm层\n",
" x = self.bn(x)\n",
" # 是否使用激活函数\n",
" if self.if_act:\n",
" if self.act == \"relu\":\n",
" x = F.relu(x)\n",
" elif self.act == \"hardswish\":\n",
" x = F.hardswish(x)\n",
" else:\n",
" print(\"The activation function({}) is selected incorrectly.\".\n",
" format(self.act))\n",
" exit()\n",
" return x\n",
"\n",
"class SEModule(nn.Layer):\n",
" def __init__(self, in_channels, reduction=4):\n",
" \"\"\"\n",
" SE模块\n",
" :param in_channels: 输入通道数\n",
" :param reduction: 通道缩放率\n",
" \"\"\" \n",
" super(SEModule, self).__init__()\n",
" self.avg_pool = nn.AdaptiveAvgPool2D(1)\n",
" self.conv1 = nn.Conv2D(\n",
" in_channels=in_channels,\n",
" out_channels=in_channels // reduction,\n",
" kernel_size=1,\n",
" stride=1,\n",
" padding=0)\n",
" self.conv2 = nn.Conv2D(\n",
" in_channels=in_channels // reduction,\n",
" out_channels=in_channels,\n",
" kernel_size=1,\n",
" stride=1,\n",
" padding=0)\n",
"\n",
" def forward(self, inputs):\n",
" # 平均池化\n",
" outputs = self.avg_pool(inputs)\n",
" # 第一个卷积层\n",
" outputs = self.conv1(outputs)\n",
" # relu激活函数\n",
" outputs = F.relu(outputs)\n",
" # 第二个卷积层\n",
" outputs = self.conv2(outputs)\n",
" # hardsigmoid 激活函数\n",
" outputs = F.hardsigmoid(outputs, slope=0.2, offset=0.5)\n",
" return inputs * outputs\n",
"\n",
"\n",
"class ResidualUnit(nn.Layer):\n",
" def __init__(self,\n",
" in_channels,\n",
" mid_channels,\n",
" out_channels,\n",
" kernel_size,\n",
" stride,\n",
" use_se,\n",
" act=None):\n",
" \"\"\"\n",
" 残差层\n",
" :param in_channels: 输入通道数\n",
" :param mid_channels: 中间通道数\n",
" :param out_channels: 输出通道数\n",
" :param kernel_size: 卷积核尺寸\n",
" :parma stride: 步长大小\n",
" :param use_se: 是否使用se模块\n",
" :param act: 激活函数\n",
" \"\"\" \n",
" super(ResidualUnit, self).__init__()\n",
" self.if_shortcut = stride == 1 and in_channels == out_channels\n",
" self.if_se = use_se\n",
"\n",
" self.expand_conv = ConvBNLayer(\n",
" in_channels=in_channels,\n",
" out_channels=mid_channels,\n",
" kernel_size=1,\n",
" stride=1,\n",
" padding=0,\n",
" if_act=True,\n",
" act=act)\n",
" self.bottleneck_conv = ConvBNLayer(\n",
" in_channels=mid_channels,\n",
" out_channels=mid_channels,\n",
" kernel_size=kernel_size,\n",
" stride=stride,\n",
" padding=int((kernel_size - 1) // 2),\n",
" groups=mid_channels,\n",
" if_act=True,\n",
" act=act)\n",
" if self.if_se:\n",
" self.mid_se = SEModule(mid_channels)\n",
" self.linear_conv = ConvBNLayer(\n",
" in_channels=mid_channels,\n",
" out_channels=out_channels,\n",
" kernel_size=1,\n",
" stride=1,\n",
" padding=0,\n",
" if_act=False,\n",
" act=None)\n",
"\n",
" def forward(self, inputs):\n",
" x = self.expand_conv(inputs)\n",
" x = self.bottleneck_conv(x)\n",
" if self.if_se:\n",
" x = self.mid_se(x)\n",
" x = self.linear_conv(x)\n",
" if self.if_shortcut:\n",
" x = paddle.add(inputs, x)\n",
" return x\n",
"\n",
"\n",
"def make_divisible(v, divisor=8, min_value=None):\n",
" \"\"\"\n",
" 确保被8整除\n",
" \"\"\" \n",
" if min_value is None:\n",
" min_value = divisor\n",
" new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)\n",
" if new_v < 0.9 * v:\n",
" new_v += divisor\n",
" return new_v\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"利用公共模块搭建骨干网络"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"class MobileNetV3(nn.Layer):\n",
" def __init__(self,\n",
" in_channels=3,\n",
" model_name='small',\n",
" scale=0.5,\n",
" small_stride=None,\n",
" disable_se=False,\n",
" **kwargs):\n",
" super(MobileNetV3, self).__init__()\n",
" self.disable_se = disable_se\n",
" \n",
" small_stride = [1, 2, 2, 2]\n",
"\n",
" if model_name == \"small\":\n",
" cfg = [\n",
" # k, exp, c, se, nl, s,\n",
" [3, 16, 16, True, 'relu', (small_stride[0], 1)],\n",
" [3, 72, 24, False, 'relu', (small_stride[1], 1)],\n",
" [3, 88, 24, False, 'relu', 1],\n",
" [5, 96, 40, True, 'hardswish', (small_stride[2], 1)],\n",
" [5, 240, 40, True, 'hardswish', 1],\n",
" [5, 240, 40, True, 'hardswish', 1],\n",
" [5, 120, 48, True, 'hardswish', 1],\n",
" [5, 144, 48, True, 'hardswish', 1],\n",
" [5, 288, 96, True, 'hardswish', (small_stride[3], 1)],\n",
" [5, 576, 96, True, 'hardswish', 1],\n",
" [5, 576, 96, True, 'hardswish', 1],\n",
" ]\n",
" cls_ch_squeeze = 576\n",
" else:\n",
" raise NotImplementedError(\"mode[\" + model_name +\n",
" \"_model] is not implemented!\")\n",
"\n",
" supported_scale = [0.35, 0.5, 0.75, 1.0, 1.25]\n",
" assert scale in supported_scale, \\\n",
" \"supported scales are {} but input scale is {}\".format(supported_scale, scale)\n",
"\n",
" inplanes = 16\n",
" # conv1\n",
" self.conv1 = ConvBNLayer(\n",
" in_channels=in_channels,\n",
" out_channels=make_divisible(inplanes * scale),\n",
" kernel_size=3,\n",
" stride=2,\n",
" padding=1,\n",
" groups=1,\n",
" if_act=True,\n",
" act='hardswish')\n",
" i = 0\n",
" block_list = []\n",
" inplanes = make_divisible(inplanes * scale)\n",
" for (k, exp, c, se, nl, s) in cfg:\n",
" se = se and not self.disable_se\n",
" block_list.append(\n",
" ResidualUnit(\n",
" in_channels=inplanes,\n",
" mid_channels=make_divisible(scale * exp),\n",
" out_channels=make_divisible(scale * c),\n",
" kernel_size=k,\n",
" stride=s,\n",
" use_se=se,\n",
" act=nl))\n",
" inplanes = make_divisible(scale * c)\n",
" i += 1\n",
" self.blocks = nn.Sequential(*block_list)\n",
"\n",
" self.conv2 = ConvBNLayer(\n",
" in_channels=inplanes,\n",
" out_channels=make_divisible(scale * cls_ch_squeeze),\n",
" kernel_size=1,\n",
" stride=1,\n",
" padding=0,\n",
" groups=1,\n",
" if_act=True,\n",
" act='hardswish')\n",
"\n",
" self.pool = nn.MaxPool2D(kernel_size=2, stride=2, padding=0)\n",
" self.out_channels = make_divisible(scale * cls_ch_squeeze)\n",
"\n",
" def forward(self, x):\n",
" x = self.conv1(x)\n",
" x = self.blocks(x)\n",
" x = self.conv2(x)\n",
" x = self.pool(x)\n",
" return x\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"至此就完成了骨干网络的定义,可通过 paddle.summary 结构可视化整个网络结构:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"-------------------------------------------------------------------------------\n",
" Layer (type) Input Shape Output Shape Param # \n",
"===============================================================================\n",
" Conv2D-1 [[1, 3, 32, 320]] [1, 8, 16, 160] 216 \n",
" BatchNorm-1 [[1, 8, 16, 160]] [1, 8, 16, 160] 32 \n",
" ConvBNLayer-1 [[1, 3, 32, 320]] [1, 8, 16, 160] 0 \n",
" Conv2D-2 [[1, 8, 16, 160]] [1, 8, 16, 160] 64 \n",
" BatchNorm-2 [[1, 8, 16, 160]] [1, 8, 16, 160] 32 \n",
" ConvBNLayer-2 [[1, 8, 16, 160]] [1, 8, 16, 160] 0 \n",
" Conv2D-3 [[1, 8, 16, 160]] [1, 8, 16, 160] 72 \n",
" BatchNorm-3 [[1, 8, 16, 160]] [1, 8, 16, 160] 32 \n",
" ConvBNLayer-3 [[1, 8, 16, 160]] [1, 8, 16, 160] 0 \n",
"AdaptiveAvgPool2D-1 [[1, 8, 16, 160]] [1, 8, 1, 1] 0 \n",
" Conv2D-4 [[1, 8, 1, 1]] [1, 2, 1, 1] 18 \n",
" Conv2D-5 [[1, 2, 1, 1]] [1, 8, 1, 1] 24 \n",
" SEModule-1 [[1, 8, 16, 160]] [1, 8, 16, 160] 0 \n",
" Conv2D-6 [[1, 8, 16, 160]] [1, 8, 16, 160] 64 \n",
" BatchNorm-4 [[1, 8, 16, 160]] [1, 8, 16, 160] 32 \n",
" ConvBNLayer-4 [[1, 8, 16, 160]] [1, 8, 16, 160] 0 \n",
" ResidualUnit-1 [[1, 8, 16, 160]] [1, 8, 16, 160] 0 \n",
" Conv2D-7 [[1, 8, 16, 160]] [1, 40, 16, 160] 320 \n",
" BatchNorm-5 [[1, 40, 16, 160]] [1, 40, 16, 160] 160 \n",
" ConvBNLayer-5 [[1, 8, 16, 160]] [1, 40, 16, 160] 0 \n",
" Conv2D-8 [[1, 40, 16, 160]] [1, 40, 8, 160] 360 \n",
" BatchNorm-6 [[1, 40, 8, 160]] [1, 40, 8, 160] 160 \n",
" ConvBNLayer-6 [[1, 40, 16, 160]] [1, 40, 8, 160] 0 \n",
" Conv2D-9 [[1, 40, 8, 160]] [1, 16, 8, 160] 640 \n",
" BatchNorm-7 [[1, 16, 8, 160]] [1, 16, 8, 160] 64 \n",
" ConvBNLayer-7 [[1, 40, 8, 160]] [1, 16, 8, 160] 0 \n",
" ResidualUnit-2 [[1, 8, 16, 160]] [1, 16, 8, 160] 0 \n",
" Conv2D-10 [[1, 16, 8, 160]] [1, 48, 8, 160] 768 \n",
" BatchNorm-8 [[1, 48, 8, 160]] [1, 48, 8, 160] 192 \n",
" ConvBNLayer-8 [[1, 16, 8, 160]] [1, 48, 8, 160] 0 \n",
" Conv2D-11 [[1, 48, 8, 160]] [1, 48, 8, 160] 432 \n",
" BatchNorm-9 [[1, 48, 8, 160]] [1, 48, 8, 160] 192 \n",
" ConvBNLayer-9 [[1, 48, 8, 160]] [1, 48, 8, 160] 0 \n",
" Conv2D-12 [[1, 48, 8, 160]] [1, 16, 8, 160] 768 \n",
" BatchNorm-10 [[1, 16, 8, 160]] [1, 16, 8, 160] 64 \n",
" ConvBNLayer-10 [[1, 48, 8, 160]] [1, 16, 8, 160] 0 \n",
" ResidualUnit-3 [[1, 16, 8, 160]] [1, 16, 8, 160] 0 \n",
" Conv2D-13 [[1, 16, 8, 160]] [1, 48, 8, 160] 768 \n",
" BatchNorm-11 [[1, 48, 8, 160]] [1, 48, 8, 160] 192 \n",
" ConvBNLayer-11 [[1, 16, 8, 160]] [1, 48, 8, 160] 0 \n",
" Conv2D-14 [[1, 48, 8, 160]] [1, 48, 4, 160] 1,200 \n",
" BatchNorm-12 [[1, 48, 4, 160]] [1, 48, 4, 160] 192 \n",
" ConvBNLayer-12 [[1, 48, 8, 160]] [1, 48, 4, 160] 0 \n",
"AdaptiveAvgPool2D-2 [[1, 48, 4, 160]] [1, 48, 1, 1] 0 \n",
" Conv2D-15 [[1, 48, 1, 1]] [1, 12, 1, 1] 588 \n",
" Conv2D-16 [[1, 12, 1, 1]] [1, 48, 1, 1] 624 \n",
" SEModule-2 [[1, 48, 4, 160]] [1, 48, 4, 160] 0 \n",
" Conv2D-17 [[1, 48, 4, 160]] [1, 24, 4, 160] 1,152 \n",
" BatchNorm-13 [[1, 24, 4, 160]] [1, 24, 4, 160] 96 \n",
" ConvBNLayer-13 [[1, 48, 4, 160]] [1, 24, 4, 160] 0 \n",
" ResidualUnit-4 [[1, 16, 8, 160]] [1, 24, 4, 160] 0 \n",
" Conv2D-18 [[1, 24, 4, 160]] [1, 120, 4, 160] 2,880 \n",
" BatchNorm-14 [[1, 120, 4, 160]] [1, 120, 4, 160] 480 \n",
" ConvBNLayer-14 [[1, 24, 4, 160]] [1, 120, 4, 160] 0 \n",
" Conv2D-19 [[1, 120, 4, 160]] [1, 120, 4, 160] 3,000 \n",
" BatchNorm-15 [[1, 120, 4, 160]] [1, 120, 4, 160] 480 \n",
" ConvBNLayer-15 [[1, 120, 4, 160]] [1, 120, 4, 160] 0 \n",
"AdaptiveAvgPool2D-3 [[1, 120, 4, 160]] [1, 120, 1, 1] 0 \n",
" Conv2D-20 [[1, 120, 1, 1]] [1, 30, 1, 1] 3,630 \n",
" Conv2D-21 [[1, 30, 1, 1]] [1, 120, 1, 1] 3,720 \n",
" SEModule-3 [[1, 120, 4, 160]] [1, 120, 4, 160] 0 \n",
" Conv2D-22 [[1, 120, 4, 160]] [1, 24, 4, 160] 2,880 \n",
" BatchNorm-16 [[1, 24, 4, 160]] [1, 24, 4, 160] 96 \n",
" ConvBNLayer-16 [[1, 120, 4, 160]] [1, 24, 4, 160] 0 \n",
" ResidualUnit-5 [[1, 24, 4, 160]] [1, 24, 4, 160] 0 \n",
" Conv2D-23 [[1, 24, 4, 160]] [1, 120, 4, 160] 2,880 \n",
" BatchNorm-17 [[1, 120, 4, 160]] [1, 120, 4, 160] 480 \n",
" ConvBNLayer-17 [[1, 24, 4, 160]] [1, 120, 4, 160] 0 \n",
" Conv2D-24 [[1, 120, 4, 160]] [1, 120, 4, 160] 3,000 \n",
" BatchNorm-18 [[1, 120, 4, 160]] [1, 120, 4, 160] 480 \n",
" ConvBNLayer-18 [[1, 120, 4, 160]] [1, 120, 4, 160] 0 \n",
"AdaptiveAvgPool2D-4 [[1, 120, 4, 160]] [1, 120, 1, 1] 0 \n",
" Conv2D-25 [[1, 120, 1, 1]] [1, 30, 1, 1] 3,630 \n",
" Conv2D-26 [[1, 30, 1, 1]] [1, 120, 1, 1] 3,720 \n",
" SEModule-4 [[1, 120, 4, 160]] [1, 120, 4, 160] 0 \n",
" Conv2D-27 [[1, 120, 4, 160]] [1, 24, 4, 160] 2,880 \n",
" BatchNorm-19 [[1, 24, 4, 160]] [1, 24, 4, 160] 96 \n",
" ConvBNLayer-19 [[1, 120, 4, 160]] [1, 24, 4, 160] 0 \n",
" ResidualUnit-6 [[1, 24, 4, 160]] [1, 24, 4, 160] 0 \n",
" Conv2D-28 [[1, 24, 4, 160]] [1, 64, 4, 160] 1,536 \n",
" BatchNorm-20 [[1, 64, 4, 160]] [1, 64, 4, 160] 256 \n",
" ConvBNLayer-20 [[1, 24, 4, 160]] [1, 64, 4, 160] 0 \n",
" Conv2D-29 [[1, 64, 4, 160]] [1, 64, 4, 160] 1,600 \n",
" BatchNorm-21 [[1, 64, 4, 160]] [1, 64, 4, 160] 256 \n",
" ConvBNLayer-21 [[1, 64, 4, 160]] [1, 64, 4, 160] 0 \n",
"AdaptiveAvgPool2D-5 [[1, 64, 4, 160]] [1, 64, 1, 1] 0 \n",
" Conv2D-30 [[1, 64, 1, 1]] [1, 16, 1, 1] 1,040 \n",
" Conv2D-31 [[1, 16, 1, 1]] [1, 64, 1, 1] 1,088 \n",
" SEModule-5 [[1, 64, 4, 160]] [1, 64, 4, 160] 0 \n",
" Conv2D-32 [[1, 64, 4, 160]] [1, 24, 4, 160] 1,536 \n",
" BatchNorm-22 [[1, 24, 4, 160]] [1, 24, 4, 160] 96 \n",
" ConvBNLayer-22 [[1, 64, 4, 160]] [1, 24, 4, 160] 0 \n",
" ResidualUnit-7 [[1, 24, 4, 160]] [1, 24, 4, 160] 0 \n",
" Conv2D-33 [[1, 24, 4, 160]] [1, 72, 4, 160] 1,728 \n",
" BatchNorm-23 [[1, 72, 4, 160]] [1, 72, 4, 160] 288 \n",
" ConvBNLayer-23 [[1, 24, 4, 160]] [1, 72, 4, 160] 0 \n",
" Conv2D-34 [[1, 72, 4, 160]] [1, 72, 4, 160] 1,800 \n",
" BatchNorm-24 [[1, 72, 4, 160]] [1, 72, 4, 160] 288 \n",
" ConvBNLayer-24 [[1, 72, 4, 160]] [1, 72, 4, 160] 0 \n",
"AdaptiveAvgPool2D-6 [[1, 72, 4, 160]] [1, 72, 1, 1] 0 \n",
" Conv2D-35 [[1, 72, 1, 1]] [1, 18, 1, 1] 1,314 \n",
" Conv2D-36 [[1, 18, 1, 1]] [1, 72, 1, 1] 1,368 \n",
" SEModule-6 [[1, 72, 4, 160]] [1, 72, 4, 160] 0 \n",
" Conv2D-37 [[1, 72, 4, 160]] [1, 24, 4, 160] 1,728 \n",
" BatchNorm-25 [[1, 24, 4, 160]] [1, 24, 4, 160] 96 \n",
" ConvBNLayer-25 [[1, 72, 4, 160]] [1, 24, 4, 160] 0 \n",
" ResidualUnit-8 [[1, 24, 4, 160]] [1, 24, 4, 160] 0 \n",
" Conv2D-38 [[1, 24, 4, 160]] [1, 144, 4, 160] 3,456 \n",
" BatchNorm-26 [[1, 144, 4, 160]] [1, 144, 4, 160] 576 \n",
" ConvBNLayer-26 [[1, 24, 4, 160]] [1, 144, 4, 160] 0 \n",
" Conv2D-39 [[1, 144, 4, 160]] [1, 144, 2, 160] 3,600 \n",
" BatchNorm-27 [[1, 144, 2, 160]] [1, 144, 2, 160] 576 \n",
" ConvBNLayer-27 [[1, 144, 4, 160]] [1, 144, 2, 160] 0 \n",
"AdaptiveAvgPool2D-7 [[1, 144, 2, 160]] [1, 144, 1, 1] 0 \n",
" Conv2D-40 [[1, 144, 1, 1]] [1, 36, 1, 1] 5,220 \n",
" Conv2D-41 [[1, 36, 1, 1]] [1, 144, 1, 1] 5,328 \n",
" SEModule-7 [[1, 144, 2, 160]] [1, 144, 2, 160] 0 \n",
" Conv2D-42 [[1, 144, 2, 160]] [1, 48, 2, 160] 6,912 \n",
" BatchNorm-28 [[1, 48, 2, 160]] [1, 48, 2, 160] 192 \n",
" ConvBNLayer-28 [[1, 144, 2, 160]] [1, 48, 2, 160] 0 \n",
" ResidualUnit-9 [[1, 24, 4, 160]] [1, 48, 2, 160] 0 \n",
" Conv2D-43 [[1, 48, 2, 160]] [1, 288, 2, 160] 13,824 \n",
" BatchNorm-29 [[1, 288, 2, 160]] [1, 288, 2, 160] 1,152 \n",
" ConvBNLayer-29 [[1, 48, 2, 160]] [1, 288, 2, 160] 0 \n",
" Conv2D-44 [[1, 288, 2, 160]] [1, 288, 2, 160] 7,200 \n",
" BatchNorm-30 [[1, 288, 2, 160]] [1, 288, 2, 160] 1,152 \n",
" ConvBNLayer-30 [[1, 288, 2, 160]] [1, 288, 2, 160] 0 \n",
"AdaptiveAvgPool2D-8 [[1, 288, 2, 160]] [1, 288, 1, 1] 0 \n",
" Conv2D-45 [[1, 288, 1, 1]] [1, 72, 1, 1] 20,808 \n",
" Conv2D-46 [[1, 72, 1, 1]] [1, 288, 1, 1] 21,024 \n",
" SEModule-8 [[1, 288, 2, 160]] [1, 288, 2, 160] 0 \n",
" Conv2D-47 [[1, 288, 2, 160]] [1, 48, 2, 160] 13,824 \n",
" BatchNorm-31 [[1, 48, 2, 160]] [1, 48, 2, 160] 192 \n",
" ConvBNLayer-31 [[1, 288, 2, 160]] [1, 48, 2, 160] 0 \n",
" ResidualUnit-10 [[1, 48, 2, 160]] [1, 48, 2, 160] 0 \n",
" Conv2D-48 [[1, 48, 2, 160]] [1, 288, 2, 160] 13,824 \n",
" BatchNorm-32 [[1, 288, 2, 160]] [1, 288, 2, 160] 1,152 \n",
" ConvBNLayer-32 [[1, 48, 2, 160]] [1, 288, 2, 160] 0 \n",
" Conv2D-49 [[1, 288, 2, 160]] [1, 288, 2, 160] 7,200 \n",
" BatchNorm-33 [[1, 288, 2, 160]] [1, 288, 2, 160] 1,152 \n",
" ConvBNLayer-33 [[1, 288, 2, 160]] [1, 288, 2, 160] 0 \n",
"AdaptiveAvgPool2D-9 [[1, 288, 2, 160]] [1, 288, 1, 1] 0 \n",
" Conv2D-50 [[1, 288, 1, 1]] [1, 72, 1, 1] 20,808 \n",
" Conv2D-51 [[1, 72, 1, 1]] [1, 288, 1, 1] 21,024 \n",
" SEModule-9 [[1, 288, 2, 160]] [1, 288, 2, 160] 0 \n",
" Conv2D-52 [[1, 288, 2, 160]] [1, 48, 2, 160] 13,824 \n",
" BatchNorm-34 [[1, 48, 2, 160]] [1, 48, 2, 160] 192 \n",
" ConvBNLayer-34 [[1, 288, 2, 160]] [1, 48, 2, 160] 0 \n",
" ResidualUnit-11 [[1, 48, 2, 160]] [1, 48, 2, 160] 0 \n",
" Conv2D-53 [[1, 48, 2, 160]] [1, 288, 2, 160] 13,824 \n",
" BatchNorm-35 [[1, 288, 2, 160]] [1, 288, 2, 160] 1,152 \n",
" ConvBNLayer-35 [[1, 48, 2, 160]] [1, 288, 2, 160] 0 \n",
" MaxPool2D-1 [[1, 288, 2, 160]] [1, 288, 1, 80] 0 \n",
"===============================================================================\n",
"Total params: 259,056\n",
"Trainable params: 246,736\n",
"Non-trainable params: 12,320\n",
"-------------------------------------------------------------------------------\n",
"Input size (MB): 0.12\n",
"Forward/backward pass size (MB): 44.38\n",
"Params size (MB): 0.99\n",
"Estimated Total Size (MB): 45.48\n",
"-------------------------------------------------------------------------------\n",
"\n"
]
},
{
"data": {
"text/plain": [
"{'total_params': 259056, 'trainable_params': 246736}"
]
},
"execution_count": null,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# 定义网络输入shape\n",
"IMAGE_SHAPE_C = 3\n",
"IMAGE_SHAPE_H = 32\n",
"IMAGE_SHAPE_W = 320\n",
"\n",
"\n",
"# 可视化网络结构\n",
"paddle.summary(MobileNetV3(),[(1, IMAGE_SHAPE_C, IMAGE_SHAPE_H, IMAGE_SHAPE_W)])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"backbone output: [1, 288, 1, 80]\n"
]
}
],
"source": [
"# 图片输入骨干网络\n",
"backbone = MobileNetV3()\n",
"# 将numpy数据转换为Tensor\n",
"input_data = paddle.to_tensor([padding_im])\n",
"# 骨干网络输出\n",
"feature = backbone(input_data)\n",
"# 查看feature map的纬度\n",
"print(\"backbone output:\", feature.shape)"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"* neck\n",
"\n",
"neck 部分将backbone输出的视觉特征图转换为1维向量输入送到 LSTM 网络中,输出序列特征( [源码位置](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.3/ppocr/modeling/necks/rnn.py) "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"class Im2Seq(nn.Layer):\n",
" def __init__(self, in_channels, **kwargs):\n",
" \"\"\"\n",
" 图像特征转换为序列特征\n",
" :param in_channels: 输入通道数\n",
" \"\"\" \n",
" super().__init__()\n",
" self.out_channels = in_channels\n",
"\n",
" def forward(self, x):\n",
" B, C, H, W = x.shape\n",
" assert H == 1\n",
" x = x.squeeze(axis=2)\n",
" x = x.transpose([0, 2, 1]) # (NWC)(batch, width, channels)\n",
" return x\n",
"\n",
"class EncoderWithRNN(nn.Layer):\n",
" def __init__(self, in_channels, hidden_size):\n",
" super(EncoderWithRNN, self).__init__()\n",
" self.out_channels = hidden_size * 2\n",
" self.lstm = nn.LSTM(\n",
" in_channels, hidden_size, direction='bidirectional', num_layers=2)\n",
"\n",
" def forward(self, x):\n",
" x, _ = self.lstm(x)\n",
" return x\n",
"\n",
"\n",
"class SequenceEncoder(nn.Layer):\n",
" def __init__(self, in_channels, hidden_size=48, **kwargs):\n",
" \"\"\"\n",
" 序列编码\n",
" :param in_channels: 输入通道数\n",
" :param hidden_size: 隐藏层size\n",
" \"\"\" \n",
" super(SequenceEncoder, self).__init__()\n",
" self.encoder_reshape = Im2Seq(in_channels)\n",
"\n",
" self.encoder = EncoderWithRNN(\n",
" self.encoder_reshape.out_channels, hidden_size)\n",
" self.out_channels = self.encoder.out_channels\n",
"\n",
" def forward(self, x):\n",
" x = self.encoder_reshape(x)\n",
" x = self.encoder(x)\n",
" return x\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"sequence shape: [1, 80, 96]\n"
]
}
],
"source": [
"neck = SequenceEncoder(in_channels=288)\n",
"sequence = neck(feature)\n",
"print(\"sequence shape:\", sequence.shape)"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"* head\n",
"\n",
"预测头部分由全连接层和softmax组成用于计算序列特征时间步上的标签概率分布本示例仅支持模型识别小写英文字母和数字26+1036个类别[源码位置](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.3/ppocr/modeling/heads/rec_ctc_head.py):"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"class CTCHead(nn.Layer):\n",
" def __init__(self,\n",
" in_channels,\n",
" out_channels,\n",
" **kwargs):\n",
" \"\"\"\n",
" CTC 预测层\n",
" :param in_channels: 输入通道数\n",
" :param out_channels: 输出通道数\n",
" \"\"\" \n",
" super(CTCHead, self).__init__()\n",
" self.fc = nn.Linear(\n",
" in_channels,\n",
" out_channels)\n",
" \n",
" # 思考out_channels 应该等于多少?\n",
" self.out_channels = out_channels\n",
"\n",
" def forward(self, x):\n",
" predicts = self.fc(x)\n",
" result = predicts\n",
"\n",
" if not self.training:\n",
" predicts = F.softmax(predicts, axis=2)\n",
" result = predicts\n",
"\n",
" return result"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"在网络随机初始化的情况下输出结果是无序的经过SoftMax之后可以得到各时间步上的概率最大的预测结果其中`pred_id` 代表预测的标签ID`pre_scores` 代表预测结果的置信度:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"predict shape: [1, 80, 37]\n",
"pred_id: Tensor(shape=[1, 80], dtype=int64, place=CUDAPlace(0), stop_gradient=False,\n",
" [[23, 28, 23, 23, 23, 23, 23, 23, 23, 23, 23, 30, 30, 30, 31, 23, 23, 23, 23, 23, 23, 23, 31, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 5 ]])\n",
"pred_scores: Tensor(shape=[1, 80], dtype=float32, place=CUDAPlace(0), stop_gradient=False,\n",
" [[0.03683758, 0.03368053, 0.03604801, 0.03504696, 0.03696444, 0.03597261, 0.03925638, 0.03650934, 0.03873367, 0.03572492, 0.03543066, 0.03618268, 0.03805700, 0.03496549, 0.03329032, 0.03565763, 0.03846950, 0.03922413, 0.03970327, 0.03638541, 0.03572393, 0.03618102, 0.03565401, 0.03636984, 0.03691722, 0.03718850, 0.03623354, 0.03877943, 0.03731697, 0.03563465, 0.03447339, 0.03365586, 0.03312979, 0.03285240, 0.03273271, 0.03269565, 0.03269779, 0.03271412, 0.03273287, 0.03274929, 0.03276210, 0.03277146, 0.03277802, 0.03278249, 0.03278547, 0.03278742, 0.03278869, 0.03278949, 0.03279000, 0.03279032, 0.03279052, 0.03279064, 0.03279071, 0.03279077, 0.03279081, 0.03279087, 0.03279094, 0.03279106, 0.03279124, 0.03279152, 0.03279196, 0.03279264, 0.03279363, 0.03279509, 0.03279718, 0.03280006, 0.03280392, 0.03280888, 0.03281487, 0.03282148, 0.03282760, 0.03283087, 0.03282646, 0.03280647, 0.03275031, 0.03263619, 0.03242587, 0.03194289, 0.03122442, 0.02986610]])\n"
]
}
],
"source": [
"ctc_head = CTCHead(in_channels=96, out_channels=37)\n",
"predict = ctc_head(sequence)\n",
"print(\"predict shape:\", predict.shape)\n",
"result = F.softmax(predict, axis=2)\n",
"pred_id = paddle.argmax(result, axis=2)\n",
"pred_socres = paddle.max(result, axis=2)\n",
"print(\"pred_id:\", pred_id)\n",
"print(\"pred_scores:\", pred_socres)"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"* 后处理\n",
"\n",
"识别网络最终返回的结果是各个时间步上的最大索引值最终期望的输出是对应的文字结果因此CRNN的后处理是一个解码过程主要逻辑如下"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def decode(text_index, text_prob=None, is_remove_duplicate=False):\n",
" \"\"\" convert text-index into text-label. \"\"\"\n",
" character = \"-0123456789abcdefghijklmnopqrstuvwxyz\"\n",
" result_list = []\n",
" # 忽略tokens [0] 代表ctc中的blank位\n",
" ignored_tokens = [0]\n",
" batch_size = len(text_index)\n",
" for batch_idx in range(batch_size):\n",
" char_list = []\n",
" conf_list = []\n",
" for idx in range(len(text_index[batch_idx])):\n",
" if text_index[batch_idx][idx] in ignored_tokens:\n",
" continue\n",
" # 合并blank之间相同的字符\n",
" if is_remove_duplicate:\n",
" # only for predict\n",
" if idx > 0 and text_index[batch_idx][idx - 1] == text_index[\n",
" batch_idx][idx]:\n",
" continue\n",
" # 将解码结果存在char_list内\n",
" char_list.append(character[int(text_index[batch_idx][\n",
" idx])])\n",
" # 记录置信度\n",
" if text_prob is not None:\n",
" conf_list.append(text_prob[batch_idx][idx])\n",
" else:\n",
" conf_list.append(1)\n",
" text = ''.join(char_list)\n",
" # 输出结果\n",
" result_list.append((text, np.mean(conf_list)))\n",
" return result_list"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"以 head 部分随机初始化预测出的结果为例,进行解码得到:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Tensor(shape=[1, 80], dtype=int64, place=CUDAPlace(0), stop_gradient=False,\n",
" [[23, 28, 23, 23, 23, 23, 23, 23, 23, 23, 23, 30, 30, 30, 31, 23, 23, 23, 23, 23, 23, 23, 31, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 5 ]])\n",
"decode out: [('mrmmmmmmmmmtttummmmmmmummmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm4', 0.034180813)]\n"
]
}
],
"source": [
"pred_id = paddle.argmax(result, axis=2)\n",
"pred_socres = paddle.max(result, axis=2)\n",
"print(pred_id)\n",
"decode_out = decode(pred_id, pred_socres)\n",
"print(\"decode out:\", decode_out)"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"**小测试:** 如果输入模型训练好的index解码结果是否正确呢"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"out: [('pain', 1.0)]\n"
]
}
],
"source": [
"# 替换模型预测好的结果\n",
"right_pred_id = paddle.to_tensor([['xxxxxxxxxxxxx']])\n",
"tmp_scores = paddle.ones(shape=right_pred_id.shape)\n",
"out = decode(right_pred_id, tmp_scores)\n",
"print(\"out:\",out)"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"上述步骤完成了网络的搭建,也实现了一个简单的前向预测过程。\n",
"\n",
"没有经过训练的网络无法正确预测结果因此需要定义损失函数、优化策略将整个网络run起来下面将详细介绍网络训练原理。\n",
"\n",
"\n",
"## 3. 训练原理详解\n",
"### 3.1 准备训练数据\n",
"PaddleOCR 支持两种数据格式:\n",
" - `lmdb` 用于训练以lmdb格式存储的数据集(LMDBDataSet);\n",
" - `通用数据` 用于训练以文本文件存储的数据集(SimpleDataSet);\n",
" \n",
" 本次只介绍通用数据格式读取\n",
"\n",
"训练数据的默认存储路径是 `./train_data`, 执行以下命令解压数据:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"!cd /home/aistudio/work/train_data/ && tar xf ic15_data.tar "
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"解压完成后训练图片都在同一个文件夹内并有一个txt文件rec_gt_train.txt记录图片路径和标签txt文件里的内容如下:\n",
"\n",
"```\n",
"\" 图像文件名 图像标注信息 \"\n",
"\n",
"train/word_1.png\tGenaxis Theatre\n",
"train/word_2.png\t[06]\n",
"...\n",
"```\n",
"\n",
"**注意:** txt文件中默认将图片路径和图片标签用 \\t 分割,如用其他方式分割将造成训练报错。\n",
"\n",
"\n",
"数据集应有如下文件结构:\n",
"```\n",
"|-train_data\n",
" |-ic15_data\n",
" |- rec_gt_train.txt\n",
" |- train\n",
" |- word_001.png\n",
" |- word_002.jpg\n",
" |- word_003.jpg\n",
" | ...\n",
" |- rec_gt_test.txt\n",
" |- test\n",
" |- word_001.png\n",
" |- word_002.jpg\n",
" |- word_003.jpg\n",
" | ...\n",
"```\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"确认配置文件中的数据路径是否正确,以 [rec_icdar15_train.yml](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.3/configs/rec/rec_icdar15_train.yml)为例:\n",
"\n",
"```\n",
"Train:\n",
" dataset:\n",
" name: SimpleDataSet\n",
" # 训练数据根目录\n",
" data_dir: ./train_data/ic15_data/\n",
" # 训练数据标签\n",
" label_file_list: [\"./train_data/ic15_data/rec_gt_train.txt\"]\n",
" transforms:\n",
" - DecodeImage: # load image\n",
" img_mode: BGR\n",
" channel_first: False\n",
" - CTCLabelEncode: # Class handling label\n",
" - RecResizeImg:\n",
" image_shape: [3, 32, 100] # [3,32,320]\n",
" - KeepKeys:\n",
" keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order\n",
" loader:\n",
" shuffle: True\n",
" batch_size_per_card: 256\n",
" drop_last: True\n",
" num_workers: 8\n",
" use_shared_memory: False\n",
"\n",
"Eval:\n",
" dataset:\n",
" name: SimpleDataSet\n",
" # 评估数据根目录\n",
" data_dir: ./train_data/ic15_data\n",
" # 评估数据标签\n",
" label_file_list: [\"./train_data/ic15_data/rec_gt_test.txt\"]\n",
" transforms:\n",
" - DecodeImage: # load image\n",
" img_mode: BGR\n",
" channel_first: False\n",
" - CTCLabelEncode: # Class handling label\n",
" - RecResizeImg:\n",
" image_shape: [3, 32, 100]\n",
" - KeepKeys:\n",
" keep_keys: ['image', 'label', 'length'] # dataloader will return list in this order\n",
" loader:\n",
" shuffle: False\n",
" drop_last: False\n",
" batch_size_per_card: 256\n",
" num_workers: 4\n",
" use_shared_memory: False\n",
" ```"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"### 3.2 数据预处理\n",
"\n",
"送入网络的训练数据需要保证一个batch内维度一致同时为了不同维度之间的特征在数值上有一定的比较性需要对数据做统一尺度**缩放**和**归一化**。\n",
"\n",
"为了增加模型的鲁棒性,抑制过拟合提升泛化性能,需要实现一定的**数据增广**。\n",
"\n",
"* 缩放和归一化\n",
"\n",
"第二节中已经介绍了相关内容,这是图片送入网络之前的最后一步操作。调用 `resize_norm_img` 完成图片缩放、padding和归一化。\n",
"\n",
"* 数据增广\n",
"\n",
"PaddleOCR中实现了多种数据增广方式颜色反转、随机切割、仿射变化、随机噪声等等这里以简单的随机切割为例更多增广方式可参考[rec_img_aug.py](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.3/ppocr/data/imaug/rec_img_aug.py)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def get_crop(image):\n",
" \"\"\"\n",
" random crop\n",
" \"\"\"\n",
" import random\n",
" h, w, _ = image.shape\n",
" top_min = 1\n",
" top_max = 8\n",
" top_crop = int(random.randint(top_min, top_max))\n",
" top_crop = min(top_crop, h - 1)\n",
" crop_img = image.copy()\n",
" ratio = random.randint(0, 1)\n",
" if ratio:\n",
" crop_img = crop_img[top_crop:h, :, :]\n",
" else:\n",
" crop_img = crop_img[0:h - top_crop, :, :]\n",
" return crop_img\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# 读图\n",
"raw_img = cv2.imread(\"/home/aistudio/work/word_1.png\")\n",
"plt.figure()\n",
"plt.subplot(2,1,1)\n",
"# 可视化原图\n",
"plt.imshow(raw_img)\n",
"# 随机切割\n",
"crop_img = get_crop(raw_img)\n",
"plt.subplot(2,1,2)\n",
"# 可视化增广图\n",
"plt.imshow(crop_img)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"### 3.3 训练主程序\n",
"\n",
"模型训练的入口代码是 [train.py](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.3/tools/train.py),它展示了训练中所需的各个模块: `build dataloader`, `build post process`, `build model` , `build loss`, `build optim`, `build metric`,将各部分串联后即可开始训练:\n",
"\n",
"* 构建 dataloader\n",
"\n",
"训练模型需要将数据组成指定数目的 batch ,并在训练过程中依次 yield 出来,本例中调用了 PaddleOCR 中实现的 [SimpleDataSet](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.3/ppocr/data/simple_dataset.py)\n",
"\n",
"基于原始代码稍作修改,其返回单条数据的主要逻辑如下"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def __getitem__(data_line, data_dir):\n",
" import os\n",
" mode = \"train\"\n",
" delimiter = '\\t'\n",
" try:\n",
" substr = data_line.strip(\"\\n\").split(delimiter)\n",
" file_name = substr[0]\n",
" label = substr[1]\n",
" img_path = os.path.join(data_dir, file_name)\n",
" data = {'img_path': img_path, 'label': label}\n",
" if not os.path.exists(img_path):\n",
" raise Exception(\"{} does not exist!\".format(img_path))\n",
" with open(data['img_path'], 'rb') as f:\n",
" img = f.read()\n",
" data['image'] = img\n",
" # 预处理操作,先注释掉\n",
" # outs = transform(data, self.ops)\n",
" outs = data\n",
" except Exception as e:\n",
" print(\"When parsing line {}, error happened with msg: {}\".format(\n",
" data_line, e))\n",
" outs = None\n",
" return outs"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"假设当前输入的标签为 `train/word_1.png\tGenaxis Theatre`, 训练数据的路径为 `/home/aistudio/work/train_data/ic15_data/`, 解析出的结果是一个字典,里面包含 `img_path` `label` `image` 三个字段:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'img_path': '/home/aistudio/work/train_data/ic15_data/train/word_1.png', 'label': 'Genaxis Theatre', 'image': b'\\x89PNG\\r\\n\\x1a\\n\\x00\\x00\\x00\\rIHDR\\x00\\x00\\x00Y\\x00\\x00\\x00\\x0e\\x08\\x02\\x00\\x00\\x00\\xcb\\xe2\\'\\xb7\\x00\\x00\\x00\\x01sRGB\\x00\\xae\\xce\\x1c\\xe9\\x00\\x00\\x00\\x04gAMA\\x00\\x00\\xb1\\x8f\\x0b\\xfca\\x05\\x00\\x00\\x00 cHRM\\x00\\x00z&\\x00\\x00\\x80\\x84\\x00\\x00\\xfa\\x00\\x00\\x00\\x80\\xe8\\x00\\x00u0\\x00\\x00\\xea`\\x00\\x00:\\x98\\x00\\x00\\x17p\\x9c\\xbaQ<\\x00\\x00\\x0bmIDATHK\\x8d\\x96\\xf9S[\\xd7\\x15\\x80\\x01\\xa7\\x93\\xa4\\xfd1\\x99L\\xea\\x80\\xc4\\xa2]B\\x0bb\\xdf\\x84\\x04\\x18\\x8c\\x01\\xb3\\x8aE\\xec\\x12\\x02\\t\\xb4KhC\\xfb\\xbe=\\xed\\xbb\\x04\\xc2l&N\\xd2\\xb4\\x93i\\x9bv\\xa6\\x7fL\\xdb\\xe9d\\xe2N\\xd3d<u6C\\x8f\\xc0I\\x9c\\xb1\\x7f\\xc8\\x9d\\x8f3\\xf7\\xdd\\xf7\\xee\\x9d{\\xbfw\\xceC\\x95\\xd3\\xc3\\xe3\\x04\\x02\\xe1\\xad\\xb7\\xde\\xbazv\\xf9\\xcd7\\xdf<~\\xfc\\xf8\\xef\\xff\\xfc\\xc7\\xe7\\x9f\\xff\\xfb\\x7fO\\x9f>y\\xf2\\xe4\\xeb\\xaf\\xbf\\xbe\\xbc\\xbc|\\xedWU\\xaf]\\xb7\\xab\\xab\\xab\\xca\\xca\\xab\\x8a\\x8a\\x8a\\xca\\xca\\xca\\xeb\\x08\\x97\\xb7\\xa0sY\\xf1\\xda\\xf7\\xdf?\\x83vUYQUU\\x05wa\\xd6w\\xcf\\x9e\\xdd\\xbau\\xeb\\xf5\\xd7_G\\xa1Po\\xfe\\xe6\\xd7\\x9f}\\xf6\\x19,\\x08+|\\xf9\\xe5\\x97U\\xe5\\xd9\\xcf\\xdb\\x1bo\\xbc\\xf1\\xf6\\xdbo\\xdf\\xbe}\\x1b\\xf6\\xf0\\xce;\\xef\\xc0\"\\xdf~\\xfb\\xedw\\xdf}\\xf7\\xf4\\xe9\\xd3\\xaf\\xbe\\xfa\\xea\\x8b/\\xbex\\xf2\\xe4\\xbf\\xffy\\xfc\\xaf[\\x95\\xcf`eh\\xcf\\xa7]\\x96\\xb7\\x01\\x8f\\x95\\xb7q\\xf5\\xe3~*\\xe16l\\xa6\\xb2\\xf2\\xcd\\xab\\x8a[\\xb0\\rh\\xd7\\xfb)?\\x00\\rV\\xae\\xac*O\\xfc\\xe1\\x08\\x97\\x15\\x15U\\x95W\\x97\\xf0W\\x05\\xf3LJ]&\\x14\\x07r\\xa1D\\xdc\\x1d\\xd2\\x8a\\x15\\x0b\\xe3\\\\J-\\x89\\x88&\\xe0\\xaa\\xb1\\xb8j\\x0c\\xbe\\x06K@\\xe1\\x08h\\x0c\\x1e\\xd5@@\\xd7\\x03xT\\x1d\\x1e\\x85\\xc6\\xd5\\xa0p\\xa8jlu\\r\\xb6\\x1aM\\xc1R\\xb0\\xb5x\\xf4\\xed\\xba\\x9aw\\xd1\\x10\\xd1\\xd5\\r5\\xb7\\xeb\\xde{\\x17M\\xc0R\\x18\\xf4\\xd6\\xd9\\x99\\xc5\\x95UAw\\x0f\\xa7\\x91\\xca\\xa4\\xd2\\x9a\\xd1\\xb5\\xd8\\xea\\xf7jQ\\xa82ht\\x1d\\x99\\xdc8<<\\xa2P\\xa8\\xc2\\xe1\\xe8\\xe9\\xe9\\xf9\\xe1\\xe1Q:\\x9d\\x8dF\\xe3\\x1e\\x8f\\x0f\\x06gg\\xe7\\xfazY4\\n\\x9eN\\xc6\\xd2H\\x18\\x80Jl\\x00h\\x84z\\x80\\xd4P\\x03P\\x1aP@#\\x06\\rP\\x1b\\x80\\xba\\x16\\x12\\x9d\\x81\\xa7Q\\xeaI\\xb0\\xf3\\xf2\\xfe\\xd1\\x18r=\\xbe\\x11C\\xa4bIT,\\x81\\x86#\\xde@\\xc7\\x13\\xe8x\\x12\\x03Oh&\\x90\\x80\\n\\x97\\xc1\\x1a\\xb0\\xba5b\\xb9f[\\xa6\\x12J\\x16F\\xa79m\\xacNz\\x07\\xa7\\xbd\\x7f|\\xe0>orq}nmy\\x9a73256p\\xf7\\x1egh\\x84=0\\xd4\\xcb\\x1e\\xe8\\xeaf\\xb7w\\xf4\\xb40\\xbb\\x98\\x8c\\xf6\\xa6\\xa6\\xb1\\xe1\\xf1;\\xec\\xe1\\xde\\x8e\\xbe\\xae\\xd6\\x9e\\xaevVO\\'\\xbb\\xbb\\xa3\\xaf\\xb3\\xad\\xb7\\xbd\\xad\\xa7\\x8f5\\xb8\\xc1\\xdf\\xde\\x16I9\\xfd\\xc3\\xadm\\xdd}\\xec;\\x10{{8,\\x16\\xbb\\xb3\\xb3\\x9b\\xc1`R\\xa9\\xf4\\xbe>\\xce\\xc6\\x86\\xc0d\\xb2 H\\x04\\x14X\\xadv\\x8b\\xc5\\x06\\x972\\x99\\x82\\xcb\\x9d\\xe7@c\\xc3\\x84vVw[w\\x1b\\xa3\\x9dIke\\x90\\x9bi\\xe4\\x16*\\x81\\x8ak\\xa0\\xe2\\xeb\\xe88\\x0c\\x1d_\\x0f\\x91Ah`\\xe0\\xb1\\x0c<N\"\\xd8\\x12\\xf0\\xd6f\\xee\\x8d\\x0fv\\xb3:\\xe8L&\\xa9\\xb1\\x89H\\x02\\xa8X\\\\\\x19\\x1c\\x86\\x86\\xc7\\x02t\\x02\\xee\\x06&\\x11\\\\P*\\xfc\\x16\\x97\\xdbh\\xdb\\\\\\\\\\x1b\\xeb\\x1bj\\'7\\xb5\\x91\\x18c\\x9c\\x11\\xadDg\\xd5\\xb9\\x10w<\\x1d\\xce\\x05\\x1c\\x88tK&\\x11J\\xbd6o\\xc8\\x13\\xc8\\xc4\\x12>\\x87K\\xabP\\xcdO\\xcd\\x88\\xf8|\\xbdJ\\x15\\xf2\\xf8b\\xa1\\x84\\xdb\\xea\\x97l)\\xf6\\xf7\\xac\\xd2\\x1d\\xb5\\x90\\xbf;9>\\'X\\x17\\xabU\\xc6M\\xc1\\xae\\\\\\xae\\xdb\\xda\\x92\\t\\x85R\\x91Ha\\xb7\\xfbuz\\xb3i\\xdff1;dR\\xd5\\xf8\\xd8T#\\x85\\x81\\xaa\\xa9\\xef\\xec\\xe8\\x15\\x8b\\xa4>oH.S//\\xadK%J$\\x14\\x8b\\xc7\\xd20\\xe2\\xf3\\x05\\x90P`\\x89\\xc7\\x9d\\x9d\\x990\\x194\\xbb\\xdb\\x9bF\\x9d\\xca\\xa8U\\x8b\\x04k3\\xf7GW\\x17\\xb9\\x06\\xa5\\xdc\\xaa\\xd3l\\xaf\\xadl,\\xce9\\x8c\\xfa\\xb3\\x83\\xc2I\\xbe\\xe0w:\\xb5J\\x99d{sG\\xc8WJ\\xc4R\\xd1&\\x8f;\\x05S\\xb4J\\xa9A#S\\xcbD\"\\xc1\\xca\\xf4\\xf8p?\\xab\\xf5No\\x17\\xb8`\\x82\\x0b\\xcf\\xbe\\x1d\\\\\\xec\\xac\\t\\xf9\\xdc\\xe5\\xd5\\xa9\\x85\\xe5\\x899!\\x8f\\xef4\\x94E\\xe4\\xe3\\xa5\\x93\\xc2E.V4i\\xacz\\xc5~,\\x10?H\\x1f\\x94\\xb2\\xa5\\\\\"\\x93\\x8a$T\\x12\\x85Eo\\n\\xfb\\x02\\xa5\\xdc\\xc1\\xf1\\xc1\\xe9\\xc9\\xc1C\\xc4\\x1b\\x8b\\x87\\xd2\\x01W8\\xe0\\x8d\\x1a\\xb4V\\x9b\\xd9c6:\\x95\\n\\xbd\\xd3\\x110\\x1a\\x1dz\\xbdM\\xa7\\xb3\\xc6\\xe3\\xf9P(\\x19\\xf0G\\xcc&\\xfb\\x96pgrb\\xb6\\xa5\\xb9\\xa3\\xa6\\xba\\x8eAoY[\\x15\\xd8mn\\x10\\x04.\\x14rM&]8*\\x9d\\xa6S\\xf9L&\\x97\\xcf\\xe6\\xe42\\x89T\\xb2\\x93I\\'\\x11\\xbf\\xaf\\x90Ig\\x12q\\xa7\\xd5bP\\xab=6[>\\x99<+\\x1e\\xc4\\xfd\\x01\\x9dL\\xae\\x95\\xca\\x02\\x0e\\xdb\\x87\\x0fO\\x83\\x1e\\xe7\\xbeV\\xe5uX\\xd21$\\x9f\\x8a\\xa5\\xa2\\x81\\xa0\\xc7\\x96\\x8e\\x05\\x8f\\n\\xa9\\xd3\\xa3\\xccI)\\x9dM\\x06\\xf7\\x94\\xa2\\xd9\\xc9;c\\xc3\\x1cH\\x19&\\x81Zv\\x818|z\\xa9\\xda\\xb1g\\x8a\\xb9\\x82~\\x93\\xd3\\xa2\\xde\\xcf\"\\x85\\x83\\xd4\\xe9i\\xf1\\xfd\\x8b\\xa3\\x8f\\x8e\\xb2g\\x01G$\\xe8\\x8c\\x9e\\x1f\\xbe\\x0f9\\xb2\\xaf19M\\xae|\\xa2`T\\x1b\\xdd\\x16w.\\x9e;/\\x9d?:\\xfe\\xe8\\xcf\\x1f\\xff\\xf5({\\x02:\\x82\\xee\\xc8\\x83\\xc2Y!UJ\\xc5\\x0b\\xfbz\\xbbVm\\x8a\\x84R~_\\xd4a\\xf7[\\xcc\\xee|\\xeeA6S\\x8aG\\xd2&\\x83M\\xb0\\xbe=39\\x0f\\xd5\\x04\\xdf\\x17\\n\\x91\\xbe\\xc0]v\\xd9}\\n\\xa9fyq].Q\\xa7\\xe2\\xb9D4c\\xb7\\xb8\\xddN_<\\x9eT\\xa9T:\\x9d\\xee\\xe4\\xe4\\xa4\\x98/\\\\\\x9c?<:,\\x85C\\x88`m]\\xbe+1\\x1b\\x8c\\xc9p\\xf4(_\\x0c\\xb8<6\\xa3)\\x1b\\x8f?:;\\xd6\\xab\\xe5\\xfc\\x95E\\xbbI\\x9fKF\\x11\\x9f\\xcb\\xb8\\'\\xd7\\xa9$`$\\x9b@\\x8a\\xd9\\xc8\\xc5i\\xe1\\xfc8g7kxs\\xa3\\xf7G\\x06\\x9a\\x88\\x94&\\xe2\\xb5\\x8b\\xa0\\xcd\\xa3\\x10\\xee\\x82\\x8b\\x07\\xc9\\xc2a,\\x8b8\\x02\\xb9p1\\x1b-%C\\xb9\\x98?\\xed\\xb7\\x87M\\x1a\\xbb\\xd7\\x1a:?\\xfc\\xc0m\\x0e\\xac/\\x08w\\x05\\x8a\\\\\\xec\\xd0at\\x07\\x9d\\xe1\\xc3\\xf4\\x83\\x8b\\xa3\\x0f\\xc0Q1Yr\\x9b}|\\x1e\\xdc\\x95\\xa5\"9H\\x93b\\xfaH.V\\xef\\x8a\\x94>w\\xd8i\\xf3C\\xa6\\xc8eZ$\\x90\\x005\\xf1H\\xd6\\xebB\\xd4R\\xdd\\x02w\\xb5\\xa7\\x8d]\\x87\\xc2\\xd3\\x88\\xcc\\xd9)\\x9e\\xdf\\x89@\\x89-L/+\\xa4\\xdad8\\x0bSV\\x17\\xf8\\xbb\"y*\\x99\\xb3Y]n\\x97\\xff\\xe2\\xe1\\x87\\xb9L\\xb1\\x98/E\\x90\\xb8\\xdd\\xea\\xda\\\\\\x13*%*\\x9dJ\\x1f\\xf6G\\xe0\\x95 \\x1eD\\xaf\\xd4G\\xfc\\xc8\\xe9\\xe1!\\x94\\xc6\\xd4\\xe8\\xa8A\\xa3LE\\x11\\x9dJ>?=\\xb18;Y\\xcc$R\\xd1P4\\xe8\\xcd%\\xc3\\x80F.\\x86\\xa4`w\\xb7\\x97]\\x10\\xe8\\x15P ^\\x93C\\xbc\\xbai\\x94j\\n\\xe1\\x14\\xb8\\x88{#p\\xfe\\xa0;\\xe1\\xb1\\x85\\xad\\x06\\xafFf\\x12\\xf1\\x15*\\x891\\x16\\xcc\\x01K\\\\\\xc1\\xea\\xfcv\"Tt\\xec\\x07=\\xd6H,\\x90/$\\x8e\\xddF\\x1f\\xa4\\x92\\xcf\\x1c\\x14\\xf2D:\\x89\\xf1A\\xee\\xfc\\xd3\\xdf\\xff\\xed\\x93\\x0f>U\\x8a\\xb5\\xbbB\\xa5\\xc7\\x16\\xb2\\xef{\\xb5\\n\\x93xK\\xe9s\\x84C\\x9ex1}|z\\xf8\\x08D\\x0bV\\xc4\\xdd\\xcdl\\\\-\\xa5\\x99\\xd21yo\\x0e\\xb2O\\xb1\\xa3]\\x9e\\xdd\\xb0\\x1a\\xdcg\\x07\\x8fLZ\\x07\\xf4\\x8d\\x1a\\x1b\\xa4R \\x18\\xf5\\x07\"\\xa5\\xa33\\x9f\\x17q\\xd8\\xbd*\\xa5nG,\\x97\\x88\\x15\\x0e\\xab\\'\\x12\\x88\\x9d\\x1c\\x9e}\\xf2\\xbb?F\\x83q\\xc9\\xb6L\\xab\\xd4\\xe7S\\x05\\xa9H2?\\xbd`1\\x98\\x0f\\xb2E\\x93n\\x7f~zne\\x91\\x97\\x8a\\xc5\\xa3\\xa1`\\xd0\\xebID\\xc2\\xf10\\xa2\\x94JF\\x87\\x87\\xd8=\\xddt\\x12\\x95N\\xa4W\\x98U\\xfa\\x98\\'\\x04:R\\xbeH)\\x9eCl\\xde}\\xa8\\xf0}_\\xc4\\x9fI\\x86\\x8ba_\\xdaf\\xf4I\\xb64\\n\\xb1\\xc1g\\x8f\\x85\\xdc\\xa9\\xb9\\x89\\x8d\\xa9{\\xcb\\x1eK\\xc2\\xbc\\x170i\\xfcvC8\\xee/\\xfa-\\xe1\\x847\\x15\\xb4\"R\\xbe\\xc2\\xa4\\xb4\\x06m\\xe1|\\xe4 \\x85\\xe4\\x1dF\\xafFb\\x00\\xb3N\\xb3\\x1f\\x8e\\x04\\xe7\\x0c\\xfb\\x92\\xf1P\\xf6A\\xee\\xe2\\xe3\\x87\\x7f\\xca\\xc5\\x8e\\xf8<q;\\x9dE\\xa8\\xa51\\xc9\\x9dC\\xac\\xf1\\x80#f\\xd28E\\xebr\\xb79\\xf4\\xe8\\xf8\\x0f.Spsy\\xd7k\\x8f\\x04|\\t\\x87#`\\xb3\\x052 \\xc5\\x97\\x08\\xf8cF\\xbdS\\xbbgVH\\xf5\\x90_\\xd9\\xf8\\xe1\\xc5\\xf1\\x87\\x9f|\\xf4\\x97R\\xee\\x04\\xbc\\xfb\\x1c\\xa1B\\xea\\x01D\\xad\\xc2\\x08\\x11\\xd2\\xd3\\xa4\\xb3p\\'\\xe7\\xe7\\xa6\\x16\\x82\\x1e$\\x19I\\xe5\\x92\\xf9B:\\x0f\\x1d\\x95T9~\\xf7>\\xab\\xb3\\x0fD\\x94]\\xf0\\xe7W@\\x07\\xfcg\\x85\\x8f\\x85\\x07jxn\\xe5\\xfe\\xc0\\xa8`I\\xa4W\\xd9 /\\xdcVD+\\xb7n\\xf0v\\xf8KR\\xa3\\xda\\xad\\x91Z\\xc7\\x06\\x17\\xef\\xf4Nk$N\\xd9\\x96y{M+\\xde\\xd0{\\xcc\\xf1<r\\x98\\xf4\\xa4\\xc2\\xb6\\x88^bth]J\\xa1Z\\xb7k\\x00)F\\xb9\\x99\\xcf\\xdb\\x96okv\\xf8\\xf2\\xad\\xd5]\\xa8/\\x8b\\xd6\\t\\x82\\xc2\\xceD\\xc4\\x95\\xda\\xdb\\xd9\\x1f\\xe3L7\\xe1\\xdb\\xf1\\xd5\\x8d4L\\x0b\\xbbuH\\xb5\\xad7\\xc8\\xac\\xeb\\xdc-\\xc1\\x82\\xd8\\xa9\\xf7K\\xf9\\xea\\xa9\\xa1\\xf9]\\x81J.5\\x8aD*\\xa1P\\xe9r\\x85\\x83\\xc1L:}\\xe4vF\\xadf\\x9flGo\\xd9\\xf7\\x04\\xbdP\\x07\\xf0\\x1d(@\\xc6\\x99\\xb4.\\xb9Xg7\\x06\"\\xbe\\xac\\xcb\\x8c\\x98\\xb5\\x1e\\xb7%,\\xdd\\xde\\x1b\\x19\\x98\\xba\\xd37n\\xd1{R\\x91\\xc2a\\xf6\\x14\\n\\x10R\\x15\\n\\xb0\\xbb\\x95\\xd3\\x88g\\xd0\\x89e*\\xfa\\xdb{GX\\x83\\xa3\\xec\\xa1\\xf9\\xb1\\xe9\\xf1\\xfe\\xbb\\xad\\xc4&\\x1a\\x86B\\xc1\\xd0\\x06zG\\xb8\\x13K\\xc0\\xdd\\xfe\\xc9\\x0e&\\xbb\\xafs\\x987#\\x98\\x1cYl\\xa1\\xf6\\x911\\xad\\x93#KC\\xac\\x99v\\xfa`+\\xb5\\x7fiZ\\xb8\\xb5\\xb4;3<3\\xce\\x19\\xe7\\xb4rF9c4,\\xbd\\x83\\xd6y\\x8f=\\xba8\\xc1\\xebnf\\rt\\x0f\\xf5\\xb5\\xf7\\x03=-}K3\\xab\\xabs\\x02\\xf9\\xa6fcn\\xebN\\xd7=\\x06\\xae\\x95\\x84\\xa6a~K\\x84\\xd8\\xcb\\xec\\x1f\\xee\\x19[\\xe7\\n\\x07;G`|bpv\\x94=\\xd9L\\xec\\x18\\xec\\x19\\xbd\\xd37\\xda\\xd5\\xde\\xcf\\xa4wq\\xa7\\x97\\x84|\\xa9Fi\\\\\\x99\\x17L\\x8dr\\xe9\\xa4\\xe6\\x81\\xde\\xe1\\x15\\xee\\xfa\\xd6\\xaa\\x18>U\\xcb\\xb3kC\\xac\\x11\\x1c\\x8a\\xdc\\xc9\\xe0\\xac\\xcf\\xef\\x00Sw\\x17a{\\xd3#\\xbc\\x0e:\\xbb\\x85\\xd2C\\xc5\\xb6\\xc2\\x88pE\\xb2\\xbe\\xb0=w\\x7f\\x052\\xb1\\x95\\xdaC\\'\\xb44\\x11\\x98M\\x04F\\x05\\xa9\\x96\\xf0\"d4\\x11.\\xf1\\xb5\\xf8k\\xa0\\xf3\\x02h\\x12\\xbe\\x0c\\xe5\\x07\\xa8x\\xf4\\r\\x14\\x1c\\x8a\\x88C\\xe3_\\t\\xfc\\x1e}\\x19b\\x1d\\x85P\\xdb\\x08\\xdc,u\\xd3\\'\\xd6Qo:7\\xfd\\x1bH\\xf5\\xf0\\xf3\\xb1\\x91\\x8e\\xa1\\xd2\\xb1\\x8d\\xd7\\x90_\\x8c\\x0c\\x1c\\x05h\\xc2\\x91^\\x84\\x81\\xa3\\xd21\\xf0F[\\xaei\\xfe!B\\xa7\\x99\\x8emy\\x15\\xcc&\\\\S\\x13\\x8e^A\\xa8\\xfb\\xd1\\x05Xx\\xce\\xab]\\xfc\\xe8\\xe5\\xb9\\x94\\x1b5e0h\\x1c\\x06\\x8d}9^[\\xc0\\xbd\\x14\\xf1\\x84:\"X~n\\xf0\\xda#,\\x02\\x83\\x84Z2\\xb1\\x9e\\x04\\xa6 \\x92\\xea\\x1bI\\rd\\x88\\x94z2\\x03\\xd3\\xc8\\xc4\\x90\\x99\\x18\\xe2\\xab\\xc031?\\x83\\x81%2\\xb04\\x06\\x96\\xc1\\xc02\\x7fadb\\x9b\\x98/\\xba\\x80\\x8c\\xb8\\x01\\x8c\\xfc\\xfc\\xfd\\xdf$\\xc2O\\'\\x7f!5\\x9e\\xdf\\xc2\\xa0\\x08e\\x11/E,\\x9aX\\x16\\xf1R,\\x8b@\\xe3\\xb0(\\x00{\\x13\\xcb:j\\xf1e\\x11\\xf5\\xc4\\xb2\\x88\\x06\\x80L\\xc6\\x00\\x94\\xc6\\x06\\xc8\\x05\\xd0\\xf1\\n\\xe8\\r\\xa4\\x17\\xf9\\xe9\\x99\\xeb|\\x81\\x04\\xf9e\\x91\\n\"\\x80\\xff\\x03\\x99\\xa0+\\x94\\xbd\\xf0X\\xa1\\x00\\x00\\x00\\x00IEND\\xaeB`\\x82'}\n"
]
}
],
"source": [
"data_line = \"train/word_1.png\tGenaxis Theatre\"\n",
"data_dir = \"/home/aistudio/work/train_data/ic15_data/\"\n",
"\n",
"item = __getitem__(data_line, data_dir)\n",
"print(item)"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"实现完单条数据返回逻辑后,调用 `padde.io.Dataloader` 即可把数据组合成batch具体可参考 [build_dataloader]()\n",
"\n",
"\n",
"* build model\n",
"\n",
" build model 即搭建主要网络结构具体细节如《2.3 代码实现》所述,本节不做过多介绍,各模块代码可参考[modeling](https://github.com/PaddlePaddle/PaddleOCR/tree/release/2.3/ppocr/modeling)\n",
"\n",
"* build loss\n",
" \n",
" CRNN 模型的损失函数为 CTC loss, 飞桨集成了常用的 Loss 函数,只需调用实现即可:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"import paddle.nn as nn\n",
"class CTCLoss(nn.Layer):\n",
" def __init__(self, use_focal_loss=False, **kwargs):\n",
" super(CTCLoss, self).__init__()\n",
" # blank 是 ctc 的无意义连接符\n",
" self.loss_func = nn.CTCLoss(blank=0, reduction='none')\n",
"\n",
" def forward(self, predicts, batch):\n",
" if isinstance(predicts, (list, tuple)):\n",
" predicts = predicts[-1]\n",
" # 转置模型 head 层的预测结果沿channel层排列\n",
" predicts = predicts.transpose((1, 0, 2)) #[80,1,37]\n",
" N, B, _ = predicts.shape\n",
" preds_lengths = paddle.to_tensor([N] * B, dtype='int64')\n",
" labels = batch[1].astype(\"int32\")\n",
" label_lengths = batch[2].astype('int64')\n",
" # 计算损失函数\n",
" loss = self.loss_func(predicts, labels, preds_lengths, label_lengths)\n",
" loss = loss.mean()\n",
" return {'loss': loss}"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"* build post process\n",
"\n",
" 具体细节同样在《2.3 代码实现》有详细介绍,实现逻辑与之前一致。"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"* build optim\n",
"\n",
"优化器使用 `Adam` , 同样调用飞桨API `paddle.optimizer.Adam`\n",
"\n",
"* build metric\n",
"\n",
"metric 部分用于计算模型指标PaddleOCR的文本识别中将整句预测正确判断为预测正确因此准确率计算主要逻辑如下"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def metric(preds, labels): \n",
" correct_num = 0\n",
" all_num = 0\n",
" norm_edit_dis = 0.0\n",
" for (pred), (target) in zip(preds, labels):\n",
" pred = pred.replace(\" \", \"\")\n",
" target = target.replace(\" \", \"\")\n",
" if pred == target:\n",
" correct_num += 1\n",
" all_num += 1\n",
" correct_num += correct_num\n",
" all_num += all_num\n",
" return {\n",
" 'acc': correct_num / all_num,\n",
" }"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"acc: {'acc': 0.6}\n"
]
}
],
"source": [
"preds = [\"aaa\", \"bbb\", \"ccc\", \"123\", \"456\"]\n",
"labels = [\"aaa\", \"bbb\", \"ddd\", \"123\", \"444\"]\n",
"acc = metric(preds, labels)\n",
"print(\"acc:\", acc)\n",
"# 五个预测结果中,完全正确的有3个因此准确率应为0.6"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"将以上各部分组合起来,即是完整的训练流程:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"\n",
"def main(config, device, logger, vdl_writer):\n",
" # init dist environment\n",
" if config['Global']['distributed']:\n",
" dist.init_parallel_env()\n",
"\n",
" global_config = config['Global']\n",
"\n",
" # build dataloader\n",
" train_dataloader = build_dataloader(config, 'Train', device, logger)\n",
" if len(train_dataloader) == 0:\n",
" logger.error(\n",
" \"No Images in train dataset, please ensure\\n\" +\n",
" \"\\t1. The images num in the train label_file_list should be larger than or equal with batch size.\\n\"\n",
" +\n",
" \"\\t2. The annotation file and path in the configuration file are provided normally.\"\n",
" )\n",
" return\n",
"\n",
" if config['Eval']:\n",
" valid_dataloader = build_dataloader(config, 'Eval', device, logger)\n",
" else:\n",
" valid_dataloader = None\n",
"\n",
" # build post process\n",
" post_process_class = build_post_process(config['PostProcess'],\n",
" global_config)\n",
"\n",
" # build model\n",
" # for rec algorithm\n",
" if hasattr(post_process_class, 'character'):\n",
" char_num = len(getattr(post_process_class, 'character'))\n",
" if config['Architecture'][\"algorithm\"] in [\"Distillation\",\n",
" ]: # distillation model\n",
" for key in config['Architecture'][\"Models\"]:\n",
" config['Architecture'][\"Models\"][key][\"Head\"][\n",
" 'out_channels'] = char_num\n",
" else: # base rec model\n",
" config['Architecture'][\"Head\"]['out_channels'] = char_num\n",
"\n",
" model = build_model(config['Architecture'])\n",
" if config['Global']['distributed']:\n",
" model = paddle.DataParallel(model)\n",
"\n",
" # build loss\n",
" loss_class = build_loss(config['Loss'])\n",
"\n",
" # build optim\n",
" optimizer, lr_scheduler = build_optimizer(\n",
" config['Optimizer'],\n",
" epochs=config['Global']['epoch_num'],\n",
" step_each_epoch=len(train_dataloader),\n",
" parameters=model.parameters())\n",
"\n",
" # build metric\n",
" eval_class = build_metric(config['Metric'])\n",
" # load pretrain model\n",
" pre_best_model_dict = load_model(config, model, optimizer)\n",
" logger.info('train dataloader has {} iters'.format(len(train_dataloader)))\n",
" if valid_dataloader is not None:\n",
" logger.info('valid dataloader has {} iters'.format(\n",
" len(valid_dataloader)))\n",
"\n",
" use_amp = config[\"Global\"].get(\"use_amp\", False)\n",
" if use_amp:\n",
" AMP_RELATED_FLAGS_SETTING = {\n",
" 'FLAGS_cudnn_batchnorm_spatial_persistent': 1,\n",
" 'FLAGS_max_inplace_grad_add': 8,\n",
" }\n",
" paddle.fluid.set_flags(AMP_RELATED_FLAGS_SETTING)\n",
" scale_loss = config[\"Global\"].get(\"scale_loss\", 1.0)\n",
" use_dynamic_loss_scaling = config[\"Global\"].get(\n",
" \"use_dynamic_loss_scaling\", False)\n",
" scaler = paddle.amp.GradScaler(\n",
" init_loss_scaling=scale_loss,\n",
" use_dynamic_loss_scaling=use_dynamic_loss_scaling)\n",
" else:\n",
" scaler = None\n",
"\n",
" # start train\n",
" program.train(config, train_dataloader, valid_dataloader, device, model,\n",
" loss_class, optimizer, lr_scheduler, post_process_class,\n",
" eval_class, pre_best_model_dict, logger, vdl_writer, scaler)"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"## 4. 完整训练任务\n",
"\n",
"### 4.1 启动训练\n",
"\n",
"PaddleOCR 识别任务与检测任务类似,是通过配置文件传输参数的。\n",
"\n",
"要进行完整的模型训练,首先需要下载整个项目并安装相关依赖:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple\n",
"Requirement already satisfied: shapely in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from -r requirements.txt (line 1)) (1.8.0)\n",
"Collecting scikit-image==0.17.2\n",
" Downloading https://pypi.tuna.tsinghua.edu.cn/packages/d7/ee/753ea56fda5bc2a5516a1becb631bf5ada593a2dd44f21971a13a762d4db/scikit_image-0.17.2-cp37-cp37m-manylinux1_x86_64.whl (12.5 MB)\n",
" |████████████████████████████████| 12.5 MB 8.4 MB/s \n",
"\u001b[?25hRequirement already satisfied: imgaug==0.4.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from -r requirements.txt (line 3)) (0.4.0)\n",
"Requirement already satisfied: pyclipper in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from -r requirements.txt (line 4)) (1.3.0.post2)\n",
"Requirement already satisfied: lmdb in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from -r requirements.txt (line 5)) (1.2.1)\n",
"Requirement already satisfied: tqdm in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from -r requirements.txt (line 6)) (4.36.1)\n",
"Requirement already satisfied: numpy in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from -r requirements.txt (line 7)) (1.20.3)\n",
"Requirement already satisfied: visualdl in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from -r requirements.txt (line 8)) (2.2.0)\n",
"Requirement already satisfied: python-Levenshtein in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from -r requirements.txt (line 9)) (0.12.2)\n",
"Requirement already satisfied: opencv-contrib-python==4.4.0.46 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from -r requirements.txt (line 10)) (4.4.0.46)\n",
"Requirement already satisfied: lxml in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from -r requirements.txt (line 11)) (4.7.1)\n",
"Requirement already satisfied: premailer in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from -r requirements.txt (line 12)) (3.10.0)\n",
"Requirement already satisfied: openpyxl in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from -r requirements.txt (line 13)) (3.0.5)\n",
"Requirement already satisfied: imageio>=2.3.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from scikit-image==0.17.2->-r requirements.txt (line 2)) (2.6.1)\n",
"Requirement already satisfied: matplotlib!=3.0.0,>=2.0.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from scikit-image==0.17.2->-r requirements.txt (line 2)) (2.2.3)\n",
"Requirement already satisfied: tifffile>=2019.7.26 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from scikit-image==0.17.2->-r requirements.txt (line 2)) (2021.11.2)\n",
"Requirement already satisfied: PyWavelets>=1.1.1 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from scikit-image==0.17.2->-r requirements.txt (line 2)) (1.2.0)\n",
"Requirement already satisfied: pillow!=7.1.0,!=7.1.1,>=4.3.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from scikit-image==0.17.2->-r requirements.txt (line 2)) (7.1.2)\n",
"Requirement already satisfied: networkx>=2.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from scikit-image==0.17.2->-r requirements.txt (line 2)) (2.4)\n",
"Requirement already satisfied: scipy>=1.0.1 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from scikit-image==0.17.2->-r requirements.txt (line 2)) (1.6.3)\n",
"Requirement already satisfied: six in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from imgaug==0.4.0->-r requirements.txt (line 3)) (1.15.0)\n",
"Requirement already satisfied: opencv-python in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from imgaug==0.4.0->-r requirements.txt (line 3)) (4.1.1.26)\n",
"Requirement already satisfied: flask>=1.1.1 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from visualdl->-r requirements.txt (line 8)) (1.1.1)\n",
"Requirement already satisfied: requests in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from visualdl->-r requirements.txt (line 8)) (2.22.0)\n",
"Requirement already satisfied: pre-commit in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from visualdl->-r requirements.txt (line 8)) (1.21.0)\n",
"Requirement already satisfied: shellcheck-py in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from visualdl->-r requirements.txt (line 8)) (0.7.1.1)\n",
"Requirement already satisfied: pandas in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from visualdl->-r requirements.txt (line 8)) (1.1.5)\n",
"Requirement already satisfied: Flask-Babel>=1.0.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from visualdl->-r requirements.txt (line 8)) (1.0.0)\n",
"Requirement already satisfied: bce-python-sdk in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from visualdl->-r requirements.txt (line 8)) (0.8.53)\n",
"Requirement already satisfied: flake8>=3.7.9 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from visualdl->-r requirements.txt (line 8)) (3.8.2)\n",
"Requirement already satisfied: protobuf>=3.11.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from visualdl->-r requirements.txt (line 8)) (3.14.0)\n",
"Requirement already satisfied: setuptools in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from python-Levenshtein->-r requirements.txt (line 9)) (56.2.0)\n",
"Requirement already satisfied: cssutils in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from premailer->-r requirements.txt (line 12)) (2.3.0)\n",
"Requirement already satisfied: cachetools in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from premailer->-r requirements.txt (line 12)) (4.0.0)\n",
"Requirement already satisfied: cssselect in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from premailer->-r requirements.txt (line 12)) (1.1.0)\n",
"Requirement already satisfied: jdcal in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from openpyxl->-r requirements.txt (line 13)) (1.4.1)\n",
"Requirement already satisfied: et-xmlfile in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from openpyxl->-r requirements.txt (line 13)) (1.0.1)\n",
"Requirement already satisfied: pycodestyle<2.7.0,>=2.6.0a1 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from flake8>=3.7.9->visualdl->-r requirements.txt (line 8)) (2.6.0)\n",
"Requirement already satisfied: pyflakes<2.3.0,>=2.2.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from flake8>=3.7.9->visualdl->-r requirements.txt (line 8)) (2.2.0)\n",
"Requirement already satisfied: importlib-metadata in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from flake8>=3.7.9->visualdl->-r requirements.txt (line 8)) (0.23)\n",
"Requirement already satisfied: mccabe<0.7.0,>=0.6.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from flake8>=3.7.9->visualdl->-r requirements.txt (line 8)) (0.6.1)\n",
"Requirement already satisfied: click>=5.1 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from flask>=1.1.1->visualdl->-r requirements.txt (line 8)) (7.0)\n",
"Requirement already satisfied: Jinja2>=2.10.1 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from flask>=1.1.1->visualdl->-r requirements.txt (line 8)) (2.11.0)\n",
"Requirement already satisfied: Werkzeug>=0.15 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from flask>=1.1.1->visualdl->-r requirements.txt (line 8)) (0.16.0)\n",
"Requirement already satisfied: itsdangerous>=0.24 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from flask>=1.1.1->visualdl->-r requirements.txt (line 8)) (1.1.0)\n",
"Requirement already satisfied: pytz in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from Flask-Babel>=1.0.0->visualdl->-r requirements.txt (line 8)) (2019.3)\n",
"Requirement already satisfied: Babel>=2.3 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from Flask-Babel>=1.0.0->visualdl->-r requirements.txt (line 8)) (2.8.0)\n",
"Requirement already satisfied: cycler>=0.10 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from matplotlib!=3.0.0,>=2.0.0->scikit-image==0.17.2->-r requirements.txt (line 2)) (0.10.0)\n",
"Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from matplotlib!=3.0.0,>=2.0.0->scikit-image==0.17.2->-r requirements.txt (line 2)) (2.4.2)\n",
"Requirement already satisfied: python-dateutil>=2.1 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from matplotlib!=3.0.0,>=2.0.0->scikit-image==0.17.2->-r requirements.txt (line 2)) (2.8.0)\n",
"Requirement already satisfied: kiwisolver>=1.0.1 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from matplotlib!=3.0.0,>=2.0.0->scikit-image==0.17.2->-r requirements.txt (line 2)) (1.1.0)\n",
"Requirement already satisfied: decorator>=4.3.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from networkx>=2.0->scikit-image==0.17.2->-r requirements.txt (line 2)) (4.4.2)\n",
"Requirement already satisfied: pycryptodome>=3.8.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from bce-python-sdk->visualdl->-r requirements.txt (line 8)) (3.9.9)\n",
"Requirement already satisfied: future>=0.6.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from bce-python-sdk->visualdl->-r requirements.txt (line 8)) (0.18.0)\n",
"Requirement already satisfied: cfgv>=2.0.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from pre-commit->visualdl->-r requirements.txt (line 8)) (2.0.1)\n",
"Requirement already satisfied: virtualenv>=15.2 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from pre-commit->visualdl->-r requirements.txt (line 8)) (16.7.9)\n",
"Requirement already satisfied: nodeenv>=0.11.1 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from pre-commit->visualdl->-r requirements.txt (line 8)) (1.3.4)\n",
"Requirement already satisfied: toml in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from pre-commit->visualdl->-r requirements.txt (line 8)) (0.10.0)\n",
"Requirement already satisfied: pyyaml in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from pre-commit->visualdl->-r requirements.txt (line 8)) (5.1.2)\n",
"Requirement already satisfied: aspy.yaml in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from pre-commit->visualdl->-r requirements.txt (line 8)) (1.3.0)\n",
"Requirement already satisfied: identify>=1.0.0 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from pre-commit->visualdl->-r requirements.txt (line 8)) (1.4.10)\n",
"Requirement already satisfied: idna<2.9,>=2.5 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from requests->visualdl->-r requirements.txt (line 8)) (2.8)\n",
"Requirement already satisfied: chardet<3.1.0,>=3.0.2 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from requests->visualdl->-r requirements.txt (line 8)) (3.0.4)\n",
"Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from requests->visualdl->-r requirements.txt (line 8)) (2019.9.11)\n",
"Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from requests->visualdl->-r requirements.txt (line 8)) (1.25.6)\n",
"Requirement already satisfied: MarkupSafe>=0.23 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from Jinja2>=2.10.1->flask>=1.1.1->visualdl->-r requirements.txt (line 8)) (1.1.1)\n",
"Requirement already satisfied: zipp>=0.5 in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from importlib-metadata->flake8>=3.7.9->visualdl->-r requirements.txt (line 8)) (3.6.0)\n",
"Installing collected packages: scikit-image\n",
" Attempting uninstall: scikit-image\n",
" Found existing installation: scikit-image 0.19.1\n",
" Uninstalling scikit-image-0.19.1:\n",
" Successfully uninstalled scikit-image-0.19.1\n",
"Successfully installed scikit-image-0.17.2\n"
]
}
],
"source": [
"# 克隆PaddleOCR代码\n",
"#!git clone https://gitee.com/paddlepaddle/PaddleOCR\n",
"# 修改代码运行的默认目录为 /home/aistudio/PaddleOCR\n",
"import os\n",
"os.chdir(\"/home/aistudio/PaddleOCR\")\n",
"# 安装PaddleOCR第三方依赖\n",
"!pip install -r requirements.txt"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"创建软链将训练数据放在PaddleOCR项目下"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"!ln -s /home/aistudio/work/train_data/ /home/aistudio/PaddleOCR/"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"下载预训练模型:\n",
"\n",
"为了加快收敛速度,建议下载训练好的模型在 icdar2015 数据上进行 finetune"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"--2021-12-22 15:39:39-- https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/rec_mv3_none_bilstm_ctc_v2.0_train.tar\n",
"Resolving paddleocr.bj.bcebos.com (paddleocr.bj.bcebos.com)... 182.61.200.195, 182.61.200.229, 2409:8c04:1001:1002:0:ff:b001:368a\n",
"Connecting to paddleocr.bj.bcebos.com (paddleocr.bj.bcebos.com)|182.61.200.195|:443... connected.\n",
"HTTP request sent, awaiting response... 200 OK\n",
"Length: 51200000 (49M) [application/x-tar]\n",
"Saving to: ./pretrain_models/rec_mv3_none_bilstm_ctc_v2.0_train.tar\n",
"\n",
"rec_mv3_none_bilstm 100%[===================>] 48.83M 15.5MB/s in 3.6s \n",
"\n",
"2021-12-22 15:39:42 (13.7 MB/s) - ./pretrain_models/rec_mv3_none_bilstm_ctc_v2.0_train.tar saved [51200000/51200000]\n",
"\n"
]
}
],
"source": [
"!cd PaddleOCR/\n",
"# 下载MobileNetV3的预训练模型\n",
"!wget -nc -P ./pretrain_models/ https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/rec_mv3_none_bilstm_ctc_v2.0_train.tar\n",
"# 解压模型参数\n",
"!tar -xf pretrain_models/rec_mv3_none_bilstm_ctc_v2.0_train.tar && rm -rf pretrain_models/rec_mv3_none_bilstm_ctc_v2.0_train.tar"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"启动训练命令很简单,指定好配置文件即可。另外在命令行中可以通过 `-o` 修改配置文件中的参数值。启动训练命令如下所示\n",
"\n",
"其中:\n",
"\n",
"* `Global.pretrained_model`: 加载的预训练模型路径\n",
"* `Global.character_dict_path` 字典路径这里只支持26个小写字母+数字)\n",
"* `Global.eval_batch_step` 评估频率\n",
"* `Global.epoch_num` 总训练轮数\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/skimage/morphology/_skeletonize.py:241: DeprecationWarning: `np.bool` is a deprecated alias for the builtin `bool`. To silence this warning, use `bool` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.bool_` here.\n",
"Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations\n",
" 0, 1, 1, 0, 0, 1, 0, 0, 0], dtype=np.bool)\n",
"/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/skimage/morphology/_skeletonize.py:256: DeprecationWarning: `np.bool` is a deprecated alias for the builtin `bool`. To silence this warning, use `bool` by itself. Doing this will not modify any behavior and is safe. If you specifically wanted the numpy scalar type, use `np.bool_` here.\n",
"Deprecated in NumPy 1.20; for more details and guidance: https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations\n",
" 0, 0, 0, 0, 0, 0, 0, 0, 0], dtype=np.bool)\n",
"[2021/12/23 20:28:15] root INFO: Architecture : \n",
"[2021/12/23 20:28:15] root INFO: Backbone : \n",
"[2021/12/23 20:28:15] root INFO: model_name : large\n",
"[2021/12/23 20:28:15] root INFO: name : MobileNetV3\n",
"[2021/12/23 20:28:15] root INFO: scale : 0.5\n",
"[2021/12/23 20:28:15] root INFO: Head : \n",
"[2021/12/23 20:28:15] root INFO: fc_decay : 0\n",
"[2021/12/23 20:28:15] root INFO: name : CTCHead\n",
"[2021/12/23 20:28:15] root INFO: Neck : \n",
"[2021/12/23 20:28:15] root INFO: encoder_type : rnn\n",
"[2021/12/23 20:28:15] root INFO: hidden_size : 96\n",
"[2021/12/23 20:28:15] root INFO: name : SequenceEncoder\n",
"[2021/12/23 20:28:15] root INFO: Transform : None\n",
"[2021/12/23 20:28:15] root INFO: algorithm : CRNN\n",
"[2021/12/23 20:28:15] root INFO: model_type : rec\n",
"[2021/12/23 20:28:15] root INFO: Eval : \n",
"[2021/12/23 20:28:15] root INFO: dataset : \n",
"[2021/12/23 20:28:15] root INFO: data_dir : ./train_data/ic15_data\n",
"[2021/12/23 20:28:15] root INFO: label_file_list : ['./train_data/ic15_data/rec_gt_test.txt']\n",
"[2021/12/23 20:28:15] root INFO: name : SimpleDataSet\n",
"[2021/12/23 20:28:15] root INFO: transforms : \n",
"[2021/12/23 20:28:15] root INFO: DecodeImage : \n",
"[2021/12/23 20:28:15] root INFO: channel_first : False\n",
"[2021/12/23 20:28:15] root INFO: img_mode : BGR\n",
"[2021/12/23 20:28:15] root INFO: CTCLabelEncode : None\n",
"[2021/12/23 20:28:15] root INFO: RecResizeImg : \n",
"[2021/12/23 20:28:15] root INFO: image_shape : [3, 32, 100]\n",
"[2021/12/23 20:28:15] root INFO: KeepKeys : \n",
"[2021/12/23 20:28:15] root INFO: keep_keys : ['image', 'label', 'length']\n",
"[2021/12/23 20:28:15] root INFO: loader : \n",
"[2021/12/23 20:28:15] root INFO: batch_size_per_card : 256\n",
"[2021/12/23 20:28:15] root INFO: drop_last : False\n",
"[2021/12/23 20:28:15] root INFO: num_workers : 4\n",
"[2021/12/23 20:28:15] root INFO: shuffle : False\n",
"[2021/12/23 20:28:15] root INFO: use_shared_memory : False\n",
"[2021/12/23 20:28:15] root INFO: Global : \n",
"[2021/12/23 20:28:15] root INFO: cal_metric_during_train : True\n",
"[2021/12/23 20:28:15] root INFO: character_dict_path : ppocr/utils/ic15_dict.txt\n",
"[2021/12/23 20:28:15] root INFO: character_type : EN\n",
"[2021/12/23 20:28:15] root INFO: checkpoints : None\n",
"[2021/12/23 20:28:15] root INFO: debug : False\n",
"[2021/12/23 20:28:15] root INFO: distributed : False\n",
"[2021/12/23 20:28:15] root INFO: epoch_num : 40\n",
"[2021/12/23 20:28:15] root INFO: eval_batch_step : [0, 200]\n",
"[2021/12/23 20:28:15] root INFO: infer_img : doc/imgs_words_en/word_19.png\n",
"[2021/12/23 20:28:15] root INFO: infer_mode : False\n",
"[2021/12/23 20:28:15] root INFO: log_smooth_window : 20\n",
"[2021/12/23 20:28:15] root INFO: max_text_length : 25\n",
"[2021/12/23 20:28:15] root INFO: pretrained_model : rec_mv3_none_bilstm_ctc_v2.0_train/best_accuracy\n",
"[2021/12/23 20:28:15] root INFO: print_batch_step : 10\n",
"[2021/12/23 20:28:15] root INFO: save_epoch_step : 3\n",
"[2021/12/23 20:28:15] root INFO: save_inference_dir : ./\n",
"[2021/12/23 20:28:15] root INFO: save_model_dir : ./output/rec/ic15/\n",
"[2021/12/23 20:28:15] root INFO: save_res_path : ./output/rec/predicts_ic15.txt\n",
"[2021/12/23 20:28:15] root INFO: use_gpu : True\n",
"[2021/12/23 20:28:15] root INFO: use_space_char : False\n",
"[2021/12/23 20:28:15] root INFO: use_visualdl : False\n",
"[2021/12/23 20:28:15] root INFO: Loss : \n",
"[2021/12/23 20:28:15] root INFO: name : CTCLoss\n",
"[2021/12/23 20:28:15] root INFO: Metric : \n",
"[2021/12/23 20:28:15] root INFO: main_indicator : acc\n",
"[2021/12/23 20:28:15] root INFO: name : RecMetric\n",
"[2021/12/23 20:28:15] root INFO: Optimizer : \n",
"[2021/12/23 20:28:15] root INFO: beta1 : 0.9\n",
"[2021/12/23 20:28:15] root INFO: beta2 : 0.999\n",
"[2021/12/23 20:28:15] root INFO: lr : \n",
"[2021/12/23 20:28:15] root INFO: learning_rate : 0.0005\n",
"[2021/12/23 20:28:15] root INFO: name : Adam\n",
"[2021/12/23 20:28:15] root INFO: regularizer : \n",
"[2021/12/23 20:28:15] root INFO: factor : 0\n",
"[2021/12/23 20:28:15] root INFO: name : L2\n",
"[2021/12/23 20:28:15] root INFO: PostProcess : \n",
"[2021/12/23 20:28:15] root INFO: name : CTCLabelDecode\n",
"[2021/12/23 20:28:15] root INFO: Train : \n",
"[2021/12/23 20:28:15] root INFO: dataset : \n",
"[2021/12/23 20:28:15] root INFO: data_dir : ./train_data/ic15_data/\n",
"[2021/12/23 20:28:15] root INFO: label_file_list : ['./train_data/ic15_data/rec_gt_train.txt']\n",
"[2021/12/23 20:28:15] root INFO: name : SimpleDataSet\n",
"[2021/12/23 20:28:15] root INFO: transforms : \n",
"[2021/12/23 20:28:15] root INFO: DecodeImage : \n",
"[2021/12/23 20:28:15] root INFO: channel_first : False\n",
"[2021/12/23 20:28:15] root INFO: img_mode : BGR\n",
"[2021/12/23 20:28:15] root INFO: CTCLabelEncode : None\n",
"[2021/12/23 20:28:15] root INFO: RecResizeImg : \n",
"[2021/12/23 20:28:15] root INFO: image_shape : [3, 32, 100]\n",
"[2021/12/23 20:28:15] root INFO: KeepKeys : \n",
"[2021/12/23 20:28:15] root INFO: keep_keys : ['image', 'label', 'length']\n",
"[2021/12/23 20:28:15] root INFO: loader : \n",
"[2021/12/23 20:28:15] root INFO: batch_size_per_card : 256\n",
"[2021/12/23 20:28:15] root INFO: drop_last : True\n",
"[2021/12/23 20:28:15] root INFO: num_workers : 8\n",
"[2021/12/23 20:28:15] root INFO: shuffle : True\n",
"[2021/12/23 20:28:15] root INFO: use_shared_memory : False\n",
"[2021/12/23 20:28:15] root INFO: train with paddle 2.1.2 and device CUDAPlace(0)\n",
"[2021/12/23 20:28:15] root INFO: Initialize indexs of datasets:['./train_data/ic15_data/rec_gt_train.txt']\n",
"[2021/12/23 20:28:15] root INFO: Initialize indexs of datasets:['./train_data/ic15_data/rec_gt_test.txt']\n",
"W1223 20:28:15.851713 306 device_context.cc:404] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 11.0, Runtime API Version: 10.1\n",
"W1223 20:28:15.857080 306 device_context.cc:422] device: 0, cuDNN Version: 7.6.\n",
"[2021/12/23 20:28:19] root INFO: loaded pretrained_model successful from rec_mv3_none_bilstm_ctc_v2.0_train/best_accuracy.pdparams\n",
"[2021/12/23 20:28:19] root INFO: train dataloader has 17 iters\n",
"[2021/12/23 20:28:19] root INFO: valid dataloader has 9 iters\n",
"[2021/12/23 20:28:19] root INFO: During the training process, after the 0th iteration, an evaluation is run every 200 iterations\n",
"[2021/12/23 20:28:19] root INFO: Initialize indexs of datasets:['./train_data/ic15_data/rec_gt_train.txt']\n",
"[2021/12/23 20:28:23] root INFO: epoch: [1/40], iter: 10, lr: 0.000500, loss: 9.336592, acc: 0.203125, norm_edit_dis: 0.674909, reader_cost: 0.27284 s, batch_cost: 0.40185 s, samples: 2816, ips: 700.75290\n",
"[2021/12/23 20:28:24] root INFO: epoch: [1/40], iter: 16, lr: 0.000500, loss: 6.955496, acc: 0.210938, norm_edit_dis: 0.678930, reader_cost: 0.00008 s, batch_cost: 0.05430 s, samples: 1536, ips: 2828.80514\n",
"[2021/12/23 20:28:24] root INFO: save model in ./output/rec/ic15/latest\n",
"[2021/12/23 20:28:24] root INFO: Initialize indexs of datasets:['./train_data/ic15_data/rec_gt_train.txt']\n",
"[2021/12/23 20:28:28] root INFO: epoch: [2/40], iter: 20, lr: 0.000500, loss: 6.402417, acc: 0.246094, norm_edit_dis: 0.695874, reader_cost: 0.24180 s, batch_cost: 0.34361 s, samples: 1024, ips: 298.00945\n",
"[2021/12/23 20:28:29] root INFO: epoch: [2/40], iter: 30, lr: 0.000500, loss: 4.007382, acc: 0.412109, norm_edit_dis: 0.743064, reader_cost: 0.00013 s, batch_cost: 0.08982 s, samples: 2560, ips: 2849.98954\n",
"[2021/12/23 20:28:29] root INFO: epoch: [2/40], iter: 33, lr: 0.000500, loss: 3.906031, acc: 0.458984, norm_edit_dis: 0.770415, reader_cost: 0.00004 s, batch_cost: 0.02684 s, samples: 768, ips: 2861.80304\n",
"^C\n",
"main proc 306 exit, kill process group 306\n"
]
}
],
"source": [
"!python3 tools/train.py -c configs/rec/rec_icdar15_train.yml \\\n",
" -o Global.pretrained_model=rec_mv3_none_bilstm_ctc_v2.0_train/best_accuracy \\\n",
" Global.character_dict_path=ppocr/utils/ic15_dict.txt \\\n",
" Global.eval_batch_step=[0,200] \\\n",
" Global.epoch_num=40"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"根据配置文件中设置的的 `save_model_dir` 字段,会有以下几种参数被保存下来:\n",
"\n",
"```\n",
"output/rec/ic15\n",
"├── best_accuracy.pdopt \n",
"├── best_accuracy.pdparams \n",
"├── best_accuracy.states \n",
"├── config.yml \n",
"├── iter_epoch_3.pdopt \n",
"├── iter_epoch_3.pdparams \n",
"├── iter_epoch_3.states \n",
"├── latest.pdopt \n",
"├── latest.pdparams \n",
"├── latest.states \n",
"└── train.log\n",
"```\n",
"其中 best_accuracy.* 是评估集上的最优模型iter_epoch_x.* 是以 `save_epoch_step` 为间隔保存下来的模型latest.* 是最后一个epoch的模型。\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"**总结:**\n",
"\n",
"如果需要训练自己的数据需要修改:\n",
"\n",
"1. 训练和评估数据路径(必须)\n",
"2. 字典路径(必须)\n",
"3. 预训练模型 (可选)\n",
"4. 学习率、image shape、网络结构可选"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"### 4.2 模型评估\n",
"\n",
"\n",
"评估数据集可以通过 `configs/rec/rec_icdar15_train.yml` 修改Eval中的 `label_file_path` 设置。\n",
"\n",
"这里默认使用 icdar2015 的评估集,加载刚刚训练好的模型权重:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[2021/12/23 14:27:51] root INFO: Architecture : \n",
"[2021/12/23 14:27:51] root INFO: Backbone : \n",
"[2021/12/23 14:27:51] root INFO: model_name : large\n",
"[2021/12/23 14:27:51] root INFO: name : MobileNetV3\n",
"[2021/12/23 14:27:51] root INFO: scale : 0.5\n",
"[2021/12/23 14:27:51] root INFO: Head : \n",
"[2021/12/23 14:27:51] root INFO: fc_decay : 0\n",
"[2021/12/23 14:27:51] root INFO: name : CTCHead\n",
"[2021/12/23 14:27:51] root INFO: Neck : \n",
"[2021/12/23 14:27:51] root INFO: encoder_type : rnn\n",
"[2021/12/23 14:27:51] root INFO: hidden_size : 96\n",
"[2021/12/23 14:27:51] root INFO: name : SequenceEncoder\n",
"[2021/12/23 14:27:51] root INFO: Transform : None\n",
"[2021/12/23 14:27:51] root INFO: algorithm : CRNN\n",
"[2021/12/23 14:27:51] root INFO: model_type : rec\n",
"[2021/12/23 14:27:51] root INFO: Eval : \n",
"[2021/12/23 14:27:51] root INFO: dataset : \n",
"[2021/12/23 14:27:51] root INFO: data_dir : ./train_data/ic15_data\n",
"[2021/12/23 14:27:51] root INFO: label_file_list : ['./train_data/ic15_data/rec_gt_test.txt']\n",
"[2021/12/23 14:27:51] root INFO: name : SimpleDataSet\n",
"[2021/12/23 14:27:51] root INFO: transforms : \n",
"[2021/12/23 14:27:51] root INFO: DecodeImage : \n",
"[2021/12/23 14:27:51] root INFO: channel_first : False\n",
"[2021/12/23 14:27:51] root INFO: img_mode : BGR\n",
"[2021/12/23 14:27:51] root INFO: CTCLabelEncode : None\n",
"[2021/12/23 14:27:51] root INFO: RecResizeImg : \n",
"[2021/12/23 14:27:51] root INFO: image_shape : [3, 32, 100]\n",
"[2021/12/23 14:27:51] root INFO: KeepKeys : \n",
"[2021/12/23 14:27:51] root INFO: keep_keys : ['image', 'label', 'length']\n",
"[2021/12/23 14:27:51] root INFO: loader : \n",
"[2021/12/23 14:27:51] root INFO: batch_size_per_card : 256\n",
"[2021/12/23 14:27:51] root INFO: drop_last : False\n",
"[2021/12/23 14:27:51] root INFO: num_workers : 4\n",
"[2021/12/23 14:27:51] root INFO: shuffle : False\n",
"[2021/12/23 14:27:51] root INFO: use_shared_memory : False\n",
"[2021/12/23 14:27:51] root INFO: Global : \n",
"[2021/12/23 14:27:51] root INFO: cal_metric_during_train : True\n",
"[2021/12/23 14:27:51] root INFO: character_dict_path : ppocr/utils/ic15_dict.txt\n",
"[2021/12/23 14:27:51] root INFO: character_type : EN\n",
"[2021/12/23 14:27:51] root INFO: checkpoints : output/rec/ic15/best_accuracy\n",
"[2021/12/23 14:27:51] root INFO: debug : False\n",
"[2021/12/23 14:27:51] root INFO: distributed : False\n",
"[2021/12/23 14:27:51] root INFO: epoch_num : 72\n",
"[2021/12/23 14:27:51] root INFO: eval_batch_step : [0, 2000]\n",
"[2021/12/23 14:27:51] root INFO: infer_img : doc/imgs_words_en/word_10.png\n",
"[2021/12/23 14:27:51] root INFO: infer_mode : False\n",
"[2021/12/23 14:27:51] root INFO: log_smooth_window : 20\n",
"[2021/12/23 14:27:51] root INFO: max_text_length : 25\n",
"[2021/12/23 14:27:51] root INFO: pretrained_model : None\n",
"[2021/12/23 14:27:51] root INFO: print_batch_step : 10\n",
"[2021/12/23 14:27:51] root INFO: save_epoch_step : 3\n",
"[2021/12/23 14:27:51] root INFO: save_inference_dir : ./\n",
"[2021/12/23 14:27:51] root INFO: save_model_dir : ./output/rec/ic15/\n",
"[2021/12/23 14:27:51] root INFO: save_res_path : ./output/rec/predicts_ic15.txt\n",
"[2021/12/23 14:27:51] root INFO: use_gpu : True\n",
"[2021/12/23 14:27:51] root INFO: use_space_char : False\n",
"[2021/12/23 14:27:51] root INFO: use_visualdl : False\n",
"[2021/12/23 14:27:51] root INFO: Loss : \n",
"[2021/12/23 14:27:51] root INFO: name : CTCLoss\n",
"[2021/12/23 14:27:51] root INFO: Metric : \n",
"[2021/12/23 14:27:51] root INFO: main_indicator : acc\n",
"[2021/12/23 14:27:51] root INFO: name : RecMetric\n",
"[2021/12/23 14:27:51] root INFO: Optimizer : \n",
"[2021/12/23 14:27:51] root INFO: beta1 : 0.9\n",
"[2021/12/23 14:27:51] root INFO: beta2 : 0.999\n",
"[2021/12/23 14:27:51] root INFO: lr : \n",
"[2021/12/23 14:27:51] root INFO: learning_rate : 0.0005\n",
"[2021/12/23 14:27:51] root INFO: name : Adam\n",
"[2021/12/23 14:27:51] root INFO: regularizer : \n",
"[2021/12/23 14:27:51] root INFO: factor : 0\n",
"[2021/12/23 14:27:51] root INFO: name : L2\n",
"[2021/12/23 14:27:51] root INFO: PostProcess : \n",
"[2021/12/23 14:27:51] root INFO: name : CTCLabelDecode\n",
"[2021/12/23 14:27:51] root INFO: Train : \n",
"[2021/12/23 14:27:51] root INFO: dataset : \n",
"[2021/12/23 14:27:51] root INFO: data_dir : ./train_data/ic15_data/\n",
"[2021/12/23 14:27:51] root INFO: label_file_list : ['./train_data/ic15_data/rec_gt_train.txt']\n",
"[2021/12/23 14:27:51] root INFO: name : SimpleDataSet\n",
"[2021/12/23 14:27:51] root INFO: transforms : \n",
"[2021/12/23 14:27:51] root INFO: DecodeImage : \n",
"[2021/12/23 14:27:51] root INFO: channel_first : False\n",
"[2021/12/23 14:27:51] root INFO: img_mode : BGR\n",
"[2021/12/23 14:27:51] root INFO: CTCLabelEncode : None\n",
"[2021/12/23 14:27:51] root INFO: RecResizeImg : \n",
"[2021/12/23 14:27:51] root INFO: image_shape : [3, 32, 100]\n",
"[2021/12/23 14:27:51] root INFO: KeepKeys : \n",
"[2021/12/23 14:27:51] root INFO: keep_keys : ['image', 'label', 'length']\n",
"[2021/12/23 14:27:51] root INFO: loader : \n",
"[2021/12/23 14:27:51] root INFO: batch_size_per_card : 256\n",
"[2021/12/23 14:27:51] root INFO: drop_last : True\n",
"[2021/12/23 14:27:51] root INFO: num_workers : 8\n",
"[2021/12/23 14:27:51] root INFO: shuffle : True\n",
"[2021/12/23 14:27:51] root INFO: use_shared_memory : False\n",
"[2021/12/23 14:27:51] root INFO: train with paddle 2.1.2 and device CUDAPlace(0)\n",
"[2021/12/23 14:27:51] root INFO: Initialize indexs of datasets:['./train_data/ic15_data/rec_gt_test.txt']\n",
"W1223 14:27:51.861889 5192 device_context.cc:404] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 10.1, Runtime API Version: 10.1\n",
"W1223 14:27:51.865501 5192 device_context.cc:422] device: 0, cuDNN Version: 7.6.\n",
"[2021/12/23 14:27:56] root INFO: resume from output/rec/ic15/best_accuracy\n",
"[2021/12/23 14:27:56] root INFO: metric in ckpt ***************\n",
"[2021/12/23 14:27:56] root INFO: acc:0.48531535869041886\n",
"[2021/12/23 14:27:56] root INFO: norm_edit_dis:0.7895228681338454\n",
"[2021/12/23 14:27:56] root INFO: fps:3266.1877400927865\n",
"[2021/12/23 14:27:56] root INFO: best_epoch:24\n",
"[2021/12/23 14:27:56] root INFO: start_epoch:25\n",
"eval model:: 100%|████████████████████████████████| 9/9 [00:02<00:00, 3.32it/s]\n",
"[2021/12/23 14:27:59] root INFO: metric eval ***************\n",
"[2021/12/23 14:27:59] root INFO: acc:0.48531535869041886\n",
"[2021/12/23 14:27:59] root INFO: norm_edit_dis:0.7895228681338454\n",
"[2021/12/23 14:27:59] root INFO: fps:4491.015930181665\n"
]
}
],
"source": [
"!python tools/eval.py -c configs/rec/rec_icdar15_train.yml -o Global.checkpoints=output/rec/ic15/best_accuracy \\\n",
" Global.character_dict_path=ppocr/utils/ic15_dict.txt\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"评估后,可以看到训练模型在验证集上的精度。\n",
"\n",
"PaddleOCR支持训练和评估交替进行, 可在 `configs/rec/rec_icdar15_train.yml` 中修改 `eval_batch_step` 设置评估频率默认每2000个iter评估一次。评估过程中默认将最佳acc模型保存为 `output/rec/ic15/best_accuracy` 。\n",
"\n",
"如果验证集很大,测试将会比较耗时,建议减少评估次数,或训练完再进行评估。"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"### 4.3 预测\n",
"\n",
"使用 PaddleOCR 训练好的模型,可以通过以下脚本进行快速预测。\n",
"\n",
"预测图片:\n",
"![](https://raw.githubusercontent.com/PaddlePaddle/PaddleOCR/release/2.3/doc/imgs_words_en/word_19.png)\n",
"\n",
"默认预测图片存储在 `infer_img` 里,通过 `-o Global.checkpoints` 加载训练好的参数文件:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[2021/12/23 14:29:19] root INFO: Architecture : \n",
"[2021/12/23 14:29:19] root INFO: Backbone : \n",
"[2021/12/23 14:29:19] root INFO: model_name : large\n",
"[2021/12/23 14:29:19] root INFO: name : MobileNetV3\n",
"[2021/12/23 14:29:19] root INFO: scale : 0.5\n",
"[2021/12/23 14:29:19] root INFO: Head : \n",
"[2021/12/23 14:29:19] root INFO: fc_decay : 0\n",
"[2021/12/23 14:29:19] root INFO: name : CTCHead\n",
"[2021/12/23 14:29:19] root INFO: Neck : \n",
"[2021/12/23 14:29:19] root INFO: encoder_type : rnn\n",
"[2021/12/23 14:29:19] root INFO: hidden_size : 96\n",
"[2021/12/23 14:29:19] root INFO: name : SequenceEncoder\n",
"[2021/12/23 14:29:19] root INFO: Transform : None\n",
"[2021/12/23 14:29:19] root INFO: algorithm : CRNN\n",
"[2021/12/23 14:29:19] root INFO: model_type : rec\n",
"[2021/12/23 14:29:19] root INFO: Eval : \n",
"[2021/12/23 14:29:19] root INFO: dataset : \n",
"[2021/12/23 14:29:19] root INFO: data_dir : ./train_data/ic15_data\n",
"[2021/12/23 14:29:19] root INFO: label_file_list : ['./train_data/ic15_data/rec_gt_test.txt']\n",
"[2021/12/23 14:29:19] root INFO: name : SimpleDataSet\n",
"[2021/12/23 14:29:19] root INFO: transforms : \n",
"[2021/12/23 14:29:19] root INFO: DecodeImage : \n",
"[2021/12/23 14:29:19] root INFO: channel_first : False\n",
"[2021/12/23 14:29:19] root INFO: img_mode : BGR\n",
"[2021/12/23 14:29:19] root INFO: CTCLabelEncode : None\n",
"[2021/12/23 14:29:19] root INFO: RecResizeImg : \n",
"[2021/12/23 14:29:19] root INFO: image_shape : [3, 32, 100]\n",
"[2021/12/23 14:29:19] root INFO: KeepKeys : \n",
"[2021/12/23 14:29:19] root INFO: keep_keys : ['image', 'label', 'length']\n",
"[2021/12/23 14:29:19] root INFO: loader : \n",
"[2021/12/23 14:29:19] root INFO: batch_size_per_card : 256\n",
"[2021/12/23 14:29:19] root INFO: drop_last : False\n",
"[2021/12/23 14:29:19] root INFO: num_workers : 4\n",
"[2021/12/23 14:29:19] root INFO: shuffle : False\n",
"[2021/12/23 14:29:19] root INFO: use_shared_memory : False\n",
"[2021/12/23 14:29:19] root INFO: Global : \n",
"[2021/12/23 14:29:19] root INFO: cal_metric_during_train : True\n",
"[2021/12/23 14:29:19] root INFO: character_dict_path : ppocr/utils/ic15_dict.txt\n",
"[2021/12/23 14:29:19] root INFO: character_type : EN\n",
"[2021/12/23 14:29:19] root INFO: checkpoints : output/rec/ic15/best_accuracy\n",
"[2021/12/23 14:29:19] root INFO: debug : False\n",
"[2021/12/23 14:29:19] root INFO: distributed : False\n",
"[2021/12/23 14:29:19] root INFO: epoch_num : 72\n",
"[2021/12/23 14:29:19] root INFO: eval_batch_step : [0, 2000]\n",
"[2021/12/23 14:29:19] root INFO: infer_img : doc/imgs_words_en/word_19.png\n",
"[2021/12/23 14:29:19] root INFO: infer_mode : False\n",
"[2021/12/23 14:29:19] root INFO: log_smooth_window : 20\n",
"[2021/12/23 14:29:19] root INFO: max_text_length : 25\n",
"[2021/12/23 14:29:19] root INFO: pretrained_model : None\n",
"[2021/12/23 14:29:19] root INFO: print_batch_step : 10\n",
"[2021/12/23 14:29:19] root INFO: save_epoch_step : 3\n",
"[2021/12/23 14:29:19] root INFO: save_inference_dir : ./\n",
"[2021/12/23 14:29:19] root INFO: save_model_dir : ./output/rec/ic15/\n",
"[2021/12/23 14:29:19] root INFO: save_res_path : ./output/rec/predicts_ic15.txt\n",
"[2021/12/23 14:29:19] root INFO: use_gpu : True\n",
"[2021/12/23 14:29:19] root INFO: use_space_char : False\n",
"[2021/12/23 14:29:19] root INFO: use_visualdl : False\n",
"[2021/12/23 14:29:19] root INFO: Loss : \n",
"[2021/12/23 14:29:19] root INFO: name : CTCLoss\n",
"[2021/12/23 14:29:19] root INFO: Metric : \n",
"[2021/12/23 14:29:19] root INFO: main_indicator : acc\n",
"[2021/12/23 14:29:19] root INFO: name : RecMetric\n",
"[2021/12/23 14:29:19] root INFO: Optimizer : \n",
"[2021/12/23 14:29:19] root INFO: beta1 : 0.9\n",
"[2021/12/23 14:29:19] root INFO: beta2 : 0.999\n",
"[2021/12/23 14:29:19] root INFO: lr : \n",
"[2021/12/23 14:29:19] root INFO: learning_rate : 0.0005\n",
"[2021/12/23 14:29:19] root INFO: name : Adam\n",
"[2021/12/23 14:29:19] root INFO: regularizer : \n",
"[2021/12/23 14:29:19] root INFO: factor : 0\n",
"[2021/12/23 14:29:19] root INFO: name : L2\n",
"[2021/12/23 14:29:19] root INFO: PostProcess : \n",
"[2021/12/23 14:29:19] root INFO: name : CTCLabelDecode\n",
"[2021/12/23 14:29:19] root INFO: Train : \n",
"[2021/12/23 14:29:19] root INFO: dataset : \n",
"[2021/12/23 14:29:19] root INFO: data_dir : ./train_data/ic15_data/\n",
"[2021/12/23 14:29:19] root INFO: label_file_list : ['./train_data/ic15_data/rec_gt_train.txt']\n",
"[2021/12/23 14:29:19] root INFO: name : SimpleDataSet\n",
"[2021/12/23 14:29:19] root INFO: transforms : \n",
"[2021/12/23 14:29:19] root INFO: DecodeImage : \n",
"[2021/12/23 14:29:19] root INFO: channel_first : False\n",
"[2021/12/23 14:29:19] root INFO: img_mode : BGR\n",
"[2021/12/23 14:29:19] root INFO: CTCLabelEncode : None\n",
"[2021/12/23 14:29:19] root INFO: RecResizeImg : \n",
"[2021/12/23 14:29:19] root INFO: image_shape : [3, 32, 100]\n",
"[2021/12/23 14:29:19] root INFO: KeepKeys : \n",
"[2021/12/23 14:29:19] root INFO: keep_keys : ['image', 'label', 'length']\n",
"[2021/12/23 14:29:19] root INFO: loader : \n",
"[2021/12/23 14:29:19] root INFO: batch_size_per_card : 256\n",
"[2021/12/23 14:29:19] root INFO: drop_last : True\n",
"[2021/12/23 14:29:19] root INFO: num_workers : 8\n",
"[2021/12/23 14:29:19] root INFO: shuffle : True\n",
"[2021/12/23 14:29:19] root INFO: use_shared_memory : False\n",
"[2021/12/23 14:29:19] root INFO: train with paddle 2.1.2 and device CUDAPlace(0)\n",
"W1223 14:29:19.803710 5290 device_context.cc:404] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 10.1, Runtime API Version: 10.1\n",
"W1223 14:29:19.807695 5290 device_context.cc:422] device: 0, cuDNN Version: 7.6.\n",
"[2021/12/23 14:29:25] root INFO: resume from output/rec/ic15/best_accuracy\n",
"[2021/12/23 14:29:25] root INFO: infer_img: doc/imgs_words_en/word_19.png\n",
"pred idx: Tensor(shape=[1, 25], dtype=int64, place=CUDAPlace(0), stop_gradient=True,\n",
" [[29, 0 , 0 , 0 , 22, 0 , 0 , 0 , 25, 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 33]])\n",
"[2021/12/23 14:29:25] root INFO: \t result: slow\t0.8795223\n",
"[2021/12/23 14:29:25] root INFO: success!\n"
]
}
],
"source": [
"!python tools/infer_rec.py -c configs/rec/rec_icdar15_train.yml -o Global.checkpoints=output/rec/ic15/best_accuracy Global.character_dict_path=ppocr/utils/ic15_dict.txt"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"得到输入图像的预测结果:\n",
"\n",
"```\n",
"infer_img: doc/imgs_words_en/word_19.png\n",
" result: slow\t0.8795223\n",
"```\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"collapsed": false
},
"source": [
"### 作业\n",
"\n",
"**【题目1】**\n",
"\n",
"可视化出 PaddleOCR 中的实现的[数据增强](https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.4/ppocr/data/imaug/rec_img_aug.py)结果noise、jitter, 并用语言解释效果。\n",
"\n",
"可选测试图片:\n",
"\n",
"![](https://raw.githubusercontent.com/PaddlePaddle/PaddleOCR/release/2.4/doc/imgs_words/ch/word_1.jpg)\n",
"\n",
"![](https://raw.githubusercontent.com/PaddlePaddle/PaddleOCR/release/2.4/doc/imgs_words/ch/word_2.jpg)\n",
"\n",
"![](https://raw.githubusercontent.com/PaddlePaddle/PaddleOCR/release/2.4/doc/imgs_words/ch/word_3.jpg)\n",
"\n",
"\n",
"**【题目2】**\n",
"\n",
"更换 configs/rec/rec_icdar15_train.yml 配置中的 backbone 为 PaddleOCR 中的 [ResNet34_vd](https://github.com/PaddlePaddle/PaddleOCR/blob/6ee301be36eb54d91dc437842f754593dce13967/ppocr/modeling/backbones/rec_resnet_vd.py#L176)当输入图片shape为332100Head 层最终输出的特征尺寸是多少?\n",
"\n",
"\n",
"**【题目3】**\n",
"\n",
"下载10W中文数据集[rec_data_lesson_demo](https://paddleocr.bj.bcebos.com/dataset/rec_data_lesson_demo.tar),修改 configs/rec/rec_icdar15_train.yml 配置文件训练一个识别模型提供训练log。\n",
"\n",
"可加载预训练模型: https://paddleocr.bj.bcebos.com/dygraph_v2.0/en/rec_mv3_none_bilstm_ctc_v2.0_train.tar \n",
"\n",
"\n",
"## 总结\n",
"\n",
"至此一个基于CRNN的文本识别任务就全部完成了更多功能和代码可以参考 [PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR)。\n",
"\n",
"如果对项目任何问题或者疑问,欢迎在评论区留言提出"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "py35-paddle1.2.0"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.4"
}
},
"nbformat": 4,
"nbformat_minor": 1
}