Red neuronal convolucional (CNN) para el reconocimiento de dígitos escritos a mano

Enlace original: https://blog.csdn.net/polyhedronx/article/details/94476824

1. Introducción La
publicación anterior del blog utiliza una red neuronal completamente conectada con una sola capa oculta, combinada con algunas estrategias de optimización de las redes neuronales, como la tasa de aprendizaje de decaimiento exponencial, la regularización, la función de activación Relu y el algoritmo de optimización Adam. Contiene 100 capas ocultas La red neuronal de neuronas alcanza un 98% de precisión en el reconocimiento de dígitos escritos a mano en el conjunto de datos del MNIST. Sin embargo, las redes neuronales completamente conectadas también tienen limitaciones. Incluso si se utiliza una red profunda, muchos nodos ocultos y una gran cantidad de iteraciones, es difícil obtener una precisión de más del 99% en el conjunto de datos MNIST. Sin embargo, la aparición de redes neuronales convolucionales ha resuelto este problema, pudiendo finalmente alcanzar una precisión superior al 99%, lo que satisface las necesidades de algunos sistemas de reconocimiento de alta precisión.

2. El principio básico de la red neuronal convolucional
2.1
Cada píxel en la imagen de la operación de convolución está estrechamente relacionado con los píxeles circundantes, pero no está necesariamente relacionado con píxeles que están demasiado lejos. Esta es la visión humana El concepto de campos receptivos, cada El campo receptivo solo recibe señales de un área pequeña. Los píxeles de esta pequeña área están relacionados entre sí. Cada neurona no necesita recibir toda la información de píxeles, solo necesita recibir píxeles locales como entrada y luego integrar la información local recibida por estas neuronas. Obtenga información global.

La operación de convolución de la imagen se refiere a comenzar desde la esquina superior izquierda de la imagen, usar una plantilla de convolución para deslizar sobre la imagen y multiplicar el valor de gris del píxel en el píxel de la imagen por el valor en el núcleo de convolución correspondiente en cada posición. La suma de todos los resultados multiplicados se usa como el valor del resultado de convolución correspondiente al píxel central del núcleo de convolución, y el proceso de deslizamiento para obtener el resultado de convolución se completa en todas las posiciones de la imagen de acuerdo con este paso. Esta plantilla de convolución generalmente se denomina kernel de convolución, o filtro, en una red neuronal convolucional. La siguiente figura muestra un diagrama esquemático de una parte del proceso de operación de convolución de imagen. La figura usa un kernel de convolución de 3 a 3 para realizar un kernel de convolución de 5 a Imagen de tamaño 5. Operación de convolución.

La operación de convolución de la imagen se puede expresar como, donde:

Entre ellos, está la matriz de la imagen a convolucionar, es la función del núcleo de convolución y es la imagen de salida de la operación de convolución de la imagen. En el aprendizaje profundo, tanto la matriz de imágenes de entrada como la matriz de resultados de salida se denominan mapas de características.

2.2 Operación de agrupación
Después de obtener el mapa de características bidimensional a través de la capa convolucional, el tamaño del mapa de características sigue siendo muy grande en circunstancias normales. Si estas características se envían directamente al clasificador para su clasificación, la cantidad de cálculo será muy grande. Además, también puede haber problemas de ajuste excesivo, por lo que no es conveniente utilizar directamente estos mapas de características para la clasificación. La operación de agrupación es una tecnología diseñada para resolver estos problemas. Agrega y cuenta entidades en diferentes posiciones en la matriz del mapa de características para condensar características.

El diagrama esquemático de la operación de agrupación se muestra en la figura anterior. Hay dos operaciones de agrupación de uso común, una es la agrupación promedio, la salida de la operación de agrupación promedio es el valor promedio de las características en el mapa de características de entrada dentro del rango correspondiente del núcleo de la agrupación; la otra es la agrupación máxima, la máxima operación de agrupación La salida es el valor máximo de la característica en el mapa de características de entrada dentro del rango correspondiente del kernel de agrupación. Puede verse que la operación de agrupación es una operación de convolución de imágenes especial.

La operación de agrupación puede mejorar significativamente el efecto de la red neuronal convolucional, que se debe principalmente a la concentración de características, la dimensión reducida del mapa de características y el fenómeno de sobreajuste frecuente de la red neuronal convolucional se reducirá correspondientemente. Por lo tanto, dado que la información de características en un cierto rango está condensada, la operación de agrupación también tiene el efecto de mejorar la invariancia de traducción en un rango pequeño en la red neuronal convolucional.

2.3 Capa convolucional La
red neuronal convolucional general se compone de múltiples capas convolucionales y las siguientes operaciones se realizan normalmente en cada capa convolucional.

La imagen es filtrada por múltiples núcleos de convolución diferentes y sesgada para extraer características locales.Cada núcleo de convolución mapeará una nueva imagen 2D.
El resultado de la salida de filtrado del kernel de convolución anterior se procesa con una función de activación no lineal.En la actualidad, la función ReLU se utiliza generalmente.
A continuación, se agrupa el resultado de la función de activación (es decir, se reduce el muestreo, por ejemplo, 2 2 imágenes se reducen a 1 1 imágenes). Actualmente, la agrupación máxima se utiliza generalmente para conservar las características más significativas y mejorar la tolerancia a la distorsión del modelo. .
Tenga en cuenta que generalmente hay varios núcleos de convolución diferentes en una capa convolucional, porque cada núcleo de convolución solo puede extraer un tipo de característica de imagen, y el número de núcleos de convolución se puede aumentar para extraer algunas características. Cada núcleo de convolución corresponde a una nueva imagen mapeada después del filtrado, y cada píxel de la misma imagen nueva proviene del mismo núcleo de convolución, que es el peso compartido del núcleo de convolución. El propósito de compartir los parámetros de peso del kernel de convolución es muy simple, lo que reduce la complejidad del modelo, reduce el sobreajuste y reduce la cantidad de cálculo.

El número de pesos que la capa convolucional necesita entrenar solo está relacionado con el tamaño del núcleo de convolución y el número de núcleos de convolución.Podemos usar muy pocos parámetros para procesar imágenes de cualquier tamaño. Las características extraídas por cada capa convolucional se combinan de forma abstracta en características de orden superior en capas posteriores.

2.4 Red neuronal convolucional
La diferencia entre la red neuronal convolucional y la red perceptrón multicapa es que la red neuronal convolucional contiene varios extractores de características compuestos por capas convolucionales y capas de agrupación, que pueden reducir eficazmente el número de parámetros y reducir en gran medida el número de parámetros. complejidad del modelo, reduciendo así el riesgo de sobreajuste. Al mismo tiempo, le da a la red neuronal convolucional tolerancia a la traducción y una ligera deformación, y mejora la capacidad de generalización del modelo.

La estructura del famoso LeNet5 se muestra en la siguiente figura, que contiene tres capas convolucionales, una capa completamente conectada y una capa conectada gaussiana. En términos generales, se pueden diseñar razonablemente diferentes redes neuronales convolucionales para diferentes conjuntos de datos y diferentes tamaños de imágenes de entrada para hacer frente a diferentes problemas prácticos.

3. El diseño de una red neuronal convolucional para el problema de reconocimiento de dígitos escritos a mano El problema de reconocimiento de dígitos escritos a mano
es relativamente simple, por lo que se utilizan dos capas convolucionales y una capa completamente conectada para construir una red neuronal convolucional simple.

3.1 Interpretación de las funciones principales de TensorFlow
(1) tf.nn.conv2d

Dada una entrada de 4 dimensiones y un tensor de filtro, calcule una convolución de 2 dimensiones.

tf.nn.conv2d(
    input,
    filter=None,
    strides=None,
    padding=None,
    use_cudnn_on_gpu=True,
    data_format='NHWC',
    dilations=[1, 1, 1, 1],
    name=None,
    filters=None
)

input: se
refiere a la imagen de entrada que debe convolucionar. Es un tensor de 4 dimensiones. El tipo es half, bfloat16, float32 y float64. El orden de las dimensiones se establece de acuerdo con data_format y el valor predeterminado es [batch, in_height, in_width, in_channels]. El significado específico de la forma es [el número de imágenes en un lote durante el entrenamiento, la altura de la imagen, el ancho de la imagen, el número de canales de imagen].

filter: Es
equivalente al kernel de convolución en CNN. Requiere un Tensor, que debe ser el mismo que el tipo de entrada. Con una forma como [filter_height, filter_width, in_channels, out_channels], el significado específico es [la altura del núcleo de convolución, el ancho del núcleo de convolución, el número de canales de imagen, el número de núcleos de convolución]. Tenga en cuenta que la tercera dimensión in_channels es la cuarta dimensión de la entrada del parámetro.

zancadas: El
tamaño de paso de la ventana deslizante en cada dimensión de la imagen durante la convolución. Este es un vector unidimensional, el orden de las dimensiones se establece de acuerdo con data_format, el valor predeterminado es [NHWC], el tipo es int o int list y la longitud es 1, 2 o 4. N y C se establecen en 1 de forma predeterminada, y el formato general es [1, zancada [1], zancada [2], 1]. En la mayoría de los casos, debido a que los pasos de altura y ancho se establecen en el mismo valor, generalmente es [1, stride, stride, 1].

padding:
La cantidad de tipo de cadena, que solo puede ser "SAME" o "VALID", que indica si se requiere padding. Debido a que el tamaño de salida después de la convolución es generalmente más pequeño que la entrada, se puede usar el relleno para obtener una salida con el mismo tamaño que la entrada en este momento.

 strides=[1, 1, 1, 1], padding="VALID"          strides=[1, 1, 1, 1], padding="SAME"

use_cudnn_on_gpu:
tipo bool, si se usa aceleración cudnn, el valor predeterminado es verdadero.

data_format
especifica el formato de los datos de entrada y salida. Tipo de cadena opcional, uno de "NHWC" o "NCHW", el predeterminado es "NHWC". Cuando se utiliza el formato predeterminado "NHWC", los datos se almacenan en el siguiente orden: [lote, alto, ancho, canales].

dilataciones El
factor de dilatación de cada dimensión de entrada. Este es un vector unidimensional, el orden de dimensión se establece de acuerdo con data_format, el valor predeterminado es [NHWC], el tipo es int o int list, la longitud es 1, 2 o 4, y el valor se establece en todos 1 por defecto. Si se da un solo valor, se copia a H y W.

Dado un tensor de entrada [batch, in_height, in_width, in_channels] y un tensor de filtro / kernel [filter_height, filter_width, in_channels, out_channels], realice las siguientes operaciones:

El filtro aplanado es una matriz bidimensional con forma [filter_height * filter_width * in_channels, output_channels].
Extraiga el bloque de imagen de la entrada de acuerdo con el tamaño del filtro para formar un tensor virtual de tamaño [lote, out_height, out_width, filter_height * filter_width * in_channels].
Para cada bloque de imagen, multiplique la matriz de filtro a la derecha.
La fórmula de cálculo es:

Algunos ejemplos:

Ingrese [1,3,3,1], el filtro es [2,2,1,1], padding = 'SAME', el método de llenado es como se muestra en la figura:

Ingrese [1,2,2,1], el filtro es [3,3,1,1], padding = 'SAME', y el método de llenado es como se muestra en la figura:

Para multicanal, la entrada [1x3x3x2] es una imagen de 3x3 con 2 canales, el filtro es [2x2x2x1], el tamaño del paso es 1, el relleno = VALID y la salida es [1x2x2x1], como se muestra en la figura:

(2) tf.nn.max_pool

La realización de la operación de agrupación máxima en la entrada se puede considerar una operación de convolución especial.

tf.nn.max_pool(
    value,
    ksize,
    strides,
    padding,
    data_format='NHWC',
    name=None,
    input=None
)

El valor
debe ser una entrada agrupada. Generalmente, la capa agrupada es seguida por la capa convolucional, por lo que la entrada suele ser un mapa de características, que sigue siendo una forma como [lote, altura, ancho, canales].


El tamaño de la ventana de agrupación de ksize es un vector de cuatro dimensiones, generalmente [1, height, width, 1], porque no queremos agrupar en lotes y canales, por lo que estas dos dimensiones se establecen en 1.

un ejemplo:

Suponiendo que la entrada es un gráfico de dos canales, es decir, valor = [1,4,4,2], el tamaño de la ventana de agrupación es ksize = [1,2,2,1], y los resultados son los siguientes.

Programa de prueba:

import tensorflow as tf
 
a = tf.constant([
    [[1.0, 2.0, 3.0, 4.0],
     [5.0, 6.0, 7.0, 8.0],
     [8.0, 7.0, 6.0, 5.0],
     [4.0, 3.0, 2.0, 1.0]],
    [[4.0, 3.0, 2.0, 1.0],
     [8.0, 7.0, 6.0, 5.0],
     [1.0, 2.0, 3.0, 4.0],
     [5.0, 6.0, 7.0, 8.0]]
])
 
a = tf.reshape(a, [1, 4, 4, 2])
 
pooling = tf.nn.max_pool(value=a, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
with tf.Session() as sess:
    print("image:")
    image = sess.run(a)
    print(image)
    print("reslut:")
    result = sess.run(pooling)
    print(result)

(3) abandono de tf.nn.

Establezca aleatoriamente algunos nodos en 0. Durante el entrenamiento, descartamos aleatoriamente algunos de los datos del nodo para reducir el ajuste excesivo y mantenemos todos los datos durante la predicción para obtener un mejor rendimiento de la predicción. Generalmente se usa en la capa completamente conectada.

En TensorFlow, sus parámetros se definen de la siguiente manera:

tf.nn.dropout(
    x,
    keep_prob=None,
    noise_shape=None,
    seed=None,
    name=None,
    rate=None
)

Entre ellos, el parámetro x representa la entrada, que es un tensor de punto flotante. keep_prob es la probabilidad de que se seleccione la neurona, y rate es la probabilidad de que se descarten los elementos en x. Obviamente, keep_prob = 1-rate (el sitio web oficial dice que se descartará keep_prob y se recomienda usar rate en su lugar). Keep_prob es un marcador de posición durante la inicialización, keep_prob = tf.placeholder (tf.float32), tensorflow establece el valor específico de keep_prob en tiempo de ejecución, por ejemplo, keep_prob: 0.5. noise_shape es un tensor de tipo int32 unidimensional, que representa una forma generada aleatoriamente con el indicador "reservar / descartar". semilla es la semilla de número aleatorio, que es un número entero.

Para cada elemento de la entrada x, la salida 0 con la tasa de probabilidad; de lo contrario, la entrada se ampliará en 1 / (1-tasa) veces para mantener la expectativa de la suma de salida sin cambios.

De forma predeterminada, cada elemento se mantiene o se elimina de forma independiente. Si se especifica noise_shape, solo las dimensiones de noise_shape [i] == shape (x) [i] son ​​independientes (los elementos en noise_shape solo pueden ser 1 o el elemento correspondiente en x.shape). Por ejemplo, si shape (x) = [k, l, m, n] y noise_shape = [k, 1, 1, n], cada lote y canal seguirá siendo independiente, y cada fila o columna estará reservada o todo cero.

A continuación se dan algunos ejemplos:

Suponiendo que hay dos imágenes en una entrada por lotes, cada imagen es una imagen de dos canales con un tamaño de 3 × 3, es decir, x = [2,3,3,2]; keep_prob = 0.5, y el resultado es como sigue.

Programa de prueba:

import tensorflow as tf
 
b = tf.constant([
    [[1.0, 2.0, 3.0],
     [4.0, 5.0, 6.0],
     [7.0, 8.0, 9.0]],
    [[9.0, 8.0, 7.0],
     [6.0, 5.0, 4.0],
     [3.0, 2.0, 1.0]],
    [[1.0, 2.0, 3.0],
     [4.0, 5.0, 6.0],
     [7.0, 8.0, 9.0]],
    [[9.0, 8.0, 7.0],
     [6.0, 5.0, 4.0],
     [3.0, 2.0, 1.0]]
])
 
b = tf.reshape(b, [2, 3, 3, 2])
 
drop = tf.nn.dropout(x=b, keep_prob=0.5, noise_shape=[2, 3, 3, 2])
with tf.Session() as sess:
    print("image:")
    image = sess.run(b)
    print(image)
    print("result:")
    result = sess.run(drop)
    print(result)

noise_shape = [2,3,3,2] o noise_shape = None, la salida se establece aleatoriamente en cero y otros números se amplían en 1 / (1-0.5) = 2 veces:

noise_shape = [1,3,3,2], el patrón de puesta a cero es el mismo entre diferentes imágenes en el lote de salida:

noise_shape = [2,1,3,2], el patrón de puesta a cero entre diferentes líneas de salida es el mismo:

noise_shape = [2,3,1,2], el patrón de puesta a cero entre diferentes columnas de salida es el mismo:

noise_shape = [2,3,3,1], el patrón de puesta a cero entre diferentes canales de salida es el mismo:

3.2 Programa y resultados La
versión en ejecución del programa es: python–> 3.7.3, tensorflow–> 1.13.1

El peso se inicializa como un número aleatorio que obedece a una distribución normal truncada con una desviación estándar de 0,1. Debido a que se usa la función ReLU, el sesgo se inicializa a un valor constante de 0.1 para evitar nodos muertos.

La imagen de entrada es una imagen en escala de grises de 28 × 28. El tamaño del núcleo de convolución de la primera capa convolucional se establece en 5 × 5, 1 canal de color, el número de núcleos de convolución (canal de salida) se establece en 32 (es decir, cuántos tipos de características extraerá esta capa convolucional), ancho Los pasos de movimiento de y alto son ambos 1, y se realiza el relleno (relleno = "SAME", el tamaño de la imagen de salida es el mismo que la imagen de entrada); la función de activación es la función ReLU; el tamaño de la ventana de la operación de agrupación es 2 × 2, el ancho y la altura son Los pasos móviles son ambos 2.

El tamaño del núcleo de convolución de la segunda capa convolucional se establece en 5 × 5, el canal de entrada es 32 (es decir, el canal de salida de la capa anterior), el número de núcleos de convolución (canal de salida) se establece en 64 y el los demás parámetros son los mismos que los del primer volumen La acumulación es la misma.

Después de las dos agrupaciones máximas anteriores con un tamaño de paso de 2 × 2, la longitud del lado se convierte en 1/4 del original, es decir, el tamaño de la imagen se cambia de 28 × 28 a 7 × 7. El número de núcleos de convolución de la segunda capa de convolución es 64 y el tamaño del tensor de salida es 7 × 7 × 64. Conviértalo en un vector unidimensional y luego conecte una capa completamente conectada, el nodo oculto es 1024 y use la función de activación ReLU.

Para reducir el ajuste excesivo, se usa una capa de abandono a continuación. Keep_prob se establece en 0.5 durante el entrenamiento y 1 durante las pruebas. La salida de la capa Dropout se conecta a una capa softmax para obtener la salida de probabilidad final.

La función de pérdida se define como entropía cruzada, el optimizador usa Adam y la tasa de aprendizaje se establece en 1e-4. El tamaño del lote es 50 y el número de entrenamiento es 20000. El procedimiento es el siguiente.

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.pyplot as plt
 
# move warning
import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
old_v = tf.logging.get_verbosity()
tf.logging.set_verbosity(tf.logging.ERROR)
 
 
# weight initialization
def weight_variable(shape):
    return tf.Variable(tf.truncated_normal(shape, stddev=0.1))
 
 
# bias initialization
def bias_variable(shape):
    return tf.Variable(tf.constant(0.1, shape=shape))
 
 
# convolution operation
def conv2d(x, W):
    return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding="SAME")
 
 
# pooling operation
def max_pool_2x2(x):
    return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME")
 
 
# Convolutional Neural Network
def cnn2(x):
    x_image = tf.reshape(x, [-1, 28, 28, 1])
 
    # Layer 1: convolutional layer
    W_conv1 = weight_variable([5, 5, 1, 32])
    b_conv1 = bias_variable([32])
    h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
    h_pool1 = max_pool_2x2(h_conv1)
 
    # Layer 2: convolutional layer
    W_conv2 = weight_variable([5, 5, 32, 64])
    b_conv2 = weight_variable([64])
    h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
    h_pool2 = max_pool_2x2(h_conv2)
 
    # Layer 3: full connection layer
    W_fc1 = weight_variable([7 * 7 * 64, 1024])
    b_fc1 = bias_variable([1024])
    h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * 64])
    h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
 
    # dropout layer
    keep_prob = tf.placeholder("float")
    h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
 
    # output layer
    W_fc2 = weight_variable([1024, 10])
    b_fc2 = bias_variable([10])
    y_conv = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)
 
    return y_conv, keep_prob
 
 
# read data
mnist = input_data.read_data_sets('MNIST_data/', one_hot=True)
 
# input layer
x = tf.placeholder("float", shape=[None, 784])
y_ = tf.placeholder("float", shape=[None, 10])
 
# cnn
y_conv, keep_prob = cnn2(x)
 
# loss function & optimization algorithm
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_*tf.log(y_conv), reduction_indices=[1]))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
 
# new session
sess = tf.Session()
sess.run(tf.global_variables_initializer())
 
# train
losss = []
accurs = []
steps = []
correct_predict = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_predict, "float"))
for i in range(20000):
    batch = mnist.train.next_batch(50)
    sess.run(train_step, feed_dict={
    
    x: batch[0], y_: batch[1], keep_prob: 0.5})
 
    if i % 100 == 0:
        loss = sess.run(cross_entropy, {
    
    x: batch[0], y_: batch[1], keep_prob: 1.0})
        accur = sess.run(accuracy, feed_dict={
    
    x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0})
        steps.append(i)
        losss.append(loss)
        accurs.append(accur)
        print('Steps: {} loss: {}'.format(i, loss))
        print('Steps: {} accuracy: {}'.format(i, accur))
 
# plot loss
plt.figure()
plt.plot(steps, losss)
plt.xlabel('Number of steps')
plt.ylabel('Loss')
 
plt.figure()
plt.plot(steps, accurs)
plt.hlines(1, 0, max(steps), colors='r', linestyles='dashed')
plt.xlabel('Number of steps')
plt.ylabel('Accuracy')
plt.show()
 
tf.logging.set_verbosity(old_v)

La curva de la precisión de la pérdida y el conjunto de prueba con el número de entrenamiento se muestra en la figura siguiente, y la precisión final es de aproximadamente el 99,2%.
Inserte la descripción de la imagen aquí

Inserte la descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/qq_42293496/article/details/110005450
Recomendado
Clasificación