この記事は機械翻訳されたものです。

テストの実行

C# による単純ベイズ分類

James McCaffrey

コード サンプルをダウンロードする

 

James McCaffrey素朴なベイズ分類カテゴリに属する特定のデータの場合を予測する機械学習手法です。 この記事では私はナイーブベイズ分類のしくみを説明して、c# 言語では、例を提示します。

たくさんの単純ベイズ分類を実行することができます利用可能なスタンドアロン ツールです。 ただし、これらのツールをアプリケーションに直接統合することは不可能困難や特定のニーズに合わせてカスタマイズすることは困難することができます。 彼らは著作権の問題を隠している可能性があります。 この記事は、ナイーブベイズ分類機能を .NET アプリケーションに任意の外部の依存関係に依存することがなく追加する、強固な基盤を与えます。

ナイーブベイズ分類を理解する最良の方法どこに向かっているの記事を参照してくださいに、デモ プログラムでのスクリーン ショットを調べることです図 1。 デモ プログラムは、40 行の分類子のトレーニングに使用するデータを生成することによって開始します。 ほとんどの場合、既存のデータ ソースを使用すると思いますが、私はデモをシンプルにダミーのデータ生成します。 データの最初の行は「管理、右、72.0、女性.」最初のフィールドは職業では、2 番目の手の優位性です第 3 インチの高さです、4 番目のセックスです。 分類子の性別、職業、優位性と高さの値の特定のセットから予測すること。 従属変数セックス 2 つの可能値があり、これはバイナリの分類の例です。

Naive Bayes Classification Demo
図 1 単純ベイズ分類デモ

生データを生成した後、デモ プログラムは各数値の高さフィールドをカテゴリに変換 — 短期、中期、背の高い — によって高さのビニングします。 私は説明するように、カテゴリカル データに数値データをビン分割の長所と短所がありますアプローチです。 トレーニング データがビン分割されて後、デモ プログラムはカテゴリカル データの 40 行をスキャンし、共同カウントを計算します。 たとえば、ここで人の職業が管理、人の性別は男性ですデータのケースの数は 2 です。 さらに、各依存値 (予測、男性または女性この例では可能属性) の合計数が計算されます。 24 男女 16 のトレーニング データがあることを参照してくださいすることができます。

デモ プログラムは、ここ、職業教育、優位性が右ですで、高さが背の高い新しいデータ例の性別を分類するために必要なすべての情報を持っています。 この例では、どうやら決定デモをデータの場合は男性である確率 0.3855 と 0.6145、女性は確率であるデータの場合、システムを終了と最も可能性の高い女性。

以下のセクションで最初ナイーブベイズ分類のしくみを説明する、デモ プログラムでコードを歩くし、独自のニーズを満たすためにデモを変更する方法について説明します。 この記事が少なくとも必要開始プログラミングの C ファミリの言語とスキルが単純ベイズ分類について何かを知っているものではありません。 デモ プログラムのコードはすべてここでは、少し長すぎますが、完了のソースは、MSDN のダウンロード サイトから入手可能です archive.msdn.microsoft.com/mag201302TestRun

どのように素朴なベイズ分類作品

例を使用して図 1、目標は右利きとその高さは背の高い教育、職業の人の性別 (男性または女性) を予測することです (71.0 インチ以上)。 これを行うには、その情報を与え、人が男性で、確率人の情報を与えられた女性は確率を計算し、セックスのより大きい確率を予測できます。 象徴的に表現する、我々 P(male | を知りたいです。X)、通常、読み取り"男性の確率独立変数の値 X") と P(female |X X は (教育、右、背の高い))。 Naive Bayes で「ナイーブ」とはすべての属性 X 数学的に独立したが大幅分類を簡素化とすることをいいます。 かなり面白い数学ナイーブベイズ分類の背後にある説明する多くのオンライン参照を見つけることができますが、結果は比較的簡単です。 象徴:

P(male | X) =
  [ P(education | male) * P(right | male) * P(tall | male) * P(male) ] /
    [ PP(male | X) + PP(female | X) ]

注意式ほんの一部です。 時々 疎、部分的な確率と呼ばれる分子を乗算した値 4 つの言葉で構成されます。 この記事では、部分的な確率用語の PP の非標準の表記法を使用します。 分母、分子は 2 つの用語の合計です。 計算するには、最初の部分です P(education | male)、または彼は男性が人の職業教育であるか。 結局のところ、これは、職業教育はセックスは男性、(すべての職業と)、男性であるケースの数で割った場合の訓練の数によって見積もることができますので。

P(education | male ) = count(education & male) / count(male) = 2/24 = 0.0833

同じロジックを使用してください。

P(right | male) = count(right & male) / count(male) = 17/24 = 0.7083
P(tall | male) = count(tall & male) / count(male) = 4/24 = 0.1667

次のパズル P(male) です。 Naive Bayes 用語では、前に呼び出されます。 いくつかの議論について事前確率を計算するために最善の方法です。 1 つの手で、男性の存在が女性の存在よりも、多かれ少なかれ可能性があることを信じるので 0.5 P(male) を割り当てる理由がないことを仮定することができます。 その一方で、我々 訓練データが 24 男女 16 名いる 24/40 の確率を見積もるという事実を使用することができます 0.6000 インチ P(male) を =。 このアプローチを好む訓練データで事前確率を推定が。

今、P(male | の以前の式は、参照してください場合X)、PP(female | が含まれている注意してくださいよX)。 下の合計は、PP(male |X) + PP(female |X)、時々 呼ばれます証拠。 PP(female | のための小品計算 X) されるようになります。

P(education | female) = count(education & female) / count(female) = 4/16 = 0.2500
P(right | female) = count(right & female) / count(female) = 14/16 = 0.8750
P(tall | female) = count(tall & female) / count(female) = 2/16 = 0.1250
P(female) = 16/40 = 0.4000

だから、部分的な確率分子 P(male | のX) です。

PP(male | X) = 0.0833 * 0.7083 * 0.1667 * 0.6000 = 0.005903

同じロジック、部分的な確率を使用してのための女性の x = (教育、右、背の高い) です。

PP(female | X) = 0.2500 * 0.8750 * 0.1250 * 0.4000 = 0.010938

そして、最後に、男性と女性の全体的な確率は:

P(male | X) = 0.005903 / (0.005903 + 0.010938) = 0.3505
P(female | X) = 0.010938 / (0.005903 + 0.010938) = 0.6495

これらの全体的な確率は、事後を呼ばれます。 P(female |X) P(male | よりも大きい値です。X)、未知の人の性別は女性であり、システムを終了します。 でもちょっと待って。 これら 2 つの確率 0.3505 と 0.6495、です近くには間違いなく 2 つの確率と同じ 0.3855 と 0.6145 に、 図 1。 この不一致の理由は、デモ プログラムのラプラシアンを平滑化と呼ばれる基本的な Naive Bayes の重要なオプション修正されます。

ラプラシアンの平滑化

場合を参照してください図 1、ことがわかる場合人が職業訓練の数建設とセックスを = = 女性は 0 です。 デモでは、X の値 (教育、右、背の高い)、建設が含まれていないです。 しかし、X (建設、右、背の高い) されていたと仮定します。 PP(female | の計算X) の計算に必要となる P(construction | female) count(construction & = 女性) が 0 とは、順番に count(female) とゼロ出力全体の部分的な確率。 一言で言えば、関節数が 0 の場合悪いです。 このような状況を避けるために、単に 1 すべての共同カウントを追加するには最も一般的です。 これはハックの感じがあるが、実際には、固体の数学的な基礎がないです。 技術系の平滑化、ラプラシアンを平滑化の特定の種類と呼ばれます。

X = 場合 (教育、右、背の高い) 前のセクションでは、P(male | とラプラシアン、スムージングX) と P(female |計算 X) です。

P(education | male ) =
count(education & male) + 1 / count(male) + 3 = 3/27 = 0.1111
P(right | male) =
count(right & male) + 1 / count(male) + 3 = 18/27 = 0.6667
P(tall | male) =
count(tall & male) + 1 / count(male) + 3 = 5/27 = 0.1852
P(male) = 24/40 = 0.6000
P(education | female) =
count(education & female) + 1 / count(female) + 3 = 5/19 = 0.2632
P(right | female) =
count(right & female) + 1 / count(female) + 3 = 15/19 = 0.7895
P(tall | female) =
count(tall & female) + 1 / count(female) + 3 = 3/19 = 0.1579
P(female) = 16/40 = 0.4000

部分的な確率です。

PP(male | X) = 0.1111 * 0.6667 * 0.1852 * 0.6000 = 0.008230
PP(female | X) = 0.2632 * 0.7895 * 0.1579 * 0.4000 = 0.013121

2 つの最終的な確率ですので:

P(male | X) = 0.008230 / (0.008230 + 0.013121) = 0.3855
P(female | X) = 0.013121 / (0.008230 + 0.013121) = 0.6145

これらのスクリーン ショットに表示されている値は図 1。 3 は、分母 count(male) と count(female) に追加されますが、その 1 各関節の数に追加されますに注意してください。 3 は、ある程度のラプラシアン平滑化使用する特定の値が指定されていないことを意味で任意です。 この場合、属性 (職業、優位性、高さ) X の数です。 これは部分的な確率で平滑化、ラプラシアンの分母を追加する最も一般的な値ですが、他の値を試してみるかもしれません。 分母に追加する値は、しばしば Naive Bayes 数学文献でシンボル k を与えられます。 また、事前確率の P(male)、P(female)、通常ではないことに注意してください Naive Bayes ラプラシアンを滑らかに変更。

プログラムの全体構造

実行されているデモ プログラム図 1 1 つの c# コンソール アプリケーションです。 Main メソッドの WriteLine ステートメントの削除、いくつかの表示されている図 2

図 2 単純なベイズ プログラム構造

using System;
namespace NaiveBayes
{
  class Program
  {
    static Random ran = new Random(25); // Arbitrary
    static void Main(string[] args)
    {
      try
      {
        string[] attributes = new string[] { "occupation", "dominance",
          "height", "sex"};
        string[][] attributeValues = new string[attributes.Length][];
        attributeValues[0] = new string[] { "administrative",
          "construction", "education", "technology" };
        attributeValues[1] = new string[] { "left", "right" };
        attributeValues[2] = new string[] { "short", "medium", "tall" };
        attributeValues[3] = new string[] { "male", "female" };
        double[][] numericAttributeBorders = new double[1][];
        numericAttributeBorders[0] = new double[] { 64.0, 71.0 };
        string[] data = MakeData(40);
        for (int i = 0; i < 4; ++i)
          Console.WriteLine(data[i]);
        string[] binnedData = BinData(data, attributeValues,
          numericAttributeBorders);
        for (int i = 0; i < 4; ++i)
          Console.WriteLine(binnedData[i]);
        int[][][] jointCounts = MakeJointCounts(binnedData, attributes,
          attributeValues);
        int[] dependentCounts = MakeDependentCounts(jointCounts, 2);
        Console.WriteLine("Total male = " + dependentCounts[0]);
        Console.WriteLine("Total female = " + dependentCounts[1]);
        ShowJointCounts(jointCounts, attributeValues);
        string occupation = "education";
        string dominance = "right";
        string height = "tall";
        bool withLaplacian = true;
        Console.WriteLine(" occupation = " + occupation);
        Console.WriteLine(" dominance = " + dominance);
        Console.WriteLine(" height = " + height);
        int c = Classify(occupation, dominance, height, jointCounts,
          dependentCounts, withLaplacian, 3);
        if (c == 0)
          Console.WriteLine("\nData case is most likely male");
        else if (c == 1)
          Console.WriteLine("\nData case is most likely female");
        Console.WriteLine("\nEnd demo\n");
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message);
      }
    } // End Main
    // Methods to create data
    // Method to bin data
    // Method to compute joint counts
    // Helper method to compute partial probabilities
    // Method to classify a data case
  } // End class Program
}

プログラムはハードコードされた X 属性職業、優位性と高さ、および依存属性セックスを設定して開始します。 いくつかの状況で特に、ソース ヘッダー、データ ファイルや SQL テーブルの列名をある場合、属性を既存のデータ ソースをスキャンすることを好むことがあります。 デモ プログラムも 9 カテゴリカル属性の値を指定します。(管理、建設、教育、技術) の職業。 (左、右) の優位性。 (、媒体、背の高い高さの短い)。 この例では 2 つの従属変数属性値があります。男性 (女性) のセックス。 もう一度、あなたのデータをスキャンすることによって、属性値をプログラムによって確認することができます。

デモは 64.0 以下として分類されて値を短いので、数値の高さの値をビンに 64.0 と 71.0 のハードコードされた境界値を設定します。 高さ 64.0 と 71.0 媒体です。 や、71.0 以上の高さは背の高いです。 Naive Bayes の数値データをビン分割すると、境界値の数は 1 つされるカテゴリ数よりも少ない。 この例では、64.0 と 71.0 高さの最小値と最大値 (57.0 と 78.0) についてのトレーニング データをスキャン、21.0 の違いを計算および、間隔サイズ 7.0 を与える 3、高さカテゴリの数で割って計算によって決定しました。 ほとんどの場合、あなたの数値を決定する境界値をお勧めします X 属性を手動ではなくプログラムによって。

デモ プログラムは多少のランダムな訓練データを生成する MakeData ヘルパー メソッドを呼び出します。 MakeData ヘルパー MakeSex、MakeOccupation、MakeDominance、MakeHeight を呼び出します。 たとえば、これらのヘルパーでは、男性の職業が建設テクノロジーが多い、男性優位が正しいらしい、男性の高さは 66.0 そして 72.0 インチの間に最も可能性が高いですがデータを生成します。

メインと呼ばれるキーのメソッドは、高さデータを分類するために BinData です。 MakeJointCounts ビン分割データをスキャンし、共同のカウントを計算します。 男性と女性の合計数を計算するために MakeDependentCounts。 分類は、関節や依存のカウントを使用して単純ベイズ分類を実行します。

データのビニング

メソッド BinData が表示されます図 3。 このメソッドは各文字列「左、67.5、男性教育のような、"になりますコンマで区切られた文字列配列を受け入れます多くの状況では、トレーニング データにそれぞれの行は、文字列と、テキスト ファイルから読んでいるんです。 メソッドでは、String.Split を使用して各文字列をトークンに解析します。 トークン [2] は、高さです。 文字列からダブル、ダブルを使用して型に変換されます。メソッドを解析します。 数値の高さに対する境界値は、高さの間隔を見つかるし、対応する高さカテゴリ文字列として決定されますと比較されます。 結果の文字列は、一緒に古いトークン、カンマ区切り記号および新しいの計算高さカテゴリ文字列を使用してステッチされています。

図 3 法 BinData 高さを分類します。

static string[] BinData(string[] data, string[][] attributeValues,
  double[][] numericAttributeBorders)
{
  string[] result = new string[data.Length];
  string[] tokens;
  double heightAsDouble;
  string heightAsBinnedString;
  for (int i = 0; i < data.Length; ++i)
  {
    tokens = data[i].Split(',');
    heightAsDouble = double.Parse(tokens[2]);
    if (heightAsDouble <= numericAttributeBorders[0][0]) // Short
      heightAsBinnedString = attributeValues[2][0];
    else if (heightAsDouble >= numericAttributeBorders[0][1]) // Tall
      heightAsBinnedString = attributeValues[2][2];
    else
      heightAsBinnedString = attributeValues[2][1]; // Medium
    string s = tokens[0] + "," + tokens[1] + "," + heightAsBinnedString +
      "," + tokens[3];
    result[i] = s;
  }
  return result;
}

数値データにゴミ箱ナイーブベイズ分類を実行するための要件ではないです。 Naive Bayes 数値データを直接扱うことができますが、それらの技術をこの資料の範囲外です。 データのビニングのシンプルさとデータの数学的分布 (ガウス ポアソンなど) について特定の明示的な前提条件を確認する必要を回避する利点があります。 しかし、本質的にビニング情報が失われます、決定し、どのように多くの分類データを分割するを指定する必要は。

共同の決定をカウントします。

単純ベイズ分類キー共同数を計算することです。 デモの例では、ある属性値 X 9 完全に独立して (管理、建設、… 背の高い) と 2 つの依存属性値 (オス、メス)、合計 9 * 2 = 18 共同のカウントは、する必要があります計算され格納されています。 私の優先のアプローチは、3 次元の配列 int jointCounts で共同のカウントを格納します。 独立した最初のインデックスを示す属性です X。 2 番目のインデックスには、独立した属性値 X を示します。 3 番目のインデックスに依存する属性値を示します。 たとえば、jointCounts [0] [3] [1] 属性 0 (職業) を意味する、属性値 3 (技術) と 1 (メス), セックスか他の言葉で jointCounts [0] [3] [1] に値トレーニングここ職業技術で、セックスが女性のケースの数。 メソッド MakeJointCounts が表示されます図 4

図 4 メソッド MakeJointCounts

static int[][][] MakeJointCounts(string[] binnedData, string[] attributes,
  string[][] attributeValues)
{
  int[][][] jointCounts = new int[attributes.Length - 1][][]; // -1 (no sex)
  jointCounts[0] = new int[4][]; // 4 occupations
  jointCounts[1] = new int[2][]; // 2 dominances
  jointCounts[2] = new int[3][]; // 3 heights
  jointCounts[0][0] = new int[2]; // 2 sexes for administrative
  jointCounts[0][1] = new int[2]; // construction
  jointCounts[0][2] = new int[2]; // education
  jointCounts[0][3] = new int[2]; // technology
  jointCounts[1][0] = new int[2]; // left
  jointCounts[1][1] = new int[2]; // right
  jointCounts[2][0] = new int[2]; // short
  jointCounts[2][1] = new int[2]; // medium
  jointCounts[2][2] = new int[2]; // tall
  for (int i = 0; i < binnedData.Length; ++i)
  {
    string[] tokens = binnedData[i].Split(',');
    int occupationIndex = AttributeValueToIndex(0, tokens[0]);
    int dominanceIndex = AttributeValueToIndex(1, tokens[1]);
    int heightIndex = AttributeValueToIndex(2, tokens[2]);
    int sexIndex = AttributeValueToIndex(3, tokens[3]);
    ++jointCounts[0][occupationIndex][sexIndex];
    ++jointCounts[1][dominanceIndex][sexIndex];
    ++jointCounts[2][heightIndex][sexIndex];
  }
  return jointCounts;
}

実装は、理解しやすく多くのハードコードされた値を持っています。 たとえば、これら 3 つのステートメントは、単一の配列属性の値の長さプロパティを使用して領域を割り当てますループを置き換えることができます。

jointCounts[0] = new int[4][]; // 4 occupations
jointCounts[1] = new int[2][]; // 2 dominances
jointCounts[2] = new int[3][]; // 3 heights

ヘルパー関数 AttributeValueToIndex は、属性インデックスと、属性値の文字列を受け取り、適切なインデックスを返します。 たとえば、AttributeValueToIndex (2、「中」) は 1 である高さ属性には、「中」のインデックスを返します。

デモ プログラムでは、メソッド MakeDependentCounts を使用して、男性と女性のデータのケースの数を決定します。 アクセス権を与えるには、いくつかの方法があります。 場合を参照してください図 1、1 つの方法で 3 つの属性のいずれかの共同のカウント数を追加するにはあることを確認できます。 たとえば、男性の数 count(administrative & の合計です。 男性)、count(construction & 男性)、count(education & 男性) と count(technology & 男性):

static int[] MakeDependentCounts(int[][][] jointCounts,
  int numDependents)
{
  int[] result = new int[numDependents];
  for (int k = 0; k < numDependents; ++k) 
  // Male then female
    for (int j = 0; j < jointCounts[0].Length; ++j)
    // Scanning attribute 0
      result[k] += jointCounts[0][j][k];
  return result;
}

データのケースの分類

メソッドに分類、 図 5、ヘルパー メソッドに依存しているために、短いです。

図 5 の方法を分類します。

static int Classify(string occupation, string dominance, string height,
  int[][][] jointCounts, int[] dependentCounts, bool withSmoothing,
  int xClasses)
{
  double partProbMale = PartialProbability("male", occupation, dominance,
    height, jointCounts, dependentCounts, withSmoothing, xClasses);
  double partProbFemale = PartialProbability("female", occupation, dominance,
    height, jointCounts, dependentCounts, withSmoothing, xClasses);
  double evidence = partProbMale + partProbFemale;
  double probMale = partProbMale / evidence;
  double probFemale = partProbFemale / evidence;
  if (probMale > probFemale) return 0;
  else return 1;
}

方法を分類する jointCounts と dependentCounts の配列を受け取ります。 ラプラシアンの平滑化を使用するかどうかを示すブール値フィールド。 ので、3 つの独立変数 (職業、優位性、高さ) はこの例では 3 パラメーター xClasses。 このパラメーターはまた、jointCounts パラメーターから推論できませんでした。

メソッドの分類予測の従属変数のインデックスを表す int を返します。 代わりに、各従属変数の確率の配列を取得する可能性があります。 分類 probMale、probFemale、どちらも部分的な確率を証拠値で割ることによって計算されます基づいていることに注意してください。 単に証明項目を省略してだけ自分での部分的な確率の値を比較する可能性があります。

分類法は、最大の可能性が従属変数のインデックスを返します。 代替のしきい値値を指定します。 たとえば、probMale は 0.5001、probFemale は 0.4999。 これらの値は、あまりにも呼び出し、「未定義」を表す分類値を返すこと近くを検討するとして可能性があります

メソッド PartialProbability の分類の仕事のほとんどに記載されている図 6

図 6 メソッド PartialProbability

static double PartialProbability(string sex, string occupation, string dominance,
  string height, int[][][] jointCounts, int[] dependentCounts,
  bool withSmoothing, int xClasses)
{
  int sexIndex = AttributeValueToIndex(3, sex);
  int occupationIndex = AttributeValueToIndex(0, occupation);
  int dominanceIndex = AttributeValueToIndex(1, dominance);
  int heightIndex = AttributeValueToIndex(2, height);
  int totalMale = dependentCounts[0];
  int totalFemale = dependentCounts[1];
  int totalCases = totalMale + totalFemale;
  int totalToUse = 0;
  if (sex == "male") totalToUse = totalMale;
  else if (sex == "female") totalToUse = totalFemale;
  double p0 = (totalToUse * 1.0) / (totalCases); // Prob male or female
  double p1 = 0.0;
  double p2 = 0.0;
  double p3 = 0.0;
  if (withSmoothing == false)
  {
    p1 = (jointCounts[0][occupationIndex][sexIndex] * 1.0) / totalToUse
    p2 = (jointCounts[1][dominanceIndex][sexIndex] * 1.0) / totalToUse;  
    p3 = (jointCounts[2][heightIndex][sexIndex] * 1.0) / totalToUse;     
  }
  else if (withSmoothing == true)
  {
    p1 = (jointCounts[0][occupationIndex][sexIndex] + 1) /
     ((totalToUse + xClasses) * 1.0); 
    p2 = (jointCounts[1][dominanceIndex][sexIndex] + 1) /
     ((totalToUse + xClasses) * 1.0 ;
    p3 = (jointCounts[2][heightIndex][sexIndex] + 1) /
     ((totalToUse + xClasses) * 1.0);
  }
  //return p0 * p1 * p2 * p3; // Risky if any very small values
  return Math.Exp(Math.Log(p0) + Math.Log(p1) + Math.Log(p2) + Math.Log(p3));
}

メソッド PartialProbability はほとんどハードコードされた明確にするためです。 たとえば、4 つの確率作品 p0、p1、p2、p3 です。 配列のサイズを jointCounts 配列から決定確率の配列を使用してより一般的な PartialProbability をすることができます。

4 確率個の製品を返す代わりに、メソッドの各部分のログの合計と同じ経験を返すことに注意してください。 ログの確率を使用して非常に小さい実数値値で発生することができます数値エラーを防ぐために使用される機械学習アルゴリズムでの標準的な手法です。

まとめ

ここで示す例ナイーブベイズ分類機能を .NET アプリケーションに追加するために良い基礎を与える必要があります。 素朴なベイズ分類は比較的原油手法ですが、ニューラル ネットワーク、ロジスティック回帰の分類とサポート ベクトル マシンの分類などのより高度な選択肢のいくつかの利点は。 Naive Bayes はシンプルで比較的簡単に実装できるとも非常に大きなデータ セットを拡張します。 Naive Bayes 語義分類問題に簡単に拡張 -これらの 3 つ以上の従属変数。

Dr.James McCaffrey動作ボルト情報科学株式会社は、彼はマイクロソフトのレドモンド、ワシントン州でキャンパス働くソフトウェア エンジニアの技術トレーニングを管理します。これまでに、Internet Explorer、MSN サーチなどの複数のマイクロソフト製品にも携わってきました。彼は、『.NET Test Automation Recipes』(Apress、2006 年) の著者でもあり、jammc@microsoft.com (英語のみ) から連絡できます。

この記事のレビューの次のマイクロソフト技術専門家のおかげで:豊富な Caruana