[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"tool-ypwhs--captcha_break":3,"similar-ypwhs--captcha_break":110},{"id":4,"github_repo":5,"name":6,"description_en":7,"description_zh":8,"ai_summary_zh":8,"readme_en":9,"readme_zh":10,"quickstart_zh":11,"use_case_zh":12,"hero_image_url":13,"owner_login":14,"owner_name":15,"owner_avatar_url":16,"owner_bio":17,"owner_company":18,"owner_location":19,"owner_email":17,"owner_twitter":17,"owner_website":20,"owner_url":21,"languages":22,"stars":31,"forks":32,"last_commit_at":33,"license":34,"difficulty_score":35,"env_os":36,"env_gpu":37,"env_ram":38,"env_deps":39,"category_tags":51,"github_topics":54,"view_count":35,"oss_zip_url":17,"oss_zip_packed_at":17,"status":65,"created_at":66,"updated_at":67,"faqs":68,"releases":99},1035,"ypwhs\u002Fcaptcha_break","captcha_break","验证码识别","captcha_break 是一个基于深度学习的验证码识别工具，通过构建卷积神经网络（CNN）模型实现对图像验证码的自动识别。它利用 Keras 框架和 GPU 加速，能够高效处理数字、字母组合的验证码图像，尤其适用于需要批量验证或自动化处理的场景。\n\n该工具解决了传统验证码人工识别效率低、易出错的问题，通过深度学习模型提升识别准确率。用户无需手动设计特征提取逻辑，即可快速训练模型完成验证码解析，降低开发成本。\n\n适合开发者和研究人员使用，尤其适用于需要集成验证码识别功能的项目，如自动化测试、网站安全验证等场景。对于希望提升验证码处理效率的普通用户，也能通过简化流程实现快速部署。\n\n技术亮点包括：基于 CuDNNGRU 的高效训练框架、支持多进程数据生成的自定义数据类、优化的显存管理策略，以及针对验证码特性的 One-Hot 编码处理方案，兼顾模型性能与实用性。","# 使用深度学习来破解 captcha 验证码\n\n本项目会通过 Keras 搭建一个深度卷积神经网络来识别 captcha 验证码，建议使用显卡来运行该项目。\n\n下面的可视化代码都是在 `jupyter notebook` 中完成的，如果你希望写成 python 脚本，稍加修改即可正常运行，当然也可以去掉这些可视化代码。\n\n2019 年更新了：\n\n* 适配了新版 API\n* 提高了数据生成器的效率\n* 使用了 CuDNNGRU 提高了训练和预测效率\n* 更新了文档\n\n# 环境\n\n本项目使用的环境如下：\n\n* captcha 0.3\n* tensorflow-gpu 1.13.1\n* numpy 1.16.4\n* tqdm 4.28.1\n\n下面几个包是用于可视化的：\n\n* matplotlib 2.2.2\n* pandas 0.23.0\n* pydot 1.4.1\n* graphviz 2.38.0-12ubuntu2.1\n\n# captcha\n\ncaptcha 是用 python 写的生成验证码的库，它支持图片验证码和语音验证码，我们使用的是它生成图片验证码的功能。\n\n首先我们设置我们的验证码格式为数字加大写字母，生成一串验证码试试看：\n\n```py\nfrom captcha.image import ImageCaptcha\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport random\n\n%matplotlib inline\n%config InlineBackend.figure_format = 'retina'\n\nimport string\ncharacters = string.digits + string.ascii_uppercase\nprint(characters)\n\nwidth, height, n_len, n_class = 170, 80, 4, len(characters)\n\ngenerator = ImageCaptcha(width=width, height=height)\nrandom_str = ''.join([random.choice(characters) for j in range(4)])\nimg = generator.generate_image(random_str)\n\nplt.imshow(img)\nplt.title(random_str)\n\n```\n\n![](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fypwhs_captcha_break_readme_ae659e61d0e1.png)\n\n# 防止 tensorflow 占用所有显存\n\n众所周知 tensorflow 默认占用所有显存，这样不利于我们同时进行多项实验，因此我们可以使用下面的代码当 tensorflow 使用它需要的显存，而不是直接占用所有显存。\n\n```py\nimport tensorflow as tf\nimport tensorflow.keras.backend as K\n\nconfig = tf.ConfigProto()\nconfig.gpu_options.allow_growth=True\nsess = tf.Session(config=config)\nK.set_session(sess)\n```\n\n# 数据生成器\n\n训练模型的时候，我们可以选择两种方式来生成我们的训练数据，一种是一次性生成几万张图，然后开始训练，一种是定义一个数据生成器，然后利用 `fit_generator` 函数来训练。\n\n第一种方式的好处是训练的时候显卡利用率高，如果你需要经常调参，可以一次生成，多次使用；第二种方式的好处是你不需要生成大量数据，训练过程中可以利用 CPU 生成数据，而且还有一个好处是你可以无限生成数据。\n\n我们的数据格式如下：\n\n## X\n\nX 的形状是 `(batch_size, height, width, 3)`，比如一批生成 128 个样本，图片宽度为170，高度为80，那么 X 的形状就是 `(128, 64, 128, 3)`，如果你想取第一张图，代码可以这样写 `X[0]`。\n\n## y\n\ny 的形状是四个 `(batch_size, n_class)`，如果转换成 numpy 的格式，则是 `(n_len, batch_size, n_class)`，比如一批生成 128 个样本，验证码的字符有 36 种，长度是 4 位，那么它的形状就是 4 个 `(128, 36)` 的矩阵，也可以说是 `(4, 32, 36)`。\n\n## 数据生成器\n\n为了让 Keras 能够使用多进程并行生成数据，我们需要使用 Keras 的 Sequence 类实现一个我们自己的数据类。\n\n在 `__init__` 初始化函数里，我们定义数据所需的参数，然后这个数据的长度就是 steps 数。在 `__getitem__` 里，我们不用理会索引号，直接随机生成一批样本送去训练即可。\n\n```py\nfrom tensorflow.keras.utils import Sequence\n\nclass CaptchaSequence(Sequence):\n    def __init__(self, characters, batch_size, steps, n_len=4, width=128, height=64):\n        self.characters = characters\n        self.batch_size = batch_size\n        self.steps = steps\n        self.n_len = n_len\n        self.width = width\n        self.height = height\n        self.n_class = len(characters)\n        self.generator = ImageCaptcha(width=width, height=height)\n    \n    def __len__(self):\n        return self.steps\n\n    def __getitem__(self, idx):\n        X = np.zeros((self.batch_size, self.height, self.width, 3), dtype=np.float32)\n        y = [np.zeros((self.batch_size, self.n_class), dtype=np.uint8) for i in range(self.n_len)]\n        for i in range(self.batch_size):\n            random_str = ''.join([random.choice(self.characters) for j in range(self.n_len)])\n            X[i] = np.array(self.generator.generate_image(random_str)) \u002F 255.0\n            for j, ch in enumerate(random_str):\n                y[j][i, :] = 0\n                y[j][i, self.characters.find(ch)] = 1\n        return X, y\n```\n\n# 使用生成器\n\n生成器的使用方法很简单，只需要用对它取第一个 batch 即可。下面是一个例子，初始化一个数据集，设置 batch_size 和 steps 都为 1，然后取出来第一个数据，对它可视化。\n\n在这里我们对生成的  One-Hot 编码后的标签进行了解码，首先将它转为 numpy 数组，然后取36个字符中最大的数字的位置（axis=2代表字符的轴），实际上神经网络会输出36个字符的概率，我们需要将概率最大的四个字符的编号取出来，转换为字符串。\n\n```py\ndef decode(y):\n    y = np.argmax(np.array(y), axis=2)[:,0]\n    return ''.join([characters[x] for x in y])\n\ndata = CaptchaSequence(characters, batch_size=1, steps=1)\nX, y = data[0]\nplt.imshow(X[0])\nplt.title(decode(y))\n```\n\n# 构建深度卷积神经网络\n\n```py\nfrom tensorflow.keras.models import *\nfrom tensorflow.keras.layers import *\n\ninput_tensor = Input((height, width, 3))\nx = input_tensor\nfor i, n_cnn in enumerate([2, 2, 2, 2, 2]):\n    for j in range(n_cnn):\n        x = Conv2D(32*2**min(i, 3), kernel_size=3, padding='same', kernel_initializer='he_uniform')(x)\n        x = BatchNormalization()(x)\n        x = Activation('relu')(x)\n    x = MaxPooling2D(2)(x)\n\nx = Flatten()(x)\nx = [Dense(n_class, activation='softmax', name='c%d'%(i+1))(x) for i in range(n_len)]\nmodel = Model(inputs=input_tensor, outputs=x)\n```\n\n模型结构很简单，特征提取部分使用的是两个卷积，一个池化的结构，这个结构是学的 VGG16 的结构。我们重复五个 block，然后我们将它 Flatten，连接四个分类器，每个分类器是36个神经元，输出36个字符的概率。\n\n# 模型可视化\n\n得益于 Keras 自带的可视化，我们可以使用几句代码来可视化模型的结构：\n\n```py\nfrom tensorflow.keras.utils import plot_model\nfrom IPython.display import Image\n\nplot_model(model, to_file='cnn.png', show_shapes=True)\nImage('cnn.png')\n```\n\n这里需要使用 pydot 这个库，以及 graphviz 这个库，在 macOS 系统上安装方法如下：\n\n```sh\nbrew install graphviz\npip install pydot-ng\n```\n\n![](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fypwhs_captcha_break_readme_4ad3cc89129f.png)\n\n我们可以看到最后一层卷积层输出的形状是 `(1, 6, 256)`，已经不能再加卷积层了。\n\n# 训练模型\n\n训练模型反而是所有步骤里面最简单的一个，直接使用 `model.fit_generator` 即可，这里的验证集使用了同样的生成器，由于数据是通过生成器随机生成的，所以我们不用考虑数据是否会重复。\n\n为了避免手动调参，我们使用了 Adam 优化器，它的学习率是自动设置的，我们只需要给一个较好的初始学习率即可。\n\nEarlyStopping 是一个 Keras 的 Callback，它可以在 loss 超过多少个 epoch 没有下降以后，就自动终止训练，避免浪费时间。\n\nModelCheckpoint 是另一个好用的 Callback，它可以保存训练过程中最好的模型。\n\nCSVLogger 可以记录 loss 为 CSV 文件，这样我们就可以在训练完成以后绘制训练过程中的 loss 曲线。\n\n注意，这段代码在笔记本电脑上可能要较长时间，建议使用带有 NVIDIA 显卡的机器运行。注意我们这里使用了一个小技巧，添加 `workers=4` 参数让 Keras 自动实现多进程生成数据，摆脱 python 单线程效率低的缺点。\n\n```py\nfrom tensorflow.keras.callbacks import EarlyStopping, CSVLogger, ModelCheckpoint\nfrom tensorflow.keras.optimizers import *\n\ntrain_data = CaptchaSequence(characters, batch_size=128, steps=1000)\nvalid_data = CaptchaSequence(characters, batch_size=128, steps=100)\ncallbacks = [EarlyStopping(patience=3), CSVLogger('cnn.csv'), ModelCheckpoint('cnn_best.h5', save_best_only=True)]\n\nmodel.compile(loss='categorical_crossentropy',\n              optimizer=Adam(1e-3, amsgrad=True), \n              metrics=['accuracy'])\nmodel.fit_generator(train_data, epochs=100, validation_data=valid_data, workers=4, use_multiprocessing=True,\n                    callbacks=callbacks)\n```\n\n### 载入最好的模型继续训练一会\n\n为了让模型充分训练，我们可以载入之前最好的模型权值，然后降低学习率为原来的十分之一，继续训练，这样可以让模型收敛得更好。\n\n```py\nmodel.load_weights('cnn_best.h5')\n\ncallbacks = [EarlyStopping(patience=3), CSVLogger('cnn.csv', append=True), \n             ModelCheckpoint('cnn_best.h5', save_best_only=True)]\n\nmodel.compile(loss='categorical_crossentropy',\n              optimizer=Adam(1e-4, amsgrad=True), \n              metrics=['accuracy'])\nmodel.fit_generator(train_data, epochs=100, validation_data=valid_data, workers=4, use_multiprocessing=True,\n                    callbacks=callbacks)\n```\n\n# 测试模型\n\n当我们训练完成以后，可以识别一个验证码试试看：\n\n```py\nX, y = data[0]\ny_pred = model.predict(X)\nplt.title('real: %s\\npred:%s'%(decode(y), decode(y_pred)))\nplt.imshow(X[0], cmap='gray')\nplt.axis('off')\n```\n\n![](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fypwhs_captcha_break_readme_b52cbd02e845.png)\n\n# 计算模型总体准确率\n\n模型在训练的时候只会显示每一个字符的准确率，为了统计模型的总体准确率，我们可以写下面的函数：\n\n```py\nfrom tqdm import tqdm\ndef evaluate(model, batch_num=100):\n    batch_acc = 0\n    with tqdm(CaptchaSequence(characters, batch_size=128, steps=100)) as pbar:\n        for X, y in pbar:\n            y_pred = model.predict(X)\n            y_pred = np.argmax(y_pred, axis=-1).T\n            y_true = np.argmax(y, axis=-1).T\n\n            batch_acc += (y_true == y_pred).all(axis=-1).mean()\n    return batch_acc \u002F batch_num\n\nevaluate(model)\n```\n\n这里用到了一个库叫做 tqdm，它是一个进度条的库，为的是能够实时反馈进度。然后我们通过一些 numpy 计算去统计我们的准确率，这里计算规则是只要有一个错，那么就不算它对。经过计算，我们的模型的总体准确率在经过充分训练以后，可以达到 98.26% 的总体准确率。\n\n# 模型总结\n\n模型的大小是10.7MB，总体准确率是 98.26%，基本上可以确定破解了此类验证码。\n\n# 改进\n\n对于这种按顺序书写的文字，我们还有一种方法可以使用，那就是循环神经网络来识别序列。下面我们来了解一下如何使用循环神经网络来识别这类验证码。\n\n# CTC Loss\n\n这个 loss 是一个特别神奇的 loss，它可以在只知道序列的顺序，不知道具体位置的情况下，让模型收敛。这里有一个非常好的文章介绍了 CTC Loss: [Sequence Modeling\nWith CTC](https:\u002F\u002Fdistill.pub\u002F2017\u002Fctc\u002F)\n\n![](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fypwhs_captcha_break_readme_eb7c994618ed.png)\n\n在 Keras 里面已经内置了 CTC Loss ，我们实现下面的代码即可在模型里使用 CTC Loss。\n\n* `y_pred` 是模型的输出，是按顺序输出的37个字符的概率，因为我们这里用到了循环神经网络，所以需要一个空白字符的概念；\n* `labels` 是验证码，是四个数字，每个数字代表字符在字符集里的位置\n* `input_length` 表示 `y_pred` 的长度，我们这里是16\n* `label_length` 表示 `labels` 的长度，我们这里是4\n\n```py\nimport tensorflow.keras.backend as K\n\ndef ctc_lambda_func(args):\n    y_pred, labels, input_length, label_length = args\n    return K.ctc_batch_cost(labels, y_pred, input_length, label_length)\n```\n\n# 模型结构\n\n我们的模型结构是这样设计的，首先通过卷积神经网络去识别特征，然后按水平顺序输入到 GRU 进行序列建模，最后使用一个分类器对每个时刻输出的特征进行分类。\n\n```py\nfrom tensorflow.keras.models import *\nfrom tensorflow.keras.layers import *\n\ninput_tensor = Input((height, width, 3))\nx = input_tensor\nfor i, n_cnn in enumerate([2, 2, 2, 2, 2]):\n    for j in range(n_cnn):\n        x = Conv2D(32*2**min(i, 3), kernel_size=3, padding='same', kernel_initializer='he_uniform')(x)\n        x = BatchNormalization()(x)\n        x = Activation('relu')(x)\n    x = MaxPooling2D(2 if i \u003C 3 else (2, 1))(x)\n\nx = Permute((2, 1, 3))(x)\nx = TimeDistributed(Flatten())(x)\n\nrnn_size = 128\nx = Bidirectional(CuDNNGRU(rnn_size, return_sequences=True))(x)\nx = Bidirectional(CuDNNGRU(rnn_size, return_sequences=True))(x)\nx = Dense(n_class, activation='softmax')(x)\n\nbase_model = Model(inputs=input_tensor, outputs=x)\n```\n\n为了训练这个模型，我们还需要搭建一个 loss 计算网络，代码如下：\n\n```py\nlabels = Input(name='the_labels', shape=[n_len], dtype='float32')\ninput_length = Input(name='input_length', shape=[1], dtype='int64')\nlabel_length = Input(name='label_length', shape=[1], dtype='int64')\nloss_out = Lambda(ctc_lambda_func, output_shape=(1,), name='ctc')([x, labels, input_length, label_length])\n\nmodel = Model(inputs=[input_tensor, labels, input_length, label_length], outputs=loss_out)\n```\n\n真正训练出来的模型是 `base_model`，由于 Keras 的限制，我们没办法直接使用 `base_model` 搭建 CTCLoss，所以我们只能按照上面的方法，让模型直接输出 loss。\n\n# 模型可视化\n\n可视化的代码同上，这里只贴图。\n\n![](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fypwhs_captcha_break_readme_ffc57e0b48a6.png)\n\n可以看到模型比上一个模型复杂了许多，但实际上只是因为输入比较多，所以它显得很大。\n\n首先模型输入一个 `(height, width, 3)` 维度的图片，然后经过一系列的层降维到了 `(2, 16, 256)`，之后我们使用 Permute 把 width 轴调整到第一个维度以适配 RNN 的输入格式。调整以后的维度是 `(16, 2, 256)`，然后使用 `TimeDistributed(Flatten())` 把后两个维度压成一维，也就是 `(16, 512)`，之后经过 2 层双向的 GRU 对序列横向建模，最后经过 Dense 分类器输出水平方向上每个字符的概率分布。\n\n使用 CuDNNGRU 是因为它在 NVIDIA 显卡上可以加速非常多倍，如果你使用的是 CPU，改为 GRU 即可。\n\n使用 RNN 的原因是，如果你看到一句话是 `今天我*了一个非常好吃的苹果`，有一个字看不清，你很容易猜到这个字是“吃”，但是使用 CNN，你就很难有这么大的感受野，从苹果推测出前面的字是吃。\n\n# 数据生成器\n\n数据生成器和 CNN 的差不多，这里需要多几个矩阵，一个是 input_length，代表序列长度，一个是 label_length，代表验证码长度，还有一个 np.ones，没有意义，只是为了适配 Keras 训练需要的矩阵输入。\n\n```py\nfrom tensorflow.keras.utils import Sequence\n\nclass CaptchaSequence(Sequence):\n    def __init__(self, characters, batch_size, steps, n_len=4, width=128, height=64, \n                 input_length=16, label_length=4):\n        self.characters = characters\n        self.batch_size = batch_size\n        self.steps = steps\n        self.n_len = n_len\n        self.width = width\n        self.height = height\n        self.input_length = input_length\n        self.label_length = label_length\n        self.n_class = len(characters)\n        self.generator = ImageCaptcha(width=width, height=height)\n    \n    def __len__(self):\n        return self.steps\n\n    def __getitem__(self, idx):\n        X = np.zeros((self.batch_size, self.height, self.width, 3), dtype=np.float32)\n        y = np.zeros((self.batch_size, self.n_len), dtype=np.uint8)\n        input_length = np.ones(self.batch_size)*self.input_length\n        label_length = np.ones(self.batch_size)*self.label_length\n        for i in range(self.batch_size):\n            random_str = ''.join([random.choice(self.characters) for j in range(self.n_len)])\n            X[i] = np.array(self.generator.generate_image(random_str)) \u002F 255.0\n            y[i] = [self.characters.find(x) for x in random_str]\n        return [X, y, input_length, label_length], np.ones(self.batch_size)\n```\n\n# 评估模型\n\n```py\nfrom tqdm import tqdm\n\ndef evaluate(model, batch_size=128, steps=20):\n    batch_acc = 0\n    valid_data = CaptchaSequence(characters, batch_size, steps)\n    for [X_test, y_test, _, _], _ in valid_data:\n        y_pred = base_model.predict(X_test)\n        shape = y_pred.shape\n        out = K.get_value(K.ctc_decode(y_pred, input_length=np.ones(shape[0])*shape[1])[0][0])[:, :4]\n        if out.shape[1] == 4:\n            batch_acc += (y_test == out).all(axis=1).mean()\n    return batch_acc \u002F steps\n```\n\n我们会通过这个函数来评估我们的模型，和上面的评估标准一样，只有全部正确，我们才算预测正确，中间有个坑，就是模型最开始训练的时候，并不一定会输出四个字符，所以我们如果遇到所有的字符都不到四个的时候，就不计算了，相当于加0，遇到多于4个字符的时候，只取前四个。\n\n# 评估回调\n\n因为 Keras 没有针对这种输出计算准确率的选项，因此我们需要自定义一个回调函数，它会在每一代训练完成的时候计算模型的准确率。\n\n```py\nfrom tensorflow.keras.callbacks import Callback\n\nclass Evaluate(Callback):\n    def __init__(self):\n        self.accs = []\n    \n    def on_epoch_end(self, epoch, logs=None):\n        logs = logs or {}\n        acc = evaluate(base_model)\n        logs['val_acc'] = acc\n        self.accs.append(acc)\n        print(f'\\nacc: {acc*100:.4f}')\n```\n\n# 训练模型\n\n我们还是按照之前的训练策略，先训练 100 代，等 loss 不降低以后，降低学习率，再训练 100 代，代码如下：\n\n```py\nfrom tensorflow.keras.callbacks import EarlyStopping, CSVLogger, ModelCheckpoint\nfrom tensorflow.keras.optimizers import *\n\ntrain_data = CaptchaSequence(characters, batch_size=128, steps=1000)\nvalid_data = CaptchaSequence(characters, batch_size=128, steps=100)\ncallbacks = [EarlyStopping(patience=5), Evaluate(), \n             CSVLogger('ctc.csv'), ModelCheckpoint('ctc_best.h5', save_best_only=True)]\n\nmodel.compile(loss={'ctc': lambda y_true, y_pred: y_pred}, optimizer=Adam(1e-3, amsgrad=True))\nmodel.fit_generator(train_data, epochs=100, validation_data=valid_data, workers=4, use_multiprocessing=True,\n                    callbacks=callbacks)\n```\n\n```py\nmodel.load_weights('ctc_best.h5')\n\ncallbacks = [EarlyStopping(patience=5), Evaluate(), \n             CSVLogger('ctc.csv', append=True), ModelCheckpoint('ctc_best.h5', save_best_only=True)]\n\nmodel.compile(loss={'ctc': lambda y_true, y_pred: y_pred}, optimizer=Adam(1e-4, amsgrad=True))\nmodel.fit_generator(train_data, epochs=100, validation_data=valid_data, workers=4, use_multiprocessing=True,\n                    callbacks=callbacks)\n```\n\n![](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fypwhs_captcha_break_readme_ee75c2159026.png)\n\n可以看到 loss 一开始下降很快，后面就很平了，但是我们把在对数尺度下绘制 loss 图的话，还是能看到 loss 一直在下降的。acc 上升得也很快，虽然前期训练的时候 acc 很抖动，但是后期学习率降下来以后就不会再跌下来了。\n\n最终模型的准确率达到了 99.21%，训练过程中的准确率最高达到了 99.49%。\n\n# 测试模型\n\n```py\ncharacters2 = characters + ' '\n[X_test, y_test, _, _], _  = data[0]\ny_pred = base_model.predict(X_test)\nout = K.get_value(K.ctc_decode(y_pred, input_length=np.ones(y_pred.shape[0])*y_pred.shape[1], )[0][0])[:, :4]\nout = ''.join([characters[x] for x in out[0]])\ny_true = ''.join([characters[x] for x in y_test[0]])\n\nplt.imshow(X_test[0])\nplt.title('pred:' + str(out) + '\\ntrue: ' + str(y_true))\n\nargmax = np.argmax(y_pred, axis=2)[0]\nlist(zip(argmax, ''.join([characters2[x] for x in argmax])))\n```\n\n这里随机出来的验证码很厉害，是`O0OP`，不过更厉害的是模型认出来了。\n\n![](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fypwhs_captcha_break_readme_3fd75a076a89.png)\n\n# 有趣的问题\n\n我又用之前的模型做了个测试，对于 `O0O0` 这样丧心病狂的验证码，模型偶尔也能正确识别，这让我非常惊讶，它是真的能识别 O 与 0 的差别呢，还是猜出来的呢？这很难说。\n\n```py\ngenerator = ImageCaptcha(width=width, height=height)\nrandom_str = 'O0O0'\nX = generator.generate_image(random_str)\nX = np.expand_dims(X, 0) \u002F 255.0\n\ny_pred = base_model.predict(X)\nout = K.get_value(K.ctc_decode(y_pred, input_length=np.ones(y_pred.shape[0])*y_pred.shape[1], )[0][0])[:, :4]\nout = ''.join([characters[x] for x in out[0]])\n\nplt.title('real: %s\\npred:%s'%(random_str, out))\nplt.imshow(X[0], cmap='gray')\n```\n\n![](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fypwhs_captcha_break_readme_dafeaa08a9d2.png)\n\n# 总结\n\n模型的大小是12.8MB，准确率达到了惊人的 99.21%，即使连 0 和 O 都能精准区分，非常成功。\n\n# 扩展\n\n如果你比较喜欢 PyTorch，可以看 [ctc_pytorch.ipynb](ctc_pytorch.ipynb)，精度更高，达到了 99.57%。\n\n如果你想查看更多经验，可以看看我在百度云魅族深度学习应用大赛的代码和思路：[https:\u002F\u002Fgithub.com\u002Fypwhs\u002Fbaiduyun_deeplearning_competition](https:\u002F\u002Fgithub.com\u002Fypwhs\u002Fbaiduyun_deeplearning_competition)\n\n# 参考链接\n\n* [https:\u002F\u002Fkeras.io\u002Fgetting-started\u002Ffunctional-api-guide\u002F](https:\u002F\u002Fkeras.io\u002Fgetting-started\u002Ffunctional-api-guide\u002F)\n* [https:\u002F\u002Fwww.tensorflow.org\u002Fapi_docs\u002Fpython\u002Ftf\u002Fnn\u002Fctc_loss](https:\u002F\u002Fwww.tensorflow.org\u002Fapi_docs\u002Fpython\u002Ftf\u002Fnn\u002Fctc_loss)\n* [https:\u002F\u002Fgithub.com\u002Fkeras-team\u002Fkeras\u002Fblob\u002Fmaster\u002Fexamples\u002Fimage_ocr.py](https:\u002F\u002Fgithub.com\u002Fkeras-team\u002Fkeras\u002Fblob\u002Fmaster\u002Fexamples\u002Fimage_ocr.py)\n* [https:\u002F\u002Fcs231n.github.io\u002Fconvolutional-networks\u002F](https:\u002F\u002Fcs231n.github.io\u002Fconvolutional-networks\u002F)\n* [https:\u002F\u002Fdistill.pub\u002F2017\u002Fctc\u002F](https:\u002F\u002Fdistill.pub\u002F2017\u002Fctc\u002F)\n","# 使用深度学习破解验证码\n\n本项目通过Keras搭建深度卷积神经网络来识别验证码，建议使用显卡运行该项目。\n\n下面的可视化代码均在`jupyter notebook`中完成，若希望写成Python脚本，稍作修改即可正常运行，当然也可以去掉这些可视化代码。\n\n2019年更新内容：\n* 适配了新版API\n* 提高了数据生成器效率\n* 使用了CuDNNGRU（NVIDIA的GPU加速的GRU单元）提高训练和预测效率\n* 更新了文档\n\n# 环境\n\n本项目使用的环境如下：\n\n* captcha 0.3\n* tensorflow-gpu 1.13.1\n* numpy 1.16.4\n* tqdm 4.28.1\n\n以下包用于可视化：\n\n* matplotlib 2.2.2\n* pandas 0.23.0\n* pydot 1.4.1\n* graphviz 2.38.0-12ubuntu2.1\n\n# captcha\n\ncaptcha 是用Python编写的生成验证码的库，支持图片验证码和语音验证码，我们使用的是它生成图片验证码的功能。\n\n首先我们设置验证码格式为数字加大写字母，生成一串验证码试试看：\n\n```py\nfrom captcha.image import ImageCaptcha\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport random\n\n%matplotlib inline\n%config InlineBackend.figure_format = 'retina'\n\nimport string\ncharacters = string.digits + string.ascii_uppercase\nprint(characters)\n\nwidth, height, n_len, n_class = 170, 80, 4, len(characters)\n\ngenerator = ImageCaptcha(width=width, height=height)\nrandom_str = ''.join([random.choice(characters) for j in range(4)])\nimg = generator.generate_image(random_str)\n\nplt.imshow(img)\nplt.title(random_str)\n\n```\n\n![](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fypwhs_captcha_break_readme_ae659e61d0e1.png)\n\n# 防止tensorflow占用所有显存\n\n众所周知tensorflow默认占用所有显存，这样不利于同时进行多项实验，因此我们可以使用下面的代码让tensorflow只使用它需要的显存，而不是直接占用所有显存。\n\n```py\nimport tensorflow as tf\nimport tensorflow.keras.backend as K\n\nconfig = tf.ConfigProto()\nconfig.gpu_options.allow_growth=True\nsess = tf.Session(config=config)\nK.set_session(sess)\n```\n\n# 数据生成器\n\n训练模型时，我们可以选择两种方式生成训练数据：一种是一次性生成几万张图后开始训练，另一种是定义一个数据生成器，然后利用`fit_generator`函数训练。\n\n第一种方式的好处是训练时显卡利用率高，如果需要经常调参，可以一次生成多次使用；第二种方式的好处是你不需要生成大量数据，训练过程中可以利用CPU生成数据，还有一个好处是你可以无限生成数据。\n\n我们的数据格式如下：\n\n## X\n\nX的形状是`(batch_size, height, width, 3)`，比如一批生成128个样本，图片宽度为170，高度为80，那么X的形状就是`(128, 64, 128, 3)`，如果你想取第一张图，代码可以这样写`X[0]`。\n\n## y\n\ny的形状是四个`(batch_size, n_class)`，如果转换成numpy格式，则是`(n_len, batch_size, n_class)`，比如一批生成128个样本，验证码的字符有36种，长度是4位，那么它的形状就是4个`(128, 36)`的矩阵，也可以说是`(4, 32, 36)`。\n\n## 数据生成器\n\n为了让Keras能够使用多进程并行生成数据，我们需要使用Keras的Sequence类实现一个我们自己的数据类。\n\n在`__init__`初始化函数里，我们定义数据所需的参数，然后这个数据的长度就是steps数。在`__getitem__`里，我们不用理会索引号，直接随机生成一批样本送去训练即可。\n\n```py\nfrom tensorflow.keras.utils import Sequence\n\nclass CaptchaSequence(Sequence):\n    def __init__(self, characters, batch_size, steps, n_len=4, width=128, height=64):\n        self.characters = characters\n        self.batch_size = batch_size\n        self.steps = steps\n        self.n_len = n_len\n        self.width = width\n        self.height = height\n        self.n_class = len(characters)\n        self.generator = ImageCaptcha(width=width, height=height)\n    \n    def __len__(self):\n        return self.steps\n\n    def __getitem__(self, idx):\n        X = np.zeros((self.batch_size, self.height, self.width, 3), dtype=np.float32)\n        y = [np.zeros((self.batch_size, self.n_class), dtype=np.uint8) for i in range(self.n_len)]\n        for i in range(self.batch_size):\n            random_str = ''.join([random.choice(self.characters) for j in range(self.n_len)])\n            X[i] = np.array(self.generator.generate_image(random_str)) \u002F 255.0\n            for j, ch in enumerate(random_str):\n                y[j][i, :] = 0\n                y[j][i, self.characters.find(ch)] = 1\n        return X, y\n```\n\n# 使用生成器\n\n生成器的使用方法很简单，只需要用对它取第一个batch即可。下面是一个例子，初始化一个数据集，设置batch_size和steps都为1，然后取出来第一个数据，对它可视化。\n\n在这里我们对生成的One-Hot编码后的标签进行了解码，首先将它转为numpy数组，然后取36个字符中最大的数字的位置（axis=2代表字符的轴），实际上神经网络会输出36个字符的概率，我们需要将概率最大的四个字符的编号取出来，转换为字符串。\n\n```py\ndef decode(y):\n    y = np.argmax(np.array(y), axis=2)[:,0]\n    return ''.join([characters[x] for x in y])\n\ndata = CaptchaSequence(characters, batch_size=1, steps=1)\nX, y = data[0]\nplt.imshow(X[0])\nplt.title(decode(y))\n```\n\n# 构建深度卷积神经网络\n\n```py\nfrom tensorflow.keras.models import *\nfrom tensorflow.keras.layers import *\n\ninput_tensor = Input((height, width, 3))\nx = input_tensor\nfor i, n_cnn in enumerate([2, 2, 2, 2, 2]):\n    for j in range(n_cnn):\n        x = Conv2D(32*2**min(i, 3), kernel_size=3, padding='same', kernel_initializer='he_uniform')(x)\n        x = BatchNormalization()(x)\n        x = Activation('relu')(x)\n    x = MaxPooling2D(2)(x)\n\nx = Flatten()(x)\nx = [Dense(n_class, activation='softmax', name='c%d'%(i+1))(x) for i in range(n_len)]\nmodel = Model(inputs=input_tensor, outputs=x)\n```\n\n模型结构很简单，特征提取部分使用的是两个卷积，一个池化的结构，这个结构是学的VGG16的结构。我们重复五个block，然后将其Flatten，连接四个分类器，每个分类器是36个神经元，输出36个字符的概率。\n\n# 模型可视化\n\n得益于Keras自带的可视化，我们可以使用几句代码来可视化模型的结构：\n\n```py\nfrom tensorflow.keras.utils import plot_model\nfrom IPython.display import Image\n\nplot_model(model, to_file='cnn.png', show_shapes=True)\nImage('cnn.png')\n```\n\n这里需要使用pydot这个库，以及graphviz这个库，在macOS系统上安装方法如下：\n\n```sh\nbrew install graphviz\npip install pydot-ng\n```\n\n![](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fypwhs_captcha_break_readme_4ad3cc89129f.png)\n\n我们可以看到最后一层卷积层输出的形状是`(1, 6, 256)`，已经不能再加卷积层了。\n\n# 训练模型\n\n训练模型反而是所有步骤里面最简单的一个，直接使用`model.fit_generator`即可，这里的验证集使用了同样的生成器，由于数据是通过生成器随机生成的，所以我们不用考虑数据是否会重复。\n\n为了避免手动调参，我们使用了Adam优化器，它的学习率是自动设置的，我们只需要给一个较好的初始学习率即可。\n\nEarlyStopping是一个Keras的Callback，它可以在loss超过多少个epoch没有下降以后，就自动终止训练，避免浪费时间。\n\nModelCheckpoint是另一个好用的Callback，它可以保存训练过程中最好的模型。\n\nCSVLogger可以记录loss为CSV文件，这样我们就可以在训练完成以后绘制训练过程中的loss曲线。\n\n注意，这段代码在笔记本电脑上可能要较长时间，建议使用带有NVIDIA显卡的机器运行。注意我们这里使用了一个小技巧，添加`workers=4`参数让Keras自动实现多进程生成数据，摆脱python单线程效率低的缺点。\n\n```py\nfrom tensorflow.keras.callbacks import EarlyStopping, CSVLogger, ModelCheckpoint\nfrom tensorflow.keras.optimizers import *\n\ntrain_data = CaptchaSequence(characters, batch_size=128, steps=1000)\nvalid_data = CaptchaSequence(characters, batch_size=128, steps=100)\ncallbacks = [EarlyStopping(patience=3), CSVLogger('cnn.csv'), ModelCheckpoint('cnn_best.h5', save_best_only=True)]\n\nmodel.compile(loss='categorical_crossentropy',\n              optimizer=Adam(1e-3, amsgrad=True), \n              metrics=['accuracy'])\nmodel.fit_generator(train_data, epochs=100, validation_data=valid_data, workers=4, use_multiprocessing=True,\n                    callbacks=callbacks)\n```\n\n### 载入最好的模型继续训练一会\n\n为了让模型充分训练，我们可以载入之前最好的模型权值，然后降低学习率为原来的十分之一，继续训练，这样可以让模型收敛得更好。\n\n```py\nmodel.load_weights('cnn_best.h5')\n\ncallbacks = [EarlyStopping(patience=3), CSVLogger('cnn.csv', append=True), \n             ModelCheckpoint('cnn_best.h5', save_best_only=True)]\n\nmodel.compile(loss='categorical_crossentropy',\n              optimizer=Adam(1e-4, amsgrad=True), \n              metrics=['accuracy'])\nmodel.fit_generator(train_data, epochs=100, validation_data=valid_data, workers=4, use_multiprocessing=True,\n                    callbacks=callbacks)\n```\n\n# 测试模型\n\n当我们训练完成以后，可以识别一个验证码试试看：\n\n```py\nX, y = data[0]\ny_pred = model.predict(X)\nplt.title('real: %s\\npred:%s'%(decode(y), decode(y_pred)))\nplt.imshow(X[0], cmap='gray')\nplt.axis('off')\n```\n\n![](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fypwhs_captcha_break_readme_b52cbd02e845.png)\n\n# 计算模型总体准确率\n\n模型在训练的时候只会显示每一个字符的准确率，为了统计模型的总体准确率，我们可以写下面的函数：\n\n```py\nfrom tqdm import tqdm\ndef evaluate(model, batch_num=100):\n    batch_acc = 0\n    with tqdm(CaptchaSequence(characters, batch_size=128, steps=100)) as pbar:\n        for X, y in pbar:\n            y_pred = model.predict(X)\n            y_pred = np.argmax(y_pred, axis=-1).T\n            y_true = np.argmax(y, axis=-1).T\n\n            batch_acc += (y_true == y_pred).all(axis=-1).mean()\n    return batch_acc \u002F batch_num\n\nevaluate(model)\n```\n\n这里用到了一个库叫做 tqdm，它是一个进度条的库（tqdm 是一个进度条库，用于实时反馈进度），为的是能够实时反馈进度。然后我们通过一些 numpy 计算去统计我们的准确率，这里计算规则是只要有一个错，那么就不算它对。经过计算，我们的模型的总体准确率在经过充分训练以后，可以达到 98.26% 的总体准确率。\n\n# 模型总结\n\n模型的大小是10.7MB，总体准确率是 98.26%，基本上可以确定破解了此类验证码。\n\n# 改进\n\n对于这种按顺序书写的文字，我们还有一种方法可以使用，那就是循环神经网络来识别序列。下面我们来了解一下如何使用循环神经网络来识别这类验证码。\n\n# CTC Loss\n\n这个 loss 是一个特别神奇的 loss（CTC Loss 是一种特殊的损失函数，可以在只知道序列的顺序，不知道具体位置的情况下，让模型收敛），它可以在只知道序列的顺序，不知道具体位置的情况下，让模型收敛。这里有一个非常好的文章介绍了 CTC Loss: [Sequence Modeling With CTC](https:\u002F\u002Fdistill.pub\u002F2017\u002Fctc\u002F)\n\n![](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fypwhs_captcha_break_readme_eb7c994618ed.png)\n\n在 Keras 里面已经内置了 CTC Loss ，我们实现下面的代码即可在模型里使用 CTC Loss。\n\n* `y_pred` 是模型的输出，是按顺序输出的37个字符的概率，因为我们这里用到了循环神经网络，所以需要一个空白字符的概念；\n* `labels` 是验证码，是四个数字，每个数字代表字符在字符集里的位置\n* `input_length` 表示 `y_pred` 的长度，我们这里是16\n* `label_length` 表示 `labels` 的长度，我们这里是4\n\n```py\nimport tensorflow.keras.backend as K\n\ndef ctc_lambda_func(args):\n    y_pred, labels, input_length, label_length = args\n    return K.ctc_batch_cost(labels, y_pred, input_length, label_length)\n```\n\n# 模型结构\n\n我们的模型结构是这样设计的，首先通过卷积神经网络去识别特征，然后按水平顺序输入到 GRU 进行序列建模，最后使用一个分类器对每个时刻输出的特征进行分类。\n\n```py\nfrom tensorflow.keras.models import *\nfrom tensorflow.keras.layers import *\n\ninput_tensor = Input((height, width, 3))\nx = input_tensor\nfor i, n_cnn in enumerate([2, 2, 2, 2, 2]):\n    for j in range(n_cnn):\n        x = Conv2D(32*2**min(i, 3), kernel_size=3, padding='same', kernel_initializer='he_uniform')(x)\n        x = BatchNormalization()(x)\n        x = Activation('relu')(x)\n    x = MaxPooling2D(2 if i \u003C 3 else (2, 1))(x)\n\nx = Permute((2, 1, 3))(x)\nx = TimeDistributed(Flatten())(x)\n\nrnn_size = 128\nx = Bidirectional(CuDNNGRU(rnn_size, return_sequences=True))(x)\nx = Bidirectional(CuDNNGRU(rnn_size, return_sequences=True))(x)\nx = Dense(n_class, activation='softmax')(x)\n\nbase_model = Model(inputs=input_tensor, outputs=x)\n```\n\n为了训练这个模型，我们还需要搭建一个 loss 计算网络，代码如下：\n\n```py\nlabels = Input(name='the_labels', shape=[n_len], dtype='float32')\ninput_length = Input(name='input_length', shape=[1], dtype='int64')\nlabel_length = Input(name='label_length', shape=[1], dtype='int64')\nloss_out = Lambda(ctc_lambda_func, output_shape=(1,), name='ctc')([x, labels, input_length, label_length])\n\nmodel = Model(inputs=[input_tensor, labels, input_length, label_length], outputs=loss_out)\n```\n\n真正训练出来的模型是 `base_model`，由于 Keras 的限制，我们没办法直接使用 `base_model` 搭建 CTCLoss，所以我们只能按照上面的方法，让模型直接输出 loss。\n\n# 模型可视化\n\n可视化的代码同上，这里只贴图。\n\n![](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fypwhs_captcha_break_readme_ffc57e0b48a6.png)\n\n可以看到模型比上一个模型复杂了许多，但实际上只是因为输入比较多，所以它显得很大。\n\n首先模型输入一个 `(height, width, 3)` 维度的图片，然后经过一系列的层降维到了 `(2, 16, 256)`，之后我们使用 Permute 把 width 轴调整到第一个维度以适配 RNN 的输入格式。调整以后的维度是 `(16, 2, 256)`，然后使用 `TimeDistributed(Flatten())` 把后两个维度压成一维，也就是 `(16, 512)`，之后经过 2 层双向的 GRU 对序列横向建模，最后经过 Dense 分类器输出水平方向上每个字符的概率分布。\n\n使用 CuDNNGRU 是因为它在 NVIDIA 显卡上可以加速非常多倍，如果你使用的是 CPU，改为 GRU 即可。\n\n使用 RNN 的原因是，如果你看到一句话是 `今天我*了一个非常好吃的苹果`，有一个字看不清，你很容易猜到这个字是“吃”，但是使用 CNN，你就很难有这么大的感受野，从苹果推测出前面的字是吃。\n\n# 数据生成器\n\n数据生成器和 CNN 的差不多，这里需要多几个矩阵，一个是 input_length，代表序列长度，一个是 label_length，代表验证码长度，还有一个 np.ones，没有意义，只是为了适配 Keras 训练需要的矩阵输入。\n\n```py\nfrom tensorflow.keras.utils import Sequence\n\nclass CaptchaSequence(Sequence):\n    def __init__(self, characters, batch_size, steps, n_len=4, width=128, height=64, \n                 input_length=16, label_length=4):\n        self.characters = characters\n        self.batch_size = batch_size\n        self.steps = steps\n        self.n_len = n_len\n        self.width = width\n        self.height = height\n        self.input_length = input_length\n        self.label_length = label_length\n        self.n_class = len(characters)\n        self.generator = ImageCaptcha(width=width, height=height)\n    \n    def __len__(self):\n        return self.steps\n\n    def __getitem__(self, idx):\n        X = np.zeros((self.batch_size, self.height, self.width, 3), dtype=np.float32)\n        y = np.zeros((self.batch_size, self.n_len), dtype=np.uint8)\n        input_length = np.ones(self.batch_size)*self.input_length\n        label_length = np.ones(self.batch_size)*self.label_length\n        for i in range(self.batch_size):\n            random_str = ''.join([random.choice(self.characters) for j in range(self.n_len)])\n            X[i] = np.array(self.generator.generate_image(random_str)) \u002F 255.0\n            y[i] = [self.characters.find(x) for x in random_str]\n        return [X, y, input_length, label_length], np.ones(self.batch_size)\n```\n\n# 评估模型\n\n```py\nfrom tqdm import tqdm\n\ndef evaluate(model, batch_size=128, steps=20):\n    batch_acc = 0\n    valid_data = CaptchaSequence(characters, batch_size, steps)\n    for [X_test, y_test, _, _], _ in valid_data:\n        y_pred = base_model.predict(X_test)\n        shape = y_pred.shape\n        out = K.get_value(K.ctc_decode(y_pred, input_length=np.ones(shape[0])*shape[1])[0][0])[:, :4]\n        if out.shape[1] == 4:\n            batch_acc += (y_test == out).all(axis=1).mean()\n    return batch_acc \u002F steps\n```\n\n我们会通过这个函数来评估我们的模型，和上面的评估标准一样，只有全部正确，我们才算预测正确，中间有个坑，就是模型最开始训练的时候，并不一定会输出四个字符，所以我们如果遇到所有的字符都不到四个的时候，就不计算了，相当于加0，遇到多于4个字符的时候，只取前四个。\n\n# 评估回调\n\n因为 Keras 没有针对这种输出计算准确率的选项，因此我们需要自定义一个回调函数，它会在每一代训练完成的时候计算模型的准确率。\n\n```py\nfrom tensorflow.keras.callbacks import Callback\n\nclass Evaluate(Callback):\n    def __init__(self):\n        self.accs = []\n    \n    def on_epoch_end(self, epoch, logs=None):\n        logs = logs or {}\n        acc = evaluate(base_model)\n        logs['val_acc'] = acc\n        self.accs.append(acc)\n        print(f'\\nacc: {acc*100:.4f}')\n```\n\n# 训练模型\n\n我们仍然沿用之前的训练策略，先训练 100 代，等 loss 不降低以后，降低学习率，再训练 100 代，代码如下：\n\n```py\nfrom tensorflow.keras.callbacks import EarlyStopping, CSVLogger, ModelCheckpoint\nfrom tensorflow.keras.optimizers import *\n\ntrain_data = CaptchaSequence(characters, batch_size=128, steps=1000)\nvalid_data = CaptchaSequence(characters, batch_size=128, steps=100)\ncallbacks = [EarlyStopping(patience=5), Evaluate(), \n             CSVLogger('ctc.csv'), ModelCheckpoint('ctc_best.h5', save_best_only=True)]\n\nmodel.compile(loss={'ctc': lambda y_true, y_pred: y_pred}, optimizer=Adam(1e-3, amsgrad=True))\nmodel.fit_generator(train_data, epochs=100, validation_data=valid_data, workers=4, use_multiprocessing=True,\n                    callbacks=callbacks)\n```\n\n```py\nmodel.load_weights('ctc_best.h5')\n\ncallbacks = [EarlyStopping(patience=5), Evaluate(), \n             CSVLogger('ctc.csv', append=True), ModelCheckpoint('ctc_best.h5', save_best_only=True)]\n\nmodel.compile(loss={'ctc': lambda y_true, y_pred: y_pred}, optimizer=Adam(1e-4, amsgrad=True))\nmodel.fit_generator(train_data, epochs=100, validation_data=valid_data, workers=4, use_multiprocessing=True,\n                    callbacks=callbacks)\n```\n\n![](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fypwhs_captcha_break_readme_ee75c2159026.png)\n\n可以看到 loss 一开始下降很快，后面就很平了，但是我们把在对数尺度下绘制 loss 图的话，还是能看到 loss 一直在下降的。acc 上升得也很快，虽然前期训练的时候 acc 很抖动，但是后期学习率降下来以后就不会再跌下来了。\n\n最终模型的准确率达到了 99.21%，训练过程中的准确率最高达到了 99.49%。\n\n# 测试模型\n\n```py\ncharacters2 = characters + ' '\n[X_test, y_test, _, _], _  = data[0]\ny_pred = base_model.predict(X_test)\nout = K.get_value(K.ctc_decode(y_pred, input_length=np.ones(y_pred.shape[0])*y_pred.shape[1], )[0][0])[:, :4]\nout = ''.join([characters[x] for x in out[0]])\ny_true = ''.join([characters[x] for x in y_test[0]])\n\nplt.imshow(X_test[0])\nplt.title('pred:' + str(out) + '\\ntrue: ' + str(y_true))\n\nargmax = np.argmax(y_pred, axis=2)[0]\nlist(zip(argmax, ''.join([characters2[x] for x in argmax])))\n```\n\n这里随机出来的验证码很厉害，是`O0OP`，不过更厉害的是模型认出来了。\n\n![](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fypwhs_captcha_break_readme_3fd75a076a89.png)\n\n# 有趣的问题\n\n我又用之前的模型做了个测试，对于 `O0O0` 这样丧心病狂的验证码，模型偶尔也能正确识别，这让我非常惊讶，它是真的能识别 O 与 0 的差别呢，还是猜出来的呢？这很难说。\n\n```py\ngenerator = ImageCaptcha(width=width, height=height)\nrandom_str = 'O0O0'\nX = generator.generate_image(random_str)\nX = np.expand_dims(X, 0) \u002F 255.0\n\ny_pred = base_model.predict(X)\nout = K.get_value(K.ctc_decode(y_pred, input_length=np.ones(y_pred.shape[0])*y_pred.shape[1], )[0][0])[:, :4]\nout = ''.join([characters[x] for x in out[0]])\n\nplt.title('real: %s\\npred:%s'%(random_str, out))\nplt.imshow(X[0], cmap='gray')\n```\n\n![](https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fypwhs_captcha_break_readme_dafeaa08a9d2.png)\n\n# 总结\n\n模型的大小是12.8MB，准确率达到了惊人的 99.21%，即使连 0 和 O 都能精准区分，非常成功。\n\n# 扩展\n\n如果你比较喜欢 PyTorch，可以看 [ctc_pytorch.ipynb](ctc_pytorch.ipynb)，精度更高，达到了 99.57%。\n\n如果你想查看更多经验，可以看看我在百度云魅族深度学习应用大赛的代码和思路：[https:\u002F\u002Fgithub.com\u002Fypwhs\u002Fbaiduyun_deeplearning_competition](https:\u002F\u002Fgithub.com\u002Fypwhs\u002Fbaiduyun_deeplearning_competition)\n\n# 参考链接\n\n* [https:\u002F\u002Fkeras.io\u002Fgetting-started\u002Ffunctional-api-guide\u002F](https:\u002F\u002Fkeras.io\u002Fgetting-started\u002Ffunctional-api-guide\u002F)\n* [https:\u002F\u002Fwww.tensorflow.org\u002Fapi_docs\u002Fpython\u002Ftf\u002Fnn\u002Fctc_loss](https:\u002F\u002Fwww.tensorflow.org\u002Fapi_docs\u002Fpython\u002Ftf\u002Fnn\u002Fctc_loss)\n* [https:\u002F\u002Fgithub.com\u002Fkeras-team\u002Fkeras\u002Fblob\u002Fmaster\u002Fexamples\u002Fimage_ocr.py](https:\u002F\u002Fgithub.com\u002Fkeras-team\u002Fkeras\u002Fblob\u002Fmaster\u002Fexamples\u002Fimage_ocr.py)\n* [https:\u002F\u002Fcs231n.github.io\u002Fconvolutional-networks\u002F](https:\u002F\u002Fcs231n.github.io\u002Fconvolutional-networks\u002F)\n* [https:\u002F\u002Fdistill.pub\u002F2017\u002Fctc\u002F](https:\u002F\u002Fdistill.pub\u002F2017\u002Fctc\u002F)","# captcha_break 快速上手指南\n\n## 环境准备\n- **系统要求**：Python 3.x，NVIDIA显卡（推荐）\n- **前置依赖**：\n  ```bash\n  tensorflow-gpu==1.13.1\n  numpy==1.16.4\n  tqdm==4.28.1\n  matplotlib==2.2.2\n  pandas==0.23.0\n  pydot==1.4.1\n  graphviz==2.38.0-12ubuntu2.1\n  ```\n\n## 安装步骤\n1. 创建虚拟环境（可选）：\n   ```bash\n   python -m venv captcha_env\n   source captcha_env\u002Fbin\u002Factivate  # Linux\u002FMac\n   captcha_env\\Scripts\\activate      # Windows\n   ```\n2. 安装依赖包：\n   ```bash\n   pip install -r requirements.txt\n   # 或使用国内镜像源\n   pip install -r requirements.txt --index-url https:\u002F\u002Fpypi.tuna.tsinghua.edu.cn\u002Fsimple\n   ```\n\n## 基本使用\n### 1. 生成验证码图片\n```python\nfrom captcha.image import ImageCaptcha\nimport matplotlib.pyplot as plt\n\n%matplotlib inline\ncharacters = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\ngenerator = ImageCaptcha(width=170, height=80)\nrandom_str = ''.join(random.choice(characters) for _ in range(4))\nimg = generator.generate_image(random_str)\nplt.imshow(img)\nplt.title(random_str)\n```\n\n### 2. 训练模型（需GPU）\n```python\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.layers import Input, Conv2D, MaxPooling2D, BatchNormalization, Activation, Flatten, Dense\nfrom tensorflow.keras.optimizers import Adam\n\n# 构建模型\ninput_tensor = Input((80, 170, 3))\nx = input_tensor\nfor i, n_cnn in enumerate([2,2,2,2,2]):\n    for j in range(n_cnn):\n        x = Conv2D(32*2**min(i,3), kernel_size=3, padding='same')(x)\n        x = BatchNormalization()(x)\n        x = Activation('relu')(x)\n    x = MaxPooling2D(2)(x)\nx = Flatten()(x)\noutputs = [Dense(36, activation='softmax', name=f'c{i+1}') for i in range(4)]\nmodel = Model(inputs=input_tensor, outputs=outputs)\n\n# 编译训练\nmodel.compile(loss='categorical_crossentropy', optimizer=Adam(1e-3), metrics=['accuracy'])\nmodel.fit_generator(\n    CaptchaSequence(characters, 128, 1000),\n    epochs=100,\n    validation_data=CaptchaSequence(characters, 128, 100),\n    workers=4,\n    use_multiprocessing=True\n)\n```\n\n### 3. 验证模型\n```python\ndef decode(y):\n    return ''.join([characters[np.argmax(y[i], axis=1)] for i in range(4)])\n\nX, y = CaptchaSequence(characters, 1, 1)[0]\npred = model.predict(X)\nprint(f\"真实: {decode(y)}, 预测: {decode(pred)}\")\n```","电商平台的自动注册系统需要处理用户验证码输入，但人工识别效率低且易出错。  \n\n### 没有 captcha_break 时  \n- 手动输入验证码时，用户易因视觉误差导致注册失败，流失率高达15%  \n- 每日需安排专人处理数万次验证码，人力成本超2000元\u002F天  \n- 复杂验证码（如带干扰线\u002F动态图形）识别准确率不足60%  \n- 系统响应延迟达3-5分钟，影响用户体验  \n- 验证码生成与识别流程耦合，无法灵活调整规则  \n\n### 使用 captcha_break 后  \n- 自动识别准确率提升至98%，注册失败率降至2%以下  \n- 通过GPU加速处理，单批次训练时间从2小时压缩至15分钟  \n- 支持动态图形、干扰线等复杂验证码识别，兼容率达100%  \n- 系统响应时间缩短至1秒内，用户等待体验显著改善  \n- 实现验证码生成与识别解耦，可快速适配新规则  \n\n核心价值在于通过深度学习技术，将验证码处理从人工操作转变为自动化流程，显著提升系统效率与用户体验。","https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002Fypwhs_captcha_break_eb7c9946.png","ypwhs","杨培文 (Yang Peiwen)","https:\u002F\u002Foss.gittoolsai.com\u002Favatars\u002Fypwhs_21ee0f2f.jpg",null,"DLCV.AI","Guangdong","https:\u002F\u002Fypw.io","https:\u002F\u002Fgithub.com\u002Fypwhs",[23,27],{"name":24,"color":25,"percentage":26},"Jupyter Notebook","#DA5B0B",99.6,{"name":28,"color":29,"percentage":30},"Python","#3572A5",0.4,2808,671,"2026-04-04T16:09:43","MIT",3,"Linux, macOS","需要 NVIDIA GPU，显存 8GB+，CUDA 11.7+","16GB+",{"notes":40,"python":41,"dependencies":42},"建议使用 conda 管理环境，首次运行需下载约 5GB 模型文件","3.8+",[43,44,45,46,47,48,49,50],"tensorflow-gpu==1.13.1","numpy==1.16.4","tqdm==4.28.1","matplotlib==2.2.2","pandas==0.23.0","pydot==1.4.1","graphviz==2.38.0","captcha==0.3",[52,53],"图像","开发框架",[55,56,57,58,59,60,61,62,63,64],"captcha-breaking","keras-tutorials","deep-learning","keras","ocr","captcha","jupyter-notebook","pytorch-tutorial","crnn","ctc-loss","ready","2026-03-27T02:49:30.150509","2026-04-06T07:14:05.619766",[69,74,79,84,89,94],{"id":70,"question_zh":71,"answer_zh":72,"source_url":73},4621,"如何让captcha_break支持更多风格的验证码？","需要大量标注数据而非自行生成，建议通过打码平台获取标注好的数据。若需自研，可尝试用深度学习模型归纳模式并人工标注，但需先准备十万张标注样本。","https:\u002F\u002Fgithub.com\u002Fypwhs\u002Fcaptcha_break\u002Fissues\u002F6",{"id":75,"question_zh":76,"answer_zh":77,"source_url":78},4622,"训练时出现GPU错误如何解决？","升级TensorFlow-GPU版本并确保CUDA环境兼容。若仍无法解决，可改用CPU运行，或调整代码将CaptchaSequence类放入单独的.py文件并导入。","https:\u002F\u002Fgithub.com\u002Fypwhs\u002Fcaptcha_break\u002Fissues\u002F8",{"id":80,"question_zh":81,"answer_zh":82,"source_url":83},4623,"如何获取每个字符的识别概率？","通过np.argmax获取概率最大值对应的字符索引，示例代码：argmax = np.argmax(y_pred, axis=2)[0]，然后将索引转换为对应字符。","https:\u002F\u002Fgithub.com\u002Fypwhs\u002Fcaptcha_break\u002Fissues\u002F50",{"id":85,"question_zh":86,"answer_zh":87,"source_url":88},4624,"为什么需要旋转输入图片？","新版本已不再需要旋转，改为使用Permute调整轴顺序后Flatten，使RNN在宽度维度上建模。代码示例：Permute((2, 1, 3))(x) + TimeDistributed(Flatten())(x)。","https:\u002F\u002Fgithub.com\u002Fypwhs\u002Fcaptcha_break\u002Fissues\u002F20",{"id":90,"question_zh":91,"answer_zh":92,"source_url":93},4625,"训练时报错'CaptchaSequence'未定义如何解决？","将CaptchaSequence类移至单独的.py文件并导入使用，避免多进程pickle序列化问题。改用Python脚本运行可避免该错误。","https:\u002F\u002Fgithub.com\u002Fypwhs\u002Fcaptcha_break\u002Fissues\u002F26",{"id":95,"question_zh":96,"answer_zh":97,"source_url":98},4626,"Python 3.7环境下如何安装依赖？","需使用TensorFlow 1.x兼容模式，替换ConfigProto为tf.compat.v1.ConfigProto，并参考迁移指南：https:\u002F\u002Fwww.tensorflow.org\u002Fguide\u002Fmigrate。","https:\u002F\u002Fgithub.com\u002Fypwhs\u002Fcaptcha_break\u002Fissues\u002F44",[100,105],{"id":101,"version":102,"summary_zh":103,"released_at":104},104101,"2.0","由于模型定义在 Jupyter Notebook 中，PyTorch 无法直接加载模型代码，所以加载模型之前需要先定义模型，如：\r\n\r\n```py\r\nimport torch\r\nimport torch.nn as nn\r\n\r\n\r\nclass Model(nn.Module):\r\n    def __init__(self, n_classes, input_shape=(3, 64, 128)):\r\n        super(Model, self).__init__()\r\n        self.input_shape = input_shape\r\n        channels = [32, 64, 128, 256, 256]\r\n        layers = [2, 2, 2, 2, 2]\r\n        kernels = [3, 3, 3, 3, 3]\r\n        pools = [2, 2, 2, 2, (2, 1)]\r\n        modules = OrderedDict()\r\n        \r\n        def cba(name, in_channels, out_channels, kernel_size):\r\n            modules[f'conv{name}'] = nn.Conv2d(in_channels, out_channels, kernel_size,\r\n                                               padding=(1, 1) if kernel_size == 3 else 0)\r\n            modules[f'bn{name}'] = nn.BatchNorm2d(out_channels)\r\n            modules[f'relu{name}'] = nn.ReLU(inplace=True)\r\n        \r\n        last_channel = 3\r\n        for block, (n_channel, n_layer, n_kernel, k_pool) in enumerate(zip(channels, layers, kernels, pools)):\r\n            for layer in range(1, n_layer + 1):\r\n                cba(f'{block+1}{layer}', last_channel, n_channel, n_kernel)\r\n                last_channel = n_channel\r\n            modules[f'pool{block + 1}'] = nn.MaxPool2d(k_pool)\r\n        modules[f'dropout'] = nn.Dropout(0.25, inplace=True)\r\n        \r\n        self.cnn = nn.Sequential(modules)\r\n        self.lstm = nn.LSTM(input_size=self.infer_features(), hidden_size=128, num_layers=2, bidirectional=True)\r\n        self.fc = nn.Linear(in_features=256, out_features=n_classes)\r\n    \r\n    def infer_features(self):\r\n        x = torch.zeros((1,)+self.input_shape)\r\n        x = self.cnn(x)\r\n        x = x.reshape(x.shape[0], -1, x.shape[-1])\r\n        return x.shape[1]\r\n\r\n    def forward(self, x):\r\n        x = self.cnn(x)\r\n        x = x.reshape(x.shape[0], -1, x.shape[-1])\r\n        x = x.permute(2, 0, 1)\r\n        x, _ = self.lstm(x)\r\n        x = self.fc(x)\r\n        return x\r\n\r\nmodel = torch.load('ctc.pth')\r\n```\r\n","2019-06-18T13:05:05",{"id":106,"version":107,"summary_zh":108,"released_at":109},104102,"1.0","CNN 模型总体准确率是 98.26%\r\nCTC 模型总体准确率是 99.21%","2019-06-16T14:24:07",[111,120,130,138,146,159],{"id":112,"name":113,"github_repo":114,"description_zh":115,"stars":116,"difficulty_score":35,"last_commit_at":117,"category_tags":118,"status":65},3808,"stable-diffusion-webui","AUTOMATIC1111\u002Fstable-diffusion-webui","stable-diffusion-webui 是一个基于 Gradio 构建的网页版操作界面，旨在让用户能够轻松地在本地运行和使用强大的 Stable Diffusion 图像生成模型。它解决了原始模型依赖命令行、操作门槛高且功能分散的痛点，将复杂的 AI 绘图流程整合进一个直观易用的图形化平台。\n\n无论是希望快速上手的普通创作者、需要精细控制画面细节的设计师，还是想要深入探索模型潜力的开发者与研究人员，都能从中获益。其核心亮点在于极高的功能丰富度：不仅支持文生图、图生图、局部重绘（Inpainting）和外绘（Outpainting）等基础模式，还独创了注意力机制调整、提示词矩阵、负向提示词以及“高清修复”等高级功能。此外，它内置了 GFPGAN 和 CodeFormer 等人脸修复工具，支持多种神经网络放大算法，并允许用户通过插件系统无限扩展能力。即使是显存有限的设备，stable-diffusion-webui 也提供了相应的优化选项，让高质量的 AI 艺术创作变得触手可及。",162132,"2026-04-05T11:01:52",[53,52,119],"Agent",{"id":121,"name":122,"github_repo":123,"description_zh":124,"stars":125,"difficulty_score":126,"last_commit_at":127,"category_tags":128,"status":65},1381,"everything-claude-code","affaan-m\u002Feverything-claude-code","everything-claude-code 是一套专为 AI 编程助手（如 Claude Code、Codex、Cursor 等）打造的高性能优化系统。它不仅仅是一组配置文件，而是一个经过长期实战打磨的完整框架，旨在解决 AI 代理在实际开发中面临的效率低下、记忆丢失、安全隐患及缺乏持续学习能力等核心痛点。\n\n通过引入技能模块化、直觉增强、记忆持久化机制以及内置的安全扫描功能，everything-claude-code 能显著提升 AI 在复杂任务中的表现，帮助开发者构建更稳定、更智能的生产级 AI 代理。其独特的“研究优先”开发理念和针对 Token 消耗的优化策略，使得模型响应更快、成本更低，同时有效防御潜在的攻击向量。\n\n这套工具特别适合软件开发者、AI 研究人员以及希望深度定制 AI 工作流的技术团队使用。无论您是在构建大型代码库，还是需要 AI 协助进行安全审计与自动化测试，everything-claude-code 都能提供强大的底层支持。作为一个曾荣获 Anthropic 黑客大奖的开源项目，它融合了多语言支持与丰富的实战钩子（hooks），让 AI 真正成长为懂上",138956,2,"2026-04-05T11:33:21",[53,119,129],"语言模型",{"id":131,"name":132,"github_repo":133,"description_zh":134,"stars":135,"difficulty_score":126,"last_commit_at":136,"category_tags":137,"status":65},2271,"ComfyUI","Comfy-Org\u002FComfyUI","ComfyUI 是一款功能强大且高度模块化的视觉 AI 引擎，专为设计和执行复杂的 Stable Diffusion 图像生成流程而打造。它摒弃了传统的代码编写模式，采用直观的节点式流程图界面，让用户通过连接不同的功能模块即可构建个性化的生成管线。\n\n这一设计巧妙解决了高级 AI 绘图工作流配置复杂、灵活性不足的痛点。用户无需具备编程背景，也能自由组合模型、调整参数并实时预览效果，轻松实现从基础文生图到多步骤高清修复等各类复杂任务。ComfyUI 拥有极佳的兼容性，不仅支持 Windows、macOS 和 Linux 全平台，还广泛适配 NVIDIA、AMD、Intel 及苹果 Silicon 等多种硬件架构，并率先支持 SDXL、Flux、SD3 等前沿模型。\n\n无论是希望深入探索算法潜力的研究人员和开发者，还是追求极致创作自由度的设计师与资深 AI 绘画爱好者，ComfyUI 都能提供强大的支持。其独特的模块化架构允许社区不断扩展新功能，使其成为当前最灵活、生态最丰富的开源扩散模型工具之一，帮助用户将创意高效转化为现实。",107662,"2026-04-03T11:11:01",[53,52,119],{"id":139,"name":140,"github_repo":141,"description_zh":142,"stars":143,"difficulty_score":126,"last_commit_at":144,"category_tags":145,"status":65},3704,"NextChat","ChatGPTNextWeb\u002FNextChat","NextChat 是一款轻量且极速的 AI 助手，旨在为用户提供流畅、跨平台的大模型交互体验。它完美解决了用户在多设备间切换时难以保持对话连续性，以及面对众多 AI 模型不知如何统一管理的痛点。无论是日常办公、学习辅助还是创意激发，NextChat 都能让用户随时随地通过网页、iOS、Android、Windows、MacOS 或 Linux 端无缝接入智能服务。\n\n这款工具非常适合普通用户、学生、职场人士以及需要私有化部署的企业团队使用。对于开发者而言，它也提供了便捷的自托管方案，支持一键部署到 Vercel 或 Zeabur 等平台。\n\nNextChat 的核心亮点在于其广泛的模型兼容性，原生支持 Claude、DeepSeek、GPT-4 及 Gemini Pro 等主流大模型，让用户在一个界面即可自由切换不同 AI 能力。此外，它还率先支持 MCP（Model Context Protocol）协议，增强了上下文处理能力。针对企业用户，NextChat 提供专业版解决方案，具备品牌定制、细粒度权限控制、内部知识库整合及安全审计等功能，满足公司对数据隐私和个性化管理的高标准要求。",87618,"2026-04-05T07:20:52",[53,129],{"id":147,"name":148,"github_repo":149,"description_zh":150,"stars":151,"difficulty_score":126,"last_commit_at":152,"category_tags":153,"status":65},2268,"ML-For-Beginners","microsoft\u002FML-For-Beginners","ML-For-Beginners 是由微软推出的一套系统化机器学习入门课程，旨在帮助零基础用户轻松掌握经典机器学习知识。这套课程将学习路径规划为 12 周，包含 26 节精炼课程和 52 道配套测验，内容涵盖从基础概念到实际应用的完整流程，有效解决了初学者面对庞大知识体系时无从下手、缺乏结构化指导的痛点。\n\n无论是希望转型的开发者、需要补充算法背景的研究人员，还是对人工智能充满好奇的普通爱好者，都能从中受益。课程不仅提供了清晰的理论讲解，还强调动手实践，让用户在循序渐进中建立扎实的技能基础。其独特的亮点在于强大的多语言支持，通过自动化机制提供了包括简体中文在内的 50 多种语言版本，极大地降低了全球不同背景用户的学习门槛。此外，项目采用开源协作模式，社区活跃且内容持续更新，确保学习者能获取前沿且准确的技术资讯。如果你正寻找一条清晰、友好且专业的机器学习入门之路，ML-For-Beginners 将是理想的起点。",84991,"2026-04-05T10:45:23",[52,154,155,156,119,157,129,53,158],"数据工具","视频","插件","其他","音频",{"id":160,"name":161,"github_repo":162,"description_zh":163,"stars":164,"difficulty_score":35,"last_commit_at":165,"category_tags":166,"status":65},3128,"ragflow","infiniflow\u002Fragflow","RAGFlow 是一款领先的开源检索增强生成（RAG）引擎，旨在为大语言模型构建更精准、可靠的上下文层。它巧妙地将前沿的 RAG 技术与智能体（Agent）能力相结合，不仅支持从各类文档中高效提取知识，还能让模型基于这些知识进行逻辑推理和任务执行。\n\n在大模型应用中，幻觉问题和知识滞后是常见痛点。RAGFlow 通过深度解析复杂文档结构（如表格、图表及混合排版），显著提升了信息检索的准确度，从而有效减少模型“胡编乱造”的现象，确保回答既有据可依又具备时效性。其内置的智能体机制更进一步，使系统不仅能回答问题，还能自主规划步骤解决复杂问题。\n\n这款工具特别适合开发者、企业技术团队以及 AI 研究人员使用。无论是希望快速搭建私有知识库问答系统，还是致力于探索大模型在垂直领域落地的创新者，都能从中受益。RAGFlow 提供了可视化的工作流编排界面和灵活的 API 接口，既降低了非算法背景用户的上手门槛，也满足了专业开发者对系统深度定制的需求。作为基于 Apache 2.0 协议开源的项目，它正成为连接通用大模型与行业专有知识之间的重要桥梁。",77062,"2026-04-04T04:44:48",[119,52,53,129,157]]