Compartir a través de


Este artículo proviene de un motor de traducción automática.

Ejecución de pruebas

Distorsión del conjunto de datos de imágenes de MNIST

James McCaffrey

Descargar el código de muestra

James McCaffreyEl conjunto de datos mixto de Instituto Nacional de estándares y tecnología (MNIST) es una colección de 70.000 pequeñas imágenes de dígitos escritos a mano. Los datos fue creados para actuar como un referente para los algoritmos de reconocimiento de imagen. Aunque MNIST las imágenes son pequeñas (28 x 28 pixeles) y sólo hay 10 dígitos posibles (cero a nueve) a reconocer y hay 60.000 imágenes de formación para la creación de un modelo de reconocimiento de imagen (con 10.000 imágenes tendidas a probar la exactitud de un modelo), la experiencia ha demostrado que reconocer las imágenes MNIST es un problema difícil.

Una manera de lidiar con los problemas de patrón difícil clasificación, incluyendo el reconocimiento de la imagen, consiste en utilizar más datos de entrenamiento. Y una forma inteligente de generar mediante programación más datos del entrenamiento es distorsionar cada imagen original. Echa un vistazo al programa de demostración en figura 1. La figura muestra el dígito 4 en forma original MNIST de la izquierda y el dígito después de distorsión con deformación elástica de la derecha. Los parámetros en la esquina superior derecha de la aplicación de demostración indican que la distorsión depende de los valores de una semilla aleatoria desplazamiento, un tamaño del núcleo y desviación estándar y una intensidad.

A Distorted MNIST Image
Figura 1 MNIST imagen distorsionada

Es probable que usted necesitará distorsionar imágenes en ambientes de trabajo más, pero se puede encontrar la información en este artículo útiles por tres razones. En primer lugar, comprender exactamente cómo funciona distorsión de la imagen al ver código real ayudará a entender muchos artículos de reconocimiento de imágenes. En segundo lugar, varias de las técnicas de programación utilizadas en distorsión de la imagen pueden ser útiles en otros escenarios de programación más comunes. Y en tercer lugar, tal vez sólo encuentre distorsión de la imagen un tema interesante para su propio bien.

Este artículo asume que usted tiene habilidades de programación de nivel avanzado pero no asume que sabe algo de distorsión de la imagen. El programa de demostración está codificado en C# y hace uso extenso de Microsoft .NET Framework refactorización a un lenguaje .NET no sería difícil. La demo tiene más normales de comprobación de errores retirada para mantener el tamaño del código pequeño y despejen las ideas principales. Porque la demo es una aplicación Windows Form creada por Visual Studio, gran parte del código se relaciona con la interfaz de usuario y se extiende por varios archivos. Sin embargo, he refactorizar el código de demostración para un sola C# archivo de código fuente, que está disponible en msdn.microsoft.com/magazine/msdnmag0714. Puedes encontrar los datos MNIST en varios lugares en Internet. El repositorio principal está en yann.lecun.com/exdb/mnist.

Estructura general del programa

Para crear el programa de demostración lanzó Visual Studio y creó una aplicación Windows Form denominada MnistDistort. La interfaz de usuario tiene ocho controles TextBox para las rutas de acceso a los archivos descomprimidos de datos MNIST (un pixel archivo, un archivo de la etiqueta); los índices de la imagen actualmente visualizada y siguiente; y un valor semilla, tamaño del núcleo, desviación estándar del kernel y valor de la intensidad para el proceso de distorsión. Un control de lista desplegable contiene valores para magnificar la imagen. Hay tres controles de botón para cargar los 60.000 imágenes MNIST y etiquetas en la memoria, mostrar una imagen y distorsionar la imagen visualizada. Hay dos controles PictureBox para mostrar imágenes distorsionadas y regulares. Por último, un control ListBox se utiliza para mostrar el progreso y los mensajes de registro.

En la parte superior del código fuente he quitado las referencias a los espacios de nombres innecesarios y agregar una referencia al espacio de nombres System.IO para poder leer los archivos de datos MNIST.

He añadido una matriz alcance clase llamada trainImages, que contiene referencias a objetos definidos por el programa de DigitImage y variables para sostener las ubicaciones de los archivos de datos MNIST dos:

DigitImage[] trainImages = null;
string pixelFile = @"C:\MnistDistort\train-images.idx3-ubyte"; // Edit
string labelFile = @"C:\MnistDistort\train-labels.idx1-ubyte"; // Edit

En el constructor del formulario he añadido estas seis líneas de código:

textBox1.Text = pixelFile;
textBox2.Text = labelFile;
comboBox1.SelectedItem = "6"; // Magnification
textBox3.Text = "NA"; // Curr index
textBox4.Text = "0"; // Next index
this.ActiveControl = button1;

El controlador del evento click Button1 carga las 60.000 imágenes en la memoria:

string pixelFile = textBox1.Text;
string labelFile = textBox2.Text;
trainImages = LoadData(pixelFile, labelFile);
listBox1.Items.Add("MNIST training images loaded into memory");

El controlador del evento click Button2 muestra la siguiente imagen y actualiza los controles de interfaz de usuario:

int nextIndex = int.Parse(textBox4.Text);
DigitImage currImage = trainImages[nextIndex];
int mag = int.Parse(comboBox1.SelectedItem.ToString());
Bitmap bitMap = MakeBitmap(currImage, mag);
pictureBox1.Image = bitMap;
textBox3.Text = textBox4.Text; // Update curr index
textBox4.Text = (nextIndex + 1).ToString(); // next index
textBox8.Text = textBox3.Text;  // Random seed
listBox1.Items.Add("Curr image index = " + textBox3.Text +
  " label = " + currImage.label);

Encontrarás los métodos LoadData y MakeBitmap en la descarga de código correspondiente. La mayoría de los trabajos de distorsión se realiza mediante los métodos llamados por el controlador del evento click Button3, que se presenta en figura 2.

Figura 2 imagen distorsión llamada código

private void button3_Click(object sender, EventArgs e)
{
  int currIndex = int.Parse(textBox3.Text);
  int mag = int.Parse(comboBox1.SelectedItem.ToString());
  int kDim = int.Parse(textBox5.Text); // Kernel dimension
  double kStdDev = double.Parse(textBox6.Text); // Kernel std dev
  double intensity = double.Parse(textBox7.Text);
  int rndSeed = int.Parse(textBox8.Text);  // Randomization
  DigitImage currImage = trainImages[currIndex];
  DigitImage distorted = Distort(currImage, kDim, kStdDev,
    intensity, rndSeed);
  Bitmap bitMapDist = MakeBitmap(distorted, mag);
  pictureBox2.Image = bitMapDist;
}

Método distorsionar llama a los métodos MakeKernel (para crear una matriz de suavizado), MakeDisplace (direcciones y distancias para deformar cada píxel de una imagen) y desplazar (que en realidad deformar la imagen de origen). Método auxiliar MakeDisplace llama sub ayudante ApplyKernel a los valores de desplazamiento suave. Método auxiliar sub ApplyKernel llama método auxiliar sub sub Pad.

Deformación elástica

La idea básica de distorsión de una imagen mediante la deformación elástica es bastante simple. En el caso de una imagen MNIST, que desea mover ligeramente cada píxel existente. Pero los detalles no son tan simples. Un enfoque ingenuo que se mueve cada píxel independientemente conduce a nuevas imágenes que aparecen roto más estirada. Por ejemplo, considere las imágenes conceptuales en figura 3. Las dos imágenes representan la distorsión de una sección de 5 x 5 de una imagen. Cada flecha indica la dirección y distancia de una maniobra de un píxel correspondiente. Imagen de la izquierda muestra los vectores más o menos al azar, que podría romper en lugar de distorsionar la imagen. La imagen de la derecha muestra los vectores que se relacionan mutuamente, conduce a una imagen estirada.

Random vs. Smoothed Displacement Fields
Figura 3 Random vs. Desplazamiento suavizado campos

Así que el truco es desplazar cada píxel de una manera tal que píxeles cercanos unos de otros mueven relativamente similar, pero no es exactamente la misma, dirección y distancia. Esto puede lograrse utilizando una matriz de un núcleo gaussiano continua. La idea general es probablemente mejor explicó mediante código. Tenga en cuenta este método en la demo:

private DigitImage Distort(DigitImage dImage, int kDim,
  double kStdDev, double intensity, int seed)
{
  double[][] kernel = MakeKernel(kDim, kStdDev);
  double[][] xField = MakeDisplace(dImage.width, dImage.height,
   seed, kernel, intensity);
  double[][] yField = MakeDisplace(dImage.width, dImage.height,
    seed + 1, kernel, intensity);
  byte[][] newPixels = Displace(dImage.pixels, xField, yField);
  return new DigitImage(dImage.width, dImage.height,
    newPixels, dImage.label);
}

Método distorsionar acepta un objeto DigitImage y cuatro parámetros numéricos relacionados con un kernel. Tipo DigitImage es un programa­define la clase que representa los 28 x 28 bytes que componen los píxeles de una imagen MNIST. En primer lugar el método crea un núcleo donde kDim es que el tamaño del núcleo y kStdDev es un valor que influye en cómo similar será el desplazamiento de píxeles.

Para desplazar a un píxel, es necesario saber cómo lejos para moverse en la dirección de izquierda a derecha y en la dirección de arriba-abajo. Esta información se almacena en las matrices xField y yField, respectivamente y se calcula utilizando el método auxiliar MakeDisplace. Método auxiliar desplazar acepta los valores de píxel de una imagen DigitImage y utiliza los campos de desplazamiento para generar nuevos valores de los píxeles. Los nuevos valores de píxeles se alimentan entonces a un constructor de DigitImage, dando una nueva imagen distorsionada. Para resumir, de distorsionar la imagen, crear un núcleo. El núcleo se utiliza para generar x y los campos de dirección y que se relacionan más independiente. Los campos de dirección se aplican a una imagen de la fuente para producir una versión distorsionada de esa imagen.

Núcleos Gaussianos

Un núcleo gaussiano continuo es una matriz de valores que suma a 1.0; tiene el valor más grande en el centro; y es radialmente simétricos. Aquí hay un núcleo Gaussiano de 5 x 5 con una desviación estándar de 1.0:

0.0030   0.0133   0.0219   0.0133   0.0030
0.0133   0.0596   0.0983   0.0596   0.0133
0.0219   0.0983   0.1621   0.0983   0.0219
0.0133   0.0596   0.0983   0.0596   0.0133
0.0030   0.0133   0.0219   0.0133   0.0030

Observe que los valores cerca uno del otro son diferentes pero similares. El valor de la desviación estándar determina cuán cerca están los valores del núcleo. Una mayor desviación estándar da los valores que están más cercano juntos. Por ejemplo, usando una desviación estándar de 1.5 da un núcleo de 5 x 5 con valores de primera fila de:

0.0232   0.0338   0.0383   0.0338   0.0232

Esto puede parecer extraño al principio porque la desviación estándar es una medida de diseminación de datos y mayores valores de desviación estándar de un conjunto de datos indican una mayor propagación. Pero en el contexto de un núcleo gaussiano, la desviación estándar se utiliza para generar los valores y no es una medida de propagación en el núcleo resultante. El método utilizado por el programa de demostración para generar un núcleo Gaussiano se presenta en figura 4.

Figura 4 método MakeKernel

private static double[][] MakeKernel(int dim, double sd)
{
  if (dim % 2 == 0)
    throw new Exception("kernel dim must be odd");
  double[][] result = new double[dim][];
  for (int i = 0; i < dim; ++i)
    result[i] = new double[dim];
  int center = dim / 2; // Note truncation
  double coef = 1.0 / (2.0 * Math.PI * (sd * sd));
  double denom = 2.0 * (sd * sd);
  double sum = 0.0; // For more accurate normalization
  for (int i = 0; i < dim; ++i) {
    for (int j = 0; j < dim; ++j) {
      int x = Math.Abs(center - j);
      int y = Math.Abs(center - i);
      double num = -1.0 * ((x * x) + (y * y));
      double z = coef * Math.Exp(num / denom);
      result[i][j] = z;
      sum += z;
    }
  }
  for (int i = 0; i < dim; ++i)
    for (int j = 0; j < dim; ++j)
      result[i][j] = result[i][j] / sum;
  return result;
}

Generando núcleos Gaussianos puede ser una tarea algo confusa porque hay muchas variaciones de algoritmo dependiendo de cómo el núcleo está destinado a ser utilizado y algunas variaciones de las técnicas de aproximación. La definición de matemáticas básicas para los valores de un núcleo gaussiano continuo bidimensional es:

z = (1.0 / (2.0 * pi^2)) * exp((-(x^2 + y^2)) / (2 * sd^2))

Aquí x e y son el x y y las coordenadas de una celda en el núcleo en comparación con la célula del centro; PI es la constante matemática; EXP es la función exponencial; y sd es la desviación estándar especificada. El término coeficiente líder de 1.0 / (2.0 * Pi ^ 2) es en realidad un término normalización para la versión unidimensional de una función gaussiana. Pero para los núcleos 2D, desea sumar todos los valores preliminares del kernel y luego dividir cada valor preliminar la suma para que todos los valores finales se sumarán a 1.0 (sujeto a errores de redondeo). En figura 4, esta normalización final se logra utilizando la variable denominada suma. Por lo tanto, la variable denominada coef es redundante y puede ser omitida del código; variable coef fue incluido aquí porque la mayoría de trabajos de investigación describen usando el término de coeficiente.

Campos de desplazamiento

Para distorsionar la imagen, cada píxel debe ser movido (virtualmente, no literalmente) a cierta distancia hacia la izquierda o derecha y hacia arriba o hacia abajo. Método MakeDisplace se define en figura 5. Método MakeDisplace devuelve una matriz de matriz de matrices-estilo-que corresponde a la mitad de la matriz conceptual en figura 3. Es decir, los valores de las celdas de la matriz de retorno corresponden a una dirección y magnitud de un pixel se mueva en la dirección x o en la dirección y. Porque el tamaño de una imagen MNIST es de 28 x 28 píxeles, la matriz de retorno de MakeDisplace también será 28 x 28.

Figura 5 haciendo un campo de desplazamiento

private static double[][] MakeDisplace(int width, int height, int seed,
  double[][] kernel, double intensity)
{
  double[][] dField = new double[height][];
  for (int i = 0; i < dField.Length; ++i)
    dField[i] = new double[width];
  Random rnd = new Random(seed);
  for (int i = 0; i < dField.Length; ++i)
    for (int j = 0; j < dField[i].Length; ++j)
      dField[i][j] = 2.0 * rnd.NextDouble() - 1.0;
  dField = ApplyKernel(dField, kernel); // Smooth
  for (int i = 0; i < dField.Length; ++i)
    for (int j = 0; j < dField[i].Length; ++j)
      dField[i][j] *= intensity;
  return dField;
}

Método MakeDisplace genera una matriz con valores iniciales al azar entre -1 y + 1. Ayudante ApplyKernel suaviza los valores aleatorios según lo sugerido por figura 3. Los valores de suavizado son esencialmente Dirección componentes con distancia entre 0 y 1. Entonces todos los valores se multiplican por un parámetro intensidad para aumentar la distancia de estiramiento.

Aplicación de un núcleo y un desplazamiento

Aplicando un núcleo a una matriz de desplazamientos y luego usando los desplazamientos resultantes de alisado para generar nuevos valores de los píxeles son bastante complicado. La primera parte del proceso se ilustra en la figura 6. La parte izquierda de la figura representa los valores de desplazamiento al azar preliminar entre -1 y + 1 en la dirección de una imagen de 8 x 8 x. El valor de la fila [3], [6] (0.40) columna se se alisa con un núcleo de 3 x 3. El nuevo desplazamiento es un promedio ponderado del valor actual y los valores de los ocho vecinos más cercanos. Porque cada nuevo valor de desplazamiento es en esencia un promedio de sus vecinos, el efecto neto es generar valores que están relacionados entre sí.

Después de alisar, los valores de desplazamiento se multiplican por un factor de intensidad (a veces llamado alpha en literatura de la investigación). Por ejemplo, si el factor de intensidad es 20, y luego el final x-desplazamiento en figura 6 para el píxel de la imagen en (3, 6) sería 0,16 * 20 = +3.20. Habría una matriz y-desplazamientos similares. Supongamos que el valor final en (3, 6) en los desplazamientos y matriz era -1.50. Los valores +3.20 y -1.50 que corresponden al píxel en (3, 6) ahora se aplican a la imagen de la fuente para producir una imagen distorsionada, pero no de una manera totalmente obvia.

Applying a Kernel to a Displacement Matrix
Figura 6 aplicando un núcleo a una matriz de desplazamiento

Se determinan los límites primeros, superiores e inferiores. Para el +3.20 x-desplazamiento, son 3 y 4. Para el -1.50 y desplazamiento, son-2 y -1. Los cuatro límites generan cuatro (x, y) pares de desplazamiento: (3, -2), (3, -1), (4, -2), (4, -1). Recordar que estos valores se corresponden con el valor original del píxel imagen en índices (3, 6). Combinando los índices pixel con los pares cuatro desplazamiento genera cuatro pares de índices: (6, 4), (6, 5), (7, 4), (7, 5). Finalmente, el valor del píxel de la imagen distorsionada en (3, 6) es el promedio de los valores de píxel original en índices (6, 4) (5, 3), (7, 4) y (7, 5).

Debido a la geometría utilizada, es más común para restringir los granos a una extraña dimensión como 3, 5 y así sucesivamente. Aviso de que habría un problema tratando de suavizar los valores de desplazamiento preliminares cerca del borde de la matriz de desplazamientos porque el núcleo se extendería, por así decirlo, más allá del borde de la matriz. Hay varias maneras de abordar la cuestión del borde. Una forma es la matriz de desplazamiento preliminar con maniquí filas y columnas del cojín. Será igual a la mitad el número de filas o columnas para rellenar (mediante truncamiento entero) de la dimensión del núcleo.

Resumen

El proceso de deformación elástica descrito en este artículo es sólo uno de muchos enfoques posibles. La mayoría, pero no todos, del algoritmo distorsión presentado aquí fue adaptado a partir del artículo de investigación, "Mejores prácticas para convolucional Neural Networks aplicado a Visual análisis de documentos," que está disponible en línea en bit.ly/REzsnM.

El programa de demostración genera imágenes deformadas para crear datos de entrenamiento adicional para un sistema de reconocimiento de imágenes. Si realmente estás entrenando a un sistema de reconocimiento de imagen usted puede refactorizar el código de demostración para generar nuevos datos de entrenamiento sobre la marcha, o usted puede refactorizar el código para generar y guardar las imágenes distorsionadas como un archivo de texto o binario.

Dr.James McCaffrey trabajos de investigación de Microsoft en Redmond, Washington Ha trabajado en varios productos de Microsoft Internet Explorer y Bing. Llegar a él en jammc@microsoft.com.

Gracias a los siguientes expertos técnicos por su ayuda en la revisión de este artículo: Lobo Kienzle (Microsoft Research)