[Inteligencia artificial y aprendizaje automático] Reconocimiento de perros y gatos basado en la red neuronal convolucional CNN

1. Introducción

Casualmente, el autor también utilizó el reconocimiento de perros y gatos en el diseño del concurso de diseño por computadora hace unos meses, que fue promovido al concurso nacional, sin embargo, el método utilizado en ese momento no cumplía con los requisitos de esta tarea
. Así que lo hice de nuevo. Lo siguiente se centrará en los requisitos de esta tarea y presentará la red neuronal convolucional CNN para realizar el reconocimiento de perros y gatos.

El reconocimiento de perros y gatos y el reconocimiento de razas de perros son un problema importante y desafiante en el campo de la visión por computadora. En el problema de reconocimiento de perros y gatos, necesitamos clasificar perros y gatos en imágenes. En el problema de identificación de razas de perros, necesitamos identificar diferentes razas de perros, como huskies, golden retrievers, etc. Estos problemas tienen amplias aplicaciones en la vida real, como la protección animal, el monitoreo inteligente y otros campos. 下图为我个人搜集的我国宠物行业现状部分图表.

2 Descripción general de las redes neuronales convolucionales

2.1 Introducción básica a las redes neuronales convolucionales

La red neuronal convolucional (CNN) es una red neuronal de avance profundo con las características de conexión local y peso compartido

Las redes neuronales convolucionales tienen capacidades de aprendizaje de representación, pueden realizar una clasificación invariante de cambio de información de entrada de acuerdo con su estructura jerárquica y pueden realizar aprendizaje supervisado y aprendizaje no supervisado La convolución en su capa oculta El intercambio de parámetros del núcleo y la escasez de capas Las conexiones permiten que las redes neuronales convolucionales aprendan características de topología similar a una cuadrícula, como píxeles y audio, con una pequeña cantidad de cálculo, con efectos estables y sin entrada adicional a los datos. Cumple con los requisitos de la ingeniería de características y se usa ampliamente en campos como Visión por computadora y procesamiento del lenguaje natural.

El lado izquierdo de la figura es una red neuronal (estructura completamente conectada) y el lado derecho es una red neuronal convolucional.

2.2 Estructura de la red CNN

El diagrama de estructura de red de CNN es el siguiente:

La estructura básica de una red neuronal convolucional incluye aproximadamente: capa convolucional, función de activación, capa de agrupación, capa completamente conectada, capa de salida, etc.

2.2.1 Capa convolucional

La capa convolucional es la capa más importante de la red neuronal convolucional y también es la fuente del nombre "red neuronal convolucional". Cada capa de convolución en la red neuronal convolucional se compone de varias unidades de convolución y los parámetros de cada unidad de convolución se optimizan mediante el algoritmo de retropropagación.

El diagrama de operación de convolución es el siguiente:

En esta capa convolucional, existen dos operaciones clave:

  1. asociación local. Cada neurona se considera un filtro.
  2. La ventana (campo receptivo) se desliza y el filtro calcula los datos locales.

La operación de convolución 目的extrae diferentes características de la entrada. Es posible que algunas capas convolucionales solo puedan extraer algunas características de bajo nivel, como bordes, líneas y esquinas. Las redes con más capas pueden extraer iterativamente características más complejas de características de bajo nivel.

La capa convolucional 作用realiza una operación de convolución en los datos de entrada, que también puede entenderse como un proceso de filtrado. Un núcleo de convolución es un filtro de ventana. Durante el proceso de entrenamiento de la red, un núcleo de convolución de tamaño personalizado se utiliza como una ventana deslizante para filtrar. la entrada Los datos están convolucionados.

El proceso de convolución es esencialmente la multiplicación de dos matrices. Después del proceso de convolución, la matriz de entrada original se reducirá hasta cierto punto. Por ejemplo, cuando el tamaño del núcleo de convolución personalizado es 3 * 3 y el tamaño del paso es 1, la longitud y el ancho de la matriz se reducirán. Reducir en 2, por lo que en algunas aplicaciones, para mantener el tamaño de la matriz de entrada, necesitamos expandir los datos antes de la operación de convolución. Un método de expansión común es el llenado 0.

Hay otros dos parámetros importantes en la capa convolucional, a saber, sesgo y activación (capas independientes, pero la capa de activación y la capa convolucional generalmente están juntas).

La función del vector de sesgo es realizar una suma lineal simple a los datos convolucionados, es decir, los datos convolucionados se agregan a los datos en el vector de sesgo. Luego, para aumentar la capacidad no lineal de la red, los datos deben En las neuronas, la velocidad de datos que no existe se elimina y se pueden introducir datos útiles en las neuronas para hacer que las personas respondan.

2.2.2 Función de activación

Las funciones de activación más utilizadas actualmente incluyen Relu, tanh y sigmoide. Centrémonos en la función Relu (es decir, la capa de Unidades lineales rectificadas (capa ReLU)). La función Relu es una función lineal. Se necesitan 0 para números negativos y Para números positivos, 0. Entonces es y = x (es decir, la entrada es igual a la salida), es decir, f (x) = max (0, x). Se caracteriza por una convergencia rápida y un cálculo de gradiente simple. pero es relativamente frágil.

Dado que el valor 0 de los datos después de la activación por la función Relu cambia repentinamente a 0, y esta parte de los datos inevitablemente tiene algunos datos que debemos cancelar a la fuerza, por lo que para reducir la pérdida tanto como sea posible, ponemos el capa de convolución delante de la capa de activación.Agregue un vector de desplazamiento después y realice una suma lineal simple en los datos, provocando un desplazamiento lateral en el valor de los datos para evitar que la función de activación filtre más información.

2.2.3 Capa de agrupación

La capa de agrupación generalmente obtiene características de grandes dimensiones después de la capa convolucional, corta las características en varias regiones y toma el valor máximo o promedio para obtener características nuevas de dimensiones más pequeñas.

Generalmente hay dos métodos de agrupación, uno es tomar el valor máximo y el otro es tomar el promedio. El proceso de agrupación también es una ventana móvil que se desliza sobre la matriz de entrada. Durante el proceso de deslizamiento, el valor máximo o promedio Se obtiene el valor de la matriz de datos en esta ventana. Como salida, el tamaño de la capa de agrupación es generalmente 2 * 2 y el paso es 1.

Las funciones específicas de la capa de agrupación :

  1. La invariancia de características es la invariancia de escala de las características que a menudo mencionamos en el procesamiento de imágenes. La operación de agrupación es el cambio de tamaño de la imagen. Por lo general, si la imagen de un perro se duplica, aún podemos reconocerla como un perro. Esto muestra que el Las características más importantes de un perro aún se conservan en esta imagen. Podemos decir de un vistazo que la imagen es un perro. La información eliminada durante la compresión de la imagen es solo información irrelevante, y la información restante es una característica con invariancia de escala y la característica que mejor expresa la imagen.
  2. Reducción de dimensionalidad de características , sabemos que una imagen contiene mucha información y tiene muchas características, pero alguna información no es de mucha utilidad o es repetitiva cuando hacemos tareas de imágenes, podemos eliminar este tipo de información redundante y convertir la Extrayendo más importante. Las características también juegan un papel importante en la operación de agrupación.
  3. Previene el sobreajuste hasta cierto punto y hace que la optimización sea más conveniente .

2.2.4 Capa completamente conectada

La capa completamente conectada se usa a menudo como la última capa de la red en problemas de clasificación. Su función principal es conectar completamente la matriz de datos y luego generar los datos de acuerdo con el número de categorías. En problemas de regresión, la capa completamente conectada se puede Se omite, pero necesitamos agregar una capa de convolución para realizar operaciones de deconvolución en los datos.

2.3 Ilustración del proceso de capacitación de CNN

  • Etapa de propagación hacia adelante:
    seleccione muestras de entrenamiento (x, y) e ingrese x en la red. Los pesos se inicializan aleatoriamente (generalmente se selecciona un decimal) y la información pasa a través de la extracción y transformación de características capa por capa desde la capa de entrada y finalmente llega a la capa de salida para obtener el resultado de salida.

  • Etapa de retropropagación:
    compare el resultado de salida con el resultado ideal y calcule el error global (pérdida). El error obtenido se transmite de regreso a las neuronas en diferentes capas y los pesos y sesgos se ajustan de acuerdo con el "método iterativo" para encontrar el resultado global óptimo.

2.4 Características básicas de CNN

2.4.1 Conectividad local

Una forma sencilla para que las capas convolucionales resuelvan este tipo de problemas es restringir las conexiones entre unidades ocultas y unidades de entrada: cada unidad oculta solo puede conectarse a una parte de la unidad de entrada. Por ejemplo, cada unidad oculta sólo conecta una pequeña región adyacente de la imagen de entrada.

2.4.2 Compartir parámetros

En la capa convolucional, el peso de cada neurona que conecta la ventana de datos es fijo y cada neurona solo se enfoca en una característica. Las neuronas son filtros en el procesamiento de imágenes, como el filtro de Sobel dedicado a la detección de bordes, es decir, cada filtro en la capa convolucional tendrá una característica de la imagen en la que se enfoca, como bordes verticales, bordes horizontales, color, textura, etc. La suma de todas estas neuronas es como un conjunto de extractores de características para toda la imagen.

El peso compartido nos permite realizar la extracción de características de manera más eficiente porque reduce en gran medida la cantidad de variables libres que deben aprenderse. Al controlar el tamaño del modelo, las redes convolucionales pueden tener buenas capacidades de generalización para problemas visuales.

3 Introducción a los conjuntos de datos

Este experimento utiliza el conjunto de datos de reconocimiento de perros y gatos de Kaggle, que contiene un total de 25.000 fotografías del conjunto de datos JPEG, de las cuales 12.500 son fotografías de perros y gatos cada una. El tamaño del conjunto de datos es de 543 MB después de la compresión y el empaquetado.

El enlace de descarga del sitio web oficial es el siguiente:
https://www.kaggle.com/c/dogs-vs-cats/data

Dado que la cantidad de conjuntos de datos es demasiado grande para la prueba de un trabajo pequeño, se debe crear un nuevo conjunto de datos pequeño basado en el conjunto de datos de Kaggle descargado, que contiene tres subconjuntos.

Es decir, los conjuntos de datos de perros y gatos: un conjunto de entrenamiento de 1000 muestras cada uno, un conjunto de verificación de 500 muestras cada uno y un conjunto de prueba de 500 muestras cada uno.

Crear un nuevo conjunto de datos pequeño

Genere cada ruta de carpeta y copie el conjunto de entrenamiento, el conjunto de verificación y el conjunto de prueba correspondientes para generar un nuevo conjunto de datos pequeño.

import os, shutil
# 下载的kaggle数据集路径
original_dataset_dir = 'data/train' 
# 新的小数据集放置路径
base_dir = 'work/cats_and_dogs_small' 
os.mkdir(base_dir)
train_dir = os.path.join(base_dir, 'train')
os.mkdir(train_dir)
validation_dir = os.path.join(base_dir, 'validation')
os.mkdir(validation_dir)
test_dir = os.path.join(base_dir, 'test')
os.mkdir(test_dir)

train_cats_dir = os.path.join(train_dir, 'cats')
os.mkdir(train_cats_dir)
train_dogs_dir = os.path.join(train_dir, 'dogs')
os.mkdir(train_dogs_dir)
validation_cats_dir = os.path.join(validation_dir, 'cats')
os.mkdir(validation_cats_dir)
validation_dogs_dir = os.path.join(validation_dir, 'dogs')
os.mkdir(validation_dogs_dir)
test_cats_dir = os.path.join(test_dir, 'cats')
os.mkdir(test_cats_dir)
test_dogs_dir = os.path.join(test_dir, 'dogs')
os.mkdir(test_dogs_dir)

fnames = ['cat.{}.jpg'.format(i) for i in range(1000)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(train_cats_dir, fname)
    shutil.copyfile(src, dst)

fnames = ['cat.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(validation_cats_dir, fname)
    shutil.copyfile(src, dst)

fnames = ['cat.{}.jpg'.format(i) for i in range(1500, 2000)]
for fname in fnames:
     src = os.path.join(original_dataset_dir, fname)
     dst = os.path.join(test_cats_dir, fname)
     shutil.copyfile(src, dst)

fnames = ['dog.{}.jpg'.format(i) for i in range(1000)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(train_dogs_dir, fname)
    shutil.copyfile(src, dst)

fnames = ['dog.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(validation_dogs_dir, fname)
    shutil.copyfile(src, dst)

fnames = ['dog.{}.jpg'.format(i) for i in range(1500, 2000)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(test_dogs_dir, fname)
    shutil.copyfile(src, dst)

print('total training cat images:', len(os.listdir(train_cats_dir)))
print('total training dog images:', len(os.listdir(train_dogs_dir)))
print('total validation cat images:', len(os.listdir(validation_cats_dir)))
print('total validation dog images:', len(os.listdir(validation_dogs_dir)))
print('total test cat images:', len(os.listdir(test_cats_dir)))
print('total test dog images:', len(os.listdir(test_dogs_dir)))

El resultado del programa anterior es:

total training cat images: 1000
total training dog images: 1000
total validation cat images: 500
total validation dog images: 500
total test cat images: 500
total test dog images: 500

La estructura de directorios del conjunto de datos dividido es la siguiente:

4 Reconocimiento de perros y gatos (tensorflow)

4.1 Construir un modelo de red neuronal convolucional

Cree un nuevo programa Python e importe numpy, tensorflow y otros paquetes de software científico básico relevantes

import tensorflow as tf
import numpy as np
import tensorflow.contrib.slim as slim
from PIL import Image,ImageFont, ImageDraw

Cree una gran cantidad de pesos y términos de sesgo, realice convolución y agrupación. Defina una función que analice los parámetros de entrada, el código es el siguiente:

def build_graph(top_k):
    # 调用tf.placeholder函数操作,定义传入图表中的shape参数,后续还会将实际的训练用例传入图表。在训练循环(train_op)的后续步骤中,传入的整个图像和标签数据集会被切片,以符合每一个操作所设置的shape参数值,占位符操作将会填补以符合这个shape参数值。然后使用feed_dict参数,将数据传入sess1.run()函数。
    keep_prob = tf.placeholder(dtype=tf.float32, shape=[], name='keep_prob')
    images = tf.placeholder(dtype=tf.float32, shape=[None, 64, 64, 3], name='image_batch')
    labels = tf.placeholder(dtype=tf.int64, shape=[None], name='label_batch')

    # 定义卷积,传入x和参数,slim..conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, name=None)
    conv_1 = slim.conv2d(images, 64, [3, 3], 3, padding='SAME', scope='conv1')
    # 定义池化,传入x, pooling, slim..max_pool(value, ksize, strides, padding, name=None)
    max_pool_1 = slim.max_pool2d(conv_1, [2, 2], [2, 2], padding='SAME')
    conv_2 = slim.conv2d(max_pool_1, 128, [3, 3], padding='SAME', scope='conv2')
    max_pool_2 = slim.max_pool2d(conv_2, [2, 2], [2, 2], padding='SAME')
    conv_3 = slim.conv2d(max_pool_2, 256, [3, 3], padding='SAME', scope='conv3')
    max_pool_3 = slim.max_pool2d(conv_3, [2, 2], [2, 2], padding='SAME')

    conv_4 = slim.conv2d(max_pool_3, 512, [3, 3], padding='SAME', scope='conv4')
    conv_5 = slim.conv2d(conv_4, 512, [3, 3], padding='SAME', scope='conv5')
    max_pool_4 = slim.max_pool2d(conv_5, [2, 2], [2, 2], padding='SAME')

    flatten = slim.flatten(max_pool_4)

    fc1 = slim.fully_connected(slim.dropout(flatten, keep_prob), 1024, activation_fn=tf.nn.tanh, scope='fc1')
    logits = slim.fully_connected(slim.dropout(fc1, keep_prob), 2, activation_fn=None, scope='fc2')
    # loss()函数通过添加所需的损失操作,进一步构建图表。
    loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits, labels=labels))
    accuracy = tf.reduce_mean(tf.cast(tf.equal(tf.argmax(logits, 1), labels), tf.float32))
    global_step = tf.get_variable("step", [], initializer=tf.constant_initializer(0.0), trainable=False)
    rate = tf.train.exponential_decay(2e-4, global_step, decay_steps=2000, decay_rate=0.97, staircase=True)
    train_op = tf.train.AdamOptimizer(learning_rate=rate).minimize(loss, global_step=global_step)
    probabilities = tf.nn.softmax(logits)
    tf.summary.scalar('loss', loss)
    tf.summary.scalar('accuracy', accuracy)
    merged_summary_op = tf.summary.merge_all()
    predicted_val_top_k, predicted_index_top_k = tf.nn.top_k(probabilities, k=top_k)
    accuracy_in_top_k = tf.reduce_mean(tf.cast(tf.nn.in_top_k(probabilities, labels, top_k), tf.float32))

    return {
    
    'images': images,
            'labels': labels,
            'keep_prob': keep_prob,
            'top_k': top_k,
            'global_step': global_step,
            'train_op': train_op,
            'loss': loss,
            'accuracy': accuracy,
            'accuracy_top_k': accuracy_in_top_k,
            'merged_summary_op': merged_summary_op,
            'predicted_distribution': probabilities,
            'predicted_index_top_k': predicted_index_top_k,
            'predicted_val_top_k': predicted_val_top_k}


4.2 Modelo de formación

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
---------------------------------------------------------
    Name:       train
    Author:     欧阳紫樱
    Date:       2023-07-02
    Time:       10:48
---------------------------------------------------------
    Software:   
                PyCharm
"""

import tensorflow.contrib.slim as slim
import matplotlib.pyplot as plt
import tensorflow as tf
import random
import os
# x = [] --- 初始化存放步数的列表
# y_1 = [] --- 初始化步存准确率的列表
# y_2 = [] --- 初始化存放loss的列表
x = []
y_1 = []
y_2 = []
# class DataIterator: --- 数据迭代器
# __init__()方法是所谓的对象的“构造函数”。self是指向该对象本身的一个引用。self代表实例。
class DataIterator:
    def __init__(self, data_dir):
        # Set FLAGS.charset_size to a small value if available computation power is limited.
        # 如果可用计算能力有限,请将FLAGS.charset_size设置为较小的值。
        # 选择的第一个`charset_size`字符来进行实验
        # Python 文件 truncate() 方法用于截断文件并返回截断的字节长度。
        # 指定长度的话,就从文件的开头开始截断指定长度,其余内容删除;
        # 不指定长度的话,就从文件开头开始截断到当前位置,其余内容删除
        truncate_path = data_dir + ('%05d' % 2)
        print(truncate_path)
        self.image_names = []
        # root为根目录。sub_folder为子文件夹。
        # os.walk() 方法是一个简单易用的文件、目录遍历器。data_dir---数据目录。
        # os.walk() 只产生文件路径。os.path.walk() 产生目录树下的目录路径和文件路径。
        # self.image_names += [os.path.join(root, file_path) for file_path in file_list]
        # os.path.join(root,file_path) 根目录与文件路径组合,形成绝对路径。
        # random.shuffle(self.image_names) --- 将序列的所有元素随机排序,返回随机排序后的序列。
              for root, sub_folder, file_list in os.walk(data_dir):
            if root < truncate_path:
                self.image_names = self.image_names+[os.path.join(root, file_path) for file_path in file_list]
                print(self.image_names)
        random.shuffle(self.image_names)
        # 遍历 --- os.sep 使得写的代码可以跨操作系统
        # split() 通过指定分隔符对字符串进行切片,如果参数 num 有指定值,则仅分隔 num 个子字符串。 返回分割后的字符串列表。
        self.labels = [int(file_name[len(data_dir):].split(os.sep)[0]) for file_name in self.image_names]
        print(self.labels)
    # @property --- 属性
    # len() 方法返回对象(字符、列表、元组等)长度或项目个数
      @property
    def size(self):
        return len(self.labels)
    # @staticmethod --- 静态方法
    # 人工增加训练集的大小. 通过加噪声等方法从已有数据中创造出一批"新"的数据.也就是Data Augmentation
    # tf.image.random_brightness(images, max_delta=0.3) --- 随机改变亮度
    # tf.image.random_contrast(images, 0.8, 1.2) --- 随机改变对比度
       @staticmethod
    def data_augmentation(images):
        images = tf.image.random_brightness(images, max_delta=0.3)
        images = tf.image.random_contrast(images, 0.8, 1.2)
        return images
    # batch_size --- 批尺寸    num_epochs --- 指把所有训练数据完整的过一遍的波数   aug --- 是否增大
    # tf.convert_to_tensor 将给定值转换为张量
    # tf.train.slice_input_producer 在tensor_list中生成一个张量片段。
    # num_epochs:一个整数(可选)。如果指定,slice_input_producer 在生成之前产生每个片段num_epochs次
    # tf.read_file 读取并输出输入文件名的全部内容。
    # tf.image.convert_image_dtype 将图像转换为dtype,并根据需要缩放其值。
    # tf.image.decode_png将PNG编码的图像解码为uint8或uint16张量。channels:解码图像的颜色通道数量为1。images_content:字符串类型的张量。0-d。 PNG编码的图像。
    # tf.image.resize_images使用指定的方法将图像调整为大小。
    # tf.train.shuffle_batch 通过随机混洗张量来创建批次。
    # [images, labels]要排队的张量或词典。batch_size:从队列中提取的新批量大小。
    # capacity:队列中元素的最大数量。min_after_dequeue出队后队列中的最小数量元素,用于确保元素的混合级别。
    def input_pipeline(self, batch_size, num_epochs=None, aug=False):
        images_tensor = tf.convert_to_tensor(self.image_names, dtype=tf.string)
        labels_tensor = tf.convert_to_tensor(self.labels, dtype=tf.int64)
        input_queue = tf.train.slice_input_producer([images_tensor, labels_tensor], num_epochs=num_epochs)
        labels = input_queue[1]
        images_content = tf.read_file(input_queue[0])
        images = tf.image.convert_image_dtype(tf.image.decode_png(images_content, channels=3), tf.float32)
        if aug:
            images = self.data_augmentation(images)
        new_size = tf.constant([64, 64], dtype=tf.int32)
        images = tf.image.resize_images(images, new_size)
        image_batch, label_batch = tf.train.shuffle_batch([images, labels], batch_size=batch_size, capacity=5000,min_after_dequeue=1000)
        return image_batch, label_batch
# 搭建神经网络
# tf.placeholder --- 设置一个容器,用于接下来存放数据
# keep_prob --- dropout的概率,也就是在训练的时候有多少比例的神经元之间的联系断开
# images --- 喂入神经网络的图像,labels --- 喂入神经网络图像的标签
# slim.conv2d --- 卷积层 --- (images, 64, [3, 3], 1, padding='SAME', scope='conv3_1')
# 第一个参数表示输入的训练图像,第二个参数表示滤波器的个数,原来的数据是宽64高64维度1,处理后的数据是维度64,
# 第三个参数是滤波器的大小,宽3高3的矩形,第四个参数是表示输入的维度,第五个参数padding表示加边的方式,第六个参数表示层的名称
# slim.max_pool2d --- 表示池化层 --- 池化就是减小卷积神经网络提取的特征,将比较明显的特征提取了,不明显的特征就略掉
# slim.flatten(max_pool_4) --- 表示将数据压缩
# slim.fully_connected --- 全连接层 --- 也就是一个个的神经元
# slim.dropout(flatten, keep_prob) --- dropout层,在训练的时候随机的断掉神经元之间的连接,keep_prob就是断掉的比例
# tf.reduce_mean --- 得到平均值
# tf.nn.sparse_softmax_cross_entropy_with_logits --- 求得交叉熵
# tf.argmax(logits, 1) --- 得到较大的值
# tf.equal() --- 两个数据相等为True,不等为False --- 用于得到预测准确的个数
# tf.cast() --- 将True和False转为1和0,
# global_step --- 训练的步数 --- initializer=tf.constant_initializer(0.0) --- 步数初始化
# tf.train.AdamOptimizer(learning_rate=0.1) --- 优化器的选择,这个训练使用的Adam优化器
# learning_rate=0.1 --- 学习率 --- 训练的过程也就是神经网络学习的过程
# tf.nn.softmax(logits) --- 得到可能性
# tf.summary.scalar / merged_summary_op --- 用于显示训练过程的数据
# predicted_val_top_k --- 喂入图像得到的可能性,也就是识别得到是哪一个汉字的可能性,top_k表示可能性最大的K个数据
# predicted_index_top_k --- 这个表示识别最大K个可能性汉字的索引 --- 也就是汉字对应的数字
# return 表示这个函数的返回值
def build_graph(top_k):
    keep_prob = tf.placeholder(dtype=tf.float32, shape=[], name='keep_prob')
    images = tf.placeholder(dtype=tf.float32, shape=[None, 64, 64, 3], name='image_batch')
    labels = tf.placeholder(dtype=tf.int64, shape=[None], name='label_batch')
    conv_1 = slim.conv2d(images, 64, [3, 3], 3, padding='SAME', scope='conv1')
    max_pool_1 = slim.max_pool2d(conv_1, [2, 2], [2, 2], padding='SAME')
    conv_2 = slim.conv2d(max_pool_1, 128, [3, 3], padding='SAME', scope='conv2')
    max_pool_2 = slim.max_pool2d(conv_2, [2, 2], [2, 2], padding='SAME')
    conv_3 = slim.conv2d(max_pool_2, 256, [3, 3], padding='SAME', scope='conv3')
    max_pool_3 = slim.max_pool2d(conv_3, [2, 2], [2, 2], padding='SAME')

    conv_4 = slim.conv2d(max_pool_3, 512, [3, 3], padding='SAME', scope='conv4')
    conv_5 = slim.conv2d(conv_4, 512, [3, 3], padding='SAME', scope='conv5')
    max_pool_4 = slim.max_pool2d(conv_5, [2, 2], [2, 2], padding='SAME')

    flatten = slim.flatten(max_pool_4)

    fc1 = slim.fully_connected(slim.dropout(flatten, keep_prob), 1024, activation_fn=tf.nn.tanh, scope='fc1')
    logits = slim.fully_connected(slim.dropout(fc1, keep_prob), 2, activation_fn=None, scope='fc2')
    loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits, labels=labels))
    accuracy = tf.reduce_mean(tf.cast(tf.equal(tf.argmax(logits, 1), labels), tf.float32))
    global_step = tf.get_variable("step", [], initializer=tf.constant_initializer(0.0), trainable=False)
    rate = tf.train.exponential_decay(2e-4, global_step, decay_steps=2000, decay_rate=0.97, staircase=True)
    train_op = tf.train.AdamOptimizer(learning_rate=rate).minimize(loss, global_step=global_step)
    probabilities = tf.nn.softmax(logits)
    tf.summary.scalar('loss', loss)
    tf.summary.scalar('accuracy', accuracy)
    merged_summary_op = tf.summary.merge_all()
    predicted_val_top_k, predicted_index_top_k = tf.nn.top_k(probabilities, k=top_k)
    accuracy_in_top_k = tf.reduce_mean(tf.cast(tf.nn.in_top_k(probabilities, labels, top_k), tf.float32))

    return {
    
    'images': images,
            'labels': labels,
            'keep_prob': keep_prob,
            'top_k': top_k,
            'global_step': global_step,
            'train_op': train_op,
            'loss': loss,
            'accuracy': accuracy,
            'accuracy_top_k': accuracy_in_top_k,
            'merged_summary_op': merged_summary_op,
            'predicted_distribution': probabilities,
            'predicted_index_top_k': predicted_index_top_k,
            'predicted_val_top_k': predicted_val_top_k}
# def train(): --- 训练神经网络
# DataIterator(data_dir='./data/train/') --- 调用函数 --- 得到训练数据集
# DataIterator(data_dir='./data/test/') --- 调用函数 --- 得到测试数据集
# with tf.Session() as sess: --- 新建一个会话
# train_feeder.input_pipeline(batch_size=128, aug=True) --- 得到训练数据集和训练数据集标签
# test_feeder.input_pipeline(batch_size=128) --- 得到测试数据集和测试数据集标签, 个数为128
# build_graph(top_k=1) --- 声明一个网络,参数为1,
# sess.run(tf.global_variables_initializer()) --- 初始化全部的参数
# tf.train.Coordinator() --- 线程的协调者。这个类实现了一个简单的机制来协调一组线程的终止。
# tf.train.start_queue_runners(sess=sess, coord=coord) --- 开始在图表中收集的所有队列运行者。为图中收集的所有队列运行者启动线程。
# coord:可选协调器,用于协调启动的线程
# tf.train.Saver() --- 保存并恢复变量
def train():
    train_feeder = DataIterator(data_dir='./data/train/')
    test_feeder = DataIterator(data_dir='./data/test/')

    with tf.Session() as sess:
        train_images, train_labels = train_feeder.input_pipeline(batch_size=28, aug=True)
        test_images, test_labels = test_feeder.input_pipeline(batch_size=28)

        graph = build_graph(top_k=1)
        sess.run(tf.global_variables_initializer())
        coord = tf.train.Coordinator()
        tf.train.start_queue_runners(sess=sess, coord=coord)
        saver = tf.train.Saver()
        # for i in range(5000) --- 循环5000次
        # sess.run([train_images, train_labels]) --- 得到一组大小为128的训练数据以及标签
        # sess.run([test_images, test_labels]) --- 得到一组大小为128的测试数据以及标签
        # feed_dict --- 用feed_dict喂数据 --- 训练数据
        # sess.run(graph['train_op'], feed_dict=feed_dict) --- 训练神经网络
               for i in range(5000):
            train_images_batch, train_labels_batch = sess.run([train_images, train_labels])
            test_images_batch, test_labels_batch = sess.run([test_images, test_labels])

            feed_dict = {
    
    graph['images']: train_images_batch, graph['labels']: train_labels_batch, graph['keep_prob']: 0.8}
            sess.run(graph['train_op'], feed_dict=feed_dict)
            # if i % 10 == 0 --- 没进行10次循环,进入下面的代码
            # feed_dict --- 喂入数据 --- 测试数据
            # loss, accuracy = sess.run() --- 喂入神经网络,得到loss以及准确率
            # print("---the step {0} ---loss {1} ---accuracy {2}".format(i, loss, accuracy)) --- 输出相关信息
            # x.append(i) --- 将步数加入列表中
            # y_1.append(accuracy) --- 将准确率加入列表
            # y_2.append(loss) --- 将loss加入列表
            if i % 10 == 0:
                feed_dict = {
    
    graph['images']: test_images_batch, graph['labels']: test_labels_batch, graph['keep_prob']: 0.8}
                _, loss, accuracy = sess.run([graph['train_op'], graph['loss'], graph['accuracy']], feed_dict=feed_dict)
                print("---the step {0} ---loss {1} ---accuracy {2}".format(i, loss, accuracy))
                x.append(i)
                y_1.append(accuracy)
                y_2.append(loss)

            if i % 100 == 0 and i > 0:
                saver.save(sess, './checkpoint/model.ckpt')
# if __name__ == "__main__": --- 主函数 --- 程序的入口
# train() --- 训练神经网络
# plt.plot(x, y_1) --- plt.plot(x, y_2) --- 绘制accuracy, loss和步数之间的图像
# plt.show() --- 显示图像
if __name__ == "__main__":
    train()
    plt.plot(x, y_1)
    plt.plot(x, y_2)
    plt.show()

El proceso de formación es el siguiente:

Se puede ver que cuando el número de pasos (paso) es 0, la tasa de pérdida (pérdida) es de aproximadamente 0,8127249479293823 y la precisión (exactitud) es de aproximadamente 0,3571428656578064. A medida que aumenta el número de pasos, la tasa de pérdida disminuye gradualmente y se acerca a 0. La precisión aumenta gradualmente y se acerca a 1.

La tasa de precisión evalúa los resultados de predicción del modelo mediante la evaluación de etiquetas después del entrenamiento del volumen de datos. La tasa de pérdida (pérdida) es el valor de pérdida de la función de pérdida diseñada y calculada antes de la predicción. La eficacia del modelo se mide en la precisión del modelo como la relación entre el número de muestras clasificadas con precisión y el número total de muestras. Eso es para medir el efecto del modelo.

Para reducir el error de optimización, podemos calcular la función de pérdida para actualizar los parámetros del modelo. Por lo tanto, impulsado por algoritmos de optimización y funciones de pérdida, se reduce el riesgo de errores del modelo. Por ejemplo, la tasa de pérdida se refiere a los puntos ciegos de conocimiento en nuestros libros de texto y preguntas de exámenes. Sigamos digiriendo y aprendiendo a reducir nuestros puntos ciegos en el conocimiento y reducir la tasa de error. La tasa de precisión es nuestra puntuación final de la prueba.

Los resultados de visualización de los resultados del entrenamiento del modelo son los siguientes:

El eje vertical representa el tamaño del paso del entrenamiento, el eje horizontal representa la probabilidad, el azul representa la precisión y el amarillo representa la pérdida. A medida que aumenta el tamaño del paso, la precisión se acerca gradualmente a 1 y la tasa de pérdida se acerca gradualmente Cuanto más cerca de 0, más Más perfecto es el modelo.

4.3 Identificar los resultados de la predicción

Aquí utilizamos la biblioteca opencv para generar imágenes para verificación experimental. El código es el siguiente:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
---------------------------------------------------------
    Name:       opencv
    Author:     欧阳紫樱
    Date:       2023-07-02
    Time:       10:49
---------------------------------------------------------
    Software:   
                PyCharm
"""

import cv2
import os
import restore
animals = ['cat', 'dog']
l = os.listdir("./test/")
length = len(l)
for i in range(0, length):
    if l[i].endswith(".jpg"):
        image_path = "./test/" + l[i]
        probabilities, animal = restore.Recognize(image_path)
        image = cv2.imread(image_path)
        text = str(animals[animal[0][0]]) + ' --> ' + str(probabilities[0][0])
        cv2.putText(image, text, (10, 50), cv2.FONT_HERSHEY_DUPLEX, 1.0, (255, 255, 255), 1)
        cv2.imshow('', image)
        cv2.waitKey(0)
        cv2.imwrite('./out/' + str(i) + '.jpg', image)

Coloque cualquier parte de la imagen de prueba en la carpeta de prueba y el resultado de salida será el siguiente:

Imagen 1 Imagen 2

Como se puede ver en la imagen, al principio de cada imagen, se muestran las características de la imagen (gato o perro) y la tasa de precisión (0 ~ 1).

La tasa de precisión se acerca a 1, lo que verifica la viabilidad del algoritmo de reconocimiento de imágenes basado en la red neuronal convolucional CNN.

5 Clasificación de perros y gatos (modelo de referencia de Keras)

5.1 Construir modelo de red

Es casi lo mismo que construir el modelo en la sección anterior, no explicaré mucho aquí,
el código es el siguiente:

#网络模型构建
from keras import layers
from keras import models
#keras的序贯模型
model = models.Sequential()
#卷积层,卷积核是3*3,激活函数relu
model.add(layers.Conv2D(32, (3, 3), activation='relu',
                        input_shape=(150, 150, 3)))
#最大池化层
model.add(layers.MaxPooling2D((2, 2)))
#卷积层,卷积核2*2,激活函数relu
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
#最大池化层
model.add(layers.MaxPooling2D((2, 2)))
#卷积层,卷积核是3*3,激活函数relu
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
#最大池化层
model.add(layers.MaxPooling2D((2, 2)))
#卷积层,卷积核是3*3,激活函数relu
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
#最大池化层
model.add(layers.MaxPooling2D((2, 2)))
#flatten层,用于将多维的输入一维化,用于卷积层和全连接层的过渡
model.add(layers.Flatten())
#全连接,激活函数relu
model.add(layers.Dense(512, activation='relu'))
#全连接,激活函数sigmoid
model.add(layers.Dense(1, activation='sigmoid'))

Verifique el estado de los parámetros de cada capa del modelo:

#输出模型各层的参数状况
model.summary()
Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 148, 148, 32)      896       
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 74, 74, 32)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 72, 72, 64)        18496     
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 36, 36, 64)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 34, 34, 128)       73856     
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 17, 17, 128)       0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 15, 15, 128)       147584    
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 7, 7, 128)         0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 6272)              0         
_________________________________________________________________
dense_1 (Dense)              (None, 512)               3211776   
_________________________________________________________________
dense_2 (Dense)              (None, 1)                 513       
=================================================================
Total params: 3,453,121
Trainable params: 3,453,121
Non-trainable params: 0
_________________________________________________________________

5.2 Configuración de entrenamiento

Configurar el método de entrenamiento

from keras import optimizers

model.compile(loss='binary_crossentropy',
              optimizer=optimizers.RMSprop(lr=1e-4),
              metrics=['acc'])

Entre ellos, el optimizador y la función de pérdida pueden ser nombres en forma de cadena o en forma de función.

Convierta las imágenes del archivo al formato requerido
. Ajuste las imágenes de entrenamiento y verificación a 150*150.

from keras.preprocessing.image import ImageDataGenerator

# 所有图像将按1/255重新缩放
train_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)

train_generator = train_datagen.flow_from_directory(
        # 这是目标目录
        train_dir,
        # 所有图像将调整为150x150
        target_size=(150, 150),
        batch_size=20,
        # 因为我们使用二元交叉熵损失,我们需要二元标签
        class_mode='binary')

validation_generator = test_datagen.flow_from_directory(
        validation_dir,
        target_size=(150, 150),
        batch_size=20,
        class_mode='binary')

Found 2000 images belonging to 2 classes.
Found 1000 images belonging to 2 classes.

Ver los resultados de las operaciones anteriores.

#查看上面对于图片预处理的处理结果
for data_batch, labels_batch in train_generator:
    print('data batch shape:', data_batch.shape)
    print('labels batch shape:', labels_batch.shape)
    break
data batch shape: (20, 150, 150, 3)
labels batch shape: (20,)

5.3 Entrenamiento modelo

Entrenamiento del modelo y guardado del modelo generado.

#模型训练过程
# 模型训练过程
history = model.fit(
      train_generator,
      steps_per_epoch=100,
      epochs=30,
      validation_data=validation_generator,
      validation_steps=50)

# 保存训练得到的模型
model.save('work/output/cats_and_dogs_small_1.h5')

El proceso de formación es el siguiente:

5.4 Visualización de resultados

#对于模型进行评估,查看预测的准确性
import matplotlib.pyplot as plt

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(len(acc))

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()

plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()

Los resultados de visualización del efecto de entrenamiento son los siguientes:

Imagen 1 Imagen 2

A partir de este efecto, se encuentra que el valor de pérdida de entrenamiento muestra una tendencia ascendente. Es decir, se puede concluir que existen algunos problemas en el modelo obtenido mediante entrenamiento, lo que conduce a un sobreajuste del modelo. El sobreajuste se produce cuando los supuestos se hacen demasiado estrictos para obtener supuestos consistentes y el efecto de clasificación del modelo entrenado real es deficiente.

6 Ajustes al modelo base

6.1 Mejora de la imagen

Utilice el generador de imágenes para definir algunas transformaciones de imágenes comunes.La mejora de imágenes consiste en mejorar la información útil de la imagen transformándola.

#该部分代码及以后的代码,用于替代基准模型中分类后面的代码(执行代码前,需要先将之前分类的目录删掉,重写生成分类,否则,会发生错误)
from keras.preprocessing.image import ImageDataGenerator
datagen = ImageDataGenerator(
      rotation_range=40,
      width_shift_range=0.2,
      height_shift_range=0.2,
      shear_range=0.2,
      zoom_range=0.2,
      horizontal_flip=True,
      fill_mode='nearest')

①rotation_range
un valor de ángulo (0-180) dentro del cual la imagen se puede girar aleatoriamente
②width_shift y height_shift
rango (como parte del ancho o alto total) en el que la imagen se puede transformar aleatoriamente vertical u horizontalmente
③shear_range
se usa para aplicar corte aleatoriamente Transformación
④zoom_range
se usa para hacer zoom aleatoriamente dentro de la imagen
⑤horizontal_flip
se usa para voltear aleatoriamente la mitad de la imagen horizontalmente - cuando no se asume ninguna asimetría horizontal (como imágenes del mundo real)
⑥fill_mode
se usa para llenar la estrategia de píxeles recién creada, se puede usar en rotación o ancho / Aparece después del movimiento de altura

Ver imagen mejorada

import matplotlib.pyplot as plt
# This is module with image preprocessing utilities
from keras.preprocessing import image
fnames = [os.path.join(train_cats_dir, fname) for fname in os.listdir(train_cats_dir)]
# We pick one image to "augment"
img_path = fnames[3]
# Read the image and resize it
img = image.load_img(img_path, target_size=(150, 150))
# Convert it to a Numpy array with shape (150, 150, 3)
x = image.img_to_array(img)
# Reshape it to (1, 150, 150, 3)
x = x.reshape((1,) + x.shape)
# The .flow() command below generates batches of randomly transformed images.
# It will loop indefinitely, so we need to `break` the loop at some point!
i = 0
for batch in datagen.flow(x, batch_size=1):
    plt.figure(i)
    imgplot = plt.imshow(image.array_to_img(batch[0]))
    i += 1
    if i % 4 == 0:
        break
plt.show()

Imagen 1 Imagen 2
Imagen 1 Imagen 2

6.2 Agregar una capa de abandono

Agregue una capa de abandono al modelo de red

#网络模型构建
from keras import layers
from keras import models
#keras的序贯模型
model = models.Sequential()
#卷积层,卷积核是3*3,激活函数relu
model.add(layers.Conv2D(32, (3, 3), activation='relu',
                        input_shape=(150, 150, 3)))
#最大池化层
model.add(layers.MaxPooling2D((2, 2)))
#卷积层,卷积核2*2,激活函数relu
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
#最大池化层
model.add(layers.MaxPooling2D((2, 2)))
#卷积层,卷积核是3*3,激活函数relu
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
#最大池化层
model.add(layers.MaxPooling2D((2, 2)))
#卷积层,卷积核是3*3,激活函数relu
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
#最大池化层
model.add(layers.MaxPooling2D((2, 2)))
#flatten层,用于将多维的输入一维化,用于卷积层和全连接层的过渡
model.add(layers.Flatten())
#退出层
model.add(layers.Dropout(0.5))
#全连接,激活函数relu
model.add(layers.Dense(512, activation='relu'))
#全连接,激活函数sigmoid
model.add(layers.Dense(1, activation='sigmoid'))
#输出模型各层的参数状况
model.summary()
from keras import optimizers

model.compile(loss='binary_crossentropy',
              optimizer=optimizers.RMSprop(lr=1e-4),
              metrics=['acc'])

La siguiente figura muestra la comparación estructural entre no agregar abandono y agregar abandono:

Imagen 1 Imagen 2

6.3 Modelo de entrenamiento

train_datagen = ImageDataGenerator(
    rescale=1./255,
    rotation_range=40,
    width_shift_range=0.2,
    height_shift_range=0.2,
    shear_range=0.2,
    zoom_range=0.2,
    horizontal_flip=True,)
# Note that the validation data should not be augmented!
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(
        # This is the target directory
        train_dir,
        # All images will be resized to 150x150
        target_size=(150, 150),
        batch_size=32,
        # Since we use binary_crossentropy loss, we need binary labels
        class_mode='binary')
validation_generator = test_datagen.flow_from_directory(
        validation_dir,
        target_size=(150, 150),
        batch_size=32,
        class_mode='binary')
history = model.fit_generator(
      train_generator,
      steps_per_epoch=100,
      epochs=100,
      validation_data=validation_generator,
      validation_steps=50)
model.save('G:\\Cat_And_Dog\\kaggle\\cats_and_dogs_small_2.h5')

El proceso de formación es el siguiente:

Visualización de resultados:

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(len(acc))
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()

Imagen 1 Imagen 2

Según la figura anterior, en comparación con el modelo de referencia, se puede encontrar claramente que la tendencia general de pérdida es cada vez menor. Para el modelo obtenido al realizar solo la mejora de la imagen y el modelo obtenido al realizar la mejora de la imagen y agregar una capa de eliminación, se puede encontrar que el primero fluctuará más durante el proceso de entrenamiento y el segundo es menos preciso que el primero. Aunque la precisión de ambos métodos se ha reducido, ambos evitan el sobreajuste.

Resumir

A través de este experimento, obtuvimos una comprensión profunda de la aplicación del modelo CNN y mejoramos el rendimiento y la solidez del modelo a través de tecnologías como la mejora de datos y las capas de abandono. A través de experimentos, descubrimos que simplemente realizar operaciones de aumento de datos puede mejorar significativamente la precisión del modelo. El aumento de datos puede aumentar efectivamente la cantidad de muestras de entrenamiento y permitir que el modelo aprenda mejor las características de los datos, mejorando así la precisión de la clasificación.


Referencia:
https://blog.csdn.net/weixin_44404350/article/details/116292586
https://blog.csdn.net/kevinjin2011/article/details/124944728
https://blog.csdn.net/qq_43279579/article/ detalles/117298169

Supongo que te gusta

Origin blog.csdn.net/apple_52030329/article/details/131495046
Recomendado
Clasificación