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

Ejecución de pruebas

Optimización multienjambre

James McCaffrey

Descargar el código de ejemplo

James McCaffreyOptimización del enjambre múltiples (MSO) es una técnica para estimar la solución a problemas numéricos difíciles o imposibles. Es una variación de optimización de enjambre de partículas (ver mi artículo sobre el tema en msdn.microsoft.com/magazine/hh335067). Enjambre de partículas regulares modelos de optimización reuniéndose el comportamiento, como el que se observa en los grupos de aves y cardúmenes de peces. MSO extiende optimización de enjambre de partículas mediante el uso de varios enjambres de partículas simulados en lugar de un simple enjambre.

MSO puede aplicarse a diversos escenarios de aprendizaje máquina, tales como estimar los pesos y los valores de sesgo para una red neuronal artificial o estimar los pesos de los educandos débiles en conjunto clasificación y predicción. MSO es una meta-heurísticos, lo que significa que la técnica es en realidad un conjunto de principios de diseño y las directrices que pueden utilizarse para construir un algoritmo específico para resolver un problema de optimización específicos.

Es una buena manera de comprender múltiples enjambre optimización examinar el programa de demostración en figura 1 y el gráfico de figura 2. El programa de demostración es estimar la solución a un problema de optimización numérica de referencia estándar llamado función de Rastrigin. El objetivo es encontrar los valores de x 0 y x 1 que minimizan la función:

Rastrigin's equation

Multi-Swarm Optimization Demo
Figura 1 Optimización multi enjambre Demo

Rastrigin’s Function
Función de figura 2 Rastrigin

La función tiene una solución conocida de f = 0.0 cuando x 0 = 0.0 y 1 = 0.0, así que el uso de MSO no es realmente necesario en esta situación. El gráfico de la figura 2 muestra la función de Rastrigin. Aunque la función tiene muchos picos y valles que representan las falsas soluciones, hay solamente un mínimo global en el centro de la imagen. Las múltiples soluciones cerca-pero-no-absolutamente de la función de Rastrigin son deliberadas y están diseñadas para causar problemas de algoritmos de optimización.

El programa de demostración en figura 1 comienza estableciendo algunos parámetros de entrada del algoritmo de MSO. Hay tres enjambres y cada enjambre tiene cuatro partículas. En la mayoría de problemas de optimización numérica, la gama de valores posibles es limitada de alguna manera. Aquí, el espacio de búsqueda está inicialmente limitado a x valores entre-100.0 y +100.0.

El programa de demostración de cada una de las 12 partículas aleatorias (x 0, x 1) inicializa los valores. Cada par de valores representa una posición de partículas que puede interpretarse también como una posible solución. El valor de la función de Rastrigin en cada posición se llama el costo de la posición, para sugerir que el objetivo es minimizar la función. Después de la inicialización, la partícula mejor (el uno con el menor costo) es índice de base cero partícula 0 en enjambre 2. Esa partícula tiene posición [-40.57, 28,54] y coste asociado 2498.93.

Optimización de múltiples enjambre es un proceso iterativo. El programa de demostración establece un valor arbitrario lazo máxima de 150. El algoritmo MSO entonces busca una solución mejor, hacer el seguimiento de la mejor solución encontrada por cualquiera de las 12 partículas. Al final de 150 iteraciones, la mejor solución encontrada era f = 0,000043 at x 0 =-0.0003, x 1 = 0,0004, que está muy cerca pero no es la solución real. El programa de demostración de hecho puede encontrar la solución actual estableciendo la variable maxLoop a 500, pero es importante recordar que en escenarios más realistas del problema no sabrás si MSO ha encontrado la solución óptima.

Este artículo asume que tiene habilidades de programación por lo menos nivel intermedio pero no asumir que sabe algo de optimización multi enjambre. El programa de demostración está codificado en C# pero no deberías tener mucho problemas de refactorización a otro idioma. Para mantener el tamaño del código pequeño y las principales ideas claras, he eliminado la mayor parte de la comprobación del código de demostración de errores normales. La demo es demasiado larga para presentar en su totalidad en este artículo, pero el código fuente completo está disponible en archive.msdn.microsoft.com/mag201309TestRun.

Estructura general del programa

La estructura del programa de demostración, con algunas modificaciones menores y la mayoría de las declaraciones de WriteLine eliminadas, se presenta en figura 3.

Estructura del programa figura 3 Demo multi enjambre

using System;
namespace MultiSwarm
{
  class MultiSwarmProgram
  {
    static void Main(string[] args)
    {
      try
      {
        Console.WriteLine(
          "\nBegin Multiple Particle Swarm optimization demo\n");
        int dim = 2;
        double minX = -100.0;
        double maxX = 100.0;
        int numParticles = 4; // Particles in each swarm
        int numSwarms = 3; // Swarms in multi-swarm
        Multiswarm ms = new Multiswarm(numSwarms, numParticles, 
          dim, minX, maxX);
        Console.WriteLine("\nInitial multiswarm:");
        Console.WriteLine(ms.ToString());
        int maxLoop = 150;
        ms.Solve(maxLoop);
        Console.WriteLine("\nFinal multiswarm:");
        Console.WriteLine(ms.ToString());
        Console.WriteLine("\nBest solution found = " +
          ms.bestMultiCost.ToString("F6"));
        Console.Write("at x0 = " + ms.bestMultiPos[0].ToString("F4"));
        Console.WriteLine(", x1 = " 
          + ms.bestMultiPos[1].ToString("F4"));
        Console.WriteLine("\nEnd demo\n");
        Console.ReadLine();
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message);
        Console.ReadLine();
      }
    }
    public static double Cost(double[] position) { .
.
}
  } // Program
  public class Particle { .
.
}
  public class Swarm { .
.
}
  public class Multiswarm { .
.
}
} // ns

Para crear el programa de demostración antes Visual Studio 2012. La demo no tiene importantes dependencias, y cualquier versión de Visual Studio debería funcionar. He seleccionado la plantilla de aplicación de consola de C# y había llamado el proyecto MultiSwarm. Después de Visual Studio cargar la plantilla de código, en el explorador de soluciones ventana renombrado el archivo Program.cs a MultiSwarmProgram.cs y Visual Studio automáticamente retitulado la clase del programa para mí. En la parte superior del código fuente que me borraron todo innecesarias referencias de espacio de nombres, dejando sólo la referencia al espacio de nombres del sistema.

El programa de demostración define un método de costo de alcance público, que es función del Rastrigin:

public static double Cost(double[] position)
{
  double result = 0.0;
  for (int i = 0; i < position.Length; ++i) {
    double xi = position[i];
    result += (xi * xi) - (10 * Math.Cos(2 * Math.PI * xi)) + 10;
  }
  return result;
}

El método del coste acepta un parámetro de matriz única que representa la posición de una partícula, que es una posible solución. En escenarios de aprendizaje más máquina, estás tratando de minimizar la función de coste representa algún tipo de error y requiere parámetros adicionales, tales como un origen de datos de entrenamiento. La función de costo es generalmente el cuello de botella de rendimiento para MSO, así se debe codificar para ser lo más eficiente posible.

La clase Multiswarm encapsula el algoritmo MSO. Clases de partículas y enjambre son clases auxiliares para la clase Multiswarm. Un diseño alternativo en lenguajes que admiten las definiciones de clase anidada es definir clases de partículas y enjambre dentro de clase Multiswarm.

El programa de demostración comienza estableciendo cinco parámetros de entrada:

int dim = 2;
double minX = -100.0;
double maxX = 100.0;
int numParticles = 4;
int numSwarms = 3;

Dim variable representa el número de dimensiones en el problema a resolver. Porque la función de Rastrigin acepta x 0 y x 1, el valor de la dim se encuentra a 2. Enjambre de optimización es muy adecuado para problemas con cualquier número de dimensiones. MaxX y descarada variables restringen la búsqueda a un rango limitado de valores. Los valores de descarada y maxX variará de problema a problema. NumParticles variable define el número de partículas que se encuentran en cada enjambre. NumSwarms variable define el número total de enjambres. En general, los valores más altos de numParticles y numSwarms producen soluciones más precisas a expensas de rendimiento.

A continuación, el objeto primario de múltiples enjambre se crea una instancia y se llama el MSO resolver algoritmo:

Multiswarm ms = new Multiswarm(numSwarms, numParticles, 
  dim, minX, maxX);
Console.WriteLine("\nInitial multiswarm:");
Console.WriteLine(ms.ToString());
int maxLoop = 150;
ms.Solve(maxLoop);

La demo llama una colección de partículas de un "enjambre" y una colección de enjambres "un enjambre de múltiples". En lugar de este esquema de nombres, algunos bibliografía llama una colección de partículas un enjambre"sub" y una colección de enjambres sub un "enjambre". El objeto Multiswarm se crea una instancia utilizando los parámetros de entrada definidos anteriormente. Variable maxLoop contiene el número máximo de veces que se repita el bucle principal de algoritmo para resolver. En general, los valores más altos de maxLoop producirán soluciones más precisas a expensas de rendimiento.

El método de resolver iterativamente busca la mejor solución para la función de Rastrigin. Observe que la función de costo se define externamente al objeto Multiswarm. En casi todas las situaciones, código MSO es integrado en un sistema de software que implementa como una biblioteca DLL, el cual requiere que pase la función de coste en el objeto Multiswarm (a través de un delegado, por ejemplo) o utilizar un enfoque de diseño de interfaz para definir un contrato de programación de clases.

Después de que el método Solve acabados de ejecución, el estado final del objeto Multiswarm se muestra, y la mejor solución encontrada por cualquier partícula en cualquier enjambre en el enjambre múltiples es explícitamente:

Console.WriteLine("\nFinal multiswarm:");
Console.WriteLine(ms.ToString());
Console.WriteLine("\nBest solution found = " +
 ms.bestMultiCost.ToString("F6"));
Console.Write("at x0 = " + ms.bestMultiPos[0].ToString("F4"));
Console.WriteLine(", x1 = " + ms.bestMultiPos[1].ToString("F4"));

Partículas

La clase de partícula tiene seis miembros:

static Random ran = new Random(0);
public double[] position;
public double[] velocity;
public double cost;
public double[] bestPartPos;
public double bestPartCost;

Yo prefiero usar el ámbito público por razones de simplicidad pero quizá quieras usar ámbito privado junto a obtener y establecer métodos de propiedad. El objeto Random es utilizado por el constructor para inicializar un objeto partícula a una posición al azar. La matriz llamada posición representa la posición de una partícula. Velocidad de la matriz representa la velocidad y dirección de una partícula. Por ejemplo, supongamos que una partícula está en la posición [12.0, 24.0] y la velocidad es [5.0, 0.0]. Esto puede interpretarse en el sentido de que durante el próximo incremento de tiempo la partícula moverá 5,0 unidades a lo largo del x 0 0.0 unidades a lo largo del x 1 y dimensión dimensión. Después de la partícula se mueve, su nueva posición será [17.0, 24.0].

Costo variable contiene el valor de la función de costo en la posición actual. Variable bestPartCost contiene el valor de costo (más pequeño) mejor que una partícula se ha enfrentado, y bestPartPos variable es la posición donde se encontró el costo más conocido.

El constructor de partículas se define en figura 4.

Figura 4 el Constructor de la partícula

public Particle(int dim, double minX, double maxX)
{
  position = new double[dim];
  velocity = new double[dim];
  bestPartPos = new double[dim];
  for (int i = 0; i < dim; ++i) {
    position[i] = (maxX - minX) * ran.NextDouble() + minX;
    velocity[i] = (maxX - minX) * ran.NextDouble() + minX;
  }
  cost = MultiSwarmProgram.Cost(position);
  bestPartCost = cost;
  Array.Copy(position, bestPartPos, dim);
}

El constructor asigna espacio para los arreglos de posición, velocidad y bestPartPos basados en el número de dimensiones del problema. Cada célula de la velocidad y posición se asignan un valor aleatorio entre pícara y maxX. Se calcula el costo de la posición inicial. Posición más conocidos de la partícula y el costo se establecen en la posición inicial y el costo.

Un enfoque alternativo importante es asignar posiciones iniciales no aleatoria a cada partícula. Algunos MSO investigación literatura sugiere que la asignación de partículas en enjambres diferentes a diferentes regiones del espacio de búsqueda es superior a un método de asignación al azar. Por ejemplo, si tuvieras dos enjambres con las 10 partículas, las partículas de 10 en el primer enjambre podrían asignarse a posiciones con valores entre-100.0 y 0.0 y las partículas de 10 x en el segundo enjambre a posiciones con valores de x entre 0.0 y +100.0. Sin embargo, yo no estoy totalmente convencido por estos resultados de investigación, y asignación de posición partícula aleatorio simple ha trabajado bien para mí en la práctica.

Enjambres

Un enjambre es una colección de partículas. La clase enjambre tiene tres miembros:

public Particle[] particles;
public double[] bestSwarmPos;
public double bestSwarmCost;

Nomenclatura puede ser un poco complicado con MSO. Nombro a la matriz de objetos de la partícula en la clase enjambre como "partículas", pero tal vez quieras usar el "enjambre" de nombre en su lugar. BestSwarmCost variable miembro tiene el mejor coste (más pequeño) encontrado por cualquiera de las partículas en el enjambre durante la ejecución del algoritmo. Matriz bestSwarmPos sostiene la posición donde se encontró este enjambre-miembro mejor costo.

El constructor de enjambre se muestra en la figura 5.

Figura 5 el Constructor enjambre

public Swarm(int numParticles, int dim, double minX, double maxX)
{
  bestSwarmCost = double.MaxValue;
  bestSwarmPos = new double[dim];
  particles = new Particle[numParticles];
  for (int i = 0; i < particles.Length; ++i) {
    particles[i] = new Particle(dim, minX, maxX);
    if (particles[i].cost < bestSwarmCost) {
      bestSwarmCost = particles[i].cost;
      Array.Copy(particles[i].position, bestSwarmPos, dim);
    }
  }
}

El constructor de enjambre asigna espacio, entonces llama las partícula constructor numParticles veces generan partículas de posición al azar. Como cada partícula es creada, se activa para ver si tiene el mejor coste de cualquiera de las partículas en el enjambre.

La clase Multiswarm

Un enjambre de múltiples es una colección de enjambres. La clase Multiswarm de alto nivel tiene siete miembros:

public Swarm[] swarms;
public double[] bestMultiPos;
public double bestMultiCost;
public int dim;
public double minX;
public double maxX;
static Random ran = new Random(0);

Enjambres de matriz posee cada objeto enjambre, cada una de ellas es una colección de objetos de la partícula. Entonces enjambres [2] [3] explosiva .position [0] representa el valor para la partícula 3 en enjambre 2 x 0.

BestMultiCost variable miembro tiene el mejor coste encontrado por cualquier partícula en cualquier enjambre durante la ejecución del algoritmo. Matriz bestMultiPos ocupa el cargo asociado donde se encontró el mejor costo global. Las variables miembro dim, descarada y maxX se almacenan por conveniencia así sus valores pueden ser utilizados por métodos de la clase sin pasar como parámetros.

Recordemos que clase de que partícula tiene un objeto Random denominado corrió que se utiliza para generar posiciones iniciales al azar. Clase Multiswarm tiene un objeto diferente al azar que es utilizado por el algoritmo MSO para insertar pseudo-aleatorio comportamiento durante el proceso de resolver.

El constructor Multiswarm cotiza en figura 6.

Figura 6 el Constructor Multiswarm

public Multiswarm(int numSwarms, int numParticles, int dim,
  double minX, double maxX)
{
  swarms = new Swarm[numSwarms];
  bestMultiPos = new double[dim];
  bestMultiCost = double.MaxValue;
  this.dim = dim;
  this.minX = minX;
  this.maxX = maxX;
  for (int i = 0; i < numSwarms; ++i)
  {
    swarms[i] = new Swarm(numParticles, dim, minX, maxX);
    if (swarms[i].bestSwarmCost < bestMultiCost)
    {
      bestMultiCost = swarms[i].bestSwarmCost;
      Array.Copy(swarms[i].bestSwarmPos, bestMultiPos, dim);
    }
  }
}

Después de la asignación de matrices y guardar los valores de parámetro de entrada, el constructor Multiswarm llama los tiempos enjambre constructor numSwarms. Como cada enjambre se crea, el mejor costo de cualquier partícula dentro de ese enjambre se revisa para ver si es un mundial mejor costo. Si es así, ese costo y su posición asociado se almacenan en bestMultiCost y bestMultiPos, respectivamente.

El algoritmo MSO

En seudocódigo muy alto nivel, el algoritmo básico de MSO es:

loop maxLoop times
  for each swarm
    for each particle
      compute new velocity
      use velocity to update position
      check if a new best cost has been found
    end for
  end for
end loop

El funcionamiento de las teclas en MSO es informática una nueva velocidad de una partícula. Una nueva velocidad para una determinada partícula está influenciada por la velocidad actual, la posición actual, la posición más conocida de la partícula, la posición más conocida de cualquier partícula en el mismo enjambre como la partícula y la posición más conocida de cualquier partícula en cualquier enjambre. En términos matemáticos, la nueva velocidad es:

v(t+1) = w * v(t) +
         (c1 * r1) * (p(t) - x(t)) +
         (c2 * r2) * (s(t) - x(t)) +
         (c3 * r3) * (m(t) - x(t))

Después de una nueva velocidad ha sido computada, nueva posición de una partícula es:

x(t+1) = x(t) + v(t+1)

Ten paciencia conmigo por un momento. El cómputo es mucho más simple que aparece primero. El término v(t+1) significa que la velocidad en el tiempo t + 1, en otras palabras, la nueva velocidad. Término v (t) es la velocidad actual. Término x (t) es la posición actual. Notar que x y v están en negrita, lo que indica son vectores tales como [12.0, 25.0] en lugar de los valores individuales.

Término p es la posición más conocido de una partícula. Término s (t) es la mejor posición de cualquier partícula en enjambre de la partícula. Término m (t) es la mejor posición de cualquier partícula en cualquier enjambre.

Término w es una constante llamada el factor de inercia. C3, c2 y c1 términos son constantes que establecen un cambio máximo para cada componente de la nueva velocidad. Términos r1, r2 y R3 son valores aleatorios entre 0 y 1 que proveen un efecto aleatorio para cada actualización de velocidad.

El nuevo cálculo de velocidad se explica probablemente por ejemplo. Supongamos que una partícula es actualmente en [12.0, 24.0] y su velocidad actual es [-1, 0, -3,0]. Además, es la posición más conocida de la partícula [8.0, 10.0], es la posición más conocida de cualquier partícula en el enjambre [7.0, 9.0], y es la posición más conocida de cualquier partícula en cualquier enjambre [5.0, 6.0]. Y supongo que w constante tiene valor 0.7, c2 y c1 constantes son ambos 1.4 y c3 constante es 0,4. Por último, supongamos que valores aleatorios r1, r2 y r3 son todos 0.2.

La nueva velocidad de la partícula se muestra en la figura 7.

Figura 7 la nueva velocidad de una partícula de computación

v(t+1) = 0.7         * [-1.0, -3.0] +
         (1.4 * 0.2) * [8.0, 10.0] - [12.0, 24.0] +
         (1.4 * 0.2) * [7.0, 9.0] - [12.0, 24.0] +
         (0.4 * 0.2) * [5.0, 6.0] - [12.0, 24.0]
       = 0.7 * [-1.0, -3.0] +
         0.3 * [-4.0, -14.0] +
         0.3 * [-5.0, -15.0] +
         0.1 * [-7.0, -18.0]
       = [-0.7, -2.1] +
         [-1.2, -4.2] +
         [-1.5, -4.5] +
         [-0.7, -1.8]
       = [-4.1, -12.6]

Y así, según figura 7, nueva posición de la partícula es:

x(t+1) = [12.0, 24.0] + [-4.1, -12.6]
       = [7.9, 11.4]

Asumiendo que la solución óptima es [0.0, 0.0], como es el caso con la función de Rastrigin, notar que la partícula se ha movido de su posición original a una nueva posición que está más cerca de la solución óptima.

El término de inercia en v(t+1) alienta una partícula para continuar avanzando en su dirección actual. El término p alienta una partícula para avanzar hacia su histórica posición más conocido. El término s (t) alienta una partícula para avanzar hacia la posición más conocida por alguno de los compañeros de la partícula enjambre. El término m (t) alienta una partícula para avanzar hacia la posición más conocida encontrada por cualquier partícula en cualquier enjambre.

Constantes c1, c2 y C3 a veces se llaman los pesos, cognitivos, sociales y globales. Esas constantes, junto con valores aleatorios r1, r2 y r3 y la inercia peso w, determinan cuánto cada término influye en el movimiento de una partícula. Algunas investigaciones en optimización de enjambre de partículas regulares indican valores razonables para w, c1, y c2 son 0.729, 1.49445 y 1.49445, respectivamente. Hay pocas investigaciones sobre la constante c3 en MSO, pero normalmente utilizo 0.3645 (la mitad del peso de la inercia), y esto ha funcionado bien para mí en la práctica.

Muerte e inmigración

Hay varias maneras fascinantes para modificar el algoritmo básico de MSO. Una posibilidad es esencialmente matar una partícula seleccionada al azar cada ahora y después, y luego dar a luz a una nueva partícula. El programa de demostración utiliza esta modificación de muerte al nacer. Las primeras líneas del método Solve se muestran en figura 8.

Figura 8 las primeras líneas de la método de resolver

public void Solve(int maxLoop)
{
  // Assign values to ct, w, c1, c2, c3
  double death = 0.005; ; // Prob of death
  while (ct < maxLoop)
  {
    ++ct;
    for (int i = 0; i < swarms.Length; ++i) {
      for (int j = 0; j < swarms[i].particles.Length; ++j) {
        double p = ran.NextDouble();
        if (p < death)
          swarms[i].particles[j] = new Particle(dim, minX, maxX);
        for (int k = 0; k < dim; ++k) {
...

El método genera un valor aleatorio entre 0 y 1 y almacena en p. Si el valor aleatorio es menor que 0.005, la partícula actual es instanciada re llamada al constructor de la partícula, efectivamente matando la partícula actual y dar a luz a una nueva partícula en un lugar al azar.

Otra opción de MSO es inmigración modelo periódicamente, tomando dos partículas en diferentes enjambres y intercambiarlos. Una partícula immigrates eficazmente en el enjambre actual y otra partícula emigra fuera del enjambre. El programa de demostración contiene esta opción. El método clave es:

private void Immigration(int i, int j)
{
  // Swap particle j in swarm i
  // with a random particle in a random swarm
  int otheri = ran.Next(0, swarms.Length);
  int otherj = ran.Next(0, swarms[0].particles.Length);
  Particle tmp = swarms[i].particles[j];
  swarms[i].particles[j] = swarms[otheri].particles[otherj];
  swarms[otheri].particles[otherj] = tmp;
}

El método es simple y permite la posibilidad indeseable que una partícula puede ser intercambiada con sí mismo. La característica de inmigración se llama método resolver así:

double immigrate = 0.005;
...
double q = ran.NextDouble();
if (q < immigrate)
  Immigration(i, j);

En resumen

La explicación presentada en este artículo y la descarga de código acompañamiento debe darle una base sólida para experi­menting con optimización multi enjambre. Comparado con optimización de enjambre de partículas regulares, optimización multi enjambre es sólo un poco más compleja y tiende a producir resultados de mejor calidad, aunque es más lento. Mi experiencia con el MSO sugiere que tiende a manejar problemas de optimización difícil — aquellos con muchos mínimos locales — mejor optimización de enjambre de partículas regulares.

MSO es una propósito general meta-heurísticos de optimización numérica y se utiliza normalmente como parte de un escenario más grande de aprendizaje máquina para encontrar un conjunto de pesas que minimizar a algún tipo de función de error. Por ejemplo, MSO puede utilizarse para encontrar el mejor conjunto de pesos y valores de sesgo para una red neuronal minimizando los errores de clasificación de un conjunto de datos de entrenamiento. Existen muchas alternativas para MSO, incluyendo algoritmos de optimización evolutiva (también llamados valor real algoritmos genéticos), optimización de forrajeo bacteriana y optimización del método de ameba.

Comparado con la mayoría enfoques alternativos de optimización numérica, en mi opinión que MSO es más simple de implementar y fácil de personalizar. Una desventaja de MSO comparado con algunas alternativas, es que en general hay algunas directrices para seleccionar el valor de parámetros MSO-libre, incluyendo las constantes de peso de la inercia y las constantes de peso cognitivo, social y global. Que dijo, sin embargo, yo soy un gran fan de MSO y se ha presentado muy bien para mí en ocasiones cuando tenía que resolver un problema de optimización numérica en mis sistemas de software.

Dr.James McCaffrey trabaja para Microsoft en el campus de Redmond, Washington,. Ha trabajado en varios productos de Microsoft Internet Explorer y MSN Search. Él es el autor de ".NET Test Automation recetas" (Apress, 2006) y puede ser contactado en jammc@microsoft.com.

Gracias al siguiente experto técnico por su ayuda en la revisión de este artículo: Kirk Olynyk (Microsoft)