¿Cómo crear un conjunto de datos de aprendizaje automático desde cero?

El libro de cocina de mi abuela se encuentra con el aprendizaje automático parte I

Figura 1: Libro de cocina alemán antiguo de mi abuela: “Praktisches Kochbuch” por Henriette Davidis

Mi abuela fue una cocinera excepcional. Entonces, cuando recientemente encontré su viejo libro de cocina, traté de leer algunas de las recetas, con la esperanza de poder recrear algunos de los platos que disfruté cuando era niño. Sin embargo, esto resultó más difícil de lo esperado ya que el libro se imprimió alrededor de 1911 en una tipografía llamada f raktur . Desafortunadamente, el tipo de letra fraktur se desvía de los tipos de letra modernos en varios casos. Por ejemplo, la letra “A” parece una “U” en fraktur y cada vez que veo una “Z” en fraktur leo un “3” (ver Figura 2).

Figura 2: Ejemplos de letras en fraktur y en una tipografía moderna (Calibri)

Así surgió la idea de desarrollar una tubería que creará una traducción en vivo de la Fraktur letras en una tipografía moderna. Con tal herramienta en la mano, podríamos leer fácilmente las recetas y enfocarnos en la parte de cocinar en lugar de descifrar el libro.

Afortunadamente, existen muchas herramientas excelentes de código abierto que nos ayudarán a desarrollar dicha canalización. Sin embargo, algunos aspectos de este proyecto deben construirse desde cero.

Principalmente necesitamos un conjunto de datos para entrenar nuestros algoritmos de aprendizaje automático. Y esto es en lo que nos centraremos en este artículo.

Pero antes de comenzar con el conjunto de datos, echemos un vistazo rápido a todas las tareas que tenemos por delante:

  1. Detectar letras individuales en una imagen
  2. Crear un conjunto de datos de entrenamiento a partir de estas letras
  3. Entrenar un algoritmo para clasificar las letras
  4. Utilice el algoritmo entrenado para clasificar letras individuales (en línea)

Cubriremos los dos primeros temas de este artículo y continuaremos con los temas 3 y 4 en un segundo y tercer artículo. Esto debería darnos suficiente espacio para explorar cada una de las tareas en detalle.

También como una observación general : en estos artículos no nos centraremos en cómo implementar cada algoritmo desde cero. En cambio, veremos cómo podemos conectar diferentes herramientas para traducir el libro de cocina a una tipografía moderna.

Si está más interesado en el código que en las explicaciones, también puede ir directamente a los Cuadernos Jupyter en Github .

Detección de letras en una imagen

Entonces, la primera tarea es encontrar una manera de extraer letras individuales de las páginas del libro de cocina. Esta es la base de todo lo que sigue. En el siguiente paso, podemos crear un conjunto de datos a partir de las letras extraídas y finalmente entrenar un clasificador en él.

Figura 3: Imagen de un párrafo del libro de cocina

Las entradas a nuestra tubería siempre serán imágenes de páginas del libro de cocina similar al que se muestra en la Figura 3 anterior. Estas entradas pueden ser imágenes individuales de alta resolución de una cámara de teléfono inteligente o una secuencia de imágenes de una cámara web.

Lo que tenemos que asegurar es que cada imagen, independientemente de su origen, se procese de manera que el algoritmo de detección pueda encontrar todas las letras individuales. Lo primero que debe recordar aquí es que las cámaras digitales almacenan imágenes en tres canales separados: R ed, G reen y B lue ( RGB ). Pero en nuestro caso, estos tres canales contienen información redundante ya que las letras se pueden identificar en cada uno de estos tres canales por separado. Por lo tanto, primero convertiremos todas las imágenes a escala de grises.

Como resultado, en lugar de tres canales, solo tenemos que lidiar con un canal. Además, también redujimos la cantidad de datos a 1/3, lo que debería mejorar el rendimiento. Pero nuestro algoritmo de detección enfrentará otro problema: condiciones variables de rayos. Esto complica la separación de las letras del fondo a medida que cambia el contraste en la imagen.

Para resolver esto, utilizaremos una técnica llamada umbral adaptativo que utiliza píxeles cercanos para crear umbrales locales que luego se utilizan para binarizar la imagen. Como resultado, la imagen procesada solo consistirá en píxeles en blanco y negro; no más gris Luego podemos optimizar aún más la imagen para la detección de letras al eliminar el ruido con un filtro de desenfoque medio.

El siguiente código describe una función de Python que realiza la conversión de imágenes de RGB a blanco y negro con la ayuda de la biblioteca openCV . El resultado de este paso de procesamiento se ejemplifica en la Figura 4.

 # Defina una función que convierta una imagen en una imagen con umbral 
 def convert_image (img, blur = 3): 
 # Convertir a escala de grises 
 conv_img = cv2.cvtColor (img, cv2.COLOR_BGR2GRAY)
     # Umbral adaptativo para binarizar la imagen 
 conv_img = cv2.adaptiveThreshold (conv_img, 255, 
 cv2.ADAPTIVESTH_, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 2). , 4)
     # Desenfoque la imagen para reducir el ruido 
 conv_img = cv2.medianBlur (conv_img, blur) 
    
     return conv_img
Figura 4: La imagen de la izquierda muestra la imagen original, la imagen del medio muestra el gris versión a escala y la imagen de la derecha muestra el resultado trillado y borroso.

Ok ahora que procesamos la imagen es hora de detectar las letras. Para esto, podemos usar el método findContours de la biblioteca openCV . El código se reduce a una sola línea llamada por la función a continuación.

Luego podemos asignar los cuadros delimitadores de los contornos encontrados por esta función nuevamente en la imagen RGB original para ver lo que realmente se detectó (Figura 5).

 # Defina una función que detecte contornos en la imagen convertida 
 def extract_char (conv_img): 
 # Buscar contornos 
 _, ctrs, _ = cv2.findContours (conv_img, cv2.RETR_TREE, 
 cv2.CHAIN_APPROX_SIMPLE)
     return ctrs
Figura 5: Resultado de la detección de ctrs

Figura 5: Resultado de contornos asignados de nuevo a la imagen original.

De la Figura 5 podemos ver que la detección funciona bastante bien. Sin embargo, en algunos casos no se detectan letras, p. algunas de las “i” s al final de la línea 1.

Y en otros casos, una sola letra se divide en dos letras, p. “b” al final de la última línea.

Otra cosa a tener en cuenta es que algunas combinaciones de letras se convierten esencialmente en una sola letra y son detectadas en consecuencia por el algoritmo, dos ejemplos de la Figura 5 son “ch” y “ck” . Más adelante veremos cómo lidiar con estos problemas.

Pero por ahora podemos seguir con el resultado actual. Entonces, dado que tenemos los cuadros delimitadores de cada letra, podemos recortarlos y guardarlos como imágenes individuales (.png) en una carpeta en nuestro disco duro. Si está interesado en cómo hacer esto, eche un vistazo al Cuaderno Jupyter .

Creación del conjunto de datos

Tener un conjunto de letras extraídas es bueno pero necesitamos organizarlas de alguna manera que el conjunto de datos se vuelve útil para nosotros. Para esto tenemos que hacer dos cosas:

  1. Eliminar imágenes que no contienen letras. Esto puede ser artefactos de todo tipo, p. una mancha en una de las páginas o solo partes de una carta como hemos visto en la Figura 5.
  2. Agrupe todas las imágenes restantes. Esto significa que todas las letras “A” van a una carpeta, todas las letras “B” van a otra carpeta y así sucesivamente.

Ambos puntos anteriores son, en principio, Fácil de resolver. Sin embargo, dado que extrajimos varios miles de cartas potenciales de muchas páginas del libro, representan una tarea larga y tediosa.

En el lado positivo, podemos automatizar la primera ronda de la agrupación para que luego “solo” tengamos que corregir el resultado de este paso de preagrupación. Pero hay una cosa más que hacer antes de que podamos comenzar con este paso de agrupamiento: tenemos que llevar todas las imágenes de las letras extraídas al mismo tamaño.

Figura 6: Gráfico de dispersión de los primeros 3 puntajes de PCA. El color representa el grupo.

La razón de esto es que los algoritmos que usaremos para la agrupación y también para la clasificación, esperan un tamaño fijo de las imágenes de entrada.

Pero como podemos ver en los cuadros delimitadores de la Figura 5, cada imagen tiene actualmente una forma diferente. Para superar esta variedad de tamaños de imagen, utilizaremos el método redimensionar de la biblioteca openCV y llevaremos todas las imágenes al mismo tamaño.

Luego almacenaremos las imágenes en una matriz de Numpy y las normalizaremos calculando sus puntuaciones z . La normalización es importante para el siguiente paso, que es reducir el número de dimensiones de cada imagen con el Análisis de componentes principales (PCA).

Los puntajes de los primeros componentes principales serán entonces la entrada al algoritmo de agrupación de K-Means que hará la agrupación previa de las letras por nosotros. Si está interesado en los detalles de este procedimiento y el algoritmo K-Means, puede consultar el Jupyter Notebook de este artículo  .

Los resultados de la agrupación de K-Means se visualizan en la Figura 6, donde el color de cada punto indica el grupo al que pertenece. Mirando la Figura 6 parece que algunos puntos de datos forman grupos que también fueron asignados al mismo grupo por el algoritmo K-Means. Sin embargo, es difícil juzgar solo en la Figura 6 qué tan bien funcionó el agrupamiento.

Una mejor manera de evaluar los resultados es mover todas las imágenes de un clúster a una carpeta separada y luego mirar el contenido de cada clúster. La Figura 7 muestra las imágenes en una carpeta de ejemplo donde el agrupamiento funcionó muy bien. El siguiente paso sería cambiar el nombre de esta carpeta a “a”.

En otros casos, la agrupación no funcionó tan bien. La Figura 8 muestra un ejemplo de un grupo que contiene diferentes tipos de letras. Si bien la mayoría de las letras son “n” s, también hay algunas “K” y “u” s en el grupo.

Sin embargo, podemos solucionarlo fácilmente buscando los grupos “K” y “u” y mover las imágenes allí. Posteriormente, la carpeta se puede renombrar a “n”.

Figura 8: Carpeta de ejemplo con datos agrupados que contiene letras diferentes

Continuaremos así hasta que todos los grupos se limpien y se renombren como se describió anteriormente. El resultado debería ser similar a la Figura 9, donde las letras mayúsculas están marcadas con “_”.

Figura 9: Ejemplo de cómo se deben organizar los datos después de la agrupación y los ajustes manuales.

Entonces, obviamente, necesitaba algún manual trabajar para poner los datos en forma. Sin embargo, pudimos automatizar una gran parte del trabajo al agrupar previamente los datos con PCA y K-Means .

El conjunto de datos ahora está limpio y organizado, pero para que trabajemos eficientemente necesitamos guardarlo de una manera más conveniente que las carpetas en el disco duro.

Conversión del conjunto de datos al formato IDX

El paso final, para envolver todo esto es, por lo tanto, convertir el conjunto de datos al formato de datos IDX. Es posible que ya esté familiarizado con este formato, ya que el famoso dataset MNIST se guarda de la misma manera.

Solo aquí, en lugar de leer los datos de un archivo IDX, tenemos que escribirlo. Lo haremos utilizando un idx_converter que toma una estructura de archivo tal como la configuramos arriba y la guarda directamente en el Formato IDX. La salida será de dos archivos: un archivo con las imágenes y un segundo archivo con las etiquetas.

Dado que más adelante queremos entrenar a un clasificador en los datos, ya deberíamos dividir las imágenes en un conjunto de datos de entrenamiento y prueba. Para esto, trasladaremos el 30% de las letras a una carpeta de prueba, mientras que las letras restantes permanecerán en la carpeta de entrenamiento. Puede consultar el código en el Jupyter Notebook para obtener detalles sobre la implementación de este procedimiento.

Ahora que creamos un conjunto de datos fraktur desde cero, podemos pasar al siguiente artículo en el que compararemos el rendimiento de varios algoritmos clasificadores para seleccionar uno para la traducción en vivo del libro de cocina.

Mientras tanto, puede consultar el código completo de este artículo aquí.

Dejá un comentario