Testlauf

Konsensklassifizierung mit C#

James McCaffrey

Laden Sie die Codebeispiele herunter

James McCaffreyBeim maschinellen Lernen (ML) ist Klassifizierung der Prozess der Erstellung eines Modells (normalerweise irgend eine Form von mathematischer Formel oder ein Regelsatz), das einen Wert vorhersagt, der diskrete, nicht-numerische Werte annehmen kann. Beispielsweise können Sie ja die politische Partei (Demokraten oder Republikaner) eines Kongressmitglieds auf der Grundlage seines aufgezeichneten Wahlverhaltens vorhersagen wollen. Das Trainieren des Modells ist der Prozess der Findung der Menge von Konstanten (im Modell mit der mathematischen Formel) oder des Regelsatzes (beim regelbasierten Modell), so dass beim Einspeisen von Trainingsdaten mit bekannten ausgabeabhängigen Variablenwerten die berechnete Ausgabe nahe am bekannten Ergebnis liegt. Anschließend kann das Modell verwendet werden, um Vorhersagen für neue Daten mit unbekannter Ausgabe zu machen.

Zwar gibt es viele Standard-Klassifizierungsalgorithmen und -techniken, wie etwa den Naiven Bayes-Klassifikator, die logistische Regressionsklassifizierung und die Klassifizierung mittels neuraler Netzwerke, für einige Probleme sind benutzerdefinierte Klassifizierungsalgorithmen jedoch sinnvoll. Dieser Artikel stellt eine benutzerdefinierte Technik vor, die, in Ermangelung eines besseren Namens, von mir als Konsensklassifizierung bezeichnet wird. Sehen Sie sich das Demoprogramm in Abbildung 1 an, um ein Gefühl dafür zu bekommen, was Konsensklassifizierung ist und worauf ich in diesem Artikel hinaus will.

Konsensklassifizierung in Aktion
Abbildung 1 Konsensklassifizierung in Aktion

Das Ziel des Demoprogramms besteht darin ein Modell zu erstellen, das die politische Partei (Demokrat oder Republikaner) eines Mitglieds des US-Repräsentantenhauses auf der Grundlage seines Stimmverhaltens zu 16 Gesetzgebungsvorhaben vorhersagt. Die Rohdaten bestehen aus 100 Elementen, von denen jedes einem Abgeordneten entspricht und das 17 Felder aufweist. Die ersten 16 Felder in jedem Element sind Abstimmungen, bei denen das Zeichen "y" für eine Abstimmung mit "Ja" (Yes) und der Buchstabe "n" für eine Abstimmung mit "Nein" (No) steht. Das letzte Feld in jedem Element ist die tatsächliche Parteizugehörigkeit des Abgeordneten. Beispielsweise sind die ersten beiden Datenelemente:

n, y, y, n, y, y, n, n, n, n, n, n, y, y, y, y, democrat
n, y, n, y, y, y, n, n, n, n, n, y, y, y, n, y, republican

Die Demodaten stellen eine Teilmenge eines Benchmarksatzes dar, der unter dem Namen "Congressional Voting Records Data Set" bekannt ist. Der vollständige Satz an Benchmarkdaten enthält 435 Elemente, von denen einige den Stimmwert "?" aufweisen, was ein unbekanntes Stimmverhalten oder eine Enthaltung darstellt. Die Demoteilmenge besteht aus den ersten 100 Elementen aus der vollständigen Menge, die keine "?"-Stimmen enthalten. Außerdem weist der Quelldatensatz die politische Partei in der ersten Spalte auf; ich habe die Partei in die letzte Spalte verschoben, was beim Programmieren bequemer ist.

Es ist zwar nicht erforderlich, zu wissen, welchem Gesetzesvorschlag die einzelnen 16 Stimmen entsprechen, das Thema der einzelnen Gesetze ist aber mit den 16 folgenden Kurzbeschreibungen angerissen: behinderte Kinder, Wasserprojekt, Budgetanpassung, Arzthonorare, El Salvador, Schule und Religion, Antisatelliten, Nicaraguanische Contras, Mx-Marschflugkörper, Einwanderungsgesetz, Kürzung bei synthetischen Kraftstoffen, Erziehungsetat, Verwendung von Superfunds, Kriminalitätsstatistik, Zollfreiheit, Südafrika.

Das Demoprogramm teilt die aus 100 Elementen bestehende Datenmenge in eine Menge aus 80 Elementen, die zum Trainieren des Modells verwendet werden, und eine Menge aus 20 Elementen, die zum Einschätzen der Genauigkeit des resultierenden Modells verwendet werden, auf. Ein Konsensklassifizierungsmodell besteht aus einer Reihe einfacher Regeln, wie etwa "Wenn der Abgeordnete zu Gesetz 0 mit 'y' und zu Gesetz 3 mit 'n' und zu Gesetz 15 mit 'y' abgestimmt hat, dann ist der Abgeordnete ein Republikaner". In der Demo ist die Anzahl der Booleschen Bedingungen in jeder einfachen Regel auf 5 und die Gesamtzahl der Regeln auf 500 festgelegt. Außerdem muss jede einfache Regel zumindest mit 90 % Genauigkeit den Trainingsdatenelementen entsprechen, auf die die Regel anwendbar ist.

Hinter den Kulissen hat der Trainingsprozess die 500 einfachen Regeln erstellt. Nachdem das Modell erstellt worden war, hat das Demoprogramm die Modellregeln auf die Trainingsdaten angewendet und eine Genauigkeit von 93,75 % erreicht. Anschließend wurde das Modell auf den 20 Elemente umfassenden Testsatz angewendet, was eine Genauigkeit von 84,21 % ergab – 16 richtige Vorhersagen, drei falsche Vorhersagen und ein Datenelement, auf das keine der 500 Regeln im Modell anwendbar war.

Dieser Artikel geht davon aus, dass Sie über mindestens mittlere Programmierkenntnisse und eine grundlegende Kenntnis der ML-Klassifizierung verfügen. Die Demo wurde mit C# codiert, Sie sollten den Code jedoch relativ leicht in eine andere Sprache, zum Beispiel Visual Basic .NET oder Python, umgestalten können. Der Code der Demo ist etwas zu lang, um ihn im Ganzen darzustellen, aber der gesamte Quellcode ist im Download verfügbar, der diesen Artikel unter msdn.microsoft.com/magazine/msdnmag1114 begleitet.

Allgemeine Programmstruktur

Die allgemeine Struktur des Demoprogramms mit einigen entfernten WriteLine-Anweisungen und kleineren Bearbeitungen, um Platz zu sparen, ist in Abbildung 2 dargestellt. Um das Demoprogramm zu erstellen, habe ich Visual Studio gestartet, eine neue C#-Konsolenanwendung erstellt und sie "Consensus­Classification" genannt. Die Demo hat keine nennenswerten Microsoft .NET Framework-Versionsabhängigkeiten, sodass jede halbwegs aktuelle Version von Visual Studio funktionieren sollte. Nachdem der aus der Vorlage generierte Code im Solution Explorer-Fenster geladen wurde, habe ich die Datei "Program.cs" umbenannt und ihr den anschaulicheren Namen "ConsensusProgram.cs" gegeben. Visual Studio hat daraufhin automatisch die Programmklasse umbenannt.

Abbildung 2: Allgemeine Programmstruktur

using System;
using System.Collections.Generic;
namespace ConsensusClassification
{
  class ConsensusProgram
  {
    static void Main(string[] args)
    {
      Console.WriteLine("Begin consensus classification demo");
      Console.WriteLine("Goal is predict political party");
      string[][] allData = new string[100][];
      allData[0] = new string[] { "n", "y", "y", "n", "y", "y", "n", "n",
        "n", "n", "n", "n", "y", "y", "y", "y", "democrat" };
      allData[1] = new string[] { "n", "y", "n", "y", "y", "y", "n", "n",
        "n", "n", "n", "y", "y", "y", "n", "y", "republican" };
      // Etc.
      allData[99] = new string[] { "y", "n", "y", "n", "n", "y", "y", "y",
        "y", "y", "y", "n", "n", "n", "y", "y", "democrat" };
      Console.WriteLine("All data: ");
      ShowData(allData, 5, true);
      Console.WriteLine("Creating 80-20 train-test data");
      string[][] trainData;
      string[][] testData;
      MakeTrainTest(allData, 0, out trainData, out testData); // 0 = seed
      Console.WriteLine("Training data: \n");
      ShowData(trainData, 3, true);
      Console.WriteLine("Test data: \n");
      ShowData(testData, 3, true);
      int numConditions = 5; // Conditions per rule
      int maxNumRules = 500;
      double minAccuracy = 0.90; // Min % rule accuracy
      Console.WriteLine("Setting number conditions per rule = " + 
        numConditions);
      Console.WriteLine("Setting max number simple rules    = " + 
        maxNumRules);
      Console.WriteLine("Setting simple rule min accuracy   = " +
        minAccuracy.ToString("F2"));
      ConsensusClassifier cc =
        new ConsensusClassifier(numConditions, maxNumRules);
      Console.WriteLine("Starting training");
      cc.Train(trainData, minAccuracy);
      Console.WriteLine("Done");
      Console.WriteLine("Created " + cc.RuleCount() + " simple rules");
      double trainAcc = cc.Accuracy(trainData);
      Console.WriteLine("Accuracy on train data = " + 
        trainAcc.ToString("F4"));
      int numCorrect, numWrong, numUnknown;
      double testAcc = cc.Accuracy(testData, out numCorrect,
        out numWrong, out numUnknown);
      Console.WriteLine("Accuracy on test data  = " + 
        testAcc.ToString("F4"));
      Console.WriteLine("Number correct = " + numCorrect);
      Console.WriteLine("Number wrong   = " + numWrong);
      Console.WriteLine("Number unknown = " + numUnknown);
      Console.WriteLine("End consensus classification demo\n");
      Console.ReadLine();
    }
    static void MakeTrainTest(string[][] allData, int seed,
      out string[][] trainData, out string[][] testData) { . . }
    static void ShowData(string[][] rawData, int numRows,
      bool indices) { . . }
  } // Program
  public class ConsensusClassifier { . . }
} // ns

Die gesamte Programmlogik ist in der Methode "Main" enthalten. Die Demo weist zwei statische Hilfsmethoden auf, "MakeTrainTest" und "ShowData". Die Klassifizierungslogik ist in einer einzelnen programmatisch definierten Klasse mit dem Namen "ConsensusClassifier" enthalten. Der Klassifizierer macht sechs öffentliche Methoden verfügbar: einen einzelnen Konstruktor, "RuleCount" (die tatsächliche Anzahl der einfachen Regeln im Modell, "ComputeOutput" (um nach dem Erstellen des Modells Vorhersagen zu treffen), "Train" (zum Erstellen des Modells) und eine überladene "Accuracy" (um den Prozentsatz der richtigen Vorhersagen zu berechnen).

In der Methode "Main" enthält das Demoprogramm die fest codierten, aus 100 Elementen bestehenden Quelldaten in einer Zeichenfolgenmatrix in der Art eines Arrays von Arrays:

string[][] allData = new string[100][];
allData[0] = new string[] { "n", "y", "y", "n", "y", "y", "n", "n",
  "n", "n", "n", "n", "y", "y", "y", "y", "democrat" };
...

In einem Nicht-Demoszenario lägen Ihre Daten wahrscheinlich in einer Textdatei vor, und Sie müssten die Daten mittels einer Hilfsmethode in den Speicher laden. Die Quelldaten sind in einen Trainingssatz und einen Testsatz aufgeteilt, in dieser Weise:

string[][] trainData;
string[][] testData;
MakeTrainTest(allData, 0, out trainData, out testData);

Die prozentuale 80/20-Aufteilung ist fest codiert. Das an die Methode "MakeTrainTest" übergebene 0-Wert-Argument ist ein Ausgangswert für ein Math.Random-Objekt, sodass die Trainings-Test-Aufteilung Datenelemente zufällig zuweisen kann. Eine Alternative besteht in der Verwendung eines stratifizierten Ansatzes, so dass die Anteile von Demokraten- und Republikaner-Datenelementen in den Trainings- und Testmatrizen grob der prozentualen Verteilung in der Gesamtheit der Quelldaten entsprechen.

Die Demo erstellt das Modell mit diesem Code:

int numConditions = 5;
int maxNumRules = 500;
double minAccuracy = 0.90;
ConsensusClassifier cc = 
  new ConsensusClassifier(numConditions, maxNumRules);
cc.Train(trainData, minAccuracy);

Hier habe ich mich entscheiden, Werte für die Anzahl der Booleschen Bedingungen in jeder Regel und die maximale Anzahl der zu erstellenden Regeln an den Konstruktor zu übergeben sowie einen Verweis auf die Trainingsdaten und die Mindestgenauigkeit, der jede Regel entsprechen muss, an die Methode "Train" zu übergeben. Viele Entwickler ziehen es vor, alle relevanten Parameter an den Konstruktor zu übergeben (um den Preis eines Konstruktors mit einer großen Anzahl Parameter). Hier habe ich die Werte den einzelnen Methoden übergeben, mit denen sie möglichst nahe zusammenhängen (um den Preis einer eher zufällig erscheinenden Schnittstelle).

Das Demoprogramm endet mit dem Berechnen und Anzeigen der Genauigkeit des Modells:

double trainAcc = cc.Accuracy(trainData);
Console.WriteLine("Accuracy on train data = " + trainAcc.ToString("F4"));
int numCorrect, numWrong, numUnknown;
double testAcc = cc.Accuracy(testData, out numCorrect, out numWrong,
  out numUnknown);
Console.WriteLine("Accuracy on test data  = " + testAcc.ToString("F4"));
Console.WriteLine("Number correct = " + numCorrect);
Console.WriteLine("Number wrong   = " + numWrong);
Console.WriteLine("Number unknown = " + numUnknown);

Die erste überladene "Accuracy" gibt nur den Prozentsatz der richtigen Klassifizierungen zurück. Die zweite überladene "Accuracy" gibt darüber hinaus die Anzahl der richtigen, falschen und nicht zutreffenden Datenelemente zurück, wobei "nicht zutreffend" bedeutet, dass keine der Regeln des Modells auf ein bestimmtes Datenelement angewendet werden kann. Nehmen wir beispielsweise an, der Parameter für die Anzahl der Bedingungen würde auf 3 festgelegt, und eins der Datenelemente weist vote0 = 'y', vote1 = 'y' und vote2 = ‘n’ auf. Selbst bei 500 Regeln ist es möglich, dass keine der Regeln diese Kombination von Stimmverhalten berücksichtigt. Eine Alternative besteht darin, die Methode "Accuracy" so zu entwerfen, dass nicht zutreffende Datenelemente nicht auftreten können. Ein gängiges Verfahren, das zu erreichen, besteht im Hinzufügen einer abschließenden Regel, etwa in der Art " . ...andernfalls ist der Abgeordnete ein Demokrat", wobei "Demokrat" den Standardwert darstellt, als der normalerweise der häufigste von den Variablen abhängige Wert gewählt wird.

Das Demoprogramm verwendet das Modell nicht, um die politische Partei eines Abgeordneten vorherzusagen. Das Vorhersagen der politischen Partei eines Abgeordneten, der "yes" bei den Gesetzesvorlagen [0] bis [7] und "no" bei den Gesetzesvorlagen [8] bis [15] gestimmt hat, könnte etwa so aussehen:

string[] newData = new string[] { "y", "y", "y", "y", "y", "y", "y", "y",
  "n", "n", "n", "n", "n", "n", "n", "n" };
int party = cc.ComputeOutput(newData);
Console.WriteLine("Predicted party = " + party);

Die Methode "ComputeOutput" gibt einen ganzzahligen Wert zurück, 0 oder 1, wobei "0" Republikaner und "1" Demokrat bedeutet.

Der Konsensalgorithmus

Das Kernstück des Konsensklassifizierers ist ein Regelsatz. Die zwei Hauptprobleme, mit denen wir uns befassen müssen, sind die Entscheidungen, wie Regeln dargestellt und wie sie generiert werden sollen. Das Demoprogramm stellt eine Regel als ganzzahliges Array dar. Das Schema wird am besten anhand eines konkreten Beispiels erläutert, wie in Abbildung 3 dargestellt. Angenommen, die Anzahl der Booleschen Bedingungen in jeder Regel ist auf drei festgelegt. Eine einzelne Regel würde als Array mit sieben Zellen dargestellt. Wenn das Array die Werte { 3, 0, 8, 1, 15, 1, 0 } enthielte, dann würde die Regel dem Satz "Falls Abtimmung [3] 0 ist und Abstimmung [8] 1 ist und Abstimmung [15] 1 ist, dann ist Partei 0". Der Regelsatz ist eine allgemeine Listensammlung von Regeln.

Datenstrukturen der Konsensklassifizierung
Abbildung 3 Datenstrukturen der Konsensklassifizierung

Die Bedeutung von 0 und 1 kann für jede Spalte/Abstimmung abweichen. Das Demoprogramm erstellt ein Array von Wörterbuchsammlungen, eine pro Spalte/Abstimmung. Nullbasierte ganzzahlige IDs werden jedem Zeichenfolgenwert nach dem Auftreten in den einzelnen Spalten in den Trainingsdaten zugewiesen. Beispielsweise ist in Spalte [0] für die Abstimmung [0] der erste Wert in den Trainingsdaten "n", dem daher 0 zugewiesen wird, während "y" als nächster Wert angetroffen wird, dem entsprechend 1 zugewiesen wird. In Spalte [1] tritt jedoch "y" zuerst auf, dem daher 0 zugewiesen wird, während "n" als zweiter Wert auftritt, dem 1 zugewiesen wird.

In ähnlicher Weise tritt in Spalte [16] der Trainingsdaten, der letzten Spalte, die die abhängigen Variablenwerte "democrat" und "republican" enthält, "republican" als erster Wert auf und ist daher 0, während "democrat" 1 ist. Die Zuordnungsinformationen der Zeichenfolge zur ganzen Zahl sind in einem Klassenmemberarray mit der Bezeichnung "stringToInt" gespeichert, wie in Abbildung 3 dargestellt. Der Ausdruck "stringToInt[1]["y"]" gibt also den nullbasierten Indexwert für eine Abstimmung mit yes für Abstimmung [1] zurück, der in den Trainingsdaten der Demo 0 ist.

Hier der Pseudocode auf hoher Ebene zum Erstellen der Regeln, die das Klassifizierungsmodell definieren:

loop while numRules < maxRules && trial < maxTrials
  ++trial
  select a random row of training data
  select numConditions random columns of selected row
  create candidate rule from selected columns of selected row
  if candidate rule already in rule list, continue
  if candidate rule does not meet minimum accuracy, continue
  candidate rule is good so add rule to rule list
end loop

Ein konkretes Beispiel mag das verdeutlichen. Nehmen wir an, in der Main-Schleife würde zufällig Zeile [0] der Trainingsdaten ausgewählt. Nehmen wir ferner an, dass "numConditions" auf 3 festgelegt wurde und diese zufällig ausgewählten Spalten [1], [2] und [15] sind. In den Trainingsdaten haben diese Spalten die Werte "y", "n" und "y", und die abhängige Variable weist den Wert "republican" auf. Die Spaltenwerte werden in den Array-von-Wörterbuch-Sammlungen nachgeschlagen und lauten 0, 0 und 0. Daher ist die Kandidatenregel ein ganzzahliges Array mit den Werten { 1, 0, 2, 0, 15, 0, 0 }, was so interpretiert werden kann "Wenn Spalte 1 gleich 0 und Spalte 2 gleich 0 und Spalte 15 gleich 0 ist, dann ist die Partei gleich 0".

Als nächstes werden die 80 Elemente in den Trainingsdaten durchsucht, um herauszufinden, ob die Kandidatenregel dem Kriterium der Mindestgenauigkeit entspricht. Beachten Sie, dass die Kandidatenregel für mindestens ein Datenelement richtig ist – das Datenelement, das zum Erstellen der Regel verwendet wurde. Die Kandidatenregel ist aber nicht notwendiger Weise auf alle Trainingselemente anwenbar. Zum Beispiel ist die Kandidatenregel { 1, 0, 2, 0, 15, 0, 0 }, die aus dem Datenelement [0] erstellt wurde, nicht auf das Datenelement [1] anwendbar, da die Spalte [2] den Wert 1 anstelle des erforderlichen Werts 0 aufweist.

Sobald der Regelsatz erstellt wurde, wird die Ausgabe für einen bestimmten Satz Eingangsdaten durch das bestimmt, was man Stimmauszählung nennen könnte. Für jedes Datenelement werden alle Regeln im Regelsatz analysiert. Angenommen, der Regelsatz hat – wie in der Demo – 500 Regeln. Und angenommen, dass für ein Datenelement 220 der Regeln als Partei die Republikaner und 250 der Regeln die Demokraten vorhersagen und 30 Regeln nicht anwendbar sind. Der Klassifizierer würde die den Daten zugeordnete Person als Demokraten vorhersagen. Anders gesagt stellt die Ausgabe den Konsens aus den Vorhersagen aufgrund des Regelsatzes dar.

Ein paar Kommentare

Die Motivation für die in diesem Artikel vorgestellte Konsensklassifizierungstechnik kam aus einem Forschungsprojekt, an dem ich vor einiger Zeit gearbeitet habe. Im besonderen Fall habe ich versucht, das Geschlecht – männlich oder weiblich – eines Chatbenutzers auf der Grundlage von Variablen wie dem Wohnort, der Altersklasse usw. vorherzusagen. Ich probierte jeden gängigen Klassifizierungsansatz aus, der mir einfiel, und setzte die vorhandenen ML-Tools ein, aber keiner meiner Ansätze konnte ein effektives Vorhersagemodell generieren, obwohl mir das Bauchgefühl sagte, dass die Daten genug Informationen enthielten, um ein Signal zu erzeugen.

Ich bemerkte, dass die Naive Bayes-Klassifizierung noch den besten meiner Versuche darstellte. Naive Bayes-Klassifizierung unterstellt, dass jede Vorhersagevariable mathematisch unabhängig ist. Obwohl diese Annahme meistens nicht wahr ist, funktioniert Naive Bayes in einigen Fällen recht gut. Für mein Problem waren die Vorhersagevariablen nahezu sicher in irgendeiner Weise aufeinander bezogen, ich wusste aber nicht, welche Variablen zusammenhingen. Also entschied ich mich, Teile verschiedener ML-Techniken zu verwenden, um den in diesem Artikel beschriebenen Ansatz zu erstellen. Schließlich war ich imstande, ein Vorhersagemodell zu erstellen, dass erheblich genauer als alle auf Standardtechniken beruhenden Modelle war.

Das Demoproblem ist ein binäres Klassifizierungsproblem, da die abhängige Variable entweder "Demokrat" oder "Republikaner" sein kann. Ferner stellen alle der Vorhersage-Elemente Variablen der Kategorie "Binär" dar, da sie entweder Ja oder Nein sein können. Die Konsensklassifizierung kann auch multinomiale Klassifizierungsprobleme (z. B. Demokrat, Republikaner, Unabhängiger) und Kategorievorhersage-Elemente mit mehr als zwei Werten (z. B. ja, nein, abwesend, Enthaltung) verarbeiten. In meinen Experimenten war unklar, wie effektiv Konsensklassifizierung ist, wenn sie Probleme mit numerischen Vorhersagevariablen verarbeiten soll – etwa beim Alter –, die in Kategorien zusammengefasst wurden (z. B. "jung", "mittel", "alt").

Das Kombinieren mehrerer Regeln oder Modelle zum Erstellen eines Klassifizierungsergebnisses wird im ML-Sprachgebrauch oft als gemeinsames Lernen (Ensemble Learning) bezeichnet. Die Idee, viele einfache Regeln zu erzeugen, wird in ML-Techniken verwendet, und diese werden gelegentlich als Boosting-Techniken bezeichnet. Ich möchte ausdrücklich darauf hinweisen, dass ich in meiner Kolumne "Testlauf" fast immer ML-Techniken vorstelle, die auf soliden Forschungsergebnissen beruhen. Dieser Artikel stellt jedoch eine Ausnahme dar. Die hier vorgestellte Technik wurde nicht formal untersucht oder ernsthafter Forschung unterzogen. Mehrere akademische Kollegen haben mich wegen meiner ungeheuren Chuzpe, benutzerdefinierte, nicht traditionelle ML-Techniken zu verwenden, zur Rede gestellt, meine übliche, vielleicht etwas abfällige Antwort ist dann, dass ich normalerweise mehr an Ergebnissen als am Begründen eleganter mathematischer Theoreme interessiert bin. Meiner Meinung nach ist die Verwendung standardmäßiger ML-Techniken anfangs die beste Möglichkeit, sich einem ML-Problem zu nähern, wenn traditionelle Techniken nicht zum Erfolg führen, kann jedoch das Erstellen einer benutzerdefinierten Lösung manchmal erstaunlich effektiv sein.


Dr. James McCaffrey arbeitet für Microsoft Research in Redmond, Wash. Er arbeitet an verschiedenen Microsoft-Produkten wie Internet Explorer und Bing. Sie erreichen ihn unter jammc@microsoft.com.

Unser Dank gilt den folgenden technischen Experten von Microsoft Research für die Durchsicht dieses Artikels: Marciano Moreno Diaz Covarrubias, Delbert Murphy, David Raskino und Alisson Sol