Este artigo foi traduzido por máquina.

Execução de teste

Otimização por múltiplos enxames

James McCaffrey

Baixar o código de exemplo

James McCaffreyOtimização de enxame múltiplos (MSO) é uma técnica para estimar a solução de problemas numéricos difíceis ou impossíveis. É uma variação de otimização de enxame de partículas (ver meu artigo sobre o assunto no msdn.microsoft.com/magazine/hh335067). Regular particle swarm modelos de otimização, reunindo-se comportamento, como a que vi em grupos de aves e cardumes de peixes. MSO estende-se a otimização de enxame de partículas usando vários enxames de partículas simuladas, em vez de um único enxame.

MSO pode ser aplicado a vários cenários de aprendizado de máquina, tais como estimar os pesos e valores viés para uma rede neural artificial ou estimar os pesos de alunos fracos na classificação de ensemble e previsão. MSO é uma meta-heurística, o que significa que a técnica realmente é um conjunto de princípios de design e orientações que podem ser usadas para construir um algoritmo específico para resolver um problema de otimização específica.

Uma boa maneira de compreender a otimização multi enxame é examinar o programa de demonstração em Figura 1 e o gráfico em Figura 2. O programa de demonstração é estimar a solução para um problema de otimização numérica de referência padrão chamado função do Rastrigin. O objetivo é encontrar os valores de x0 e X1 que minimizam a função:

Rastrigin's equation

Multi-Swarm Optimization Demo
Figura 1 otimização multi enxame Demo

Rastrigin’s Function
Função do Figura 2 Rastrigin

A função possui uma solução conhecida de f = 0.0 quando x 0.0 = 0 e x 1 = 0.0, então o uso de MSO não é realmente necessário nesta situação. O gráfico em Figura 2 mostra a função do Rastrigin. Embora a função tem muitos picos e vales que representa falsas soluções, há apenas um mínimo global no centro da imagem. As várias soluções de perto-mas-não-muito da função do Rastrigin são deliberadas e são projetadas para causar problemas para os algoritmos de otimização.

O programa de demonstração em Figura 1 começa pela criação de alguns parâmetros de entrada para o algoritmo MSO. Existem três enxames, e cada enxame tem quatro partículas. Na maioria dos problemas de otimização numérica, o intervalo de valores possíveis é limitado de alguma forma. Aqui, o espaço de busca é inicialmente limitado a x valores entre 100.0 e +100.0.

O programa de demonstração inicializa cada uma das 12 partículas para aleatório (x0, X1) valores. Cada par de valores representa a posição de uma partícula que também pode ser interpretada como uma possível solução. O valor da função do Rastrigin em cada posição é chamado o custo da posição, a sugerir que o objetivo é minimizar a função. Após a inicialização, a partícula melhor (aquele com o menor custo) é partícula de índice baseado em zero 0 no enxame 2. Essa partícula tem posição [-40.57, 28.54] e o custo associado 2498.93.

Otimização multi enxame é um processo iterativo. O programa de demonstração define um valor arbitrário de loop máxima de 150. Em seguida, o algoritmo MSO procura soluções melhores, mantendo-se a par da melhor solução encontrada por qualquer uma das 12 partículas. A melhor solução encontrada foi o final de 150 iterações, f = 0,000043 at x 0 =-0.0003, x 1 = 0,0004, que está muito perto mas não é bem a solução real. O programa de demonstração, na verdade, pode encontrar a solução real, definindo a variável maxLoop para 500, mas é importante lembrar que, em cenários mais realistas do problema você não saberá se o MSO encontrou a solução ideal.

Este artigo pressupõe que você tem habilidades de programação de nível intermediário, pelo menos, mas não assumir que sabe alguma coisa sobre otimização multi enxame. O programa de demonstração é codificado em c#, mas você não deve ter muito problemas de refatoração para outro idioma. A fim de manter o tamanho do código pequeno e principais ideias claras, eu removi maior parte o verificação do código de demonstração de erro normal. O demo é muito longo para apresentar na íntegra este artigo, mas o código-fonte inteiro está disponível em archive.msdn.microsoft.com/mag201309TestRun.

Estrutura geral do programa

A estrutura do programa de demonstração, com algumas pequenas edições e a maioria das instruções WriteLine removidas, é apresentada em Figura 3.

Figura 3 Demo enxame multi programa estrutura

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 criar o programa de demonstração, usei Visual Studio 2012. O demo não tem significativas dependências, e qualquer versão do Visual Studio deve funcionar. Selecionei o modelo de aplicativo de console c# e chamado o projeto MultiSwarm. Depois Visual Studio carregado o modelo de código, no Solution Explorer janela renomeei o arquivo Program.cs para MultiSwarmProgram.cs e Visual Studio automaticamente renomeado classe programa para mim. Na parte superior do código-fonte que eu deletei todos desnecessários e referências a namespace, deixando apenas a referência ao namespace System.

O programa de demonstração define um método de custo escopo público, que é a função do 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;
}

O método de custo aceita um parâmetro de matriz única que representa a posição de uma partícula, que é uma solução possível. Em cenários mais aprendizado de máquina, a função de custo que quer minimizar representa algum tipo de erro e exigirá parâmetros adicionais, como uma fonte de dados de treinamento. A função de custo geralmente é o gargalo de desempenho para o MSO, então você deve código para ser tão eficiente quanto possível.

O Multiswarm classe encapsula o algoritmo MSO. Partículas de classes e enxame são classes auxiliares para a classe Multiswarm. Concepção alternativa em idiomas que suportam as definições de classe aninhada é definir classes de partículas e enxame dentro da classe Multiswarm.

O programa de demonstração começa pela criação de cinco parâmetros de entrada:

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

Variável dim representa o número de dimensões no problema a ser resolvido. Porque a função do Rastrigin aceita x0 e X1, o valor de não ofuscante é definido como 2. Otimização multi enxame é well-suited para problemas com qualquer número de dimensões. MaxX e variáveis minX restringem a pesquisa a um intervalo limitado de valores. Os valores para minX e maxX variarão de um problema para o problema. Variável numParticles define o número de partículas que estão em cada enxame. Variável numSwarms define o número total de enxames. Em geral, maiores valores de numParticles e numSwarms produzem soluções mais precisas em detrimento do desempenho.

Em seguida, o objeto principal do enxame multi é instanciado e chama-se a MSO resolver o algoritmo:

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

O demo chama uma coleção de partículas um "enxame" e uma coleção de enxames, um "enxame multi". Em vez deste esquema de nomeação, alguma literatura de pesquisa chama uma coleção de partículas um enxame de"sub" e uma coleção de enxames sub um "enxame". O Multiswarm objeto é instanciado usando os parâmetros de entrada definidos anteriormente. Variável maxLoop contém o número máximo de vezes que o loop principal de algoritmo resolução irá iterar. Em geral, valores maiores de maxLoop irão produzir soluções mais precisas em detrimento do desempenho.

O método Solve iterativamente procura a melhor solução para a função do Rastrigin. Observe que a função de custo é definida externamente ao objeto Multiswarm. Na maioria das situações, MSO código é integrado em um sistema de software em vez de implementado como uma DLL, que requer que você passe a função de custo para o objeto Multiswarm (através de um representante, por exemplo) ou usar uma abordagem de design de interface para definir um contrato de programação de biblioteca de classe.

Depois que o método Solve termina a execução, o estado final do objeto Multiswarm é exibido, e a melhor solução encontrada por qualquer partícula em qualquer enxame em enxame multi explicitamente é exibida:

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

A classe de partícula tem seis membros:

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

Eu prefiro usar o escopo público para manter a simplicidade, mas você pode querer usar a privada escopo juntamente com get e set métodos da propriedade. O objeto Random é usado pelo construtor para inicializar um objeto de partículas para uma posição aleatória. A matriz chamada posição representa a posição de uma partícula. Velocidade de matriz representa a velocidade e a direção para uma partícula. Por exemplo, suponha que uma partícula esteja na posição [12.0, 24,0], e a velocidade é [5.0, 0.0]. Isto pode ser interpretado no sentido de que, durante o próximo incremento de tempo, a partícula irá mover 5,0 unidades junto a 0 x 0.0 unidades ao longo do X1 e dimensão dimensão. Depois que a partícula se move, sua nova posição será [17,0, 24,0].

Custo variável contém o valor da função de custo na posição atual. Variável bestPartCost contém o valor de custo (menor) melhor que uma partícula nunca encontrou, e bestPartPos variável é a posição onde o custo mais conhecido foi encontrado.

O construtor de partícula é definido em Figura 4.

Figura 4 o construtor de 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);
}

O Construtor aloca espaço para as matrizes de posição, velocidade e bestPartPos com base no número de dimensões do problema. Cada posição e velocidade célula é atribuído um valor aleatório entre minX e maxX. O custo da posição inicial é calculado. Posição mais conhecida da partícula e o custo são definidas para a posição inicial e o custo.

Uma abordagem alternativa significativa é atribuir posições iniciais não-aleatório para cada partícula. Alguma literatura de pesquisa MSO sugere que atribuir partículas em enxames diferentes para diferentes regiões do espaço de busca é superior a uma abordagem de atribuição aleatória. Por exemplo, se você tivesse dois enxames com 10 partículas cada, 10 partículas em primeiro enxame podem ser atribuídas às posições com valores de x entre 100.0 e 0,0 e as partículas de 10 no segundo enxame para posições com valores de x entre 0,0 e +100.0. Eu não estou totalmente convencido por estes resultados de pesquisa, no entanto, e atribuição de posição da partícula aleatória simples tem funcionado bem para mim na prática.

Enxames

Um enxame é uma coleção de partículas. A classe do enxame tem três membros:

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

Nomeação pode ser um pouco complicado com MSO. Nomeio a matriz de objetos de partícula na classe enxame como "partículas", mas você pode querer usar o "enxame" nome em vez disso. BestSwarmCost variável de membro detém o melhor custo (menor) encontrado por qualquer das partículas na limalha de ferro durante a execução do algoritmo. Matriz bestSwarmPos ocupa a posição onde esta melhor custo enxame-membro foi encontrado.

O enxame Construtor é mostrado no Figura 5.

Figura 5 o enxame Construtor

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);
    }
  }
}

O enxame Construtor aloca espaço e, em seguida, chama as partícula Construtor numParticles vezes para gerar partículas posição aleatória. Como cada partícula é criada, ele é verificado para ver se ele tem o melhor custo de qualquer das partículas na limalha de ferro.

A classe Multiswarm

Um enxame de multi é uma coleção de enxames. A classe Multiswarm de nível superior tem sete membros:

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

Enxames de matriz mantém cada objeto de enxame, cada uma das quais é uma coleção de objetos de partícula. Então enxames [2] [3] de táquions eExplica [0] representa o valor para partícula 3 em enxame 2 x 0.

BestMultiCost variável de membro detém o melhor custo encontrado por nenhuma partícula em qualquer enxame durante a execução do algoritmo. Matriz bestMultiPos ocupa a posição de associado onde o melhor custo global foi encontrado. Variáveis de membro dim, minX e maxX são armazenados para sua conveniência, então seus valores podem ser usados pelos métodos da classe sem ser passados como parâmetros.

Lembre-se que classe de que partícula tem um objeto de Random chamado correu que é usado para gerar posições iniciais aleatórias. Classe Multiswarm tem um objeto diferente do aleatório que é usado pelo algoritmo MSO para inserir pseudo-aleatório comportamento durante o processo de resolução.

O Construtor Multiswarm está listado em Figura 6.

Figura 6 o Construtor 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);
    }
  }
}

Depois de alocar matrizes e salvar os valores de parâmetro de entrada, o construtor de Multiswarm chama os tempos de numSwarms de construtor de enxame. Como cada enxame é criado, o melhor custo de qualquer partícula dentro do enxame é verificado para ver se é um global melhor custo. Em caso afirmativo, o custo e a sua posição associada são armazenados em bestMultiCost e bestMultiPos, respectivamente.

O algoritmo MSO

Em pseudocódigo de muito alto nível, o algoritmo básico de MSO é:

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

A operação chave na MSO está computando uma nova velocidade de uma partícula. Uma nova velocidade para uma determinada partícula é influenciada pela velocidade atual, a posição atual, a posição mais conhecida da partícula, a posição mais conhecida de qualquer partícula no mesmo enxame como a partícula e a posição mais conhecida de qualquer partícula em qualquer enxame. Em termos matemáticos, a nova velocidade é:

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

Após uma nova velocidade tem sido calculada, a nova posição de uma partícula é:

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

Acompanhe-me por um momento. O cálculo é muito mais simples do que aparece pela primeira vez. V o termo significa a velocidade no tempo t + 1, em outras palavras, a nova velocidade. Termo v (t) é a velocidade atual. Termo x(t) é a posição atual. Observe que x e v está em negrito, que indica são vetores tais como [12.0, 25,0] ao invés de valores únicos.

Prazo p é mais conhecida posição de uma partícula. Termo s(t) é a melhor posição de qualquer partícula em swarm da partícula. Termo m(t) é a melhor posição de qualquer partícula em qualquer enxame.

W termo é uma constante denominada o fator de inércia. C3, c2 e c1 termos são constantes que estabelecer uma mudança máxima para cada componente da nova velocidade. Termos r1, r2 e R3 são valores aleatórios entre 0 e 1 que proporcionam um efeito de randomização para cada atualização de velocidade.

O novo cálculo de velocidade é provavelmente mais bem explicado por exemplo. Suponha que uma partícula está atualmente na [12.0, 24,0] e sua velocidade atual é [-1,0, 3,0]. Também, é a posição mais conhecida da partícula [8.0, 10.0], é a posição mais conhecida de qualquer partícula no swarm [7.0, 9.0], e é a posição mais conhecida de qualquer partícula em qualquer enxame [5.0, 6.0]. E suponha que w constante tem valor 0,7, c2 e c1 constantes são ambos 1.4 e c3 constante é 0,4. Finalmente, suponha que valores aleatórios r1, r2 e r3 são todos 0.2.

A nova velocidade da partícula é mostrada no Figura 7.

Figura 7 a nova velocidade de uma partícula de computação

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]

E assim, de acordo com Figura 7, nova posição da partícula é:

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

Supondo que a solução ideal é [0.0, 0.0], como é o caso com a função do Rastrigin, observe que a partícula se moveu de sua posição original para uma nova posição que está próxima a solução ideal.

O termo inércia em v incentiva uma partícula para continuar se movendo em sua direção atual. O termo p incentiva uma partícula se mover em direção a sua posição histórica mais conhecida. O termo s(t) incentiva uma partícula a se mover para a posição mais conhecida encontrada por qualquer um dos companheiros de swarm da partícula. O termo m(t) incentiva uma partícula a se mover para a posição mais conhecida encontrada por qualquer partícula em qualquer enxame.

Constantes c1, c2 e C3 são às vezes chamados os pesos, cognitivos, sociais e globais. Essas constantes, juntamente com valores aleatórios r1, r2 e r3 e a inércia peso w, determinam quanto cada termo influencia o movimento de uma partícula. Algumas pesquisas em otimização de enxame de partículas regulares sugerem valores razoáveis para w, c1, e c2 são 0.729, 1.49445 e 1.49445, respectivamente. Há pouca investigação sobre a constante de c3 na MSO, mas eu normalmente uso 0.3645 (metade do peso da inércia), e isso tem funcionado bem para mim na prática.

Morte e imigração

Existem várias maneiras fascinantes para modificar o algoritmo básico de MSO. Uma possibilidade é essencialmente matar uma partícula selecionada aleatoriamente cada agora e então, e então dar à luz a uma nova partícula. O programa de demonstração usa essa modificação de nascimento-morte. As primeiras linhas do método Solve são mostradas em Figura 8.

Figura 8 as primeiras linhas do 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) {
...

O método gera um valor aleatório entre 0 e 1 e armazena-lo em p. Se o valor aleatório for menor que 0,005, a partícula atual re-é instanciada, chamando o construtor de partícula, efetivamente matando a partícula atual e dando origem a uma nova partícula em um local aleatório.

Outra opção de MSO é imigração modelo periodicamente tomando duas partículas diferentes enxames e trocá-los. Uma partícula efetivamente imigra para o enxame atual e outra partícula emigra fora do enxame. O programa de demonstração contém essa opção. O método principal é:

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;
}

O método é simples e permite a possibilidade indesejável que uma partícula pode ser trocada com ele próprio. O recurso de imigração é chamado de método Solve da seguinte forma:

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

Conclusão

A explicação apresentada no presente artigo e no download de código que acompanha deve dar-lhe uma base sólida para tranforme em­menting com otimização multi enxame. Comparado a otimização de enxame de partículas regular, otimização multi enxame é apenas ligeiramente mais complexa e tende a produzir resultados de melhor qualidade, que é mais lento. Minha experiência com MSO sugere que ela tende a lidar com problemas de otimização difícil — aqueles com muitos mínimos locais — melhor do que a otimização de enxame de partículas regular.

MSO é uma meta-heurística de otimização numérica de uso geral e normalmente é usada como parte de um cenário maior de aprendizado de máquina, a fim de encontrar um conjunto de pesos que minimizam a algum tipo de função de erro. Por exemplo, MSO pode ser usado para encontrar o melhor conjunto de pesos e valores de viés para uma rede neural, minimizando erros de classificação em um conjunto de dados de treinamento. Há muitas alternativas a MSO, incluindo algoritmos de otimização evolutiva (também chamados de algoritmos genéticos reais), otimização de forrageamento bacteriana e otimização de método de ameba.

Em comparação com a maioria das abordagens alternativas otimização numérica de uso geral, na minha opinião que mso é mais simples de implementar e fácil de personalizar. Uma desvantagem do MSO comparado com algumas alternativas é que, em geral, existem algumas diretrizes para selecionar o valor dos parâmetros de MSO-livre, incluindo as constantes de peso de inércia e as constantes de peso cognitivo, social e global. Que disse, no entanto, eu sou um grande fã de MSO e ele executou muito bem para mim em ocasiões em que eu precisava para resolver um problema de otimização numérica em meus sistemas de software.

Dr. James McCaffrey* trabalha para a Microsoft, no campus de Redmond, Wash.,. Ele trabalhou em vários produtos da Microsoft, como o Internet Explorer e o MSN Busca. Ele é autor de “.NET Test Automation Recipes” (Apress, 2006) e pode ser contatado pelo email jammc@microsoft.com.*

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Kirk Olynyk (Microsoft)