深度学习之卷积神经网络识别图片验证码实战案例(十)

news2024/11/27 3:44:43

案例背景:程序自动化的爬虫而无需人工介入是我们的最终目标。自动化爬虫避免不了自动登录的问题,在爬取XX数据的过程中,遇到登录图形验证码的识别的问题,那我们该如何攻破这种验证码呢?

字符验证码图片如下:
在这里插入图片描述
在这个案例中,我会通过案例一步一步攻破这种验证码。

现有能力调研

在这里插入图片描述

  1. pytesseract 是一款开源的免费的OCR识别工具,它能识别一些很基础、很简单的验证码,但是面对稍微复杂一点的验证码识别准确率就很低了。
  2. 超级鹰等云打码平台,能够识别复杂的验证码,识别准确率较好,但是其是收费的,费用标准大约几百每年。在平台上注册后会送一些免费的使用次数,大家感兴趣的可以尝试一下。

靠人不如靠己!不就是个简单的验证码识别吗?弄他!

验证码分析

我们先分析一下,我们这个验证码相较于市面上常见的验证码,有何区别?

在这里插入图片描述
这个验证码主要有2个问题影响识别:

  1. 干扰元素较多
  2. 字符倾斜,严重的字符展示不全。

如何让机器像人一样准确地识别出图片上的验证码字符呢?下面我们一起用机器学习搞定这个实战案例!开始之前,我们再来回顾一下六步法实战!

在这里插入图片描述

1、数据收集

首先,我们要搞定训练数据收集的问题,也就是收集一些验证码图片的数据。数据从何而来呢?在六步法实战中,我们介绍了获取数据的几种常用手段。显然,这个数据集需要我们自己去手动搞定他了!怎么搞?答案就是爬虫!

爬虫分析实现

  1. XX平台获取验证码的接口是动态的,也就是同样的请求会随机返回一张二维码。接口的URL是:http://xxxxx
  2. 获取的图片的数据后,我们将数据保存下来。
  3. 重复1、2步骤…

20行代码实现爬取图片数据

import requests
import time

headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
                         "Chrome/88.0.4324.182 Safari/537.36 Edg/88.0.705.81"}

url = "https://xxxxx"


def get_captcha():
    """
    获取验证码并存储
    :return: 
    """
    for i in range(0, 10000):
        time.sleep(2)
        print("sleeping...")
        resp = requests.get(url, headers=headers)
        with open(f"../picture/{i}.png", 'wb') as file:
            file.write(resp.content)


def main():
    get_captcha()


if __name__ == '__main__':
    main()


这里我们通过 resp = requests.get(url, headers=headers) 请求验证码图片并返回数据,然后通过 file.write(resp.content) 保存图片数据。

这里爬了很多个小时,终于把数据准备好了!(不敢爬的太快!爬虫爬得快,监狱进的早🌝)爬完之后的数据长这样子。

在这里插入图片描述

2、数据预处理

数据准备好之后,重头戏才刚刚开始。

在数据处理之前,我们先明确一个问题,验证码识别应该属于什么类型的机器学习项目?其实这是一个分类的项目,并且属于监督学习。以机器的视角来看,验证码识别,其实就是对验证码图片上的字符进行分类成A、B、C、7、8、9等等,所以这是监督学习中的分类问题,那为什么是监督学习呢?请你思考一下。

在数据的预处理阶段,我们要做哪些工作?再回顾一下数据预处理的经典5步:
在这里插入图片描述

数据可视化阶段在这个案例中可以省略了!

数据清洗对于验证码图片数据而言,叫图片清洗分类更为贴切,其中包括图片的灰度、降噪、裁剪、分类等工作。

2.1、让图片更干净些

我们先看一下效果,如下是经过了验证码背景图清洗前和清洗后的对比:

在这里插入图片描述

这里主要使用OpenCV库对图片进行灰度、降噪、二值化,这些都是图片预处理的常用手段。这里我们简单的介绍一下。

OpenCV是一个开源的计算机视觉库。它提供了一系列图像处理和计算机视觉算法,包括图像滤波、目标检测、人脸识别等。

灰度图像

在一张彩色图片中,每个像素点由三个RGB颜色通道(红、绿、蓝)组成,每个通道的颜色值可以是0到255之间的任何值。灰度图像是一种单通道图像,每个像素只有一个灰度值。灰度图像可以看作是将彩色图像的三个颜色通道(红、绿、蓝)合并为一个通道得到的结果,也可以通过将三个通道的值加权平均得到。

在机器学习领域,通常将彩色图像转换为灰度图像进行处理,因为与RGB图像相比,灰度图像具有更简单的结构和更低的计算复杂度。在OpenCV中使用 cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) 方法将RGB三通道图像转换为灰度图像。

降噪

降噪就是用于减少图像中的噪声。图像噪声是由于图像获取和传输过程中的信号干扰、电磁辐射等原因导致的随机扰动。这些噪声会影响图像质量和后续处理结果,因此需要使用降噪技术来减少噪声的影响。在OpenCV中,可以使用`cv2.GaussianBlur()函数实现高斯滤波,从而提高图像的质量和后续处理结果。

二值化

图片二值化是一种重要的图像预处理技术,可以用于去除噪声、提高对比度、简化图像信息、分离目标、降低数据量等目的。在实际应用中,需要根据具体情况选择合适的二值化方法和参数,以达到最佳的效果。在OpenCV中,可以使用 cv2.threshold()函数对图像进行二值化操作。

以下是降噪、灰度和二值化的完整代码,主要流程为

  1. OpenCV载入待处理图片:img = cv2.imread(image_path, 1)
  2. 将RGB三通道图像转为灰度图像 gray_img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
  3. 使用高斯滤波降噪 cv2.GaussianBlur(gray_img, (5, 5), 0)
  4. 二值化图片 cv2.threshold(blur, 134, 255, 0, gray_img)
  5. 返回处理后的图片

完整代码如下:

def cv2_noise_remove(image_path, k):
    """
    8邻域降噪
    Args:
        image_path: 图片文件命名
        k: 判断阈值

    Returns:
    """

    def calculate_noise_count(img_obj, w, h):
        """
        计算邻域非白色的个数
        Args:
            img_obj: img obj
            w: width
            h: height
        Returns:
            count (int)
        """
        count = 0
        width, height = img_obj.shape
        for _w_ in [w - 1, w, w + 1]:
            for _h_ in [h - 1, h, h + 1]:
                if _w_ > width - 1:
                    continue
                if _h_ > height - 1:
                    continue
                if _w_ == w and _h_ == h:
                    continue
                if img_obj[_w_, _h_] < 230:  # 二值化的图片设置为255
                    count += 1
        return count

    img = cv2.imread(image_path, 1)
    # 灰度
    gray_img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
    w, h = gray_img.shape
    for _w in range(w):
        for _h in range(h):
            if _w == 0 or _h == 0:
                gray_img[_w, _h] = 255
                continue
            # 计算邻域pixel值小于255的个数
            pixel = gray_img[_w, _h]
            if pixel == 255:
                continue

            if calculate_noise_count(gray_img, _w, _h) < k:
                gray_img[_w, _h] = 255

    # gray_img = cv2.adaptiveThreshold(gray_img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2)

    # 先使用一个 5x5 的高斯核除去噪音,然后再使用 Otsu 二值化
    blur = cv2.GaussianBlur(gray_img, (5, 5), 0)
    # ret3, th3 = cv2.threshold(blur, 1, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
    # ret3, th3 = cv2.threshold(blur, 144, 255, 0, gray_img)
    ret3, th3 = cv2.threshold(blur, 134, 255, 0, gray_img)  # 更优秀
    # # 二值化处理
    # ret3, th3 = cv2.threshold(th3, 134, 255, 0, th3)  # 二值化函数

    return th3

2.2、裁剪图片

图片清理干净后,我们需要对图片进行裁剪。裁剪的目的是将图片按照字符进行切分,以便于后续的训练及识别。
在这里插入图片描述

切割的方法比较简单,这里我们将验证码图片等比例切分为4份。并保存切割后的图片。主要逻辑为:

  1. 等比例裁剪图片 image.copy()[box[1]:box[3], box[0]:box[2]] # 裁剪坐标为[y0:y1, x0:x1]
  2. 使用OpenCV保存图片 cv2.imwrite(temp_path, temp_image)
def cv2_split_image_and_save(img_path):
    """
    根据图片路径cv2处理图片并拆分
    :param img_path: 图片download路径
    :return: 拆分后图片路径
    """
    # '/Users/rongtao7/IdeaProjects/yaoCaptchaDiscern/temp/97090039-4859-4bb6-a47e-003654b36881.png'
    image = cv2_noise_remove(img_path, 4)
    save_path = []
    for i in range(ConfigUtil.IMG_CHAR_COUNT):
        box = cv2_img_crop_box(i)
        temp_image = image.copy()[box[1]:box[3], box[0]:box[2]]  # 裁剪坐标为[y0:y1, x0:x1]
        file_name = f"{img_path.split('/temp/')[1].split('.')[0]}{ConfigUtil.TEMP_IMG_NAME_SEPARATOR}{i}.png"
        # print(file_name)
        temp_path = f"{sys.path[0]}/temp/{file_name}.png"
        cv2.imwrite(temp_path, temp_image)
        save_path.append(temp_path)
    return save_path

2.3、图片分类

裁剪好的图片,我们需要将图片分类,相当于给图片打标签,告诉机器这张图片应该属于什么分类,也就是它是什么。那我们一共有哪些分类呢?

由数字和英文字母组成,一共32个分类类别,其中排除掉了 1I 0O的易错选项,因为他们容易混淆,人眼都容易弄错,用户的体验也不好。

在这里插入图片描述

那我们现在要做的工作就是将所有切割后的图片分类到这32个文件夹中!好家伙!这可是一个庞大的工程啊,看一下我是怎么做的?

。。。。。。省略人工标注过程

前期我做了大量的人工标注工作,简直怀疑人生🤣

那有没有什么工具能帮帮我呢!这让我又想起了 pytesseract 这个开源的OCR识别库,前面我们提到过,针对整个验证码图片它的识别率是极低的,因为它只能处理简单的字符图片识别工作!但是我们的图片经过预处理和切割后,相对也好识别一些了,所以我就想到了让 pytesseract 帮我识别一部分,它识别不了的或者识别错误的,我们再手动标注一部分数据,这样我们图片手动标注的工作也是提效了约 50%

在这里插入图片描述
那有的同学就想到了!是不是图片切割好以后,用 pytesseract 识别字符,就可以完成验证码识别工作了?

当然可以!这个我也尝试过了,只不过识别的准确率较低,每个字符图片的准确率大概在 40%左右,但是4张图片结果加到一起后准确率相对就比较低了,一个免费开源的库你还要啥自行车🚴🏻啊!

使用 pytesseract辅助分类的代码如下,主要有2个步骤:(1)通过 ocr方法识别图片(2)通过 collect将图片移动到指定文件夹下

import os
import cv2
from PIL import Image, ImageFilter
import shutil

import pytesseract

"""
OCR图片分类
"""

split_list = os.listdir("./splits")
list_new = os.listdir("./captchas_new")


def ocr(img_path):
    """
    pytesseract ocr识别
    :param img_path: 图片路径
    :return: 识别结果
    """
    image = cv2.imread(img_path, 1)
    if image is None:
        return ""
    test_message = Image.fromarray(image)
    if test_message is None:
        return ""
    target = pytesseract.image_to_string(test_message, lang='eng',
                                         config='--psm 10 --oem 3 -c '
                                                'tessedit_char_whitelist=0123456789QWERTYUIOPLKJHGFDSAZXCVBNM')
    target = target.replace('\n', '').replace('\r', '')
    print(f"图片路径:{img_path},ocr识别结果为:{target}")
    return target


def collect(target, source):
    """
    收集图片
    :param target: 目标路径
    :param source: 源路径
    :return: 
    """
    target = "./captchas_new/" + target + "/"
    if str(target) in list_new:
        shutil.move(source, target)
        print(f"移动文件成功,源文件为:[{source}]")
    else:
        print(f"移动文件失败,{source}文件识别结果为[{target}]")


for path in split_list:
    print(path)
    new_path = "./splits/" + path
    source = ocr(new_path)
    collect(source, new_path)

至此,我们的数据清洗工作就完成了!

2.4、特征工程

在特征工程阶段,我们要对图片的数据特征进行变换。特征变换的手段对于数值特征和分类特征是不同的。

我们这里属于分类特征,那对标签我们使用独热编码进行变换。而图片数据,我们通过特征缩放变换来压缩特征的空间。

在这里插入图片描述

在这个环节,我们要将图片转换成机器可以识别的数据,然后就可以把数据集喂给模型了。这里我们将图片转换成 NumPy 数组也叫张量数组,对张量数组进行归一化缩放;将标签转换成One-Hot编码

什么是张量数组

在深度学习中,模型的输入通常都是张量数组。通过使用张量数组,可以对大量的数据进行高效的处理和计算。

张量数组其实就是一个多维数组。他可以存储和处理大量的数据。例如,图像可以表示为三维数组,其中第一个维度表示图像的高度,第二个维度表示图像的宽度,第三个维度表示图像的颜色通道。文本数据可以表示为二维数组,其中第一个维度表示文本的序列长度,第二个维度表示每个单词的向量表示。

什么是One-Hot编码

One-hot编码是一种常见的数据编码方式,用于将离散的、分类的数据转换为机器学习算法能够使用的数值型数据。在One-hot编码中,每个离散的特征值(也称为类别、标签或因子)被编码为一个长度为特征数量的二进制向量,其中只有一个元素为1,其他元素均为0。这个1所在的位置就代表了该特征值所属的类别。

首先,我们通过OpenCV来读入图片的数据,并使用NumPy将图片转换成张量数组。

import numpy as np
import pandas as pd
import os
import cv2


list = ["2","3","4","5","6","7","8","9","A","B","C","D","E","F","G","H","J","K","L","M","N","P","Q","R","S","T","U","V","W","X","Y","Z"]

X = []
y_label = []
imgsize = [105, 96]

def training_data(label, data_dir):
    for img in os.listdir(data_dir):
        path = os.path.join(data_dir, img)  # 目录+文件名
        img = cv2.imread(path,cv2.IMREAD_COLOR) #读入图片
        img = cv2.resize(img,(imgsize[0],imgsize[1])) #设定图片像素维度
        X.append(np.array(img)) #X特征集
        y_label.append(str(label)) #y标签

for label in list:
    training_data(label, f'./captchas_new/{label}')

这段代码很简单,我们通过 for 循环调用 training_data() 方法,在这个方法中,通过 cv2.imread 读取os路径的图片数据,然后通过 cv2.resize(img,(imgsize[0],imgsize[1]))统一图片的像素维度。收集特征和标签分别到 X = []y_label = []集合中。

然后通过 NumPy将图片转换为张量数组

X = np.array(X) # 将X从列表转换为张量数组

现在我们要对张量数组再进行特征变换,由于我们的特征都是0~255之间的数值,所以这里选择特征缩放,即归一化缩放。

X = X/255 # 将X张量归一化

然后我们对标签进行编码及特征变换的处理。这里要使用 sklearn 库的LabelEncoder编码工具,然后通过 keras 库的 to_categorical 进行One-hot编码。

from sklearn.preprocessing import LabelEncoder # 导入标签编码工具
from keras.utils import to_categorical # 导入One-hot编码工具

label_encoder = LabelEncoder()
y = label_encoder.fit_transform(y_label) # 标签编码
y = to_categorical(y, 32) # 将标签转换为One-hot编码

好了,现在我们的特征集X和标签集 y 就构建完毕了,在特征工程阶段,我们对特征集X进行了归一化缩放,对标签集y进行了One-hot编码,做这些工作的目的就是为了能够把数据集喂给模型。

2.4、构建特征集和标签集

这里我们要借助 sklearn 库的 model_selection 模块的 train_test_split 拆分工具来拆分数据集。我们将特征集拆分为训练集 X_train 和 测试集 X_test,将标签集拆分为训练集 y_train 和 测试集 y_test。并且训练集和测试集的比例是8:2

from sklearn.model_selection import train_test_split # 导入拆分工具

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,random_state=1)

3、选择算法

图像的处理,使用深度学习的神经网络算法肯定是更胜一筹,它擅长对复杂特征的提取。

神经网络算法又分为CNN、RNN、DNN等,而卷积神经网络对图像等数据进行特征提取和分类则是比较有优势,所以这里我们选择卷积神经网络CNN算法。

举例:车牌识别

在这里插入图片描述

卷积神经网络?

卷积神经网络(CNN)是一种常用的神经网络模型,特别适用于图像、视频和语音等高维度数据的处理和分析。其主要原理是通过卷积操作和池化操作来提取数据的特征,并通过全连接层将这些特征映射到输出层进行分类或回归。

卷积神经网络结构:卷积神经网络由输入层、一个或多个卷积层和输出层的全连接层组成。

在这里插入图片描述

  1. 输入层:接收输入数据,并将其传递到下一层。什么输入数据?就是特征缩放后的数据。
  2. 卷积层:主要负责提取图片的特征。其中的卷积核(上图中红框部分)也叫滤波器,能够自动进行图像特征的提取。
  3. 最大池化层:就是将特征映射划分为若干个矩形区域,挑选每个区域中的最大值,也就是最明显的特征作为采样的结果。可以避免过拟合。
  4. 多个卷积层和池化层能够实现对图像特征的逐层提取
  5. 展平层,主要负责将网络展平。展平之后通常会接一个普通的全连接层。而最右边的输出层也是全连接层,用 Softmax 进行激活分类输出层,Softmax 函数的主要作用是将神经网络输出的实数值转化为概率分布。

建立CNN算法模型

借助 keras库的layers和models工具我们建立CNN模型

from keras import layers # 导入所有层 
from keras import models # 导入所有模型 
import joblib

# 贯序模型 ,序贯模型也是最简单的模型,就是像盖楼一样,一层一层往上堆叠着搭新的层。
cnn = models.Sequential() 


# 激活函数接收神经元的输入信号,经过非线性变换后输出神经元的激活值。这个激活值通常被用于传递到下一层神经元或输出层中。激活函数可以增加模型的表达能力和拟合能力
cnn.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(96, 105, 3)))# 输入卷积层
cnn.add(layers.MaxPooling2D((2, 2))) # 最大池化层 

cnn.add(layers.Conv2D(64, (3, 3), activation='relu')) # 卷积层 
cnn.add(layers.MaxPooling2D((2, 2))) # 最大池化层 

cnn.add(layers.Conv2D(128, (3, 3), activation='relu')) # 卷积层 
cnn.add(layers.MaxPooling2D((2, 2))) # 最大池化层 

cnn.add(layers.Conv2D(128, (3, 3), activation='relu')) # 卷积层 
cnn.add(layers.MaxPooling2D((2, 2))) # 最大池化层 

cnn.add(layers.Flatten()) # 展平层 

cnn.add(layers.Dense(512, activation='relu')) # 全连接层 

# 32表示种类,激活函数使用Softmax
cnn.add(layers.Dense(32, activation='softmax')) # 分类输出层 


# 设置优化器
cnn.compile(loss='categorical_crossentropy', # 损失函数 
            optimizer='RMSprop',
            metrics=['acc']) # 评估指标

4、训练模型

通过fit 拟合模型,指定训练轮次,训练的同事进行验证

在训练过程中,我们还指定了 validation_split,它可以在训练的同时,自动把训练集部分拆出来,进行验证,在每一个训练轮次中,求出该轮次在训练集和验证集上面的损失和预测准确率。

# 训练网络并把训练过程信息存入history对象
history = cnn.fit(X_train,y_train, #训练数据
                  epochs=50, #训练轮次(梯度下降)
                  validation_split=0.2) #训练的同时进行验证

然后将训练的模型保存到 model.h5文件中

cnn.save(os.path.join(os.path.dirname("./result"), 'model.h5'))
Train on 2089 samples, validate on 523 samples
Epoch 1/50
2089/2089 [==============================] - 86s 41ms/step - loss: 1.3523 - acc: 0.3978 - val_loss: 1.0567 - val_acc: 0.5411
Epoch 2/50
2089/2089 [==============================] - 85s 41ms/step - loss: 1.0167 - acc: 0.5692 - val_loss: 1.0336 - val_acc: 0.5526
Epoch 3/50
2089/2089 [==============================] - 85s 41ms/step - loss: 0.8912 - acc: 0.6343 - val_loss: 0.9183 - val_acc: 0.6310
Epoch 4/50
2089/2089 [==============================] - 84s 40ms/step - loss: 0.8295 - acc: 0.6596 - val_loss: 0.9289 - val_acc: 0.6138
Epoch 5/50
2089/2089 [==============================] - 85s 41ms/step - loss: 0.7228 - acc: 0.7056 - val_loss: 1.0086 - val_acc: 0.5736
... ...

这个输出的信息包括了训练的轮次(梯度下降的次数)、每轮训练的时长、每轮训练过程中的平均损失,以及分类的准确度。这里的每一个轮次,其实就是神经网络对其中的每一个神经元自动调参、通过梯度下降进行最优化的过程。

5、评估和优化模型

优化器和学习速率

在卷积神经网络中,优化器和学习速率是两个常用的超参数,用于调节模型的训练过程和优化效果。

**优化器(Optimizer)**是指模型在训练过程中使用的优化算法,用于更新模型的权重和偏置参数,使得模型的损失函数最小化。前较常用的是 RMSprop 和 Adam

学习速率(Learning Rate)是指模型在每次参数更新时,更新的步长大小。学习速率通常是一个非常重要的超参数,它能够影响模型的训练速度和优化效果。如果学习速率过大,可能会导致模型参数在更新过程中产生过大的波动,使得模型无法收敛;而如果学习速率过小,可能会导致模型训练缓慢,需要更长时间才能收敛。

优化器是用于解决神经网络中的局部最低点的问题。

神经网络也是通过梯度下降来实现参数的最优化,梯度下降是通过求导实现的。神经网络因为函数十分复杂,会出现很多的局部最低点,在每一个局部最低点,导数的值都为 0。没有求导后的正负,梯度下降也就没有任何方向感,所以这时候,优化神经网络的参数也不知道应该往哪里走了。
在这里插入图片描述

学习速率(Learning Rate)是指模型在每次参数更新时,更新的步长大小。学习速率通常是一个非常重要的超参数,它能够影响模型的训练速度和优化效果。如果学习速率过大,可能会导致模型参数在更新过程中产生过大的波动,使得模型无法收敛;而如果学习速率过小,可能会导致模型训练缓慢,需要更长时间才能收敛。因此,选择合适的学习速率非常重要,通常需要通过实验和调参来确定最佳的学习速率。

如何设定优化器?==**

cnn.compile(loss='categorical_crossentropy', # 损失函数 行15
            optimizer=Adam(learning_rate=1e-4), # 优化器和学习速率
            metrics=['acc']) # 评估指标

显示训练过程中的损失曲线

在训练模型时,我们将训练的过程信息保存到了history对象中,通过history对象我们可以查看训练集上的损失以及验证集上的准确率。

# 训练网络并把训练过程信息存入history对象
history = cnn.fit(X_train,y_train, #训练数据
                  epochs=50, #训练轮次(梯度下降)
                  validation_split=0.2) #训练的同时进行验证

通过matplotlib画出损失曲线和准确率曲线

def show_history(history): # 显示训练过程中的学习曲线
    loss = history.history['loss'] #训练损失
    val_loss = history.history['val_loss'] #验证损失
    epochs = range(1, len(loss) + 1) #训练轮次
    plt.figure(figsize=(12,4)) # 图片大小
    plt.subplot(1, 2, 1) #子图1
    plt.plot(epochs, loss, 'bo', label='Training loss') #训练损失
    plt.plot(epochs, val_loss, 'b', label='Validation loss') #验证损失
    plt.title('Training and validation loss') #图题
    plt.xlabel('Epochs') #X轴文字
    plt.ylabel('Loss') #Y轴文字
    plt.legend() #图例
    acc = history.history['acc'] #训练准确率
    val_acc = history.history['val_acc'] #验证准确率
    plt.subplot(1, 2, 2) #子图2
    plt.plot(epochs, acc, 'bo', label='Training acc') #训练准确率
    plt.plot(epochs, val_acc, 'b', label='Validation acc') #验证准确率
    plt.title('Training and validation accuracy') #图题
    plt.xlabel('Epochs') #X轴文字
    plt.ylabel('Accuracy') #Y轴文字
    plt.legend() #图例
    plt.show() #绘图
show_history(history) # 调用这个函数

通过 evaluate方法 可以评估模型在测试集上的准确率

result = cnn.evaluate(X_test, y_test) #评估测试集上的准确率
print('CNN的测试准确率为',"{0:.2f}%".format(result[1]))

下面是训练集和验证集的损失曲线和准确率曲线。

1)训练5次的结果。下面的损失曲线在验证集上是有一些波动的,效果并不是很好
在这里插入图片描述

2)训练24次,模型在测试集上的准确率为 95%
在这里插入图片描述
3)训练50次,模型在测试集上的准确率为 96%
在这里插入图片描述

这个时候,其实无论训练多少次,都没有多大作用了!我们要考虑优化模型的超参数了!

调整超参数:优化器和学习速率

我们将优化器由 Adam 更换为 RMSprop ,并且我们没有指定学习速率,RMSprop 优化器是一种自适应学习率算法,它可以根据梯度的大小自动调整学习速率。

cnn.compile(loss='categorical_crossentropy', # 损失函数 行15
            # optimizer=Adam(learning_rate=1e-4), # 优化器
            optimizer='RMSprop',
            metrics=['acc']) # 评估指标

训练50次,使用RMSprop优化器,模型在测试集上的准确率为 99%
在这里插入图片描述
至此,我们训练的模型已经满足我们的要求了,它对验证码的识别准确率达到了 99%。

6、部署模型

模型已经训练出来了,我们现在要将模型部署到服务器上,然后通过域名对外提供API服务,这里我们选择了Django框架。因为有同事是使用这个框架部署的,可以借鉴一些经验,少踩坑!

Diango: Django是一个基于Python的Web应用程序框架。使用简单,社区很活跃,是python语言中开发Web应用的首选框架。

https://www.djangoproject.com/

具体涉及公司隐私内容,不便展示。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/629245.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

facenet, dlib人脸识别,人体检测,云数据库mysql,QQ邮箱,手机验证码,语音播报

目录 部分代码展示&#xff1a; 录入部分 识别部分​编辑 活体检测部分​编辑 同步到云数据库MySQL 其他操作 部分图片展示&#xff1a; 完整代码加ui链接&#xff1a; 涉及到的一些知识点的文章 部分代码展示&#xff1a; 录入部分 识别部分 活体检测部分 同步到云数…

峰终定律原理

峰终定律 峰终定律&#xff08; Peak–End Rule&#xff09;&#xff0c;是由丹尼尔卡尼曼&#xff08;2002年诺贝尔经济学奖获得者&#xff0c;心理学家&#xff09;提出的。 模型介绍 峰终定律是指如果在一段体验的高峰和结尾&#xff0c;体验是愉悦的&#xff0c;那么对整个…

走进机器学习

作者简介&#xff1a;本人是一名大二学生&#xff0c;就读于人工智能专业&#xff0c;学习过c&#xff0c;c&#xff0c;java&#xff0c;python&#xff0c;Mysql等编程知识&#xff0c;现在致力于学习人工智能方面的知识&#xff0c;感谢CSDN让我们相遇&#xff0c;我也会致力…

javaScript蓝桥杯---传送门

目录 一、介绍二、准备三、目标四、代码五、知识点六、完成 一、介绍 日常浏览网页的时候&#xff0c;我们会发现一个问题&#xff0c;当页面太长、内容太多的时候我们很难快速浏览到心仪的内容。为了解决这个烦恼&#xff0c;优秀的产品研发团队发明了一种类似传送门的功能&a…

对比分析:黑盒测试 VS 白盒测试

一、引言 在软件开发过程中&#xff0c;测试是确保产品质量的关键环节。其中&#xff0c;黑盒测试和白盒测试是两种常见的测试方法。本文将详细解析这两种测试方法的定义、特点&#xff0c;同时通过具体示例进行对比分析。 二、黑盒测试 黑盒测试&#xff0c;又称功能测试&…

2023最新性能测试面试题合集含答案,看完拿个20Koffer不是问题

1、描述一下你们公司的性能测试流程&#xff1f; 1&#xff09;分析性能需求&#xff08;用户使用最频繁的场景进行测试&#xff09;确定性能指标&#xff08;例如&#xff1a;事务通过率100%&#xff0c;top99%是5秒&#xff0c;最大并发是2000&#xff0c;CPU和内存都是70%以…

Git教程笔记

概念 Git是一个分布式版本控制工具&#xff0c;主要用于管理开发过程中的源代码文件&#xff08;Java类、xml文件、html页面等&#xff09;在软件开发过程中被广泛使用。 Git常用命令 Git全局设置 获取Git仓库 工作区、暂存区、版本库 概念 Git工作区中文件的状态 工作区中…

ROS EKF 机器人位姿估计功能包:robot_pose_ekf | 仿真环境实践

ROS EKF 机器人位姿估计功能包&#xff1a;robot_pose_ekf | 仿真环境实践 在仿真下使用robot_pose_ekf 在仿真下使用robot_pose_ekf 仿真环境为 一个无人机&#xff0c;具备3D POSE里程计数据&#xff0c;和imu数据。 将robot_pose_ekf.launch文件进行如下更改 <launc…

C++ unordered_map 性能优化

一 插入加速 unordered_map 在桶满时自动进行 rehash 操作。手动调用 rehash 函数可以手动调整 桶数量。 rehash 函数被调用时&#xff0c;需要注意以下几点&#xff1a; rehash 函数可能会造成 unordered_map 的迭代器失效。如果我们在重新哈希后仍需要继续迭代 unordered_…

机器学习实战六步法之数据预处理(五)

要落地一个机器学习的项目&#xff0c;是有章可循的&#xff0c;通过这六个步骤&#xff0c;小白也能搞定机器学习。 看我闪电六连鞭&#xff01;&#x1f923; 数据的预处理通常包括 5 个步骤&#xff0c;如下&#xff1a;这个是比较完整的一个步骤&#xff0c;不同的算法可能…

内网隧道代理技术(一)之内网隧道代理概述

内网隧道代理技术 内网转发 在渗透测试中&#xff0c;当我们获得了外网服务器&#xff08;如web服务器&#xff0c;ftp服务器&#xff0c;mali服务器等等&#xff09;的一定权限后发现这台服务器可以直接或者间接的访问内网。此时渗透测试进入后渗透阶段&#xff0c;一般情况…

FreeRTOS(8)----任务通知

一&#xff0c;任务通知的简介 相对于之前的信号量&#xff0c;事件组等&#xff0c;所谓的任务通知核心就是一个32位的无符号整数和8位的通知状态 任务通知可以通过如下方法更新接收任务的通知值&#xff1a; ● 不覆盖接收任务的通知值 ( 如果上次发送给接收任务的通知还没…

MyBatisPlus总结(1.0)

MyBatis-Plus MyBatis-Plus介绍 MyBatis-Plus&#xff08;简称MP&#xff09;是一个MyBatis的增强工具&#xff0c;在MyBatis的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生 特性 无侵入&#xff1a;只做增强不做改变&#xff0c;引入它不会对现有工程产生影…

为何波卡被称为Layer 0?

理解区块链的技术本质&#xff0c;将揭示加密货币运行轨迹的神秘面纱。了解这背后的原理&#xff0c;将为你带来全新的视角&#xff0c;让你对加密货币的奇妙世界充满无尽的好奇。 波卡是一个内部互连的区块链平台&#xff0c;被赋予技术堆栈元协议或Layer 0的定义&#xff0c…

Golang 基础案例集合:中文拼音转换、解析二维码、压缩 zip、执行定时任务

前言 曾经&#xff0c;因为不够注重基础吃了好多亏。总是很喜欢去看那些高大上的东西&#xff0c;却忽略了最基本的东西。然后会错误的以为自己懂的很多&#xff0c;但是其实是沙堆中筑高台&#xff0c;知道很多高大上的架构&#xff0c;但是基础的东西却不太了解。我觉得&…

PySpark实战指南:大数据处理与分析的终极指南【上进小菜猪大数据】

上进小菜猪&#xff0c;沈工大软件工程专业&#xff0c;爱好敲代码&#xff0c;持续输出干货。 大数据处理与分析是当今信息时代的核心任务之一。本文将介绍如何使用PySpark&#xff08;Python的Spark API&#xff09;进行大数据处理和分析的实战技术。我们将探讨PySpark的基本…

P2[1-2]STM32简介(stm32简介+ARM介绍+片上外设+命名规则+系统结构+引脚定义+启动配置+最小系统电路+实物图介绍)

1.1 stm32简介 解释:ARM是核心部分,程序的内核,加减乘除的计算,都是在ARM内完成。 智能车方向:循迹小车,读取光电传感器或者摄像头数据,驱动电机前进和转弯。 无人机:用STM32读取陀螺仪加速度计的姿态数据,根据控制算法控制电机的速度,从而保证飞机稳定飞行。 机…

maven的pom文件

maven项目中会有pom文件&#xff0c; 当新建项目时候&#xff0c; 需要添加我们需要的依赖包。所以整理了一份比较常用的依赖包的pom&#xff0c;方便以后新建项目或者添加依赖包时copy且快捷。不需要的依赖可以删掉&#xff0c;避免首次远程拉取失败和缩小项目打包大小。 <…

爆料,华为重回深圳,深圳第二个硅谷来了-龙华九龙山未来可期

房地产最重要的决定因素&#xff1a;科技等高附加值产业&#xff01;过去几年&#xff0c;发生的最大的变化就是——科技巨头对全球经济的影响力越来越大&#xff0c;中美之间的博弈&#xff0c;由贸易战升级为科技战&#xff0c;就是基于此原因。人工智能、电子信息技术产业、…

工程数值分析(散装/自食/非全)

1.蒙特卡洛 基本流程 蒙特卡洛模拟法基于随机抽样原理&#xff0c;通过生成大量的随机样本&#xff0c;从而对目标变量进行估计和分析。具体来说&#xff0c;蒙特卡洛模拟法的基本流程如下&#xff1a; 1.确定问题&#xff1a;首先需要明确要解决的问题是什么&#xff0c;以及需…