├── .gitattributes ├── LICENSE ├── README.md ├── imgs ├── 0O0O.png ├── deep-speech-ctc-small.png ├── generator_vis.png ├── level1.png ├── level1_117422.png ├── level1_142660.png ├── level1_base_model.png ├── level1_gen1.png ├── level1_gen2.png ├── level1_leaderboard.png ├── level1_loss.png ├── level1_model.png ├── level1_preprocessing.png ├── level1_score.png ├── level1_visualization.png ├── level2.jpg ├── level2_95170.png ├── level2_95170_0.png ├── level2_barplot.png ├── level2_basemodel.png ├── level2_cnn_model.png ├── level2_generator.png ├── level2_generator2.png ├── level2_loss.png ├── level2_loss_lstm.png ├── level2_preprocessing1.png ├── level2_preprocessing2.png ├── level2_preprocessing3.png ├── level2_preprocessing4.png ├── level2_preprocessing5.png ├── level2_preprocessing6.png ├── level2_sample1.png ├── level2_scatter_matrix.png ├── loss.png ├── loss_acc_3.png ├── model2.png └── test_model2.png ├── 使用循环神经网络破解验证码.md ├── 决赛代码 ├── .ipynb_checkpoints │ ├── plot_model-checkpoint.ipynb │ ├── 数据集探索-checkpoint.ipynb │ ├── 数据集预处理-checkpoint.ipynb │ ├── 数据集预处理分析-Copy1-checkpoint.ipynb │ ├── 数据集预处理分析-checkpoint.ipynb │ └── 测试固定图像大小的模型_346_split2_3-checkpoint.ipynb ├── loss 曲线.ipynb ├── make_parallel.py ├── plot_model.ipynb ├── 在测试集上预测_0.9976.ipynb ├── 在测试集上预测_0.9977.ipynb ├── 在测试集上预测_全集训练150代.ipynb ├── 数据集并行预处理_72.ipynb ├── 数据集并行预处理_72_测试集.ipynb ├── 数据集并行预处理_81.ipynb ├── 数据集并行预处理_81_测试集.ipynb ├── 数据集探索.ipynb ├── 数据集预处理.ipynb ├── 数据集预处理分析.ipynb ├── 测试固定图像大小的模型_346_split2_3.ipynb ├── 测试固定图像大小的模型_346_split2_4_全集训练150代.ipynb ├── 测试固定图像大小的模型_端到端3改结构_mc_l2正则_加层_生成器2.ipynb └── 融合结果.ipynb ├── 初赛代码 ├── .ipynb_checkpoints │ ├── 四则混合运算识别 深度学习应用大赛3-checkpoint.ipynb │ ├── 数据集预处理-checkpoint.ipynb │ └── 生成器对比-checkpoint.ipynb ├── fonts │ ├── Andale Mono.ttf │ ├── Arial Black.ttf │ ├── Arial.ttf │ ├── CALIBRI.TTF │ ├── Comic Sans MS.ttf │ ├── Futura.ttc │ ├── Georgia.ttf │ ├── GillSans.ttc │ ├── Helvetica.ttf │ ├── Impact.ttf │ ├── Menlo.ttc │ ├── Optima.ttc │ ├── Times New Roman Bold.ttf │ ├── Times New Roman.ttf │ ├── Verdana.ttf │ └── consola.ttf ├── image.py ├── image.pyc ├── make_parallel.py ├── 四则混合运算识别 深度学习应用大赛3.ipynb ├── 数据集预处理.ipynb ├── 生成器对比.ipynb └── 转换多 GPU 模型为普通模型.ipynb ├── 四则混合运算识别_决赛.md └── 四则混合运算识别_初赛.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 杨培文 (Yang Peiwen) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /imgs/0O0O.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/0O0O.png -------------------------------------------------------------------------------- /imgs/deep-speech-ctc-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/deep-speech-ctc-small.png -------------------------------------------------------------------------------- /imgs/generator_vis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/generator_vis.png -------------------------------------------------------------------------------- /imgs/level1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level1.png -------------------------------------------------------------------------------- /imgs/level1_117422.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level1_117422.png -------------------------------------------------------------------------------- /imgs/level1_142660.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level1_142660.png -------------------------------------------------------------------------------- /imgs/level1_base_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level1_base_model.png -------------------------------------------------------------------------------- /imgs/level1_gen1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level1_gen1.png -------------------------------------------------------------------------------- /imgs/level1_gen2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level1_gen2.png -------------------------------------------------------------------------------- /imgs/level1_leaderboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level1_leaderboard.png -------------------------------------------------------------------------------- /imgs/level1_loss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level1_loss.png -------------------------------------------------------------------------------- /imgs/level1_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level1_model.png -------------------------------------------------------------------------------- /imgs/level1_preprocessing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level1_preprocessing.png -------------------------------------------------------------------------------- /imgs/level1_score.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level1_score.png -------------------------------------------------------------------------------- /imgs/level1_visualization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level1_visualization.png -------------------------------------------------------------------------------- /imgs/level2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level2.jpg -------------------------------------------------------------------------------- /imgs/level2_95170.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level2_95170.png -------------------------------------------------------------------------------- /imgs/level2_95170_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level2_95170_0.png -------------------------------------------------------------------------------- /imgs/level2_barplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level2_barplot.png -------------------------------------------------------------------------------- /imgs/level2_basemodel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level2_basemodel.png -------------------------------------------------------------------------------- /imgs/level2_cnn_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level2_cnn_model.png -------------------------------------------------------------------------------- /imgs/level2_generator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level2_generator.png -------------------------------------------------------------------------------- /imgs/level2_generator2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level2_generator2.png -------------------------------------------------------------------------------- /imgs/level2_loss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level2_loss.png -------------------------------------------------------------------------------- /imgs/level2_loss_lstm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level2_loss_lstm.png -------------------------------------------------------------------------------- /imgs/level2_preprocessing1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level2_preprocessing1.png -------------------------------------------------------------------------------- /imgs/level2_preprocessing2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level2_preprocessing2.png -------------------------------------------------------------------------------- /imgs/level2_preprocessing3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level2_preprocessing3.png -------------------------------------------------------------------------------- /imgs/level2_preprocessing4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level2_preprocessing4.png -------------------------------------------------------------------------------- /imgs/level2_preprocessing5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level2_preprocessing5.png -------------------------------------------------------------------------------- /imgs/level2_preprocessing6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level2_preprocessing6.png -------------------------------------------------------------------------------- /imgs/level2_sample1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level2_sample1.png -------------------------------------------------------------------------------- /imgs/level2_scatter_matrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/level2_scatter_matrix.png -------------------------------------------------------------------------------- /imgs/loss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/loss.png -------------------------------------------------------------------------------- /imgs/loss_acc_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/loss_acc_3.png -------------------------------------------------------------------------------- /imgs/model2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/model2.png -------------------------------------------------------------------------------- /imgs/test_model2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/imgs/test_model2.png -------------------------------------------------------------------------------- /使用循环神经网络破解验证码.md: -------------------------------------------------------------------------------- 1 | # 使用循环神经网络破解验证码 2 | 3 | 对于这种按顺序书写的文字,我们可以使用循环神经网络来识别序列。下面我们来了解一下如何使用循环神经网络来识别这类验证码。 4 | 5 | captcha 部分的代码和之前卷积神经网络识别的一样,只是将 `n_class` 改为了 `len(characters)+1`,因为我们需要添加一个空白类用于 CTC Loss。 6 | 7 | 参考链接:[使用深度学习来破解 captcha 验证码](https://zhuanlan.zhihu.com/p/26078299) 8 | 9 | ```py 10 | from captcha.image import ImageCaptcha 11 | import matplotlib.pyplot as plt 12 | import numpy as np 13 | import random 14 | 15 | %matplotlib inline 16 | %config InlineBackend.figure_format = 'retina' 17 | 18 | import string 19 | characters = string.digits + string.ascii_uppercase 20 | print(characters) 21 | 22 | width, height, n_len, n_class = 170, 80, 4, len(characters)+1 23 | 24 | generator = ImageCaptcha(width=width, height=height) 25 | random_str = ''.join([random.choice(characters) for j in range(4)]) 26 | img = generator.generate_image(random_str) 27 | 28 | plt.imshow(img) 29 | plt.title(random_str) 30 | ``` 31 | 32 | ## CTC Loss 33 | 34 | 这个 loss 是一个特别神奇的 loss,它可以在只知道序列的顺序,不知道具体位置的情况下,让模型收敛。([warp-ctc](https://github.com/baidu-research/warp-ctc)) 35 | 36 | ![](imgs/deep-speech-ctc-small.png) 37 | 38 | 那么在 Keras 里面,CTC Loss 已经内置了,我们直接定义这样一个函数即可,由于我们使用的是循环神经网络,所以默认丢掉前面两个输出,因为它们通常无意义,且会影响模型的输出。 39 | 40 | * y\_pred 是模型的输出,是按顺序输出的37个字符的概率,因为我们这里用到了循环神经网络,所以需要一个空白字符的类; 41 | * labels 是验证码,是四个数字,每个数字对应字符的编号; 42 | * input\_length 表示 y\_pred 的长度,我们这里是15; 43 | * label\_length 表示 labels 的长度,我们这里是4。 44 | 45 | ```py 46 | from keras import backend as K 47 | 48 | def ctc_lambda_func(args): 49 | y_pred, labels, input_length, label_length = args 50 | y_pred = y_pred[:, 2:, :] 51 | return K.ctc_batch_cost(labels, y_pred, input_length, label_length) 52 | ``` 53 | 54 | ## 模型结构 55 | 56 | 我们的模型结构是这样设计的,首先通过卷积神经网络去识别特征,然后经过一个全连接降维,再按水平顺序输入到一种特殊的循环神经网络,叫 GRU,全程是 Gated Recurrent Unit,可以理解为是 LSTM 的简化版。LSTM 早在1997年就已经被发明出来了,但是 GRU 直到2014年才出现。经过实验,GRU 效果比 LSTM 要好。 57 | 58 | 参考链接:https://zhuanlan.zhihu.com/p/28297161 59 | 60 | ```py 61 | from keras.models import * 62 | from keras.layers import * 63 | from keras.optimizers import * 64 | rnn_size = 128 65 | 66 | input_tensor = Input((width, height, 3)) 67 | x = input_tensor 68 | x = Lambda(lambda x:(x-127.5)/127.5)(x) 69 | for i in range(3): 70 | for j in range(2): 71 | x = Convolution2D(32*2**i, 3, kernel_initializer='he_uniform')(x) 72 | x = BatchNormalization()(x) 73 | x = Activation('relu')(x) 74 | x = MaxPooling2D((2, 2))(x) 75 | 76 | conv_shape = x.get_shape().as_list() 77 | rnn_length = conv_shape[1] 78 | rnn_dimen = conv_shape[2]*conv_shape[3] 79 | print(conv_shape, rnn_length, rnn_dimen) 80 | x = Reshape(target_shape=(rnn_length, rnn_dimen))(x) 81 | rnn_length -= 2 82 | 83 | x = Dense(rnn_size, kernel_initializer='he_uniform')(x) 84 | x = BatchNormalization()(x) 85 | x = Activation('relu')(x) 86 | x = Dropout(0.2)(x) 87 | 88 | gru_1 = GRU(rnn_size, return_sequences=True, kernel_initializer='he_uniform', name='gru1')(x) 89 | gru_1b = GRU(rnn_size, return_sequences=True, kernel_initializer='he_uniform', 90 | go_backwards=True, name='gru1_b')(x) 91 | x = add([gru_1, gru_1b]) 92 | 93 | gru_2 = GRU(rnn_size, return_sequences=True, kernel_initializer='he_uniform', name='gru2')(x) 94 | gru_2b = GRU(rnn_size, return_sequences=True, kernel_initializer='he_uniform', 95 | go_backwards=True, name='gru2_b')(x) 96 | x = concatenate([gru_2, gru_2b]) 97 | 98 | x = Dropout(0.2)(x) 99 | x = Dense(n_class, activation='softmax')(x) 100 | base_model = Model(inputs=input_tensor, outputs=x) 101 | 102 | labels = Input(name='the_labels', shape=[n_len], dtype='float32') 103 | input_length = Input(name='input_length', shape=[1], dtype='int64') 104 | label_length = Input(name='label_length', shape=[1], dtype='int64') 105 | loss_out = Lambda(ctc_lambda_func, output_shape=(1,), 106 | name='ctc')([x, labels, input_length, label_length]) 107 | 108 | model = Model(inputs=[input_tensor, labels, input_length, label_length], outputs=[loss_out]) 109 | model.compile(loss={'ctc': lambda y_true, y_pred: y_pred}, optimizer='adam') 110 | ``` 111 | 112 | 从 Input 到 最后一个 MaxPooling2D,是一个很深的卷积神经网络,它负责学习字符的各个特征,尽可能区分不同的字符。它输出 shape 是 `[None, 17, 6, 128]`,这个形状相当于把一张宽为 170,高为 80 的彩色图像 (170, 80, 3),压缩为宽为 17,高为 6 的 128维特征的特征图 (17, 6, 128)。 113 | 114 | 然后我们把图像 reshape 成 (17, 768),也就是把高和特征放在一个维度,然后降维成 (17, 128),也就是从左到右有17条特征,每个特征128个维度。 115 | 116 | 这128个维度就是这一条图像的非常高维,非常抽象的概括,然后我们将17个特征向量依次输入到 GRU 中,GRU 有能力学会不同特征向量的组合会代表什么字符,即使是字符之间有粘连也不会怕。这里使用了双向 GRU, 117 | 118 | 最后 Dropout 接一个全连接层,作为分类器输出每个字符的概率。 119 | 120 | 这个是 base\_model 的结构,也是我们模型的结构。那么后面的 labels, input\_length, label\_length 和 loss_out 都是为了输入必要的数据来计算 CTC Loss 的。 121 | 122 | 123 | ## 模型可视化 124 | 125 | 可视化的代码同上,这里只贴图。 126 | 127 | ![](imgs/model2.png) 128 | 129 | 可以看到模型比上一个模型复杂了许多,但实际上只是因为输入比较多,所以它显得很大。还有一个值得注意的地方,我们的图片在输入的时候是经过了旋转的,这是因为我们希望以水平方向输入循环神经网络,而图片在 numpy 里默认是这样的形状:(height, width, 3),因此我们使用了 `transpose` 函数将图片转为了(width, height, 3)的格式,这样就能把 X 轴转到第一个维度,方便输入到循环神经网络。 130 | 131 | ## 数据生成器 132 | 133 | 根据模型的输入,我们需要输入四个数据: 134 | 135 | * X 是一批图片; 136 | * y 是每个图片对应的 label,最大长度为 n_len; 137 | * input\_length 表示模型输出的长度,我们这里是15; 138 | * label\_length 表示 labels 的长度,我们这里是4。 139 | 140 | 最后还有一个输入是 `np.ones(batch_size)`,这是因为 Keras 在训练模型的时候必须输入一个 X 和一个 y,我们这里把上面四个都合并为一个 X 了,因此实际上 y 没有参与 loss 的计算,所以随便编一个 `batch_size` 长度的数据输入进去就好了。 141 | 142 | ```py 143 | def gen(batch_size=128): 144 | X = np.zeros((batch_size, width, height, 3), dtype=np.uint8) 145 | y = np.zeros((batch_size, n_len), dtype=np.uint8) 146 | generator = ImageCaptcha(width=width, height=height) 147 | while True: 148 | for i in range(batch_size): 149 | random_str = ''.join([random.choice(characters) for j in range(n_len)]) 150 | X[i] = np.array(generator.generate_image(random_str)).transpose(1, 0, 2) 151 | y[i] = [characters.find(x) for x in random_str] 152 | yield [X, y, np.ones(batch_size)*rnn_length, np.ones(batch_size)*n_len], np.ones(batch_size) 153 | ``` 154 | 155 | 我们可以举个例子,使用一次生成器,看看输出的是什么内容: 156 | 157 | ```py 158 | (X_vis, y_vis, input_length_vis, label_length_vis), _ = next(gen(1)) 159 | print(X_vis.shape, y_vis, input_length_vis, label_length_vis) 160 | 161 | plt.imshow(X_vis[0].transpose(1, 0, 2)) 162 | plt.title(''.join([characters[i] for i in y_vis[0]])) 163 | ``` 164 | 165 | 我们可以看到输出了下面的内容: 166 | 167 | `(1, 170, 80, 3) [[29 4 21 21]] [ 15.] [ 4.]` 168 | 169 | ![](imgs/generator_vis.png) 170 | 171 | 这里: 172 | 173 | * X 的 shape 是 `(1, 170, 80, 3)`,如果有 n 张图,shape 就是 `(n, 170, 80, 3)` 174 | * y 是 label,我们可以看到生成的图片是 T4LL,那么按上面的 characters,label 就是 `[29 4 21 21]`,外面还有一个框是因为这里面可以有 n 个 label 175 | * input\_length 表示模型输出的长度,我们这里是15; 176 | * label\_length 表示 labels 的长度,我们这里是4。 177 | 178 | ## 评估模型 179 | 180 | 我们会通过这个函数来评估我们的模型,和上面的评估标准一样,只有全部正确,我们才算预测正确。这里有个坑,就是模型最开始训练的时候,并不一定会输出四个字符,所以我们如果遇到所有的字符都不到四个的时候,就不用计算了,一定是全错。遇到多于四个字符的时候,只取前四个。 181 | 182 | ```py 183 | def evaluate(batch_size=128, steps=10): 184 | batch_acc = 0 185 | generator = gen(batch_size) 186 | for i in range(steps): 187 | [X_test, y_test, _, _], _ = next(generator) 188 | y_pred = base_model.predict(X_test) 189 | shape = y_pred[:,2:,:].shape 190 | ctc_decode = K.ctc_decode(y_pred[:,2:,:], input_length=np.ones(shape[0])*shape[1])[0][0] 191 | out = K.get_value(ctc_decode)[:, :n_len] 192 | if out.shape[1] == n_len: 193 | batch_acc += (y_test == out).all(axis=1).mean() 194 | return batch_acc / steps 195 | ``` 196 | 197 | ## 评估回调 198 | 199 | 因为 Keras 没有针对 CTC 模型计算准确率的选项,因此我们需要自定义一个回调函数,它会在每一代训练完成的时候计算模型的准确率。 200 | 201 | ```py 202 | from keras.callbacks import * 203 | 204 | class Evaluator(Callback): 205 | def __init__(self): 206 | self.accs = [] 207 | 208 | def on_epoch_end(self, epoch, logs=None): 209 | acc = evaluate(steps=20)*100 210 | self.accs.append(acc) 211 | print('') 212 | print('acc: %f%%' % acc) 213 | 214 | evaluator = Evaluator() 215 | ``` 216 | 217 | ## 训练模型 218 | 219 | 我们先按 `Adam(1e-3)` 的学习率训练20代,让模型快速收敛,然后以 `Adam(1e-4)` 的学习率再训练20代。这里设置每代训练 400 个 step,也就是每代 `400*128=51200` 个样本,验证集设置的是 `20*128=2048` 个样本。 220 | 221 | ```py 222 | h = model.fit_generator(gen(128), steps_per_epoch=400, epochs=20, 223 | callbacks=[evaluator], 224 | validation_data=gen(128), validation_steps=20) 225 | ``` 226 | 227 | ```py 228 | model.compile(loss={'ctc': lambda y_true, y_pred: y_pred}, optimizer=Adam(1e-4)) 229 | h2 = model.fit_generator(gen(128), steps_per_epoch=400, epochs=20, 230 | callbacks=[evaluator], 231 | validation_data=gen(128), validation_steps=20) 232 | ``` 233 | 234 | 然后我们将 loss 和 acc 的曲线图画出来: 235 | 236 | ```py 237 | plt.figure(figsize=(10, 4)) 238 | plt.subplot(1, 2, 1) 239 | plt.plot(h.history['loss'] + h2.history['loss']) 240 | plt.plot(h.history['val_loss'] + h2.history['val_loss']) 241 | plt.legend(['loss', 'val_loss']) 242 | plt.ylabel('loss') 243 | plt.xlabel('epoch') 244 | plt.ylim(0, 1) 245 | 246 | plt.subplot(1, 2, 2) 247 | plt.plot(evaluator.accs) 248 | plt.ylabel('acc') 249 | plt.xlabel('epoch') 250 | ``` 251 | 252 | ![](imgs/loss_acc_3.png) 253 | 254 | 训练到20代的时候,模型是这样的表现: 255 | 256 | ``` 257 | Epoch 20/20 258 | 399/400 [============================>.] - ETA: 0s - loss: 0.1593 259 | acc: 97.929688% 260 | 400/400 [==============================] - 122s - loss: 0.1589 - val_loss: 0.1671 261 | ``` 262 | 263 | 训练到40代的时候,模型是这样的表现: 264 | 265 | ``` 266 | Epoch 20/20 267 | 399/400 [============================>.] - ETA: 0s - loss: 0.1317 268 | acc: 99.570312% 269 | 400/400 [==============================] - 123s - loss: 0.1315 - val_loss: 0.1130 270 | ``` 271 | 272 | ## 测试模型 273 | 274 | ```py 275 | (X_vis, y_vis, input_length_vis, label_length_vis), _ = next(gen(12)) 276 | 277 | y_pred = base_model.predict(X_vis) 278 | shape = y_pred[:,2:,:].shape 279 | ctc_decode = K.ctc_decode(y_pred[:,2:,:], input_length=np.ones(shape[0])*shape[1])[0][0] 280 | out = K.get_value(ctc_decode)[:, :4] 281 | 282 | plt.figure(figsize=(16, 8)) 283 | for i in range(12): 284 | plt.subplot(3, 4, i+1) 285 | plt.imshow(X_vis[i].transpose(1, 0, 2)) 286 | plt.title('pred:%s\nreal :%s' % (''.join([characters[x] for x in out[i]]), 287 | ''.join([characters[x] for x in y_vis[i]]))) 288 | ``` 289 | 290 | ![](imgs/test_model2.png) 291 | 292 | ## 评估模型 293 | 294 | 我们可以尝试计算模型的总体准确率,以及看看模型到底错在哪。首先生成1024个样本,然后用 `base_model` 进行预测,然后裁剪并进行 ctc 解码,最后裁剪到4个 label 并与真实值进行对比。 295 | 296 | ```py 297 | (X_vis, y_vis, input_length_vis, label_length_vis), _ = next(gen(10000)) 298 | 299 | y_pred = base_model.predict(X_vis, verbose=1) 300 | shape = y_pred[:,2:,:].shape 301 | ctc_decode = K.ctc_decode(y_pred[:,2:,:], input_length=np.ones(shape[0])*shape[1])[0][0] 302 | out = K.get_value(ctc_decode)[:, :4] 303 | 304 | (y_vis == out).all(axis=1).mean() 305 | 306 | # 0.99460000000000004 307 | ``` 308 | 309 | 输出结果是99.46%的准确率,已经比上一个模型强很多了。 310 | 311 | 我们可以对预测错的样本进行统计: 312 | 313 | ```py 314 | from collections import Counter 315 | Counter(''.join([characters[i] for i in y_vis[y_vis != out]])) 316 | 317 | Counter({'0': 37, 'O': 14, 'Q': 1, 'T': 1, 'W': 1}) 318 | ``` 319 | 320 | 我们可以发现模型在 0 和 O 的准确率稍微低一点,其他的错误都只是个例。0与 O 确实是很难分辨的,我们可以尝试用代码生成一个 '0O0O' 的图像,然后用模型预测: 321 | 322 | ```py 323 | characters2 = characters + ' ' 324 | 325 | generator = ImageCaptcha(width=width, height=height) 326 | random_str = '0O0O' 327 | X_test = np.array(generator.generate_image(random_str)) 328 | X_test = X_test.transpose(1, 0, 2) 329 | X_test = np.expand_dims(X_test, 0) 330 | 331 | y_pred = base_model.predict(X_test) 332 | shape = y_pred[:,2:,:].shape 333 | ctc_decode = K.ctc_decode(y_pred[:,2:,:], input_length=np.ones(shape[0])*shape[1])[0][0] 334 | out = K.get_value(ctc_decode)[:, :4] 335 | out = ''.join([characters[x] for x in out[0]]) 336 | 337 | plt.imshow(X_test[0].transpose(1, 0, 2)) 338 | plt.title('pred:' + str(out)) 339 | 340 | argmax = np.argmax(y_pred, axis=2)[0] 341 | list(zip(argmax, ''.join([characters2[x] for x in argmax]))) 342 | ``` 343 | 344 | ![](imgs/0O0O.png) 345 | 346 | 可以看到模型预测得还是很准的。 347 | 348 | ## 总结 349 | 350 | 模型的大小是3.3MB,在显卡上跑10000张验证码需要用9秒,平均一秒识别一千张以上,完全可以拼过网速。即使是在笔记本上跑,也可以跑到一秒几十张的速度,因此此类验证码可以说已经被破解了。 351 | -------------------------------------------------------------------------------- /决赛代码/.ipynb_checkpoints/plot_model-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 6, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from keras.layers import *\n", 10 | "from keras.models import *\n", 11 | "from keras.optimizers import *\n", 12 | "from keras.regularizers import l2\n", 13 | "from keras.utils.vis_utils import model_to_dot, plot_model\n", 14 | "import keras.backend as K" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 3, 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "characters = u'0123456789()+-*/=君不见黄河之水天上来奔流到海复回烟锁池塘柳深圳铁板烧; '\n", 24 | "n, width, height, n_class, channels = 100000, 900, 81, len(characters), 3\n" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 5, 30 | "metadata": {}, 31 | "outputs": [ 32 | { 33 | "name": "stdout", 34 | "output_type": "stream", 35 | "text": [ 36 | "[None, 112, 10, 128] 112 1280\n" 37 | ] 38 | } 39 | ], 40 | "source": [ 41 | "def ctc_lambda_func(args):\n", 42 | " y_pred, labels, input_length, label_length = args\n", 43 | " y_pred = y_pred[:, 2:, :]\n", 44 | " return K.ctc_batch_cost(labels, y_pred, input_length, label_length)\n", 45 | "\n", 46 | "rnn_size = 128\n", 47 | "\n", 48 | "l2_rate = 1e-5\n", 49 | "\n", 50 | "input_tensor = Input((width, height, 3))\n", 51 | "x = input_tensor\n", 52 | "for i, n_cnn in enumerate([3, 4, 6]):\n", 53 | " for j in range(n_cnn):\n", 54 | " x = Conv2D(32*2**i, (3, 3), padding='same', kernel_initializer='he_uniform', \n", 55 | " kernel_regularizer=l2(l2_rate))(x)\n", 56 | " x = BatchNormalization(gamma_regularizer=l2(l2_rate), beta_regularizer=l2(l2_rate))(x)\n", 57 | " x = Activation('relu')(x)\n", 58 | " x = MaxPooling2D((2, 2))(x)\n", 59 | "\n", 60 | "# x = AveragePooling2D((1, 2))(x)\n", 61 | "cnn_model = Model(input_tensor, x, name='cnn')\n", 62 | "\n", 63 | "input_tensor = Input((width, height, 3))\n", 64 | "x = cnn_model(input_tensor)\n", 65 | "\n", 66 | "conv_shape = x.get_shape().as_list()\n", 67 | "rnn_length = conv_shape[1]\n", 68 | "rnn_dimen = conv_shape[3]*conv_shape[2]\n", 69 | "\n", 70 | "print conv_shape, rnn_length, rnn_dimen\n", 71 | "\n", 72 | "x = Reshape(target_shape=(rnn_length, rnn_dimen))(x)\n", 73 | "rnn_length -= 2\n", 74 | "rnn_imp = 0\n", 75 | "\n", 76 | "x = Dense(rnn_size, kernel_initializer='he_uniform', kernel_regularizer=l2(l2_rate), bias_regularizer=l2(l2_rate))(x)\n", 77 | "x = BatchNormalization(gamma_regularizer=l2(l2_rate), beta_regularizer=l2(l2_rate))(x)\n", 78 | "x = Activation('relu')(x)\n", 79 | "# x = Dropout(0.2)(x)\n", 80 | "\n", 81 | "gru_1 = GRU(rnn_size, implementation=rnn_imp, return_sequences=True, name='gru1')(x)\n", 82 | "gru_1b = GRU(rnn_size, implementation=rnn_imp, return_sequences=True, go_backwards=True, name='gru1_b')(x)\n", 83 | "gru1_merged = add([gru_1, gru_1b])\n", 84 | "\n", 85 | "gru_2 = GRU(rnn_size, implementation=rnn_imp, return_sequences=True, name='gru2')(gru1_merged)\n", 86 | "gru_2b = GRU(rnn_size, implementation=rnn_imp, return_sequences=True, go_backwards=True, name='gru2_b')(gru1_merged)\n", 87 | "x = concatenate([gru_2, gru_2b])\n", 88 | "\n", 89 | "# x = Dropout(0.2)(x)\n", 90 | "x = Dense(n_class, activation='softmax', kernel_regularizer=l2(l2_rate), bias_regularizer=l2(l2_rate))(x)\n", 91 | "rnn_out = x\n", 92 | "base_model = Model(input_tensor, x)" 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": 8, 98 | "metadata": { 99 | "collapsed": true 100 | }, 101 | "outputs": [], 102 | "source": [ 103 | "plot_model(base_model, show_shapes=True)" 104 | ] 105 | }, 106 | { 107 | "cell_type": "code", 108 | "execution_count": 11, 109 | "metadata": {}, 110 | "outputs": [], 111 | "source": [ 112 | "plot_model(cnn_model, 'level2_cnn_model.png', show_shapes=True)" 113 | ] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": null, 118 | "metadata": { 119 | "collapsed": true 120 | }, 121 | "outputs": [], 122 | "source": [] 123 | } 124 | ], 125 | "metadata": { 126 | "kernelspec": { 127 | "display_name": "Python 2", 128 | "language": "python", 129 | "name": "python2" 130 | }, 131 | "language_info": { 132 | "codemirror_mode": { 133 | "name": "ipython", 134 | "version": 2 135 | }, 136 | "file_extension": ".py", 137 | "mimetype": "text/x-python", 138 | "name": "python", 139 | "nbconvert_exporter": "python", 140 | "pygments_lexer": "ipython2", 141 | "version": "2.7.13" 142 | } 143 | }, 144 | "nbformat": 4, 145 | "nbformat_minor": 2 146 | } 147 | -------------------------------------------------------------------------------- /决赛代码/make_parallel.py: -------------------------------------------------------------------------------- 1 | from keras.layers.merge import Concatenate 2 | from keras.layers.core import Lambda 3 | from keras.models import Model 4 | 5 | import tensorflow as tf 6 | 7 | def make_parallel(model, gpu_count): 8 | def get_slice(data, idx, parts): 9 | shape = tf.shape(data) 10 | size = tf.concat([ shape[:1] // parts, shape[1:] ],axis=0) 11 | stride = tf.concat([ shape[:1] // parts, shape[1:]*0 ],axis=0) 12 | start = stride * idx 13 | return tf.slice(data, start, size) 14 | 15 | outputs_all = [] 16 | for i in range(len(model.outputs)): 17 | outputs_all.append([]) 18 | 19 | #Place a copy of the model on each GPU, each getting a slice of the batch 20 | for i in range(gpu_count): 21 | with tf.device('/gpu:%d' % i): 22 | with tf.name_scope('tower_%d' % i) as scope: 23 | 24 | inputs = [] 25 | #Slice each input into a piece for processing on this GPU 26 | for x in model.inputs: 27 | input_shape = tuple(x.get_shape().as_list())[1:] 28 | slice_n = Lambda(get_slice, output_shape=input_shape, arguments={'idx':i,'parts':gpu_count})(x) 29 | inputs.append(slice_n) 30 | 31 | outputs = model(inputs) 32 | 33 | if not isinstance(outputs, list): 34 | outputs = [outputs] 35 | 36 | #Save all the outputs for merging back together later 37 | for l in range(len(outputs)): 38 | outputs_all[l].append(outputs[l]) 39 | 40 | # merge outputs on CPU 41 | with tf.device('/cpu:0'): 42 | merged = [] 43 | for i, outputs in enumerate(outputs_all): 44 | merged.append(Concatenate(axis=0, name='c%d' % (i+1))(outputs)) 45 | 46 | return Model(model.inputs, merged) -------------------------------------------------------------------------------- /决赛代码/plot_model.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 6, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from keras.layers import *\n", 10 | "from keras.models import *\n", 11 | "from keras.optimizers import *\n", 12 | "from keras.regularizers import l2\n", 13 | "from keras.utils.vis_utils import model_to_dot, plot_model\n", 14 | "import keras.backend as K" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 3, 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "characters = u'0123456789()+-*/=君不见黄河之水天上来奔流到海复回烟锁池塘柳深圳铁板烧; '\n", 24 | "n, width, height, n_class, channels = 100000, 900, 81, len(characters), 3\n" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 5, 30 | "metadata": {}, 31 | "outputs": [ 32 | { 33 | "name": "stdout", 34 | "output_type": "stream", 35 | "text": [ 36 | "[None, 112, 10, 128] 112 1280\n" 37 | ] 38 | } 39 | ], 40 | "source": [ 41 | "def ctc_lambda_func(args):\n", 42 | " y_pred, labels, input_length, label_length = args\n", 43 | " y_pred = y_pred[:, 2:, :]\n", 44 | " return K.ctc_batch_cost(labels, y_pred, input_length, label_length)\n", 45 | "\n", 46 | "rnn_size = 128\n", 47 | "\n", 48 | "l2_rate = 1e-5\n", 49 | "\n", 50 | "input_tensor = Input((width, height, 3))\n", 51 | "x = input_tensor\n", 52 | "for i, n_cnn in enumerate([3, 4, 6]):\n", 53 | " for j in range(n_cnn):\n", 54 | " x = Conv2D(32*2**i, (3, 3), padding='same', kernel_initializer='he_uniform', \n", 55 | " kernel_regularizer=l2(l2_rate))(x)\n", 56 | " x = BatchNormalization(gamma_regularizer=l2(l2_rate), beta_regularizer=l2(l2_rate))(x)\n", 57 | " x = Activation('relu')(x)\n", 58 | " x = MaxPooling2D((2, 2))(x)\n", 59 | "\n", 60 | "# x = AveragePooling2D((1, 2))(x)\n", 61 | "cnn_model = Model(input_tensor, x, name='cnn')\n", 62 | "\n", 63 | "input_tensor = Input((width, height, 3))\n", 64 | "x = cnn_model(input_tensor)\n", 65 | "\n", 66 | "conv_shape = x.get_shape().as_list()\n", 67 | "rnn_length = conv_shape[1]\n", 68 | "rnn_dimen = conv_shape[3]*conv_shape[2]\n", 69 | "\n", 70 | "print conv_shape, rnn_length, rnn_dimen\n", 71 | "\n", 72 | "x = Reshape(target_shape=(rnn_length, rnn_dimen))(x)\n", 73 | "rnn_length -= 2\n", 74 | "rnn_imp = 0\n", 75 | "\n", 76 | "x = Dense(rnn_size, kernel_initializer='he_uniform', kernel_regularizer=l2(l2_rate), bias_regularizer=l2(l2_rate))(x)\n", 77 | "x = BatchNormalization(gamma_regularizer=l2(l2_rate), beta_regularizer=l2(l2_rate))(x)\n", 78 | "x = Activation('relu')(x)\n", 79 | "# x = Dropout(0.2)(x)\n", 80 | "\n", 81 | "gru_1 = GRU(rnn_size, implementation=rnn_imp, return_sequences=True, name='gru1')(x)\n", 82 | "gru_1b = GRU(rnn_size, implementation=rnn_imp, return_sequences=True, go_backwards=True, name='gru1_b')(x)\n", 83 | "gru1_merged = add([gru_1, gru_1b])\n", 84 | "\n", 85 | "gru_2 = GRU(rnn_size, implementation=rnn_imp, return_sequences=True, name='gru2')(gru1_merged)\n", 86 | "gru_2b = GRU(rnn_size, implementation=rnn_imp, return_sequences=True, go_backwards=True, name='gru2_b')(gru1_merged)\n", 87 | "x = concatenate([gru_2, gru_2b])\n", 88 | "\n", 89 | "# x = Dropout(0.2)(x)\n", 90 | "x = Dense(n_class, activation='softmax', kernel_regularizer=l2(l2_rate), bias_regularizer=l2(l2_rate))(x)\n", 91 | "rnn_out = x\n", 92 | "base_model = Model(input_tensor, x)" 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": 8, 98 | "metadata": { 99 | "collapsed": true 100 | }, 101 | "outputs": [], 102 | "source": [ 103 | "plot_model(base_model, show_shapes=True)" 104 | ] 105 | }, 106 | { 107 | "cell_type": "code", 108 | "execution_count": 11, 109 | "metadata": {}, 110 | "outputs": [], 111 | "source": [ 112 | "plot_model(cnn_model, 'level2_cnn_model.png', show_shapes=True)" 113 | ] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": null, 118 | "metadata": { 119 | "collapsed": true 120 | }, 121 | "outputs": [], 122 | "source": [] 123 | } 124 | ], 125 | "metadata": { 126 | "kernelspec": { 127 | "display_name": "Python 2", 128 | "language": "python", 129 | "name": "python2" 130 | }, 131 | "language_info": { 132 | "codemirror_mode": { 133 | "name": "ipython", 134 | "version": 2 135 | }, 136 | "file_extension": ".py", 137 | "mimetype": "text/x-python", 138 | "name": "python", 139 | "nbconvert_exporter": "python", 140 | "pygments_lexer": "ipython2", 141 | "version": "2.7.13" 142 | } 143 | }, 144 | "nbformat": 4, 145 | "nbformat_minor": 2 146 | } 147 | -------------------------------------------------------------------------------- /决赛代码/在测试集上预测_0.9976.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "GPU1 Memory: 11172MB\n" 13 | ] 14 | } 15 | ], 16 | "source": [ 17 | "from pynvml import *\n", 18 | "\n", 19 | "nvmlInit()\n", 20 | "vram = nvmlDeviceGetMemoryInfo(nvmlDeviceGetHandleByIndex(1)).free/1024.**2\n", 21 | "print('GPU1 Memory: %dMB' % vram)\n", 22 | "if vram < 8000:\n", 23 | " raise Exception('GPU Memory too low')\n", 24 | "nvmlShutdown()" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 2, 30 | "metadata": {}, 31 | "outputs": [ 32 | { 33 | "name": "stderr", 34 | "output_type": "stream", 35 | "text": [ 36 | "Using TensorFlow backend.\n" 37 | ] 38 | } 39 | ], 40 | "source": [ 41 | "import os\n", 42 | "import cv2\n", 43 | "import h5py\n", 44 | "import numpy as np\n", 45 | "import matplotlib.pyplot as plt\n", 46 | "from IPython.display import *\n", 47 | "from collections import Counter\n", 48 | "import seaborn as sns\n", 49 | "from tqdm import tqdm\n", 50 | "import pandas as pd\n", 51 | "import re\n", 52 | "import time\n", 53 | "import random\n", 54 | "\n", 55 | "from keras.layers import *\n", 56 | "from keras.models import *\n", 57 | "from keras.optimizers import *\n", 58 | "from keras.regularizers import l2\n", 59 | "from keras.utils.vis_utils import model_to_dot\n", 60 | "import keras.backend as K\n", 61 | "from make_parallel import make_parallel\n", 62 | "\n", 63 | "%matplotlib inline\n", 64 | "%config InlineBackend.figure_format = 'retina'\n", 65 | "IMAGE_DIR = 'image_contest_level_2'\n", 66 | "\n", 67 | "MODEL_NAME = 'model_l2加层生成器2_0.997656.h5'" 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": 3, 73 | "metadata": { 74 | "collapsed": true 75 | }, 76 | "outputs": [], 77 | "source": [ 78 | "df = pd.read_csv('image_contest_level_2/labels.txt', sep=' ', header=None)\n", 79 | "characters = u'0123456789()+-*/=君不见黄河之水天上来奔流到海复回烟锁池塘柳深圳铁板烧; '\n", 80 | "\n", 81 | "labels_len = np.array(map(lambda x:len(x.decode('utf-8')), df[0]))\n", 82 | "n_len = 45\n", 83 | "n, width, height, n_class, channels = 100000, 900, 72, len(characters), 3" 84 | ] 85 | }, 86 | { 87 | "cell_type": "code", 88 | "execution_count": 4, 89 | "metadata": { 90 | "collapsed": true 91 | }, 92 | "outputs": [], 93 | "source": [ 94 | "def decode(out):\n", 95 | " return ''.join([characters[x] for x in out if x < n_class-1 and x > -1])\n", 96 | "\n", 97 | "def disp2(img):\n", 98 | " cv2.imwrite('a.png', img)\n", 99 | " return Image('a.png')\n", 100 | "\n", 101 | "def disp(img, txt=None, first=False):\n", 102 | " global index\n", 103 | " if first:\n", 104 | " index = 1\n", 105 | " plt.figure(figsize=(16, 9))\n", 106 | " else:\n", 107 | " index += 1\n", 108 | " plt.subplot(4, 1, index)\n", 109 | " if len(img.shape) == 2:\n", 110 | " plt.imshow(img, cmap='gray')\n", 111 | " else:\n", 112 | " plt.imshow(img[:,:,::-1])\n", 113 | " if txt:\n", 114 | " plt.title(txt)" 115 | ] 116 | }, 117 | { 118 | "cell_type": "markdown", 119 | "metadata": {}, 120 | "source": [ 121 | "# 读取测试集" 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": 5, 127 | "metadata": {}, 128 | "outputs": [ 129 | { 130 | "name": "stderr", 131 | "output_type": "stream", 132 | "text": [ 133 | "100%|██████████| 100000/100000 [01:12<00:00, 1381.44it/s]\n" 134 | ] 135 | } 136 | ], 137 | "source": [ 138 | "X = np.zeros((n, width, height, channels), dtype=np.uint8)\n", 139 | "\n", 140 | "for i in tqdm(range(n)):\n", 141 | " img = cv2.imread('crop_split_test/%d.png'%i).transpose(1, 0, 2)\n", 142 | " a, b, _ = img.shape\n", 143 | " X[i, :a, :b] = img" 144 | ] 145 | }, 146 | { 147 | "cell_type": "markdown", 148 | "metadata": {}, 149 | "source": [ 150 | "# 载入模型" 151 | ] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "execution_count": 6, 156 | "metadata": {}, 157 | "outputs": [ 158 | { 159 | "name": "stdout", 160 | "output_type": "stream", 161 | "text": [ 162 | "[None, 112, 9, 128] 112 1152\n" 163 | ] 164 | } 165 | ], 166 | "source": [ 167 | "def ctc_lambda_func(args):\n", 168 | " y_pred, labels, input_length, label_length = args\n", 169 | " y_pred = y_pred[:, 2:, :]\n", 170 | " return K.ctc_batch_cost(labels, y_pred, input_length, label_length)\n", 171 | "\n", 172 | "rnn_size = 128\n", 173 | "\n", 174 | "l2_rate = 1e-5\n", 175 | "\n", 176 | "input_tensor = Input((width, height, 3))\n", 177 | "x = input_tensor\n", 178 | "for i, n_cnn in enumerate([2, 3, 6]):\n", 179 | " for j in range(n_cnn):\n", 180 | " x = Conv2D(32*2**i, (3, 3), padding='same', kernel_initializer='he_normal', kernel_regularizer=l2(l2_rate))(x)\n", 181 | " x = BatchNormalization()(x)\n", 182 | " x = Activation('relu')(x)\n", 183 | " x = MaxPooling2D((2, 2))(x)\n", 184 | "\n", 185 | "cnn_model = Model(input_tensor, x, name='cnn')\n", 186 | "\n", 187 | "input_tensor = Input((width, height, 3))\n", 188 | "x = cnn_model(input_tensor)\n", 189 | "\n", 190 | "conv_shape = x.get_shape().as_list()\n", 191 | "rnn_length = conv_shape[1]\n", 192 | "rnn_dimen = conv_shape[3]*conv_shape[2]\n", 193 | "\n", 194 | "print conv_shape, rnn_length, rnn_dimen\n", 195 | "\n", 196 | "x = Reshape(target_shape=(rnn_length, rnn_dimen))(x)\n", 197 | "rnn_length -= 2\n", 198 | "\n", 199 | "x = Dense(rnn_size, kernel_initializer='he_normal', kernel_regularizer=l2(l2_rate))(x)\n", 200 | "x = BatchNormalization()(x)\n", 201 | "x = Activation('relu')(x)\n", 202 | "x = Dropout(0.25)(x)\n", 203 | "\n", 204 | "gru_1 = GRU(rnn_size, implementation=2, return_sequences=True, name='gru1')(x)\n", 205 | "gru_1b = GRU(rnn_size, implementation=2, return_sequences=True, go_backwards=True, name='gru1_b')(x)\n", 206 | "gru1_merged = add([gru_1, gru_1b])\n", 207 | "\n", 208 | "gru_2 = GRU(rnn_size, implementation=2, return_sequences=True, name='gru2')(gru1_merged)\n", 209 | "gru_2b = GRU(rnn_size, implementation=2, return_sequences=True, go_backwards=True, name='gru2_b')(gru1_merged)\n", 210 | "x = concatenate([gru_2, gru_2b])\n", 211 | "x = Dropout(0.25)(x)\n", 212 | "x = Dense(n_class, activation='softmax', kernel_regularizer=l2(l2_rate))(x)\n", 213 | "rnn_out = x\n", 214 | "base_model = Model(input_tensor, x)\n", 215 | "\n", 216 | "base_model2 = make_parallel(base_model, 4)" 217 | ] 218 | }, 219 | { 220 | "cell_type": "markdown", 221 | "metadata": {}, 222 | "source": [ 223 | "# 预测\n" 224 | ] 225 | }, 226 | { 227 | "cell_type": "code", 228 | "execution_count": 8, 229 | "metadata": { 230 | "collapsed": true 231 | }, 232 | "outputs": [], 233 | "source": [ 234 | "def disp3(index):\n", 235 | " s = decode(out[index])\n", 236 | " plt.figure(figsize=(16, 4))\n", 237 | " plt.imshow(X[index].transpose(1, 0, 2))\n", 238 | " plt.title('pred:%s'%s)" 239 | ] 240 | }, 241 | { 242 | "cell_type": "code", 243 | "execution_count": 11, 244 | "metadata": {}, 245 | "outputs": [ 246 | { 247 | "name": "stdout", 248 | "output_type": "stream", 249 | "text": [ 250 | "100000/100000 [==============================] - 197s \n" 251 | ] 252 | } 253 | ], 254 | "source": [ 255 | "z = '0.997656'\n", 256 | "base_model.load_weights('model_l2加层生成器2_%s.h5' % z)\n", 257 | "y_pred = base_model2.predict(X, batch_size=500, verbose=1)\n", 258 | "out = K.get_value(K.ctc_decode(y_pred[:,2:], input_length=np.ones(y_pred.shape[0])*rnn_length)[0][0])[:, :n_len]" 259 | ] 260 | }, 261 | { 262 | "cell_type": "code", 263 | "execution_count": 12, 264 | "metadata": { 265 | "scrolled": false 266 | }, 267 | "outputs": [ 268 | { 269 | "name": "stderr", 270 | "output_type": "stream", 271 | "text": [ 272 | "100%|██████████| 100000/100000 [00:01<00:00, 79744.05it/s]\n" 273 | ] 274 | }, 275 | { 276 | "name": "stdout", 277 | "output_type": "stream", 278 | "text": [ 279 | "42\n", 280 | "0.99958\n" 281 | ] 282 | } 283 | ], 284 | "source": [ 285 | "ss = map(decode, out)\n", 286 | "\n", 287 | "vals = []\n", 288 | "errs = []\n", 289 | "for i in tqdm(range(100000)):\n", 290 | " a = ss[i].split(';')\n", 291 | " s = a[-1]\n", 292 | " for x in a[:-1]:\n", 293 | " x, c = x.split('=')\n", 294 | " s = s.replace(x, c+'.0')\n", 295 | " \n", 296 | " val = ''\n", 297 | " try:\n", 298 | " val = '%.2f' % eval(s)\n", 299 | " except:\n", 300 | " ss[i] = ''\n", 301 | " errs.append(s)\n", 302 | " \n", 303 | " vals.append(val)\n", 304 | " \n", 305 | "with open('result_%s.txt' % z, 'w') as f:\n", 306 | " f.write('\\n'.join(map(' '.join, list(zip(ss, vals)))).encode('utf-8'))\n", 307 | " \n", 308 | "print len(errs)\n", 309 | "print 1-len(errs)/100000." 310 | ] 311 | }, 312 | { 313 | "cell_type": "code", 314 | "execution_count": null, 315 | "metadata": { 316 | "collapsed": true 317 | }, 318 | "outputs": [], 319 | "source": [] 320 | } 321 | ], 322 | "metadata": { 323 | "kernelspec": { 324 | "display_name": "Python 2", 325 | "language": "python", 326 | "name": "python2" 327 | }, 328 | "language_info": { 329 | "codemirror_mode": { 330 | "name": "ipython", 331 | "version": 2 332 | }, 333 | "file_extension": ".py", 334 | "mimetype": "text/x-python", 335 | "name": "python", 336 | "nbconvert_exporter": "python", 337 | "pygments_lexer": "ipython2", 338 | "version": "2.7.13" 339 | } 340 | }, 341 | "nbformat": 4, 342 | "nbformat_minor": 2 343 | } 344 | -------------------------------------------------------------------------------- /决赛代码/在测试集上预测_0.9977.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "GPU1 Memory: 11172MB\n" 13 | ] 14 | } 15 | ], 16 | "source": [ 17 | "from pynvml import *\n", 18 | "\n", 19 | "nvmlInit()\n", 20 | "vram = nvmlDeviceGetMemoryInfo(nvmlDeviceGetHandleByIndex(1)).free/1024.**2\n", 21 | "print('GPU1 Memory: %dMB' % vram)\n", 22 | "if vram < 8000:\n", 23 | " raise Exception('GPU Memory too low')\n", 24 | "nvmlShutdown()" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 2, 30 | "metadata": {}, 31 | "outputs": [ 32 | { 33 | "name": "stderr", 34 | "output_type": "stream", 35 | "text": [ 36 | "Using TensorFlow backend.\n" 37 | ] 38 | } 39 | ], 40 | "source": [ 41 | "import os\n", 42 | "import cv2\n", 43 | "import h5py\n", 44 | "import numpy as np\n", 45 | "import matplotlib.pyplot as plt\n", 46 | "from IPython.display import *\n", 47 | "from collections import Counter\n", 48 | "import seaborn as sns\n", 49 | "from tqdm import tqdm\n", 50 | "import pandas as pd\n", 51 | "import re\n", 52 | "import time\n", 53 | "import random\n", 54 | "\n", 55 | "from keras.models import *\n", 56 | "import keras.backend as K\n", 57 | "from make_parallel import make_parallel\n", 58 | "\n", 59 | "%matplotlib inline\n", 60 | "%config InlineBackend.figure_format = 'retina'" 61 | ] 62 | }, 63 | { 64 | "cell_type": "code", 65 | "execution_count": 7, 66 | "metadata": { 67 | "collapsed": true 68 | }, 69 | "outputs": [], 70 | "source": [ 71 | "characters = u'0123456789()+-*/=君不见黄河之水天上来奔流到海复回烟锁池塘柳深圳铁板烧; '\n", 72 | "\n", 73 | "n_len = 45\n", 74 | "rnn_length = 110\n", 75 | "n, width, height, n_class, channels = 100000, 900, 81, len(characters), 3" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": 4, 81 | "metadata": { 82 | "collapsed": true 83 | }, 84 | "outputs": [], 85 | "source": [ 86 | "def decode(out):\n", 87 | " return ''.join([characters[x] for x in out if x < n_class-1 and x > -1])\n", 88 | "\n", 89 | "def disp3(index):\n", 90 | " s = decode(out[index])\n", 91 | " plt.figure(figsize=(16, 4))\n", 92 | " plt.imshow(X[index].transpose(1, 0, 2))\n", 93 | " plt.title('pred:%s'%s)\n", 94 | "\n", 95 | "def disp2(img):\n", 96 | " cv2.imwrite('a.png', img)\n", 97 | " return Image('a.png')\n", 98 | "\n", 99 | "def disp(img, txt=None, first=False):\n", 100 | " global index\n", 101 | " if first:\n", 102 | " index = 1\n", 103 | " plt.figure(figsize=(16, 9))\n", 104 | " else:\n", 105 | " index += 1\n", 106 | " plt.subplot(4, 1, index)\n", 107 | " if len(img.shape) == 2:\n", 108 | " plt.imshow(img, cmap='gray')\n", 109 | " else:\n", 110 | " plt.imshow(img[:,:,::-1])\n", 111 | " if txt:\n", 112 | " plt.title(txt)" 113 | ] 114 | }, 115 | { 116 | "cell_type": "markdown", 117 | "metadata": {}, 118 | "source": [ 119 | "# 读取测试集" 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": 5, 125 | "metadata": {}, 126 | "outputs": [ 127 | { 128 | "name": "stderr", 129 | "output_type": "stream", 130 | "text": [ 131 | "100%|██████████| 100000/100000 [01:38<00:00, 1013.91it/s]\n" 132 | ] 133 | } 134 | ], 135 | "source": [ 136 | "X = np.zeros((n, width, height, channels), dtype=np.uint8)\n", 137 | "\n", 138 | "for i in tqdm(range(n)):\n", 139 | " img = cv2.imread('crop_split2_test/%d.png'%i).transpose(1, 0, 2)\n", 140 | " a, b, _ = img.shape\n", 141 | " X[i, :a, :b] = img" 142 | ] 143 | }, 144 | { 145 | "cell_type": "markdown", 146 | "metadata": {}, 147 | "source": [ 148 | "# 预测\n" 149 | ] 150 | }, 151 | { 152 | "cell_type": "code", 153 | "execution_count": 8, 154 | "metadata": {}, 155 | "outputs": [ 156 | { 157 | "name": "stdout", 158 | "output_type": "stream", 159 | "text": [ 160 | "100000/100000 [==============================] - 358s \n" 161 | ] 162 | } 163 | ], 164 | "source": [ 165 | "z = '0.997754'\n", 166 | "\n", 167 | "base_model = load_model('model_346_split2_3_%s.h5' % z)\n", 168 | "\n", 169 | "base_model2 = make_parallel(base_model, 4)\n", 170 | "y_pred = base_model2.predict(X, batch_size=500, verbose=1)\n", 171 | "out = K.get_value(K.ctc_decode(y_pred[:,2:], input_length=np.ones(y_pred.shape[0])*rnn_length)[0][0])[:, :n_len]" 172 | ] 173 | }, 174 | { 175 | "cell_type": "code", 176 | "execution_count": 25, 177 | "metadata": { 178 | "scrolled": false 179 | }, 180 | "outputs": [ 181 | { 182 | "name": "stderr", 183 | "output_type": "stream", 184 | "text": [ 185 | "100%|██████████| 100000/100000 [00:01<00:00, 87655.64it/s]\n" 186 | ] 187 | }, 188 | { 189 | "name": "stdout", 190 | "output_type": "stream", 191 | "text": [ 192 | "22\n", 193 | "0.99978\n" 194 | ] 195 | } 196 | ], 197 | "source": [ 198 | "ss = map(decode, out)\n", 199 | "\n", 200 | "vals = []\n", 201 | "errs = []\n", 202 | "errsid = []\n", 203 | "for i in tqdm(range(100000)):\n", 204 | " val = ''\n", 205 | " try:\n", 206 | " a = ss[i].split(';')\n", 207 | " s = a[-1]\n", 208 | " for x in a[:-1]:\n", 209 | " x, c = x.split('=')\n", 210 | " s = s.replace(x, c+'.0')\n", 211 | " val = '%.2f' % eval(s)\n", 212 | " except:\n", 213 | "# disp3(i)\n", 214 | " errs.append(ss[i])\n", 215 | " errsid.append(i)\n", 216 | " ss[i] = ''\n", 217 | " \n", 218 | " vals.append(val)\n", 219 | " \n", 220 | "with open('result_%s.txt' % z, 'w') as f:\n", 221 | " f.write('\\n'.join(map(' '.join, list(zip(ss, vals)))).encode('utf-8'))\n", 222 | " \n", 223 | "print len(errs)\n", 224 | "print 1-len(errs)/100000." 225 | ] 226 | }, 227 | { 228 | "cell_type": "code", 229 | "execution_count": null, 230 | "metadata": { 231 | "collapsed": true 232 | }, 233 | "outputs": [], 234 | "source": [] 235 | }, 236 | { 237 | "cell_type": "code", 238 | "execution_count": 26, 239 | "metadata": {}, 240 | "outputs": [ 241 | { 242 | "name": "stdout", 243 | "output_type": "stream", 244 | "text": [ 245 | "100000/100000 [==============================] - 277s \n" 246 | ] 247 | } 248 | ], 249 | "source": [ 250 | "z = '0.997559'\n", 251 | "\n", 252 | "base_model = load_model('model_346_split2_3_%s.h5' % z)\n", 253 | "\n", 254 | "base_model2 = make_parallel(base_model, 4)\n", 255 | "y_pred = base_model2.predict(X, batch_size=500, verbose=1)\n", 256 | "out = K.get_value(K.ctc_decode(y_pred[:,2:], input_length=np.ones(y_pred.shape[0])*rnn_length)[0][0])[:, :n_len]" 257 | ] 258 | }, 259 | { 260 | "cell_type": "code", 261 | "execution_count": 28, 262 | "metadata": {}, 263 | "outputs": [ 264 | { 265 | "name": "stderr", 266 | "output_type": "stream", 267 | "text": [ 268 | "100%|██████████| 100000/100000 [00:01<00:00, 87565.60it/s]" 269 | ] 270 | }, 271 | { 272 | "name": "stdout", 273 | "output_type": "stream", 274 | "text": [ 275 | "20\n", 276 | "0.9998\n" 277 | ] 278 | }, 279 | { 280 | "name": "stderr", 281 | "output_type": "stream", 282 | "text": [ 283 | "\n" 284 | ] 285 | } 286 | ], 287 | "source": [ 288 | "ss = map(decode, out)\n", 289 | "\n", 290 | "vals = []\n", 291 | "errs = []\n", 292 | "errsid = []\n", 293 | "for i in tqdm(range(100000)):\n", 294 | " val = ''\n", 295 | " try:\n", 296 | " a = ss[i].split(';')\n", 297 | " s = a[-1]\n", 298 | " for x in a[:-1]:\n", 299 | " x, c = x.split('=')\n", 300 | " s = s.replace(x, c+'.0')\n", 301 | " val = '%.2f' % eval(s)\n", 302 | " except:\n", 303 | "# disp3(i)\n", 304 | " errs.append(ss[i])\n", 305 | " errsid.append(i)\n", 306 | " ss[i] = ''\n", 307 | " \n", 308 | " vals.append(val)\n", 309 | " \n", 310 | "with open('result_%s_%d.txt' % (z, len(errs)), 'w') as f:\n", 311 | " f.write('\\n'.join(map(' '.join, list(zip(ss, vals)))).encode('utf-8'))\n", 312 | " \n", 313 | "print len(errs)\n", 314 | "print 1-len(errs)/100000." 315 | ] 316 | }, 317 | { 318 | "cell_type": "code", 319 | "execution_count": null, 320 | "metadata": { 321 | "collapsed": true 322 | }, 323 | "outputs": [], 324 | "source": [] 325 | } 326 | ], 327 | "metadata": { 328 | "kernelspec": { 329 | "display_name": "Python 2", 330 | "language": "python", 331 | "name": "python2" 332 | }, 333 | "language_info": { 334 | "codemirror_mode": { 335 | "name": "ipython", 336 | "version": 2 337 | }, 338 | "file_extension": ".py", 339 | "mimetype": "text/x-python", 340 | "name": "python", 341 | "nbconvert_exporter": "python", 342 | "pygments_lexer": "ipython2", 343 | "version": "2.7.13" 344 | } 345 | }, 346 | "nbformat": 4, 347 | "nbformat_minor": 2 348 | } 349 | -------------------------------------------------------------------------------- /决赛代码/在测试集上预测_全集训练150代.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "GPU1 Memory: 11172MB\n" 13 | ] 14 | } 15 | ], 16 | "source": [ 17 | "from pynvml import *\n", 18 | "\n", 19 | "nvmlInit()\n", 20 | "vram = nvmlDeviceGetMemoryInfo(nvmlDeviceGetHandleByIndex(1)).free/1024.**2\n", 21 | "print('GPU1 Memory: %dMB' % vram)\n", 22 | "if vram < 8000:\n", 23 | " raise Exception('GPU Memory too low')\n", 24 | "nvmlShutdown()" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 2, 30 | "metadata": {}, 31 | "outputs": [ 32 | { 33 | "name": "stderr", 34 | "output_type": "stream", 35 | "text": [ 36 | "Using TensorFlow backend.\n" 37 | ] 38 | } 39 | ], 40 | "source": [ 41 | "import os\n", 42 | "import cv2\n", 43 | "import h5py\n", 44 | "import numpy as np\n", 45 | "import matplotlib.pyplot as plt\n", 46 | "from IPython.display import *\n", 47 | "from collections import Counter\n", 48 | "import seaborn as sns\n", 49 | "from tqdm import tqdm\n", 50 | "import pandas as pd\n", 51 | "import re\n", 52 | "import time\n", 53 | "import random\n", 54 | "\n", 55 | "from keras.models import *\n", 56 | "import keras.backend as K\n", 57 | "from make_parallel import make_parallel\n", 58 | "\n", 59 | "%matplotlib inline\n", 60 | "%config InlineBackend.figure_format = 'retina'" 61 | ] 62 | }, 63 | { 64 | "cell_type": "code", 65 | "execution_count": 3, 66 | "metadata": { 67 | "collapsed": true 68 | }, 69 | "outputs": [], 70 | "source": [ 71 | "characters = u'0123456789()+-*/=君不见黄河之水天上来奔流到海复回烟锁池塘柳深圳铁板烧; '\n", 72 | "\n", 73 | "n_len = 51\n", 74 | "rnn_length = 110\n", 75 | "n, width, height, n_class, channels = 100000, 900, 81, len(characters), 3" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": 4, 81 | "metadata": { 82 | "collapsed": true 83 | }, 84 | "outputs": [], 85 | "source": [ 86 | "def decode(out):\n", 87 | " return ''.join([characters[x] for x in out if x < n_class-1 and x > -1])\n", 88 | "\n", 89 | "def disp3(index):\n", 90 | " s = decode(out[index])\n", 91 | " plt.figure(figsize=(16, 4))\n", 92 | " plt.imshow(X[index].transpose(1, 0, 2))\n", 93 | " plt.title('pred:%s'%s)\n", 94 | "\n", 95 | "def disp2(img):\n", 96 | " cv2.imwrite('a.png', img)\n", 97 | " return Image('a.png')\n", 98 | "\n", 99 | "def disp(img, txt=None, first=False):\n", 100 | " global index\n", 101 | " if first:\n", 102 | " index = 1\n", 103 | " plt.figure(figsize=(16, 9))\n", 104 | " else:\n", 105 | " index += 1\n", 106 | " plt.subplot(4, 1, index)\n", 107 | " if len(img.shape) == 2:\n", 108 | " plt.imshow(img, cmap='gray')\n", 109 | " else:\n", 110 | " plt.imshow(img[:,:,::-1])\n", 111 | " if txt:\n", 112 | " plt.title(txt)" 113 | ] 114 | }, 115 | { 116 | "cell_type": "markdown", 117 | "metadata": {}, 118 | "source": [ 119 | "# 读取测试集" 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": 5, 125 | "metadata": {}, 126 | "outputs": [ 127 | { 128 | "name": "stderr", 129 | "output_type": "stream", 130 | "text": [ 131 | "100%|██████████| 100000/100000 [01:36<00:00, 1038.14it/s]\n" 132 | ] 133 | } 134 | ], 135 | "source": [ 136 | "X = np.zeros((n, width, height, channels), dtype=np.uint8)\n", 137 | "\n", 138 | "for i in tqdm(range(n)):\n", 139 | " img = cv2.imread('crop_split2_test/%d.png'%i).transpose(1, 0, 2)\n", 140 | " a, b, _ = img.shape\n", 141 | " X[i, :a, :b] = img" 142 | ] 143 | }, 144 | { 145 | "cell_type": "markdown", 146 | "metadata": {}, 147 | "source": [ 148 | "# 预测\n" 149 | ] 150 | }, 151 | { 152 | "cell_type": "code", 153 | "execution_count": 6, 154 | "metadata": {}, 155 | "outputs": [ 156 | { 157 | "name": "stderr", 158 | "output_type": "stream", 159 | "text": [ 160 | "/home/ypw/.local/lib/python2.7/site-packages/keras/models.py:245: UserWarning: No training configuration found in save file: the model was *not* compiled. Compile it manually.\n", 161 | " warnings.warn('No training configuration found in save file: '\n" 162 | ] 163 | }, 164 | { 165 | "name": "stdout", 166 | "output_type": "stream", 167 | "text": [ 168 | "100000/100000 [==============================] - 292s \n" 169 | ] 170 | } 171 | ], 172 | "source": [ 173 | "z = '150'\n", 174 | "\n", 175 | "base_model = load_model('model_346_split2_4_%s.h5' % z)\n", 176 | "\n", 177 | "base_model2 = make_parallel(base_model, 4)\n", 178 | "y_pred = base_model2.predict(X, batch_size=500, verbose=1)\n", 179 | "out = K.get_value(K.ctc_decode(y_pred[:,2:], input_length=np.ones(y_pred.shape[0])*rnn_length)[0][0])[:, :n_len]" 180 | ] 181 | }, 182 | { 183 | "cell_type": "code", 184 | "execution_count": 7, 185 | "metadata": { 186 | "scrolled": false 187 | }, 188 | "outputs": [ 189 | { 190 | "name": "stderr", 191 | "output_type": "stream", 192 | "text": [ 193 | "100%|██████████| 100000/100000 [00:01<00:00, 85251.85it/s]\n" 194 | ] 195 | }, 196 | { 197 | "name": "stdout", 198 | "output_type": "stream", 199 | "text": [ 200 | "36\n", 201 | "0.99964\n" 202 | ] 203 | } 204 | ], 205 | "source": [ 206 | "ss = map(decode, out)\n", 207 | "\n", 208 | "vals = []\n", 209 | "errs = []\n", 210 | "errsid = []\n", 211 | "for i in tqdm(range(100000)):\n", 212 | " val = ''\n", 213 | " try:\n", 214 | " a = ss[i].split(';')\n", 215 | " s = a[-1]\n", 216 | " for x in a[:-1]:\n", 217 | " x, c = x.split('=')\n", 218 | " s = s.replace(x, c+'.0')\n", 219 | " val = '%.2f' % eval(s)\n", 220 | " except:\n", 221 | "# disp3(i)\n", 222 | " errs.append(ss[i])\n", 223 | " errsid.append(i)\n", 224 | " ss[i] = ''\n", 225 | " \n", 226 | " vals.append(val)\n", 227 | " \n", 228 | "with open('result_%s_%d.txt' % (z, len(errs)), 'w') as f:\n", 229 | " f.write('\\n'.join(map(' '.join, list(zip(ss, vals)))).encode('utf-8'))\n", 230 | "\n", 231 | "print len(errs)\n", 232 | "print 1-len(errs)/100000." 233 | ] 234 | }, 235 | { 236 | "cell_type": "code", 237 | "execution_count": null, 238 | "metadata": { 239 | "collapsed": true 240 | }, 241 | "outputs": [], 242 | "source": [] 243 | } 244 | ], 245 | "metadata": { 246 | "kernelspec": { 247 | "display_name": "Python 2", 248 | "language": "python", 249 | "name": "python2" 250 | }, 251 | "language_info": { 252 | "codemirror_mode": { 253 | "name": "ipython", 254 | "version": 2 255 | }, 256 | "file_extension": ".py", 257 | "mimetype": "text/x-python", 258 | "name": "python", 259 | "nbconvert_exporter": "python", 260 | "pygments_lexer": "ipython2", 261 | "version": "2.7.13" 262 | } 263 | }, 264 | "nbformat": 4, 265 | "nbformat_minor": 2 266 | } 267 | -------------------------------------------------------------------------------- /决赛代码/融合结果.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 结果文件命名规则\n", 8 | "\n", 9 | "`result_模型文件名_空个数.txt`" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "metadata": {}, 16 | "outputs": [ 17 | { 18 | "data": { 19 | "text/plain": [ 20 | "['result_model_346_split2_3_0.997754.h5_22.txt',\n", 21 | " 'result_model_346_split2_4_150.h5_36.txt',\n", 22 | " 'result_model_l2\\xe5\\x8a\\xa0\\xe5\\xb1\\x82\\xe7\\x94\\x9f\\xe6\\x88\\x90\\xe5\\x99\\xa82_0.997656.h5_42.txt']" 23 | ] 24 | }, 25 | "execution_count": 1, 26 | "metadata": {}, 27 | "output_type": "execute_result" 28 | } 29 | ], 30 | "source": [ 31 | "import glob\n", 32 | "import numpy as np\n", 33 | "from collections import Counter\n", 34 | "\n", 35 | "glob.glob('result_model*.txt')" 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "metadata": {}, 41 | "source": [ 42 | "# 融合规则\n", 43 | "\n", 44 | "对所有数据进行次数统计,先去掉空,然后取最高次数的结果。" 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": 2, 50 | "metadata": { 51 | "collapsed": true 52 | }, 53 | "outputs": [], 54 | "source": [ 55 | "def fun(x):\n", 56 | " c = Counter(x)\n", 57 | " c[' '] = 0\n", 58 | " return c.most_common()[0][0]" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": 3, 64 | "metadata": { 65 | "collapsed": true 66 | }, 67 | "outputs": [], 68 | "source": [ 69 | "ss = [open(fname, 'r').read().split('\\n') for fname in glob.glob('result_model*.txt')]\n", 70 | "s = np.array(ss).T\n", 71 | "with open('result.txt', 'w') as f:\n", 72 | " f.write('\\n'.join(map(fun, s)))" 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": null, 78 | "metadata": { 79 | "collapsed": true 80 | }, 81 | "outputs": [], 82 | "source": [] 83 | } 84 | ], 85 | "metadata": { 86 | "kernelspec": { 87 | "display_name": "Python 2", 88 | "language": "python", 89 | "name": "python2" 90 | }, 91 | "language_info": { 92 | "codemirror_mode": { 93 | "name": "ipython", 94 | "version": 2 95 | }, 96 | "file_extension": ".py", 97 | "mimetype": "text/x-python", 98 | "name": "python", 99 | "nbconvert_exporter": "python", 100 | "pygments_lexer": "ipython2", 101 | "version": "2.7.13" 102 | } 103 | }, 104 | "nbformat": 4, 105 | "nbformat_minor": 2 106 | } 107 | -------------------------------------------------------------------------------- /初赛代码/.ipynb_checkpoints/数据集预处理-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import os\n", 12 | "import cv2\n", 13 | "import numpy as np\n", 14 | "import matplotlib.pyplot as plt\n", 15 | "from IPython.display import *\n", 16 | "from collections import Counter\n", 17 | "from skimage import exposure\n", 18 | "import seaborn as sns\n", 19 | "\n", 20 | "%matplotlib inline\n", 21 | "%config InlineBackend.figure_format = 'retina'\n", 22 | "IMAGE_DIR = 'image_contest_level_1_validate'" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": 23, 28 | "metadata": {}, 29 | "outputs": [ 30 | { 31 | "data": { 32 | "text/plain": [ 33 | "" 34 | ] 35 | }, 36 | "execution_count": 23, 37 | "metadata": {}, 38 | "output_type": "execute_result" 39 | }, 40 | { 41 | "data": { 42 | "image/png": "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\nGQwAAAAAMAA6gwEAAAAABqBaa/OuAwAAAAAAM+bOYAAAAACAAdAZDAAAAAAwADqDAQAAAAAGQGcw\nAAAAAMAA6AwGAAAAABgAncEAAAAAAAOgMxgAAAAAYACWsjO4qu5fVUer6j1VdXtV3VhVL6iqc+Zd\nt2mrqn1V9Yyq+p2quqGqbquqm6vqj6vqW6tqw9+4qi6qqtdU1U3dNn9ZVc+qqjN3ex9mraq+uapa\n93rGhHWeVFVXdW334ar606q6ZLfrOitV9cXdMfKP3Tnxnqp6bVX92w3WXepjo6q+oqpeV1X/0O3f\nO6vqVVX1iAnrL3R7VNVTqurFVfXmqrqlOw9+9RTbnPY+L8o5dDrtUVUPrqpDVfWGqvr7qvpYVf1T\nVb26qh5/iu+5pKr+rGuLm7u2edJs9mp7tnNsjG3/y71r67+csM6ZVfW93TF0W3dMvaaqLprenkzH\nNs+VM7t/g99UVR/oXVNeWVUHJmyz548N2EwNKM5OxNpbUWJtsXanxNnibHH2mu0cH2PbL02svc1z\nRZw9S621pXoleVCSf0rSkvz3JM9L8obu/bEk++Zdxynv77d3+/aeJL+W5P9JcjTJB7vlv5mkxrb5\n6iSfSPLhJL+S5L90bdOSvGre+zTl9nlA1xYf6vbvGRus811d2fEk/2+S5yf5+27ZT817H6bQBke6\nffn7JL+Y5D8n+aUk1yQ5MqRjI8nh3m/9y9314TeTfCzJp5J887K1R5K3d/X9UJJru/lf3WT9097n\nRTqHTqc9kryiK/+/Sf5rd3397a59WpJnTtjup3rn3PO7NjnRLfuuebfBdo+NsW2/srdtS/IvN1in\nkrwqJ//9/S/dMfXhrg2/et5tsJP2SHKPJH/Yrfe2JC/orilXJrkxyZMW9djw8pr0ysDi7G6fxdqb\nt49YW6y9um/ibHG2OHsHx8fYtksVa2/jXBFnz/o3mXcFpr5DyWu7H/u7x5b/TLf8F+Zdxynv7xO6\nC8UZY8s/I8nfdfv8tb3l90ryviS3J/nC3vK7Jnlrt/5T571fU2qbSvL6JH/bXRjvEKAmOTfJR7uL\nxLm95eckuaHb5hHz3pcdtMG3dfvw0iSftkH5nYdybHTnxCeT/GOS+46VPb7bv3cuW3t0+/bg7nx4\n3Gb/8G5nnxftHDrN9viWJA/ZYPljM/qPze1J7jdWdlH3mTckOWesnU50bXXutPZnt9pibLtP786j\nVyS5KpMD1G/oyt6S5K695Q/t2u59Se4573bYbntk1CnUkvzHCeV3Hnu/MMeGl9ekVwYWZ3f7Jtae\n3DZibbH26j6Is8XZp9se35IljrNPtz3Gtlu6WPt02yLi7Nn/JvOuwFR3ZnS3QkvyrtwxYLtnRn8h\n+UiSu8+7rrvUHj/atceLe8tWumUv22D9J3Rlb5x33ae0/9+T0V+hH5PkOdk4QP3JbvlPbLD9xLZa\nhFeSu3T/ALw7GwSnp7O/y3BsJPmibh9ePaH8liQfWub2ONU/vNvZ50U+h7YSiGyy7esy1gHQLX95\nt/zpG2wzsa3m/TqdtkjyOxkFqPuyeYD6pq7s8RuUTWynvfDawrlyQVf+itP4zIU8Nry8Vl8RZ2/U\nJmJtsbZYu4mzu3qfKnYQZ29926WKs0+3PbLksfYWzhVx9i68li1n8Gpumde11j7VL2itfSijv5jc\nLcnDd7tic/LxbvqJ3rIndNPf32D9NyW5NclFVXWXWVZs1qrq/IyGEbywtfamTVbdrD1+b2ydRfMl\nGf1V8beTfKrL4XWoqr5nQt6uZT823pHRX5kfVlX7+wVV9ZiM/iP7+t7iZW+PjWxnn5f5HNrMRtfX\nZMnbo6q+JcmTM/or/YlN1rtrRn+hvzXJmzdYZdHb4hu76a9X1dldvswfqar/MCmnW5b82GAQxNl3\nJNYWa4u1R8TZpybO3rpBxtmJWLsjzt4Fd5p3BabsvG56/YTydyR5YpIDGeUfWVpVdackT+ve9k+I\niW3UWvtEVb0ryeck+eyMcrksnG7fr8xo6N6PnmL1zdrjvVX1kST3r6q7tdZunW5NZ+6h3fSjGeXZ\n+dx+YVW9KclTWmvv7xYt9bHRWrupqg5lNJT1b6rqv2c0ZORBSb4qyR8k+Y+9TZa6PSbYzj4v8zm0\noar6rCRfnFHw9abe8rsn+cwkH26tvXeDTd/RTTd84MFe1+33CzP6K/6rT7H6g5KcmdGQ0PFAPlnw\ntsjJ6+tnZTQ8el+vrFXVz2eU6+6TyfIfGwyGOLtHrC3Wjlh7jTh7S8TZWzDUODsRa/eIs3fBst0Z\nfHY3vXlC+erye+9CXebteRkFJK9prb22t3wIbXRZkock+ZbW2m2nWHer7XH2hPK97L7d9AczGhbx\n6Iz+Kv+vMxp685iMEs6vWvpjo7X2giRfk9Efwr4tyQ8n+bqMksy/tLX2vt7qS98eG9jOPi/zOXQH\n3d0av5bR0NDntNY+0Cte2mOmqs5I8rKMhoE/cwubLG1bdFavrz+T0fC98zO6vv6bjILW70jy7N76\ny94eDIPjeD2xtlhbrN0jzj4lcfYpDDXOTsTaY8TZu2DZOoNJUlXPTPL9GT1R8uI5V2dXVdUXZXSH\nwk+31v5k3vWZs9Xz+xNJvqq19settQ+31v4qyb9L8g9JHjthGNtSqqofyuipxi/N6K+pd09yYZJ3\nJvm1qjoyv9qx11XVmRndCfXIJK/M6Im1Q/G9GT3Q49vGAvOhWr2+Hkvy9a21Y9319Q+TPCWjHJrf\nV1WfNrcaAjMj1hZrd8TaPeJsdmLgcXYi1u4TZ++CZesMPtVfxlaXf3AX6jIXVfVdGQ0t+JuMEonf\nNLbK0rZRN2Tt5RkNo3n2KVZftdX2mPRXpr1s9Td8W2vtxn5BN4xo9S6Wh3XTpT02kqSqHpfkcJL/\n0Vr7vtbaO1trt7bWrskoYP//knx/VX12t8lSt8cE29nnZT6H1nQB6q9mdIfLbyT55tZGTyToWcpj\npqoOJLkiyUtaa6/Z4mZL2RY9q/X+3dUhaqtaa/8nowds3TOjOxmS5W8PhsFxHLF2xNp9Yu2OOHtL\nxNkTDDnOTsTaGxBn74Jl6wy+rptOygXy4G46KdfZQquqZyV5cZK/zig4/ccNVpvYRl2A98CM/rr9\nzlnVc4bukdF+nZ/ko1XVVl9Jfrxb55e6ZS/o3m/WHvfL6C/a/7CgOZhW923SRW/1L45nja2/jMdG\nkjypm/7ReEH3+/5ZRtfEh3SLl709NrKdfV7mcyhJUlV3TvLrSZ6a5L8l+caNcnO11j6S0X927tHt\n+7hF/TfoX2U0XO/p/etqd219bLfOO7plT+7e/22STyb57O7YGbeobbHqtK6vS3xsMCyDjrMTsXbE\n2uPE2ieJs09NnL0BcXYSsfY4cfYuWLbO4NV/fJ7Y5VxZU1X3zGjIwa1J/vduV2zWuoT9z0/y9oyC\n0/dNWPUN3fTLNih7TEZPgX5ra+326ddy5m5P8isTXm/r1vnj7v3qsLbN2uPLx9ZZNH+YUf6yfzV+\nPnRWH3Lxrm66zMdGMvoHNhk99Xkjq8s/1k2XvT02sp19XuZzKN3wo1dldKfCy5NcPP4X6jHL2B43\nZvK1dbUj5FXd+xuTpLX20SRvzeiYefQGn7mobbFq9Ynonzte0OW7Ww06b+wVLeOxwbAMNs5OxNod\n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\ngg==\n", 43 | "text/plain": [ 44 | "" 45 | ] 46 | }, 47 | "metadata": { 48 | "image/png": { 49 | "height": 296, 50 | "width": 705 51 | } 52 | }, 53 | "output_type": "display_data" 54 | } 55 | ], 56 | "source": [ 57 | "e = [629,2271,6579,17416,71857,77631,95303,102187,117422,142660,183693]\n", 58 | "index = e[8]\n", 59 | "\n", 60 | "img = cv2.imread('%s/%d.png' % (IMAGE_DIR, index))\n", 61 | "gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n", 62 | "\n", 63 | "h = cv2.equalizeHist(gray)\n", 64 | "# _, h = cv2.threshold(h, 127, 255, cv2.THRESH_BINARY)\n", 65 | "# h = cv2.dilate(h, np.ones((3, 3)))\n", 66 | "# h = cv2.morphologyEx(h, cv2.MORPH_CLOSE, np.ones((3, 3)))\n", 67 | "\n", 68 | "# c = Counter(h.flatten().tolist())\n", 69 | "# # h2 = h == c.most_common()[1][0]\n", 70 | "# h2 = h == sorted(c.keys())[-1]\n", 71 | "\n", 72 | "plt.figure(figsize=(12, 5))\n", 73 | "plt.subplot(2, 2, 1);plt.title('raw');plt.imshow(img[:,:,::-1])\n", 74 | "plt.subplot(2, 2, 2);plt.title('gray');plt.imshow(gray, cmap='gray')\n", 75 | "plt.subplot(2, 2, 3);plt.title('result');plt.imshow(h, cmap='gray')\n", 76 | "# plt.subplot(2, 2, 4);plt.title('result');plt.imshow(h2, cmap='gray')" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": null, 82 | "metadata": { 83 | "collapsed": true 84 | }, 85 | "outputs": [], 86 | "source": [] 87 | } 88 | ], 89 | "metadata": { 90 | "kernelspec": { 91 | "display_name": "Python 2", 92 | "language": "python", 93 | "name": "python2" 94 | }, 95 | "language_info": { 96 | "codemirror_mode": { 97 | "name": "ipython", 98 | "version": 2 99 | }, 100 | "file_extension": ".py", 101 | "mimetype": "text/x-python", 102 | "name": "python", 103 | "nbconvert_exporter": "python", 104 | "pygments_lexer": "ipython2", 105 | "version": "2.7.13" 106 | } 107 | }, 108 | "nbformat": 4, 109 | "nbformat_minor": 2 110 | } 111 | -------------------------------------------------------------------------------- /初赛代码/.ipynb_checkpoints/生成器对比-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "0123456789+-*() \n", 13 | "17\n" 14 | ] 15 | } 16 | ], 17 | "source": [ 18 | "import string\n", 19 | "import os\n", 20 | "\n", 21 | "digits = string.digits\n", 22 | "operators = '+-*'\n", 23 | "characters = digits + operators + '() '\n", 24 | "print(characters)\n", 25 | "\n", 26 | "width, height, n_len, n_class = 180, 60, 7, len(characters) + 1\n", 27 | "print(n_class)" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": 2, 33 | "metadata": {}, 34 | "outputs": [ 35 | { 36 | "data": { 37 | "image/png": "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\nAElFTkSuQmCC\n", 38 | "text/plain": [ 39 | "" 40 | ] 41 | }, 42 | "execution_count": 2, 43 | "metadata": {}, 44 | "output_type": "execute_result" 45 | } 46 | ], 47 | "source": [ 48 | "from captcha.image import ImageCaptcha\n", 49 | "generator = ImageCaptcha(width=width, height=height, font_sizes=range(35, 56), \n", 50 | " fonts=['fonts/%s'%x for x in os.listdir('fonts') if '.tt' in x])\n", 51 | "generator.generate_image('(1-2)-3')" 52 | ] 53 | }, 54 | { 55 | "cell_type": "code", 56 | "execution_count": 3, 57 | "metadata": {}, 58 | "outputs": [ 59 | { 60 | "data": { 61 | "image/png": "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\n", 62 | "text/plain": [ 63 | "" 64 | ] 65 | }, 66 | "execution_count": 3, 67 | "metadata": {}, 68 | "output_type": "execute_result" 69 | } 70 | ], 71 | "source": [ 72 | "from image import ImageCaptcha\n", 73 | "generator = ImageCaptcha(width=width, height=height, font_sizes=range(35, 56), \n", 74 | " fonts=['fonts/%s'%x for x in os.listdir('fonts') if '.tt' in x])\n", 75 | "generator.generate_image('(1-2)-3')" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": null, 81 | "metadata": { 82 | "collapsed": true 83 | }, 84 | "outputs": [], 85 | "source": [] 86 | } 87 | ], 88 | "metadata": { 89 | "kernelspec": { 90 | "display_name": "Python 2", 91 | "language": "python", 92 | "name": "python2" 93 | }, 94 | "language_info": { 95 | "codemirror_mode": { 96 | "name": "ipython", 97 | "version": 2 98 | }, 99 | "file_extension": ".py", 100 | "mimetype": "text/x-python", 101 | "name": "python", 102 | "nbconvert_exporter": "python", 103 | "pygments_lexer": "ipython2", 104 | "version": "2.7.13" 105 | } 106 | }, 107 | "nbformat": 4, 108 | "nbformat_minor": 2 109 | } 110 | -------------------------------------------------------------------------------- /初赛代码/fonts/Andale Mono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/初赛代码/fonts/Andale Mono.ttf -------------------------------------------------------------------------------- /初赛代码/fonts/Arial Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/初赛代码/fonts/Arial Black.ttf -------------------------------------------------------------------------------- /初赛代码/fonts/Arial.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/初赛代码/fonts/Arial.ttf -------------------------------------------------------------------------------- /初赛代码/fonts/CALIBRI.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/初赛代码/fonts/CALIBRI.TTF -------------------------------------------------------------------------------- /初赛代码/fonts/Comic Sans MS.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/初赛代码/fonts/Comic Sans MS.ttf -------------------------------------------------------------------------------- /初赛代码/fonts/Futura.ttc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/初赛代码/fonts/Futura.ttc -------------------------------------------------------------------------------- /初赛代码/fonts/Georgia.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/初赛代码/fonts/Georgia.ttf -------------------------------------------------------------------------------- /初赛代码/fonts/GillSans.ttc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/初赛代码/fonts/GillSans.ttc -------------------------------------------------------------------------------- /初赛代码/fonts/Helvetica.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/初赛代码/fonts/Helvetica.ttf -------------------------------------------------------------------------------- /初赛代码/fonts/Impact.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/初赛代码/fonts/Impact.ttf -------------------------------------------------------------------------------- /初赛代码/fonts/Menlo.ttc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/初赛代码/fonts/Menlo.ttc -------------------------------------------------------------------------------- /初赛代码/fonts/Optima.ttc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/初赛代码/fonts/Optima.ttc -------------------------------------------------------------------------------- /初赛代码/fonts/Times New Roman Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/初赛代码/fonts/Times New Roman Bold.ttf -------------------------------------------------------------------------------- /初赛代码/fonts/Times New Roman.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/初赛代码/fonts/Times New Roman.ttf -------------------------------------------------------------------------------- /初赛代码/fonts/Verdana.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/初赛代码/fonts/Verdana.ttf -------------------------------------------------------------------------------- /初赛代码/fonts/consola.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/初赛代码/fonts/consola.ttf -------------------------------------------------------------------------------- /初赛代码/image.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | captcha.image 4 | ~~~~~~~~~~~~~ 5 | 6 | Generate Image CAPTCHAs, just the normal image CAPTCHAs you are using. 7 | """ 8 | 9 | import os 10 | import random 11 | from PIL import Image 12 | from PIL import ImageFilter 13 | from PIL.ImageDraw import Draw 14 | from PIL.ImageFont import truetype 15 | try: 16 | from cStringIO import StringIO as BytesIO 17 | except ImportError: 18 | from io import BytesIO 19 | try: 20 | from wheezy.captcha import image as wheezy_captcha 21 | except ImportError: 22 | wheezy_captcha = None 23 | 24 | DATA_DIR = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'data') 25 | DEFAULT_FONTS = [os.path.join(DATA_DIR, 'DroidSansMono.ttf')] 26 | 27 | if wheezy_captcha: 28 | __all__ = ['ImageCaptcha', 'WheezyCaptcha'] 29 | else: 30 | __all__ = ['ImageCaptcha'] 31 | 32 | 33 | table = [] 34 | for i in range( 256 ): 35 | table.append( i * 1.97 ) 36 | 37 | 38 | class _Captcha(object): 39 | def generate(self, chars, format='png'): 40 | """Generate an Image Captcha of the given characters. 41 | 42 | :param chars: text to be generated. 43 | :param format: image file format 44 | """ 45 | im = self.generate_image(chars) 46 | out = BytesIO() 47 | im.save(out, format=format) 48 | out.seek(0) 49 | return out 50 | 51 | def write(self, chars, output, format='png'): 52 | """Generate and write an image CAPTCHA data to the output. 53 | 54 | :param chars: text to be generated. 55 | :param output: output destionation. 56 | :param format: image file format 57 | """ 58 | im = self.generate_image(chars) 59 | return im.save(output, format=format) 60 | 61 | 62 | class WheezyCaptcha(_Captcha): 63 | """Create an image CAPTCHA with wheezy.captcha.""" 64 | def __init__(self, width=200, height=75, fonts=None): 65 | self._width = width 66 | self._height = height 67 | self._fonts = fonts or DEFAULT_FONTS 68 | 69 | def generate_image(self, chars): 70 | text_drawings = [ 71 | wheezy_captcha.warp(), 72 | wheezy_captcha.rotate(), 73 | wheezy_captcha.offset(), 74 | ] 75 | fn = wheezy_captcha.captcha( 76 | drawings=[ 77 | wheezy_captcha.background(), 78 | wheezy_captcha.text(fonts=self._fonts, drawings=text_drawings), 79 | wheezy_captcha.curve(), 80 | wheezy_captcha.noise(), 81 | wheezy_captcha.smooth(), 82 | ], 83 | width=self._width, 84 | height=self._height, 85 | ) 86 | return fn(chars) 87 | 88 | 89 | class ImageCaptcha(_Captcha): 90 | """Create an image CAPTCHA. 91 | 92 | Many of the codes are borrowed from wheezy.captcha, with a modification 93 | for memory and developer friendly. 94 | 95 | ImageCaptcha has one built-in font, DroidSansMono, which is licensed under 96 | Apache License 2. You should always use your own fonts:: 97 | 98 | captcha = ImageCaptcha(fonts=['/path/to/A.ttf', '/path/to/B.ttf']) 99 | 100 | You can put as many fonts as you like. But be aware of your memory, all of 101 | the fonts are loaded into your memory, so keep them a lot, but not too 102 | many. 103 | 104 | :param width: The width of the CAPTCHA image. 105 | :param height: The height of the CAPTCHA image. 106 | :param fonts: Fonts to be used to generate CAPTCHA images. 107 | :param font_sizes: Random choose a font size from this parameters. 108 | """ 109 | def __init__(self, width=160, height=60, fonts=None, font_sizes=None): 110 | self._width = width 111 | self._height = height 112 | self._fonts = fonts or DEFAULT_FONTS 113 | self._font_sizes = font_sizes or (42, 50, 56) 114 | self._truefonts = [] 115 | 116 | @property 117 | def truefonts(self): 118 | if self._truefonts: 119 | return self._truefonts 120 | self._truefonts = tuple([ 121 | truetype(n, s) 122 | for n in self._fonts 123 | for s in self._font_sizes 124 | ]) 125 | return self._truefonts 126 | 127 | @staticmethod 128 | def create_noise_curve(image, color): 129 | w, h = image.size 130 | x1 = random.randint(0, int(w / 5)) 131 | x2 = random.randint(w - int(w / 5), w) 132 | y1 = random.randint(int(h / 5), h - int(h / 5)) 133 | y2 = random.randint(y1, h - int(h / 5)) 134 | points = [(x1, y1), (x2, y2)] 135 | end = random.randint(160, 200) 136 | start = random.randint(0, 20) 137 | Draw(image).arc(points, start, end, fill=color) 138 | return image 139 | 140 | @staticmethod 141 | def create_noise_dots(image, color, width=3, number=30): 142 | draw = Draw(image) 143 | w, h = image.size 144 | while number: 145 | x1 = random.randint(0, w) 146 | y1 = random.randint(0, h) 147 | draw.line(((x1, y1), (x1 - 1, y1 - 1)), fill=color, width=width) 148 | number -= 1 149 | return image 150 | 151 | def create_captcha_image(self, chars, color, background): 152 | """Create the CAPTCHA image itself. 153 | 154 | :param chars: text to be generated. 155 | :param color: color of the text. 156 | :param background: color of the background. 157 | 158 | The color should be a tuple of 3 numbers, such as (0, 255, 255). 159 | """ 160 | image = Image.new('RGB', (self._width, self._height), background) 161 | draw = Draw(image) 162 | 163 | def _draw_character(c): 164 | font = random.choice(self.truefonts) 165 | w, h = draw.textsize(c, font=font) 166 | 167 | dx = random.randint(0, 4) 168 | dy = random.randint(0, 6) 169 | im = Image.new('RGBA', (w + dx, h + dy)) 170 | Draw(im).text((dx, dy), c, font=font, fill=color) 171 | 172 | # rotate 173 | im = im.crop(im.getbbox()) 174 | im = im.rotate(random.uniform(-30, 30), Image.BILINEAR, expand=1) 175 | 176 | # warp 177 | dx = w * random.uniform(0.1, 0.3) 178 | dy = h * random.uniform(0.2, 0.3) 179 | x1 = int(random.uniform(-dx, dx)) 180 | y1 = int(random.uniform(-dy, dy)) 181 | x2 = int(random.uniform(-dx, dx)) 182 | y2 = int(random.uniform(-dy, dy)) 183 | w2 = w + abs(x1) + abs(x2) 184 | h2 = h + abs(y1) + abs(y2) 185 | data = ( 186 | x1, y1, 187 | -x1, h2 - y2, 188 | w2 + x2, h2 + y2, 189 | w2 - x2, -y1, 190 | ) 191 | if c != '-': 192 | im = im.resize((w2, h2)) 193 | im = im.transform((w, h), Image.QUAD, data) 194 | return im 195 | 196 | images = [] 197 | for c in chars: 198 | images.append(_draw_character(c)) 199 | 200 | text_width = sum([im.size[0] for im in images]) 201 | 202 | width = max(text_width, self._width) 203 | image = image.resize((width, self._height)) 204 | 205 | average = int(text_width / len(chars)) 206 | rand = int(0.25 * average) 207 | offset = int(average * 0.1) 208 | 209 | for im in images: 210 | w, h = im.size 211 | mask = im.convert('L').point(table) 212 | image.paste(im, (offset, int((self._height - h) / 2)), mask) 213 | offset = offset + w + random.randint(-rand, 0) 214 | 215 | if width > self._width: 216 | image = image.resize((self._width, self._height)) 217 | 218 | return image 219 | 220 | def generate_image(self, chars): 221 | """Generate the image of the given characters. 222 | 223 | :param chars: text to be generated. 224 | """ 225 | background = random_color(238, 255) 226 | color = random_color(0, 200, random.randint(220, 255)) 227 | im = self.create_captcha_image(chars, color, background) 228 | self.create_noise_dots(im, color) 229 | # self.create_noise_curve(im, color) 230 | im = im.filter(ImageFilter.SMOOTH) 231 | return im 232 | 233 | 234 | def random_color(start, end, opacity=None): 235 | red = random.randint(start, end) 236 | green = random.randint(start, end) 237 | blue = random.randint(start, end) 238 | if opacity is None: 239 | return (red, green, blue) 240 | return (red, green, blue, opacity) 241 | -------------------------------------------------------------------------------- /初赛代码/image.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ypwhs/baiduyun_deeplearning_competition/64baade732d0bf1ecfe918c274137f4e1e09ec7f/初赛代码/image.pyc -------------------------------------------------------------------------------- /初赛代码/make_parallel.py: -------------------------------------------------------------------------------- 1 | from keras.layers.merge import Concatenate 2 | from keras.layers.core import Lambda 3 | from keras.models import Model 4 | 5 | import tensorflow as tf 6 | 7 | def make_parallel(model, gpu_count): 8 | def get_slice(data, idx, parts): 9 | shape = tf.shape(data) 10 | size = tf.concat([ shape[:1] // parts, shape[1:] ],axis=0) 11 | stride = tf.concat([ shape[:1] // parts, shape[1:]*0 ],axis=0) 12 | start = stride * idx 13 | return tf.slice(data, start, size) 14 | 15 | outputs_all = [] 16 | for i in range(len(model.outputs)): 17 | outputs_all.append([]) 18 | 19 | #Place a copy of the model on each GPU, each getting a slice of the batch 20 | for i in range(gpu_count): 21 | with tf.device('/gpu:%d' % i): 22 | with tf.name_scope('tower_%d' % i) as scope: 23 | 24 | inputs = [] 25 | #Slice each input into a piece for processing on this GPU 26 | for x in model.inputs: 27 | input_shape = tuple(x.get_shape().as_list())[1:] 28 | slice_n = Lambda(get_slice, output_shape=input_shape, arguments={'idx':i,'parts':gpu_count})(x) 29 | inputs.append(slice_n) 30 | 31 | outputs = model(inputs) 32 | 33 | if not isinstance(outputs, list): 34 | outputs = [outputs] 35 | 36 | #Save all the outputs for merging back together later 37 | for l in range(len(outputs)): 38 | outputs_all[l].append(outputs[l]) 39 | 40 | # merge outputs on CPU 41 | with tf.device('/cpu:0'): 42 | merged = [] 43 | for outputs in outputs_all: 44 | merged.append(Concatenate(axis=0)(outputs)) 45 | 46 | return Model(model.inputs, merged) -------------------------------------------------------------------------------- /初赛代码/数据集预处理.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import os\n", 12 | "import cv2\n", 13 | "import numpy as np\n", 14 | "import matplotlib.pyplot as plt\n", 15 | "from IPython.display import *\n", 16 | "from collections import Counter\n", 17 | "from skimage import exposure\n", 18 | "import seaborn as sns\n", 19 | "\n", 20 | "%matplotlib inline\n", 21 | "%config InlineBackend.figure_format = 'retina'\n", 22 | "IMAGE_DIR = 'image_contest_level_1_validate'" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": 23, 28 | "metadata": {}, 29 | "outputs": [ 30 | { 31 | "data": { 32 | "text/plain": [ 33 | "" 34 | ] 35 | }, 36 | "execution_count": 23, 37 | "metadata": {}, 38 | "output_type": "execute_result" 39 | }, 40 | { 41 | "data": { 42 | "image/png": "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\nGQwAAAAAMAA6gwEAAAAABqBaa/OuAwAAAAAAM+bOYAAAAACAAdAZDAAAAAAwADqDAQAAAAAGQGcw\nAAAAAMAA6AwGAAAAABgAncEAAAAAAAOgMxgAAAAAYACWsjO4qu5fVUer6j1VdXtV3VhVL6iqc+Zd\nt2mrqn1V9Yyq+p2quqGqbquqm6vqj6vqW6tqw9+4qi6qqtdU1U3dNn9ZVc+qqjN3ex9mraq+uapa\n93rGhHWeVFVXdW334ar606q6ZLfrOitV9cXdMfKP3Tnxnqp6bVX92w3WXepjo6q+oqpeV1X/0O3f\nO6vqVVX1iAnrL3R7VNVTqurFVfXmqrqlOw9+9RTbnPY+L8o5dDrtUVUPrqpDVfWGqvr7qvpYVf1T\nVb26qh5/iu+5pKr+rGuLm7u2edJs9mp7tnNsjG3/y71r67+csM6ZVfW93TF0W3dMvaaqLprenkzH\nNs+VM7t/g99UVR/oXVNeWVUHJmyz548N2EwNKM5OxNpbUWJtsXanxNnibHH2mu0cH2PbL02svc1z\nRZw9S621pXoleVCSf0rSkvz3JM9L8obu/bEk++Zdxynv77d3+/aeJL+W5P9JcjTJB7vlv5mkxrb5\n6iSfSPLhJL+S5L90bdOSvGre+zTl9nlA1xYf6vbvGRus811d2fEk/2+S5yf5+27ZT817H6bQBke6\nffn7JL+Y5D8n+aUk1yQ5MqRjI8nh3m/9y9314TeTfCzJp5J887K1R5K3d/X9UJJru/lf3WT9097n\nRTqHTqc9kryiK/+/Sf5rd3397a59WpJnTtjup3rn3PO7NjnRLfuuebfBdo+NsW2/srdtS/IvN1in\nkrwqJ//9/S/dMfXhrg2/et5tsJP2SHKPJH/Yrfe2JC/orilXJrkxyZMW9djw8pr0ysDi7G6fxdqb\nt49YW6y9um/ibHG2OHsHx8fYtksVa2/jXBFnz/o3mXcFpr5DyWu7H/u7x5b/TLf8F+Zdxynv7xO6\nC8UZY8s/I8nfdfv8tb3l90ryviS3J/nC3vK7Jnlrt/5T571fU2qbSvL6JH/bXRjvEKAmOTfJR7uL\nxLm95eckuaHb5hHz3pcdtMG3dfvw0iSftkH5nYdybHTnxCeT/GOS+46VPb7bv3cuW3t0+/bg7nx4\n3Gb/8G5nnxftHDrN9viWJA/ZYPljM/qPze1J7jdWdlH3mTckOWesnU50bXXutPZnt9pibLtP786j\nVyS5KpMD1G/oyt6S5K695Q/t2u59Se4573bYbntk1CnUkvzHCeV3Hnu/MMeGl9ekVwYWZ3f7Jtae\n3DZibbH26j6Is8XZp9se35IljrNPtz3Gtlu6WPt02yLi7Nn/JvOuwFR3ZnS3QkvyrtwxYLtnRn8h\n+UiSu8+7rrvUHj/atceLe8tWumUv22D9J3Rlb5x33ae0/9+T0V+hH5PkOdk4QP3JbvlPbLD9xLZa\nhFeSu3T/ALw7GwSnp7O/y3BsJPmibh9ePaH8liQfWub2ONU/vNvZ50U+h7YSiGyy7esy1gHQLX95\nt/zpG2wzsa3m/TqdtkjyOxkFqPuyeYD6pq7s8RuUTWynvfDawrlyQVf+itP4zIU8Nry8Vl8RZ2/U\nJmJtsbZYu4mzu3qfKnYQZ29926WKs0+3PbLksfYWzhVx9i68li1n8Gpumde11j7VL2itfSijv5jc\nLcnDd7tic/LxbvqJ3rIndNPf32D9NyW5NclFVXWXWVZs1qrq/IyGEbywtfamTVbdrD1+b2ydRfMl\nGf1V8beTfKrL4XWoqr5nQt6uZT823pHRX5kfVlX7+wVV9ZiM/iP7+t7iZW+PjWxnn5f5HNrMRtfX\nZMnbo6q+JcmTM/or/YlN1rtrRn+hvzXJmzdYZdHb4hu76a9X1dldvswfqar/MCmnW5b82GAQxNl3\nJNYWa4u1R8TZpybO3rpBxtmJWLsjzt4Fd5p3BabsvG56/YTydyR5YpIDGeUfWVpVdackT+ve9k+I\niW3UWvtEVb0ryeck+eyMcrksnG7fr8xo6N6PnmL1zdrjvVX1kST3r6q7tdZunW5NZ+6h3fSjGeXZ\n+dx+YVW9KclTWmvv7xYt9bHRWrupqg5lNJT1b6rqv2c0ZORBSb4qyR8k+Y+9TZa6PSbYzj4v8zm0\noar6rCRfnFHw9abe8rsn+cwkH26tvXeDTd/RTTd84MFe1+33CzP6K/6rT7H6g5KcmdGQ0PFAPlnw\ntsjJ6+tnZTQ8el+vrFXVz2eU6+6TyfIfGwyGOLtHrC3Wjlh7jTh7S8TZWzDUODsRa/eIs3fBst0Z\nfHY3vXlC+erye+9CXebteRkFJK9prb22t3wIbXRZkock+ZbW2m2nWHer7XH2hPK97L7d9AczGhbx\n6Iz+Kv+vMxp685iMEs6vWvpjo7X2giRfk9Efwr4tyQ8n+bqMksy/tLX2vt7qS98eG9jOPi/zOXQH\n3d0av5bR0NDntNY+0Cte2mOmqs5I8rKMhoE/cwubLG1bdFavrz+T0fC98zO6vv6bjILW70jy7N76\ny94eDIPjeD2xtlhbrN0jzj4lcfYpDDXOTsTaY8TZu2DZOoNJUlXPTPL9GT1R8uI5V2dXVdUXZXSH\nwk+31v5k3vWZs9Xz+xNJvqq19settQ+31v4qyb9L8g9JHjthGNtSqqofyuipxi/N6K+pd09yYZJ3\nJvm1qjoyv9qx11XVmRndCfXIJK/M6Im1Q/G9GT3Q49vGAvOhWr2+Hkvy9a21Y9319Q+TPCWjHJrf\nV1WfNrcaAjMj1hZrd8TaPeJsdmLgcXYi1u4TZ++CZesMPtVfxlaXf3AX6jIXVfVdGQ0t+JuMEonf\nNLbK0rZRN2Tt5RkNo3n2KVZftdX2mPRXpr1s9Td8W2vtxn5BN4xo9S6Wh3XTpT02kqSqHpfkcJL/\n0Vr7vtbaO1trt7bWrskoYP//knx/VX12t8lSt8cE29nnZT6H1nQB6q9mdIfLbyT55tZGTyToWcpj\npqoOJLkiyUtaa6/Z4mZL2RY9q/X+3dUhaqtaa/8nowds3TOjOxmS5W8PhsFxHLF2xNp9Yu2OOHtL\nxNkTDDnOTsTaGxBn74Jl6wy+rptOygXy4G46KdfZQquqZyV5cZK/zig4/ccNVpvYRl2A98CM/rr9\nzlnVc4bukdF+nZ/ko1XVVl9Jfrxb55e6ZS/o3m/WHvfL6C/a/7CgOZhW923SRW/1L45nja2/jMdG\nkjypm/7ReEH3+/5ZRtfEh3SLl709NrKdfV7mcyhJUlV3TvLrSZ6a5L8l+caNcnO11j6S0X927tHt\n+7hF/TfoX2U0XO/p/etqd219bLfOO7plT+7e/22STyb57O7YGbeobbHqtK6vS3xsMCyDjrMTsXbE\n2uPE2ieJs09NnL0BcXYSsfY4cfYuWLbO4NV/fJ7Y5VxZU1X3zGjIwa1J/vduV2zWuoT9z0/y9oyC\n0/dNWPUN3fTLNih7TEZPgX5ra+326ddy5m5P8isTXm/r1vnj7v3qsLbN2uPLx9ZZNH+YUf6yfzV+\nPnRWH3Lxrm66zMdGMvoHNhk99Xkjq8s/1k2XvT02sp19XuZzKN3wo1dldKfCy5NcPP4X6jHL2B43\nZvK1dbUj5FXd+xuTpLX20SRvzeiYefQGn7mobbFq9Ynonzte0OW7Ww06b+wVLeOxwbAMNs5OxNod\n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\ngg==\n", 43 | "text/plain": [ 44 | "" 45 | ] 46 | }, 47 | "metadata": { 48 | "image/png": { 49 | "height": 296, 50 | "width": 705 51 | } 52 | }, 53 | "output_type": "display_data" 54 | } 55 | ], 56 | "source": [ 57 | "e = [629,2271,6579,17416,71857,77631,95303,102187,117422,142660,183693]\n", 58 | "index = e[8]\n", 59 | "\n", 60 | "img = cv2.imread('%s/%d.png' % (IMAGE_DIR, index))\n", 61 | "gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)\n", 62 | "\n", 63 | "h = cv2.equalizeHist(gray)\n", 64 | "# _, h = cv2.threshold(h, 127, 255, cv2.THRESH_BINARY)\n", 65 | "# h = cv2.dilate(h, np.ones((3, 3)))\n", 66 | "# h = cv2.morphologyEx(h, cv2.MORPH_CLOSE, np.ones((3, 3)))\n", 67 | "\n", 68 | "# c = Counter(h.flatten().tolist())\n", 69 | "# # h2 = h == c.most_common()[1][0]\n", 70 | "# h2 = h == sorted(c.keys())[-1]\n", 71 | "\n", 72 | "plt.figure(figsize=(12, 5))\n", 73 | "plt.subplot(2, 2, 1);plt.title('raw');plt.imshow(img[:,:,::-1])\n", 74 | "plt.subplot(2, 2, 2);plt.title('gray');plt.imshow(gray, cmap='gray')\n", 75 | "plt.subplot(2, 2, 3);plt.title('result');plt.imshow(h, cmap='gray')\n", 76 | "# plt.subplot(2, 2, 4);plt.title('result');plt.imshow(h2, cmap='gray')" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": null, 82 | "metadata": { 83 | "collapsed": true 84 | }, 85 | "outputs": [], 86 | "source": [] 87 | } 88 | ], 89 | "metadata": { 90 | "kernelspec": { 91 | "display_name": "Python 2", 92 | "language": "python", 93 | "name": "python2" 94 | }, 95 | "language_info": { 96 | "codemirror_mode": { 97 | "name": "ipython", 98 | "version": 2 99 | }, 100 | "file_extension": ".py", 101 | "mimetype": "text/x-python", 102 | "name": "python", 103 | "nbconvert_exporter": "python", 104 | "pygments_lexer": "ipython2", 105 | "version": "2.7.13" 106 | } 107 | }, 108 | "nbformat": 4, 109 | "nbformat_minor": 2 110 | } 111 | -------------------------------------------------------------------------------- /初赛代码/生成器对比.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "0123456789+-*() \n", 13 | "17\n" 14 | ] 15 | } 16 | ], 17 | "source": [ 18 | "import string\n", 19 | "import os\n", 20 | "\n", 21 | "digits = string.digits\n", 22 | "operators = '+-*'\n", 23 | "characters = digits + operators + '() '\n", 24 | "print(characters)\n", 25 | "\n", 26 | "width, height, n_len, n_class = 180, 60, 7, len(characters) + 1\n", 27 | "print(n_class)" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": 2, 33 | "metadata": {}, 34 | "outputs": [ 35 | { 36 | "data": { 37 | "image/png": "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\nAElFTkSuQmCC\n", 38 | "text/plain": [ 39 | "" 40 | ] 41 | }, 42 | "execution_count": 2, 43 | "metadata": {}, 44 | "output_type": "execute_result" 45 | } 46 | ], 47 | "source": [ 48 | "from captcha.image import ImageCaptcha\n", 49 | "generator = ImageCaptcha(width=width, height=height, font_sizes=range(35, 56), \n", 50 | " fonts=['fonts/%s'%x for x in os.listdir('fonts') if '.tt' in x])\n", 51 | "generator.generate_image('(1-2)-3')" 52 | ] 53 | }, 54 | { 55 | "cell_type": "code", 56 | "execution_count": 3, 57 | "metadata": {}, 58 | "outputs": [ 59 | { 60 | "data": { 61 | "image/png": "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\n", 62 | "text/plain": [ 63 | "" 64 | ] 65 | }, 66 | "execution_count": 3, 67 | "metadata": {}, 68 | "output_type": "execute_result" 69 | } 70 | ], 71 | "source": [ 72 | "from image import ImageCaptcha\n", 73 | "generator = ImageCaptcha(width=width, height=height, font_sizes=range(35, 56), \n", 74 | " fonts=['fonts/%s'%x for x in os.listdir('fonts') if '.tt' in x])\n", 75 | "generator.generate_image('(1-2)-3')" 76 | ] 77 | }, 78 | { 79 | "cell_type": "code", 80 | "execution_count": null, 81 | "metadata": { 82 | "collapsed": true 83 | }, 84 | "outputs": [], 85 | "source": [] 86 | } 87 | ], 88 | "metadata": { 89 | "kernelspec": { 90 | "display_name": "Python 2", 91 | "language": "python", 92 | "name": "python2" 93 | }, 94 | "language_info": { 95 | "codemirror_mode": { 96 | "name": "ipython", 97 | "version": 2 98 | }, 99 | "file_extension": ".py", 100 | "mimetype": "text/x-python", 101 | "name": "python", 102 | "nbconvert_exporter": "python", 103 | "pygments_lexer": "ipython2", 104 | "version": "2.7.13" 105 | } 106 | }, 107 | "nbformat": 4, 108 | "nbformat_minor": 2 109 | } 110 | -------------------------------------------------------------------------------- /初赛代码/转换多 GPU 模型为普通模型.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stderr", 10 | "output_type": "stream", 11 | "text": [ 12 | "Using TensorFlow backend.\n" 13 | ] 14 | } 15 | ], 16 | "source": [ 17 | "from keras.layers import *\n", 18 | "from keras.models import *\n", 19 | "from make_parallel import make_parallel" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": 2, 25 | "metadata": {}, 26 | "outputs": [ 27 | { 28 | "name": "stderr", 29 | "output_type": "stream", 30 | "text": [ 31 | "/usr/local/lib/python2.7/dist-packages/ipykernel_launcher.py:44: UserWarning: Update your `Model` call to the Keras 2 API: `Model(outputs=Tensor(\"de..., inputs=Tensor(\"in...)`\n" 32 | ] 33 | } 34 | ], 35 | "source": [ 36 | "MODEL_FILENAME = 'model.h5'\n", 37 | "\n", 38 | "characters = '0123456789+-*() '\n", 39 | "width, height, n_len, n_class = 180, 60, 7, len(characters) + 1\n", 40 | "\n", 41 | "from keras import backend as K\n", 42 | "\n", 43 | "def ctc_lambda_func(args):\n", 44 | " y_pred, labels, input_length, label_length = args\n", 45 | " y_pred = y_pred[:, 2:, :]\n", 46 | " return K.ctc_batch_cost(labels, y_pred, input_length, label_length)\n", 47 | "\n", 48 | "rnn_size = 128\n", 49 | "\n", 50 | "input_tensor = Input((width, height, 3))\n", 51 | "x = input_tensor\n", 52 | "for i in range(3):\n", 53 | " x = Conv2D(32*2**i, (3, 3))(x)\n", 54 | " x = BatchNormalization()(x)\n", 55 | " x = Activation('relu')(x)\n", 56 | " x = Conv2D(32*2**i, (3, 3))(x)\n", 57 | " x = BatchNormalization()(x)\n", 58 | " x = Activation('relu')(x)\n", 59 | " x = MaxPooling2D(pool_size=(2, 2))(x)\n", 60 | "\n", 61 | "conv_shape = x.get_shape()\n", 62 | "x = Reshape(target_shape=(int(conv_shape[1]), int(conv_shape[2]*conv_shape[3])))(x)\n", 63 | "\n", 64 | "x = Dense(128)(x)\n", 65 | "x = BatchNormalization()(x)\n", 66 | "x = Activation('relu')(x)\n", 67 | "\n", 68 | "gru_1 = GRU(rnn_size, return_sequences=True, name='gru1')(x)\n", 69 | "gru_1b = GRU(rnn_size, return_sequences=True, go_backwards=True, \n", 70 | " name='gru1_b')(x)\n", 71 | "gru1_merged = add([gru_1, gru_1b])\n", 72 | "\n", 73 | "gru_2 = GRU(rnn_size, return_sequences=True, name='gru2')(gru1_merged)\n", 74 | "gru_2b = GRU(rnn_size, return_sequences=True, go_backwards=True, \n", 75 | " name='gru2_b')(gru1_merged)\n", 76 | "x = concatenate([gru_2, gru_2b])\n", 77 | "x = Dropout(0.25)(x)\n", 78 | "x = Dense(n_class, activation='softmax')(x)\n", 79 | "base_model = Model(input=input_tensor, output=x)\n", 80 | "\n", 81 | "base_model_parallel = make_parallel(base_model, 4)\n", 82 | "\n", 83 | "labels = Input(name='the_labels', shape=[n_len], dtype='float32')\n", 84 | "input_length = Input(name='input_length', shape=(1,), dtype='int64')\n", 85 | "label_length = Input(name='label_length', shape=(1,), dtype='int64')\n", 86 | "loss_out = Lambda(ctc_lambda_func, name='ctc')([base_model_parallel.output, labels, input_length, label_length])\n", 87 | "\n", 88 | "model = Model(inputs=(input_tensor, labels, input_length, label_length), outputs=loss_out)\n", 89 | "model.compile(loss={'ctc': lambda y_true, y_pred: y_pred}, optimizer='adam')" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": 3, 95 | "metadata": { 96 | "collapsed": true 97 | }, 98 | "outputs": [], 99 | "source": [ 100 | "model.load_weights('model_gru_best.h5')" 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": 4, 106 | "metadata": { 107 | "collapsed": true 108 | }, 109 | "outputs": [], 110 | "source": [ 111 | "model2 = Model(inputs=input_tensor, outputs=x)\n", 112 | "model2.save('model.h5', overwrite=True)" 113 | ] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": null, 118 | "metadata": { 119 | "collapsed": true 120 | }, 121 | "outputs": [], 122 | "source": [] 123 | } 124 | ], 125 | "metadata": { 126 | "kernelspec": { 127 | "display_name": "Python 2", 128 | "language": "python", 129 | "name": "python2" 130 | }, 131 | "language_info": { 132 | "codemirror_mode": { 133 | "name": "ipython", 134 | "version": 2 135 | }, 136 | "file_extension": ".py", 137 | "mimetype": "text/x-python", 138 | "name": "python", 139 | "nbconvert_exporter": "python", 140 | "pygments_lexer": "ipython2", 141 | "version": "2.7.12" 142 | } 143 | }, 144 | "nbformat": 4, 145 | "nbformat_minor": 2 146 | } 147 | -------------------------------------------------------------------------------- /四则混合运算识别_决赛.md: -------------------------------------------------------------------------------- 1 | # 四则混合运算识别(决赛) 2 | 3 | 本节会详细介绍我在进行四则混合运算识别竞赛决赛时的所有思路。 4 | 5 | ## 问题描述 6 | 7 | 本次竞赛目的是为了解决一个 OCR 问题,通俗地讲就是实现图像到文字的转换过程。 8 | 9 | ### 数据集 10 | 11 | 决赛数据集一共包含10万张图片和一个labels.txt的文本文件。每张图片包含一个数学运算式,运算式中包含: 12 | 13 | 1. 图片大小不固定 14 | 2. 图片中的某一块区域为公式部分 15 | 3. 图片中包含二行或者三行的公式 16 | 4. 公式类型有两种:赋值和四则运算的公式。两行的包括由一个赋值公式和一个计算公式,三行的包括两个赋值公式和一个计算公式。加号(+) 即使旋转为 x ,仍为加号, * 是乘号 17 | 5. 赋值类的公式,变量名为一个汉字。 汉字来自两句诗(不包括逗号): 君不见,黄河之水天上来,奔流到海不复回 烟锁池塘柳,深圳铁板烧 18 | 6. 四则运算的公式包括加法、减法、乘法、分数、括号。 其中的数字为多位数字,汉字为变量,由上面的语句赋值。 19 | 7. 输出结果的格式为:图片中的公式,一个英文空格,计算结果。 其中: 不同行公式之间使用英文分号分隔 计算结果时,分数按照浮点数计算,计算结果误差不超过0.01,视为正确。 20 | 8. 整个label文件使用UTF8编码 21 | 22 | 决赛样例: 23 | 24 | ![](imgs/level2.jpg) 25 | 26 | 初赛的题不难,只需要识别文本序列即可,决赛的算式比较复杂,需要先经过图像处理,然后才能输入到神经网络中进行端到端的文本序列识别。 27 | 28 | ### 评价指标 29 | 30 | 官方的评价指标是准确率,初赛只有整数的加减乘运算,所得的结果一定是整数,所以要求序列与运算结果都正确才会判定为正确。 31 | 32 | 但决赛的数字通常都是五位数,并且会有很多乘法和加法,以及一定会存在的一个分数,所以结果很容易超出64位浮点数所能表示的范围,因此官方在经过讨论后决定只考虑文本序列的识别,不评价运算结果。 33 | 34 | 而我们本地除了会使用官方的准确率作为评估标准以外,还会使用 CTC loss 来评估模型。 35 | 36 | ## 数据的探索 37 | 38 | ### 定义 39 | 40 | 决赛的数据集探索就复杂得多,我们先明确两个概念: 41 | 42 | `流=42072;圳=86;(圳-(97510*45921))*流/35864` 43 | 44 | 在这个式子中,`流=42072;圳=86;`被称为赋值式,`(圳-(97510*45921))*流/35864`被称为表达式,赋值式和表达式统称为公式,`+-*/`被称为运算符。 45 | 46 | ### 分析 47 | 48 | 首先我们对样本的每个字出现的次数进行了统计: 49 | 50 | ![](imgs/level2_barplot.png) 51 | 52 | 可以看到数字的分布很有意思,0出现的次数比其他数字都低,其他的数字出现次数基本一样,所以立即推这是直接按随机数生成的,0不能出现在首位,所以概率变低。 53 | 54 | 分号和等号出现的次数一样,这是因为每个赋值式都有一个等号和一个分号。它出现的概率是 1.65807,因此可以猜出一个赋值式和两个赋值式的比例是 1:2。 55 | 56 | 运算符出现的概率都是一样的,所以可以推断它们是直接随机取的。 57 | 58 | 括号出现的概率是 1.36505,我们统计了一下括号出现的所有可能: 59 | 60 | ``` 61 | 1+1+1+1 62 | 63 | (1+1)+1+1 64 | 1+(1+1)+1 65 | 1+1+(1+1) 66 | (1+1+1)+1 67 | 1+(1+1+1) 68 | 69 | ((1+1)+1)+1 70 | (1+(1+1))+1 71 | 72 | 1+((1+1)+1) 73 | 1+(1+(1+1)) 74 | 75 | (1+1)+(1+1) 76 | ``` 77 | 78 | 一共有11种可能,按括号的数量统计括号出现的频率可以得出 2*5/11.0+5/11.0 = 1.3636,因此括号也是从上面几种模板随机取的。 79 | 80 | 中文除了“不”字出现了两次,概率翻倍,其他字概率基本相等。中文字取自于下面两句诗:“君不见,黄河之水天上来,奔流到海不复回 烟锁池塘柳,深圳铁板烧”,所以也可以推断出是按字直接随机取的。 81 | 82 | ### 总结 83 | 84 | * 中文直接等概率取,“不”概率加倍 85 | * 括号从11种情况中随机取 86 | * 运算符每次必出四个 87 | * 1/3概率取一个赋值式,2/3概率取2个赋值式 88 | * 运算符/永远都会出现一次,中文在上 89 | * 运算符+-*随机取,概率都是1/3 90 | * 数字取值范围是[0, 100000] 91 | 92 | ## 数据预处理 93 | 94 | 由于原始的图像十分巨大,直接输入到 CNN 中会有90%以上的区域是没有用的,所以我们需要对图像做预处理,裁剪出有用的部分。然后因为图像有两到三个式子,因此我们采取的方案是从左至右拼接在一起,这样的好处是图像比较小。(900\*80=72000 vs 600\*270=162000) 95 | 96 | 我主要使用了以下几种技术: 97 | 98 | * [转灰度图](http://docs.opencv.org/master/df/d9d/tutorial_py_colorspaces.html) 99 | * [直方图均衡](http://docs.opencv.org/master/d5/daf/tutorial_py_histogram_equalization.html) 100 | * [中值滤波](http://docs.opencv.org/master/d4/d13/tutorial_py_filtering.html) 101 | * [开闭运算](http://docs.opencv.org/master/d9/d61/tutorial_py_morphological_ops.html) 102 | * [二值化](http://docs.opencv.org/master/d7/d4d/tutorial_py_thresholding.html) 103 | * [轮廓查找](http://docs.opencv.org/master/d4/d73/tutorial_py_contours_begin.html) 104 | * [边界矩形](http://docs.opencv.org/master/dd/d49/tutorial_py_contour_features.html) 105 | 106 | 首先先进行初步的关键区域提取: 107 | 108 | ```py 109 | def plot(index): 110 | img = cv2.imread('%s/%d.png'%(IMAGE_DIR, index)) 111 | gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 112 | 113 | eq = cv2.equalizeHist(gray) 114 | b = cv2.medianBlur(eq, 9) 115 | 116 | m, n = img.shape[:2] 117 | b2 = cv2.resize(b, (n//4, m//4)) 118 | 119 | m1 = cv2.morphologyEx(b2, cv2.MORPH_OPEN, np.ones((7, 40))) 120 | m2 = cv2.morphologyEx(m1, cv2.MORPH_CLOSE, np.ones((4, 4))) 121 | _, bw = cv2.threshold(m2, 127, 255, cv2.THRESH_BINARY_INV) 122 | 123 | bw = cv2.resize(bw, (n, m)) 124 | 125 | r = img.copy() 126 | img2, ctrs, hier = cv2.findContours(bw, cv2.RETR_EXTERNAL, 127 | cv2.CHAIN_APPROX_SIMPLE) 128 | for ctr in ctrs: 129 | x, y, w, h = cv2.boundingRect(ctr) 130 | cv2.rectangle(r, (x, y), (x+w, y+h), (0, 255, 0), 10) 131 | ``` 132 | 133 | ![](imgs/level2_preprocessing1.png) 134 | 135 | ### 去噪 136 | 137 | 首先要将图像[转灰度图](http://docs.opencv.org/master/df/d9d/tutorial_py_colorspaces.html),然后用初赛使用的[直方图均衡](http://docs.opencv.org/master/d5/daf/tutorial_py_histogram_equalization.html)提高图像的对比度,这里噪点还在,所以需要进行滤波,我们这里使用了[中值滤波](http://docs.opencv.org/master/d4/d13/tutorial_py_filtering.html),它能很好地滤掉噪点和干扰线。(上图的 blur) 138 | 139 | ### 连接公式 140 | 141 | 现在我们只关心公式的提取,而不在意字符的提取(因为无法保证准确提取),所以我们需要将这些字符连接起来。这里首先对图像进行了4倍的缩放,然后使用了一种叫做[开闭运算](http://docs.opencv.org/master/d9/d61/tutorial_py_morphological_ops.html)的算法来连接字符。因为我们要的是横向连接,纵向不需要连接,所以我们选择了 (7, 40) 大小的开运算,然后为了滤掉不必要的噪声,我们使用了 (4, 4) 的闭运算。(位于上图中间的 m2) 142 | 143 | ### 关键区域提取 144 | 145 | 在拼接好公式以后,我们就可以对图像使用[轮廓查找](http://docs.opencv.org/master/d4/d73/tutorial_py_contours_begin.html)的算法了,很容易我们就可以抓到图像的三个边缘点集,然后我们使用[边界矩形](http://docs.opencv.org/master/dd/d49/tutorial_py_contour_features.html)函数得到矩形的 (x, y, w, h),完成关键区域提取。提取之后我们将绿色的矩形画在了原图上。(位于上图右下角的 rect) 146 | 147 | ### 微调 148 | 149 | 由于之前使用了很大的 kernel 进行滤波,所以这里需要进行一个微调的操作: 150 | 151 | ```py 152 | # 微调三个公式 153 | d = 20 154 | d2 = 5 155 | imgs = [] 156 | sizes = [] 157 | for i, ctr in enumerate(ctrs): 158 | x, y, w, h = cv2.boundingRect(ctr) 159 | roi = img[max(0, y-d):min(m, y+h+d),max(0, x-d):min(n, x+w+d)] 160 | p, q, _ = roi.shape 161 | 162 | x = b[max(0, y-d):min(m, y+h+d),max(0, x-d):min(n, x+w+d)] 163 | x = cv2.morphologyEx(x, cv2.MORPH_CLOSE, np.ones((3, 3))) 164 | _, x = cv2.threshold(x, 127, 255, cv2.THRESH_BINARY_INV) 165 | _, x, _ = cv2.findContours(x, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) 166 | x, y, w, h = cv2.boundingRect(np.vstack(x)) 167 | roi2 = roi[max(0, y-d2):min(p, y+h+d2),max(0, x-d2):min(q, x+w+d2)] 168 | imgs.append(roi2) 169 | sizes.append(roi2.shape) 170 | ``` 171 | 172 | 首先通过之前的矩形,扩充20像素,然后裁剪出关键区域,这里是直接对滤波的图裁剪,所以分辨率很高。然后经过简单的闭运算滤波,二值化,提取边框,这里即使有噪点也不用担心,裁多了不要紧,裁少了才麻烦,然后裁出来的图可能会比较小,因为滤波过了,所以再扩充5个像素,达到不错的效果。 173 | 174 | 以下是几个例子: 175 | 176 | ![](imgs/level2_preprocessing2.png) 177 | 178 | ![](imgs/level2_preprocessing3.png) 179 | 180 | ![](imgs/level2_preprocessing4.png) 181 | 182 | ### 连接三个公式 183 | 184 | 裁出来准确的公式以后,我们就可以直接进行横向连接了: 185 | 186 | ```py 187 | # 连接三个公式 188 | sizes = np.array(sizes) 189 | img2 = np.zeros((sizes[:,0].max(), sizes[:,1].sum()+2*(len(sizes)-1), 3), 190 | dtype=np.uint8) 191 | x = 0 192 | for a in imgs[::-1]: 193 | w = a.shape[1] 194 | img2[:a.shape[0], x:x+w] = a 195 | x += w + 2 196 | ``` 197 | 198 | 下图是拼接好的图像: 199 | 200 | ![](imgs/level2_preprocessing5.png) 201 | 202 | ### 并行预处理 203 | 204 | 如果直接使用 python 的 for 循环去跑,只能占用一个核的 CPU 利用率,为了充分利用 CPU,我们使用了多进行并行预处理的方法让每个 CPU 都能满载运行。为了能够实时查看进度,我使用了 tqdm 这个进度条的库。 205 | 206 | ```py 207 | p = Pool(12) 208 | 209 | n = 100000 210 | if __name__ == '__main__': 211 | rs = [] 212 | for r in tqdm(p.imap_unordered(f, range(n)), total=n): 213 | rs.append(r) 214 | ``` 215 | 216 | ![](imgs/level2_preprocessing6.png) 217 | 218 | ### 总结 219 | 220 | 这里我们把各个量之间的关系都画出来了,很有意思。 221 | 222 | ```py 223 | pd.plotting.scatter_matrix(df, alpha=0.1, figsize=(14,8), diagonal='kde'); 224 | ``` 225 | 226 | ![](imgs/level2_scatter_matrix.png) 227 | 228 | 其中的 x, y 表示公式的起始坐标,w, h 表示公式的宽和高,n, m 表示原图的宽和高,r 表示有几个公式。我们可以从图中看到,x, y 没有明显的规律,稍微有一点规律就是越宽的图能得到的 x 越大(废话,宽1000的图不可能有公式出现在1200)。 229 | 230 | w 也没有明显的规律,是典型的正态分布,而 h 则有两个峰,这是因为公式有两个和三个的差别。 231 | 232 | m, n 很有规律,它们是按某几个固定的数随机取的,m 的取值是从 [400, 500, 600, 700, 800, 900, 1000] 中随机选取的,n 是从 [800, 1600, 2400, 3200, 4000] 中随机取的。 233 | 234 | ```py 235 | Counter(df['m']) 236 | Counter({400: 14233, 237 | 500: 14414, 238 | 600: 14332, 239 | 700: 14304, 240 | 800: 14293, 241 | 900: 14299, 242 | 1000: 14125}) 243 | 244 | Counter(df['n']) 245 | Counter({800: 19872, 1600: 19937, 2400: 20128, 3200: 19975, 4000: 20088}) 246 | ``` 247 | 248 | ## 模型结构 249 | 250 | 由于我们只对 `base_model` 进行了修改,ctc 部分直接照搬之前的代码即可,因此这里我们只讨论 `base_model`,下面是代码: 251 | 252 | ```py 253 | def ctc_lambda_func(args): 254 | y_pred, labels, input_length, label_length = args 255 | y_pred = y_pred[:, 2:, :] 256 | return K.ctc_batch_cost(labels, y_pred, input_length, label_length) 257 | 258 | rnn_size = 128 259 | l2_rate = 1e-5 260 | 261 | input_tensor = Input((width, height, 3)) 262 | x = input_tensor 263 | for i, n_cnn in enumerate([3, 4, 6]): 264 | for j in range(n_cnn): 265 | x = Conv2D(32*2**i, (3, 3), padding='same', kernel_initializer='he_uniform', 266 | kernel_regularizer=l2(l2_rate))(x) 267 | x = BatchNormalization(gamma_regularizer=l2(l2_rate), beta_regularizer=l2(l2_rate))(x) 268 | x = Activation('relu')(x) 269 | x = MaxPooling2D((2, 2))(x) 270 | 271 | # x = AveragePooling2D((1, 2))(x) 272 | cnn_model = Model(input_tensor, x, name='cnn') 273 | 274 | input_tensor = Input((width, height, 3)) 275 | x = cnn_model(input_tensor) 276 | 277 | conv_shape = x.get_shape().as_list() 278 | rnn_length = conv_shape[1] 279 | rnn_dimen = conv_shape[3]*conv_shape[2] 280 | 281 | print conv_shape, rnn_length, rnn_dimen 282 | 283 | x = Reshape(target_shape=(rnn_length, rnn_dimen))(x) 284 | rnn_length -= 2 285 | rnn_imp = 0 286 | 287 | x = Dense(rnn_size, kernel_initializer='he_uniform', kernel_regularizer=l2(l2_rate), bias_regularizer=l2(l2_rate))(x) 288 | x = BatchNormalization(gamma_regularizer=l2(l2_rate), beta_regularizer=l2(l2_rate))(x) 289 | x = Activation('relu')(x) 290 | # x = Dropout(0.2)(x) 291 | 292 | gru_1 = GRU(rnn_size, implementation=rnn_imp, return_sequences=True, name='gru1')(x) 293 | gru_1b = GRU(rnn_size, implementation=rnn_imp, return_sequences=True, go_backwards=True, name='gru1_b')(x) 294 | gru1_merged = add([gru_1, gru_1b]) 295 | 296 | gru_2 = GRU(rnn_size, implementation=rnn_imp, return_sequences=True, name='gru2')(gru1_merged) 297 | gru_2b = GRU(rnn_size, implementation=rnn_imp, return_sequences=True, go_backwards=True, name='gru2_b')(gru1_merged) 298 | x = concatenate([gru_2, gru_2b]) 299 | 300 | # x = Dropout(0.2)(x) 301 | x = Dense(n_class, activation='softmax', kernel_regularizer=l2(l2_rate), bias_regularizer=l2(l2_rate))(x) 302 | rnn_out = x 303 | base_model = Model(input_tensor, x) 304 | ``` 305 | 306 | 在经过多次的代码迭代以后,我将 cnn 打包为了一个 model,这样模型会简洁很多: 307 | 308 | ![](imgs/level2_basemodel.png) 309 | 310 | 模型思路是这样的:首先输入一张图,然后通过 cnn 导出 (112, 10, 128) 的特征图,其中112就是输入到 rnn 的序列长度,10 指的是每一条特征的高度是10像素,将后面 (10, 128) 的特征合并成1280,然后经过一个全连接降维到128维,就得到了 (112, 128) 的特征,输入到 RNN 中,然后经过两层双向 GRU 输出112个字的概率,然后用 CTC loss 去优化模型,得到能够准确识别字符序列的模型。 311 | 312 | ### CNN 313 | 314 | CNN 的结构如下图: 315 | 316 | ![](imgs/level2_cnn_model.png) 317 | 318 | 理论最大序列长度为46个字符(数字可能为100000,所以是 `2*9+3*6+4+4+2=46`,对于 CTC 来说,我们最好要输入大于最大长度2倍的序列,才能收敛得比较好。之前我直接卷积到50左右了,然后对于连续字符来说,没有空白能将它们分隔开来,所以收敛效果会差很多。这里的最大序列长度我之前总是算错,因为我用的是 Python2,没有 decode 成 utf-8 的话,一个中文占三个字节。 319 | 320 | CNN 的结构由原来的两层卷积一层池化,改为了多层卷积,一层池化的结构,由于卷积层分别是3,4和6层,我称之为 346 结构。 321 | 322 | ### GRU 323 | 324 | 为什么使用 RNN 呢,这里我举一个很经典的例子:研表究明,汉字的序顺并不定一能影阅响读,比如当你看完这句话后,才发这现里的字全是都乱的。 325 | 326 | 人眼去阅读一段话的时候,是会顾及到上下文的,不是依次单个字符的识别,因此引入 RNN 去识别上下文能够极大提升模型的准确率。在决赛中,序列有几个地方都是有上下文关系的: 327 | 328 | * 前面一个或两个赋值式一定是 中文=数字; 这样的形式 329 | * 左括号一定会有右括号 330 | * 括号的位置是有语法规则的 331 | * 一定会有一个分式 332 | * 分式的分子一定是中文 333 | * 如果只有一个赋值式,那么表达式中的中文一定是赋值式的中文 334 | * 如果有两个赋值式,赋值式容易看清,表达式不容易看清,那么可以通过赋值式的中文去修正表达式的中文,特别是分子中文被裁掉的时候 335 | 336 | ### 其他参数 337 | 338 | 相比之前初赛的模型,这里进行了一些修改: 339 | 340 | * padding 变为了 same,不然我觉得特征图的高度不够,无法识别分数 341 | * 增加了 l2 正则化,loss loss 变得更大了,但是准确率变得更高了(添加 l2 的部分包括卷积层的 kernel,BN 层的 gamma 和 beta,以及全连接层的 weights 和 bias) 342 | * 各个层的初始化变为了 he_uniform,效果比之前好 343 | * 去掉了 dropout,不清楚影响如何,但是反正有生成器,应该不会出现过拟合的情况 344 | * 修改过 GRU 的 implementation 为2,原因是希望显卡能加速 GRU 的速度,但是似乎速度还不如设置为0,使用 CPU 来跑,所以又改回来了 345 | 346 | l2 正则化的参数直接参考了 Xception 论文的 4.3 节给的参数: 347 | 348 | > Weight decay: The Inception V3 model uses a weight decay (L2 regularization) rate of 4e-5, which has been carefully tuned for performance on ImageNet. We found this rate to be quite suboptimal for Xception and instead settled for 1e-5. 349 | 350 | ## 生成器 351 | 352 | 为了得到更多的数据,提高模型的泛化能力,我使用了一种很简单的数据扩充办法,那就是根据表达式中的中文随机挑选赋值式,组成新的样本。这里我们取了前 350*256=89600 个样本来生成,用之后的 10240 个样本来做验证集,还有一点零头因为太少就没有用了。 353 | 354 | 导入数据的时候,先读取运算式的图像,然后按中文导入赋值式的图像到字典中。因为字典中的 key 是无序的,所以我们在字典中存的是 list,列表是有序的。 355 | 356 | ```py 357 | from collections import defaultdict 358 | 359 | cn_imgs = defaultdict(list) 360 | cn_labels = defaultdict(list) 361 | ss_imgs = [] 362 | ss_labels = [] 363 | 364 | for i in tqdm(range(n1)): 365 | ss = df[0][i].decode('utf-8').split(';') 366 | m = len(ss)-1 367 | ss_labels.append(ss[-1]) 368 | ss_imgs.append(cv2.imread('crop_split2/%d_%d.png'%(i, 0)).transpose(1, 0, 2)) 369 | for j in range(m): 370 | cn_labels[ss[j][0]].append(ss[j]) 371 | cn_imgs[ss[j][0]].append(cv2.imread('crop_split2/%d_%d.png'%(i, m-j)).transpose(1, 0, 2)) 372 | ``` 373 | 374 | 然后实现生成器,这里继承了 keras 里的 Sequence 类: 375 | 376 | ```py 377 | from keras.utils import Sequence 378 | 379 | class SGen(Sequence): 380 | def __init__(self, batch_size): 381 | self.batch_size = batch_size 382 | self.X_gen = np.zeros((batch_size, width, height, 3), dtype=np.uint8) 383 | self.y_gen = np.zeros((batch_size, n_len), dtype=np.uint8) 384 | self.input_length = np.ones(batch_size)*rnn_length 385 | self.label_length = np.ones(batch_size)*38 386 | 387 | def __len__(self): 388 | return 350*256 // self.batch_size 389 | 390 | def __getitem__(self, idx): 391 | self.X_gen[:] = 0 392 | for i in range(self.batch_size): 393 | try: 394 | random_index = random.randint(0, n1-1) 395 | cls = [] 396 | ss = ss_labels[random_index] 397 | cs = re.findall(ur'[\u4e00-\u9fff]', df[0][random_index].decode('utf-8').split(';')[-1]) 398 | random.shuffle(cs) 399 | x = 0 400 | for c in cs: 401 | random_index2 = random.randint(0, len(cn_labels[c])-1) 402 | cls.append(cn_labels[c][random_index2]) 403 | img = cn_imgs[c][random_index2] 404 | w, h, _ = img.shape 405 | self.X_gen[i, x:x+w, :h] = img 406 | x += w+2 407 | img = ss_imgs[random_index] 408 | w, h, _ = img.shape 409 | self.X_gen[i, x:x+w, :h] = img 410 | cls.append(ss) 411 | 412 | random_str = u';'.join(cls) 413 | self.y_gen[i,:len(random_str)] = [characters.find(x) for x in random_str] 414 | self.y_gen[i,len(random_str):] = n_class-1 415 | self.label_length[i] = len(random_str) 416 | except: 417 | pass 418 | 419 | return [self.X_gen, self.y_gen, self.input_length, self.label_length], np.ones(self.batch_size) 420 | ``` 421 | 422 | 首先随机取一个表达式,然后用正则表达式找里面的中文,再从{中文:图像数组}的字典中随机取图像,经过之前预处理的方式拼接成一个新的序列。 423 | 424 | 比如随机取了一个 `85882*(河/76020-37023)-铁`,然后我们从铁的赋值式中随机取一个,再从河的赋值式中随便取一个,拼起来就能得到下图: 425 | 426 | ![](imgs/level2_generator.png) 427 | 428 | 可以看到背景颜色是不同的,但是并不影响模型去识别。 429 | 430 | ## 训练 431 | 432 | 我们训练的策略是先用 Adam() 默认的学习率 1e-3 快速收敛50代,然后用 Adam(1e-4) 跑50代,达到一个不错的 loss,最后用 Adam(1e-5)微调50代,每一代都保存权值,并且把验证集的准确率跑出来。图中的绿色的线 0.9977 就是按上面的方法训练的模型, 433 | 434 | ![](imgs/loss.png) 435 | 436 | 当然我们还尝试过先按 1e-3 的学习率训练20代,然后 1e-4 和 1e-5 交替训练2次,每次训练取验证集 loss 最低的结果继续训练,也就是图中红色的线,虽然速度快,但是准确率不够好。 437 | 438 | 之后我们将全部训练集都用于训练,得到了蓝色的线,效果和绿色差不多。 439 | 440 | ## 预测结果 441 | 442 | 读取测试集的样本,然后用 `base_model` 进行预测,这个过程很简单,就不讲了。 443 | 444 | ```py 445 | X = np.zeros((n, width, height, channels), dtype=np.uint8) 446 | 447 | for i in tqdm(range(n)): 448 | img = cv2.imread('crop_split2_test/%d.png'%i).transpose(1, 0, 2) 449 | a, b, _ = img.shape 450 | X[i, :a, :b] = img 451 | 452 | base_model = load_model('model_346_split2_3_%s.h5' % z) 453 | base_model2 = make_parallel(base_model, 4) 454 | 455 | y_pred = base_model2.predict(X, batch_size=500, verbose=1) 456 | out = K.get_value(K.ctc_decode(y_pred[:,2:], input_length=np.ones(y_pred.shape[0])*rnn_length)[0][0])[:, :n_len] 457 | ``` 458 | 459 | 输出到文件的部分有一点值得一提,就是如何计算出真实值: 460 | 461 | ```py 462 | ss = map(decode, out) 463 | 464 | vals = [] 465 | errs = [] 466 | errsid = [] 467 | for i in tqdm(range(100000)): 468 | val = '' 469 | try: 470 | a = ss[i].split(';') 471 | s = a[-1] 472 | for x in a[:-1]: 473 | x, c = x.split('=') 474 | s = s.replace(x, c+'.0') 475 | val = '%.2f' % eval(s) 476 | except: 477 | # disp3(i) 478 | errs.append(ss[i]) 479 | errsid.append(i) 480 | ss[i] = '' 481 | 482 | vals.append(val) 483 | 484 | with open('result_%s.txt' % z, 'w') as f: 485 | f.write('\n'.join(map(' '.join, list(zip(ss, vals)))).encode('utf-8')) 486 | 487 | print len(errs) 488 | print 1-len(errs)/100000. 489 | 490 | # output 491 | 22 492 | 0.99978 493 | ``` 494 | 495 | 其中的思路说起来也很简单,就是将表达式中的赋值式中文替换为赋值式的数字,然后直接用 python eval 得到结果,算不出来的直接留空即可。这个0.9977模型的可算率达到了0.99978,也就是说十万个样本里面只有22个样本不可算,当然,实际上还是有一些样本即使可算,也会因为各种原因识别错,比如5和6就是错误的重灾区,某些数字被干扰线切过,导致肉眼都辨认不清等。 496 | 497 | ## 模型结果融合 498 | 499 | 模型结果融合的规则很简单,对所有的结果进行次数统计,先去掉空的结果,然后取最高次数的结果即可,其实就是简单的投票。 500 | 501 | ```py 502 | import glob 503 | import numpy as np 504 | from collections import Counter 505 | 506 | def fun(x): 507 | c = Counter(x) 508 | c[' '] = 0 509 | return c.most_common()[0][0] 510 | 511 | ss = [open(fname, 'r').read().split('\n') for fname in glob.glob('result_model*.txt')] 512 | s = np.array(ss).T 513 | with open('result.txt', 'w') as f: 514 | f.write('\n'.join(map(fun, s))) 515 | ``` 516 | 517 | 将上面 loss 图中的三个模型结果融合以后,最后得到了0.99868的测试集准确率。 518 | 519 | ## 其他尝试 520 | 521 | ### 不定长图像识别 522 | 523 | 在比赛刚开始的时候,尝试过将图像的宽度设置为 None,也就是不定长的宽度,但是由于无法解决 reshape 的问题,这个方案被否了。 524 | 525 | ### 分别识别 526 | 527 | 之前尝试过图像切成几块,分别识别,赋值式和表达式的模型分开,考虑到由于无法得到上下文的信息,可能会丢失一定的准确率,做到一半否掉了这个方案。 528 | 529 | ### 生成器尝试 530 | 531 | 我们尝试过写一个生成器,但是由于和官方给的图像差太远,并且实际测试的时候要么是生成的准确率高,官方的准确率低,要么反过来,所以没有投入使用。 532 | 533 | ![](imgs/level2_generator2.png) 534 | 535 | 上图第一个是官方的图像,后面五个是我们的生成器生成的,可以看到我们的字没有官方的紧凑,等号也不太一样,分式我们的字又太紧凑了。 536 | 537 | ### 其他 CNN 模型的尝试 538 | 539 | 除了自己搭模型,我还尝试过用 ResNet,DenseNet 替换 CNN,然后去训练,但是由于本身这些模型就很大,训练起来速度很慢,然后主要问题又不在模型不够复杂,因为从绘制出来的 loss 曲线来看,虽然前面的 val_loss 一直在抖,但是在第50代学习率下降以后就非常平缓了,这模型是没有过拟合的: 540 | 541 | ![](imgs/level2_loss.png) 542 | 543 | ### 替换 GRU 为 LSTM 544 | 545 | 在比赛最后尝试过将 GRU 替换为 LSTM,得到的结果是十分类似的,但是提交上去以后准确率有轻微下降(多错了几个样本,可能是运气问题),之前做验证码识别的时候也是替换过,效果差不多,因此没有继续尝试。理论上这个序列长度并没有很长,GRU 和 LSTM 影响不大。 546 | 547 | ![](imgs/level2_loss_lstm.png) 548 | 549 | ## 总结 550 | 551 | ### 对项目的思考 552 | 553 | 本项目中,需要注意以下几个重要的点: 554 | 555 | * 数据准备: 556 | * 深度学习同传统图像处理技术结合,可以达到更好的准确率 557 | * 文本识别可以构造验证码生成器进行数据增强,增加训练样本数 558 | * 模型优化: 559 | * 如何根据项目特点,对模型结构进行调整,如CNN 部分减少池化层使用,等等 560 | * 为了防止过拟合,在模型中引入 L2 正则化 561 | * 模型训练: 562 | * 使用学习率衰减策略,训练模型 563 | * 对复杂的模型,可以将同一批次输入数据分摊给多个GPU进行计算。 564 | 565 | ### 有趣的样本 566 | 567 | #### 95170 568 | 569 | 在测试集里有一个 95170.png 样本很难分割: 570 | 571 | ![](imgs/level2_95170.png) 572 | 573 | 因为它的字太浅了,很难被切割出来,肉眼也基本无法分辨。 574 | 575 | 它的表达式也很难切,稍有不慎就切掉中文了: 576 | 577 | ![](imgs/level2_95170_0.png) 578 | 579 | #### 干扰线 580 | 581 | 在我们分割的验证集中,发现了被干扰线成功干扰的样本: 582 | 583 | ![](imgs/level2_sample1.png) 584 | 585 | 我们可以看到第一个 7 倾斜以后加上一条干扰线,很容易就被模型认成4了,但是人类却不会犯这样的错,这也是 CNN 和 人类之间的区别,目测卷积层自动把图像转灰度图了。 586 | 587 | ### 可能的改进 588 | 589 | * 将真正的生成器写出来,这样就可以获取无穷无尽的样本,而不是使用已有的样本进行拼接,对5和6的识别,以及很多横向的中文的识别,会有很好的帮助,因为它们在已有样本中十分罕见,以至于模型无法准确分辨5和6,以及横向的中文 590 | * 做更好的预处理,比如干扰线和字的颜色是不同的,可以通过程序去除,切图可以更精准一些,可以极大提高训练速度 591 | * 使用其他的模型,比如群里有人提到的 attention 模型,或者看看 OCR 相关的论文,找更多的模型,融合结果,比直接跑类似结构的模型来融合的效果会好很多 -------------------------------------------------------------------------------- /四则混合运算识别_初赛.md: -------------------------------------------------------------------------------- 1 | # 四则混合运算识别(初赛) 2 | 3 | 本节会详细介绍我在进行四则混合运算识别竞赛初赛时的所有思路。 4 | 5 | 核心思想在前面,所以此处会省略部分重复内容。 6 | 7 | ## 问题描述 8 | 9 | 本次竞赛目的是为了解决一个 OCR 问题,通俗地讲就是实现图像到文字的转换过程。 10 | 11 | ### 数据集 12 | 13 | 初赛数据集一共包含10万张180*60的图片和一个labels.txt的文本文件。每张图片包含一个数学运算式,运算式包含: 14 | 15 | 3个运算数:3个0到9的整型数字; 16 | 2个运算符:可以是+、-、*,分别代表加法、减法、乘法 17 | 0或1对括号:括号可能是0对或者1对 18 | 19 | 图片的名称从0.png到99999.png,下面是一些样例图片(这里只取了一张): 20 | 21 | ![](imgs/level1.png) 22 | 23 | 文本文件 labels.txt 包含10w行文本,每行文本包含每张图片对应的公式以及公式的计算结果,公式和计算结果之间空格分开,例如图片中的示例图片对应的文本如下所示: 24 | 25 | ``` 26 | (3-7)+5 1 27 | 5-6+2 1 28 | (6+7)*2 26 29 | (4+2)+7 13 30 | (6*4)*4 96 31 | ``` 32 | 33 | ### 评价指标 34 | 35 | 官方的评价指标是准确率,初赛只有整数的加减乘运算,所得的结果一定是整数,所以要求序列与运算结果都正确才会判定为正确。 36 | 37 | 我们本地除了会使用官方的准确率作为评估标准以外,还会使用 CTC loss 来评估模型。 38 | 39 | ## 使用 captcha 进行数据增强 40 | 41 | 官方提供了10万张图片,我们可以直接使用官方数据进行训练,也可以通过Captcha,参照官方训练集,随机生成更多数据,进而提高准确性。根据题目要求,label 必定是三个数字,两个运算符,一对或没有括号,根据括号规则,只有可能是没括号,左括号和右括号,因此很容易就可以写出数据生成器的代码。 42 | 43 | ### 生成器 44 | 45 | 生成器的生成规则很简单: 46 | 47 | ```py 48 | import string 49 | import random 50 | 51 | digits = string.digits 52 | operators = '+-*' 53 | characters = digits + operators + '() ' 54 | 55 | def generate(): 56 | seq = '' 57 | k = random.randint(0, 2) 58 | 59 | if k == 1: 60 | seq += '(' 61 | seq += random.choice(digits) 62 | seq += random.choice(operators) 63 | if k == 2: 64 | seq += '(' 65 | seq += random.choice(digits) 66 | if k == 1: 67 | seq += ')' 68 | seq += random.choice(operators) 69 | seq += random.choice(digits) 70 | if k == 2: 71 | seq += ')' 72 | 73 | return seq 74 | ``` 75 | 76 | 相信大家都能看懂。当然,我写文章的时候又想到一种更好的写法: 77 | 78 | ```py 79 | import random 80 | 81 | def generate(): 82 | ts = [u'{}{}{}{}{}', '({}{}{}){}{}', '{}{}({}{}{})'] 83 | ds = u'0123456789' 84 | os = u'+-*' 85 | cs = [random.choice(ds) if x%2 == 0 else random.choice(os) for x in range(5)] 86 | return random.choice(ts).format(*cs) 87 | ``` 88 | 89 | 除了生成算式以外,还有一个值得注意的地方就是初赛所有的减号(也就是“-”)都是细的,但是我们直接用 captcha 库生成图像会得到粗的减号,所以我们修改了 [image.py](https://github.com/lepture/captcha/blob/v0.2.2/captcha/image.py) 中的代码,在 `_draw_character` 函数中我们增加了一句判断,如果是减号,我们就不进行 resize 操作,这样就能防止减号变粗: 90 | 91 | ```py 92 | # line 191-194 93 | if c != '-': 94 | im = im.resize((w2, h2)) 95 | im = im.transform((w, h), Image.QUAD, data) 96 | ``` 97 | 98 | 我们继而使用生成器生成四则运算验证码: 99 | 100 | ```py 101 | import string 102 | import os 103 | 104 | digits = string.digits 105 | operators = '+-*' 106 | characters = digits + operators + '() ' 107 | width, height, n_len, n_class = 180, 60, 7, len(characters) + 1 108 | from captcha.image import ImageCaptcha 109 | generator = ImageCaptcha(width=width, height=height, 110 | font_sizes=range(35, 56), 111 | fonts=['fonts/%s'%x for x in os.listdir('fonts') if '.tt' in x] 112 | ) 113 | generator.generate_image('(1-2)-3') 114 | ``` 115 | 116 | ![](imgs/level1_gen1.png) 117 | 118 | 上图就是原版生成器生成的图,我们可以看到减号是很粗的。 119 | 120 | ![](imgs/level1_gen2.png) 121 | 122 | 上图是修改过的生成器,可以看到减号已经不粗了。 123 | 124 | ## 模型结构 125 | 126 | ```py 127 | from keras.layers import * 128 | from keras.models import * 129 | from make_parallel import make_parallel 130 | rnn_size = 128 131 | 132 | input_tensor = Input((width, height, 3)) 133 | x = input_tensor 134 | for i in range(3): 135 | x = Conv2D(32*2**i, (3, 3), kernel_initializer='he_normal')(x) 136 | x = BatchNormalization()(x) 137 | x = Activation('relu')(x) 138 | x = Conv2D(32*2**i, (3, 3), kernel_initializer='he_normal')(x) 139 | x = BatchNormalization()(x) 140 | x = Activation('relu')(x) 141 | x = MaxPooling2D(pool_size=(2, 2))(x) 142 | 143 | conv_shape = x.get_shape() 144 | x = Reshape(target_shape=(int(conv_shape[1]), int(conv_shape[2]*conv_shape[3])))(x) 145 | 146 | x = Dense(128, kernel_initializer='he_normal')(x) 147 | x = BatchNormalization()(x) 148 | x = Activation('relu')(x) 149 | 150 | gru_1 = GRU(rnn_size, return_sequences=True, kernel_initializer='he_normal', name='gru1')(x) 151 | gru_1b = GRU(rnn_size, return_sequences=True, go_backwards=True, kernel_initializer='he_normal', 152 | name='gru1_b')(x) 153 | gru1_merged = add([gru_1, gru_1b]) 154 | 155 | gru_2 = GRU(rnn_size, return_sequences=True, kernel_initializer='he_normal', name='gru2')(gru1_merged) 156 | gru_2b = GRU(rnn_size, return_sequences=True, go_backwards=True, kernel_initializer='he_normal', 157 | name='gru2_b')(gru1_merged) 158 | x = concatenate([gru_2, gru_2b]) 159 | x = Dropout(0.25)(x) 160 | x = Dense(n_class, kernel_initializer='he_normal', activation='softmax')(x) 161 | base_model = Model(input=input_tensor, output=x) 162 | 163 | base_model2 = make_parallel(base_model, 4) 164 | 165 | labels = Input(name='the_labels', shape=[n_len], dtype='float32') 166 | input_length = Input(name='input_length', shape=(1,), dtype='int64') 167 | label_length = Input(name='label_length', shape=(1,), dtype='int64') 168 | loss_out = Lambda(ctc_lambda_func, name='ctc')([base_model2.output, labels, input_length, label_length]) 169 | 170 | model = Model(inputs=(input_tensor, labels, input_length, label_length), outputs=loss_out) 171 | model.compile(loss={'ctc': lambda y_true, y_pred: y_pred}, optimizer='adam') 172 | ``` 173 | 174 | 模型结构像之前写的文章一样,只是把卷积核的个数改多了一点,加了一些 BN 层,并且在四卡上做了一点小改动以支持多GPU训练。如果你是单卡,可以直接去掉 `base_model2 = make_parallel(base_model, 4)` 的代码。 175 | 176 | BN 层主要是为了训练加速,实验结果非常好,模型收敛快了很多。 177 | 178 | base_model 的可视化: 179 | 180 | ![](imgs/level1_base_model.png) 181 | 182 | model 的可视化: 183 | 184 | ![](imgs/level1_model.png) 185 | 186 | ## 模型训练 187 | 188 | 在经过几次测试以后,我已经抛弃了 evaluate 函数,因为在验证集上已经能做到 100% 识别率了,所以只需要看 val_loss 就可以了。在经过之前的几次尝试以后,我发现在有生成器的情况下,训练代数越多越好,因此直接用 adam 跑了50代,每代10万样本,可以看到模型在10代以后基本已经收敛。 189 | 190 | ![](imgs/level1_loss.png) 191 | 192 | 我们可以看到模型先分为四份,在四个显卡上并行计算,然后合并结果,计算最后的 ctc loss,进而训练模型。 193 | 194 | ## 结果可视化 195 | 196 | 这里我们对生成的数据进行了可视化,可以看到模型基本已经做到万无一失,百发百中。 197 | 198 | ![](imgs/level1_visualization.png) 199 | 200 | 201 | 打包成 docker 以后提交到比赛系统中,经过十几分钟的运行,我们得到了完美的1分。 202 | 203 | ![](imgs/level1_score.png) 204 | 205 | ## 总结 206 | 207 | 初赛是非常简单的,因此我们才能得到这么准的分数,之后官方进一步提升了难度,将初赛测试集提高到了20万张,在这个集上我们的模型只能拿到0.999925的成绩,可行的改进方法是将准确率进一步降低,充分训练模型,将多个模型结果融合等。 208 | 209 | ### 官方扩充测试集的难点 210 | 211 | 在扩充数据集上,我们发现有一些图片预测出来无法计算,比如 `[629,2271,6579,17416,71857,77631,95303,102187,117422,142660,183693]` 等,这里我们取 117422.png 为例。 212 | 213 | ![](imgs/level1_117422.png) 214 | 215 | 我们可以看到肉眼基本无法认出这个图,但是经过一定的图像处理,我们可以显现出来它的真实面貌: 216 | 217 | ```py 218 | IMAGE_DIR = 'image_contest_level_1_validate' 219 | index = 117422 220 | 221 | img = cv2.imread('%s/%d.png' % (IMAGE_DIR, index)) 222 | gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 223 | h = cv2.equalizeHist(gray) 224 | ``` 225 | 226 | 然后我们可以看到这样的结果: 227 | 228 | ![](imgs/level1_preprocessing.png) 229 | 230 | 当然,还有一张图是无法通过预处理得到结果的,142660,这有可能是程序的 bug 造成的小概率事件,所以初赛除了我们跑了一个 docker 得到满分以外,没有第二个人达到满分。 231 | 232 | ![](imgs/level1_142660.png) 233 | 234 | ![](imgs/level1_leaderboard.png) 235 | --------------------------------------------------------------------------------