本文章是由機器翻譯。

測試回合

分類和神經網路的預測

James McCaffrey

下載代碼示例

James McCaffrey

在本月的專欄中,我解釋了如何使用神經網路來解決分類和預測的問題。分類的目的是最好的例子解釋。假設您有鳶尾花一些歷史資料,類似于:

5.1 3.5 1.4 0.2 Setosa

7.0 3.2 4.7 1.4 Versicolor

6.3 3.3 6.0 2.5 錦葵

6.4 3.2 4.5 1.5 Versicolor

...

以空格分隔的資料的每行有五個欄位。 前四個數值欄位是繪製 (綠芽覆蓋) 長度、 繪製寬度、 花瓣 (彩色花朵的部分) 長度和花瓣寬度。 第五場是物種: Setosa、 Versicolor 或錦葵。 分類的目的是確定一個方程式或預測哪些物種或類虹膜所屬的規則集。 然後可以使用的規則集來預測新的虹膜基於其繪製和花瓣長度和寬度的值的類。 此虹膜植物資料是一個經典的例子,第一次使用鋼筋 A. 1936 年的費雪。 它可能不會讓你,激動,但分類是極為重要的。 例子包括分類基於變數,如收入和每月的開支的申請人的信用評級 (或相等,預測其信用) 和分類醫院病人是否有癌症基於從血液測試的值。

有許多方法分類資料,其中包括神經網路的使用。 想想神經網路的一種方法是它們是虛擬的輸入輸出裝置,接受任何數量的數位輸入並產生任何數量的數位輸出。 您可以看到我駛向哪裡的最佳方式是以檢查中的截圖圖 1 、 圖像的圖 2圖 1 顯示神經網路分類中的行動。 若要保持清晰利用神經網路分類的概念,我並沒有用真實的資料。 而我使用人工資料輸入的 x 值在哪裡四個任一數字值沒有任何特別的意義。 輸出 y-變數進行分類是彩色的和它可以採取斷然的三個值之一: 紅色、 綠色或藍色。 中顯示的程式圖 1 開始通過生成帶有 100 行人工資料的文字檔 (例如,"8.0 5.0 9.0 5.0 綠色"),,然後顯示該資料的前四行。 下一步,程式的原始資料檔案讀入記憶體作為培訓與 80 行的資料以及為測試矩陣 20 行。 請注意兩個轉換應用於原始資料。 原始數位輸入的資料歸一化以便為-1.00 和 +1.00,之間的所有值和原始輸出的資料 (如"紅色") 編碼到一個載體,與三個值 ("1.0 0.0 0.0")。


圖 1 在訴訟中的神經網路分類

Neural Network Structure
圖 2 神經網路的結構

創建培訓和測試矩陣,完成後演示計畫有三個輸入的神經元、 五個隱藏的神經元的計算和三個輸出神經元創建完全連接的前饋神經網路。 原來,4-5-3 完全連接神經網路需要 43 重量和偏見。 下一步,分類程式將分析要查找的最佳 43 權重和偏見 (即那些總分類誤差最小) 的培訓資料。 程式使用粒子群優化和交叉熵錯誤估計重量和偏見的最佳值。

分類程式然後載入到神經網路的最佳權重和偏見,並評估 20 行的測試矩陣中的資料模型的預測準確性。 通知的神經網路輸出設計了這樣的三個輸出值總和為 1.0。 在此示例中,該模型正確預測 17 20 測試向量。 中的圖像圖 2 說明接受輸入的 (-1.00、 1.00、 0.25、-0.50) 和生成輸出預測 (0.9、 0.1、 0.0),它對應于紅色的神經網路。

示常式序指出有五個主要決定要使用的輸入的資料是數值,輸出的資料分類的分類的神經網路時:

  1. 如何規範數位輸入的資料
  2. 如何進行分類編碼輸出資料
  3. 如何生成神經輸出中的範圍 [0.0,1.0]
  4. 如何衡量訓練時的錯誤
  5. 如何測量精度測試時

在下面幾節中,我會解釋在示常式序中所做的選擇是:

  1. 輸入的數值資料上執行的線性變換
  2. 1 N 為使用編碼分類輸出資料
  3. 使用 Softmax 啟動函數的輸出層
  4. 使用交叉熵的錯誤來確定最佳權重
  5. 使用贏家全時間的方法來確定精度

生成的截圖中的程式碼圖 1 有點太長,目前在這篇文章,所以我專注而使用的演算法。 完整的程式,源是可從 MSDN 代碼下載網站在 archive.msdn.microsoft.com/mag201207TestRun。 本文假定您擁有先進的程式設計技巧和神經網路的一個基本的瞭解。 我解釋的 MSDN 雜誌五月 2012年問題中的神經網路基礎知識 (msdn.microsoft.com/magazine/hh975375)。

整體程式結構

圖 3 列出了顯示在運行的示例中,程式結構圖 1。 Visual Studio 2010 用於創建單個 C# 主控台應用程式命名為 NeuralClassification。 在解決方案資源管理器視窗中,我重 program.cs,然後從檔命名為更具描述性的 NeuralClassificationProgram.cs,自動重命名包含主要的類。 我刪除了不需要使用語句生成的 Visual Studio 範本並添加 System.IO 命名空間的引用。

圖 3 神經分類程式結構

using System;
using System.IO;
namespace NeuralClassification
{
  class NeuralClassificationProgram
  {
    static Random rnd = null;
    static void Main(string[] args)
    {
      try
      {
        Console.WriteLine("\nBegin Neural network classification\n");
        rnd = new Random(159); // 159 makes a nice example
        string dataFile = "..\\..\\colors.txt";
        MakeData(dataFile, 100);
        double[][] trainMatrix = null;
        double[][] testMatrix = null;
        MakeTrainAndTest(dataFile, out trainMatrix, out testMatrix);
        NeuralNetwork nn = new NeuralNetwork(4, 5, 3);
        double[] bestWeights = nn.Train(trainMatrix);
        nn.SetWeights(bestWeights);
        double accuracy = nn.Test(testMatrix);
        Console.WriteLine("\nEnd neural network classification\n");
      }
      catch (Exception ex)
      {
        Console.WriteLine("Fatal: " + ex.Message);
      }
    } // Main()
    static void MakeData(string dataFile, int numLines) { ... }
    static void MakeTrainAndTest(string file, out double[][] trainMatrix,
      out double[][] testMatrix) { ... }
  }
  class NeuralNetwork
  {
    // Class member fields here
    public NeuralNetwork(int numInput, int numHidden,
      int numOutput) { ... }
    public void SetWeights(double[] weights) { ... }
    public double[] ComputeOutputs(double[] currInputs) { ... }
    private static double SigmoidFunction(double x) { ... }
    private static double[] Softmax(double[] hoSums) { ... }
    public double[] Train(double[][] trainMatrix) { ... }
    private double CrossEntropy(double[][] trainData,
      double[] weights) { ... }
    public double Test(double[][] testMatrix) { ... }
  }
  public class Helpers
  {
    static Random rnd = new Random(0);
    public static double[][] MakeMatrix(int rows, int cols) { ... }
    public static void ShuffleRows(double[][] matrix) { ... }
    public static int IndexOfLargest(double[] vector) { ... }
    public static void ShowVector(double[] vector, int decimals,
      bool newLine) { ... }
    public static void ShowMatrix(double[][] matrix, int numRows) { ... }
    public static void ShowTextFile(string textFile, int numLines) { ... }
  }
  public class Particle
  {
    // Class member fields here
    public Particle(double[] position, double fitness,
      double[] velocity, double[] bestPosition,
     double bestFitness) { ... }
    public override string ToString() { ... }
  }
} // ns

除了包含 Main 方法的類,該程式具有其他三個類。 神經網路類封裝一個完全連接的前饋神經網路。 所有核心程式邏輯包含在此類。 類傭工包含六個實用程式常式。 類粒子定義粒子物件所用的粒子群優化演算法在神經網路類的培訓方法。 分類程式的一個特點是有許多可能的程式結構 ; 這裡介紹的組織是只是一種可能性。

生成的原始資料檔案

在大多數分類方案中您將已有一套的原始資料,但對於這篇文章我創建的虛擬的原始資料,使用 MakeData 方法。 這裡是偽代碼中的過程:

create 43 arbitrary weights between -2.0 and +2.0
create a 4-5-3 neural network
load weights into the neural network
open a result file for writing
loop 100 times
  generate four random inputs x0, x1, x2, x3 between 1.0 and 9.0
  compute the y0, y1, y2 neural outputs for the input values
  determine largest of y0, y1, y2
  if y0 is largest write x0, x1, x2, x3, red
  else if y1 is largest write x0, x1, x2, x3, green
  else if y2 is largest write x0, x1, x2, x3, blue
end loop
close result file

在這裡的目標是讓一些資料,是絕對分類準確率 100%,而不是它的亂數據不清楚任何的分類方法將如何有效。 換句話說,我使用的一個神經網路來創建原始資料然後我從頭開始使用一種神經網路來嘗試對該資料進行分類。

創建培訓和測試矩陣

執行分類分析與一個現有的資料集時,稱為定型驗證的一個常用方法是將資料拆分為一個較大的資料集 (通常為 80%) 的培訓的神經網路和用於測試模型較小的資料集 (20%)。 訓練手段發現神經網路重量和偏見,儘量減少一些錯誤的值。 發現在訓練中,使用某種程度的準確性的最佳權重評價的神經網路的檢測手段。 在這裡,MakeTrainAndTest 方法創建培訓和測試矩陣也正常化數位輸入的資料和進行編碼的分類輸出資料。 偽代碼,在該方法的工作方式:

determine how many rows to split data into
create a matrix to hold all data
loop
  read a line of data
  parse each field
  foreach numeric input
    normalize input to between -1.0 and +1.0
  end foreach
  encode categorical output using 1-of-N
  place normalized and encoded data in matrix
end loop
shuffle matrix
create train and test matrices
transfer first 80% rows of matrix to train, remaining rows to test

方法的簽名是:

static void MakeTrainAndTest(string file, out double[][] trainMatrix,
  out double[][] testMatrix)

被稱為檔的參數是要創建的原始資料檔案的名稱。 參數 trainMatrix 和 testMatrix 是輸出參數結果的放置位置。 該方法的開頭:

int numLines = 0;
FileStream ifs = new FileStream(file, FileMode.Open);
StreamReader sr = new StreamReader(ifs);
while (sr.ReadLine() != null)
  ++numLines;
sr.Close(); ifs.Close();
int numTrain = (int)(0.80 * numLines);
int numTest = numLines - numTrain;

此代碼進行計數的數位中的原始資料檔案,然後計算行多少行構成 80%和 20%的資料。 在這裡,百分比是硬編碼 ; 您可能想要參數化他們。 下一步,被分配一個矩陣,將保存所有資料:

double[][] allData = new double[numLines][];
  for (int i = 0; i < allData.Length; ++i)
    allData[i] = new double[7];

有七個列: 每年的四個數字輸入和三列的 1 N 編碼值的分類顏色變數的一列。 還記得對於此示例,目標是預測可以是三個範疇值之一的顏色: 紅色、 綠色或藍色。 編碼使用 1 N 技術在這種情況意味著編碼作為 0.0 1.0 0.0) 作為 (1.0、 0.0、 0.0)、 紅色、 綠色、 藍色作為 1.0 0.0 0.0)。 因為神經網路直接只處理數位值,必須按數位順序編碼分類資料。 事實證明編碼顏色為紅色使用一個簡單的方法,如 1、 2 的綠色和藍色 3 是一個糟糕的主意。 為什麼這是壞的解釋是有點冗長和範圍內的這篇文章。

分類輸出資料的 1 N 編碼準則的一個例外是時只有兩個可能的值,如"男"或"女,"您就可以使用 1-of-(N-1) 編碼以便 0.0,例如指女性的男性和 1.0 的手段,你有一個單一的數位輸出值。

編碼是由這段代碼執行的:

tokens = line.Split(' ');
allData[row][0] = double.Parse(tokens[0]);
allData[row][1] = double.Parse(tokens[1]);
allData[row][2] = double.Parse(tokens[2]);
allData[row][3] = double.Parse(tokens[3]);
for (int i = 0; i < 4; ++i)
  allData[row][i] = 0.25 * allData[row][i] - 1.25;
if (tokens[4] == "red") {
  allData[row][4] = 1.0; 
  allData[row][5] = 0.0; 
  allData[row][6] = 0.0; }
else if (tokens[4] == "green") {
  allData[row][4] = 0.0; 
  allData[row][5] = 1.0; 
  allData[row][6] = 0.0; }
else if (tokens[4] == "blue") {
  allData[row][4] = 0.0; 
  allData[row][5] = 0.0; 
  allData[row][6] = 1.0; }

記得行的原始資料看起來像這樣:

8.0 5.0 9.0 5.0 綠色

五個欄位被解析使用 String.Split。 經驗表明在大多數情況下,更好的結果時獲得數位輸入縮放到-1.0 和 +1.0 之間的值。 每年的第四個數字輸入轉換由乘以 0.25 和減去 1.25。 為 1.0 和 9.0 之間所有的虛擬資料檔案中的數位輸入的召回。 在實際分類問題中,你將必須掃描的原始資料,並確定最小和最大值。 我們想要-1.0 1.0 版和 +1.0,對應于 9.0 對應。 做的線性變換意味著找到坡 (這裡 0.25) 和攔截 (-1.25)。 這些值可以計算為:

slope = 2 / (max value - min value) = 2 / (9.0 - 1.0) = 0.25
intercept = 1.0 - (slope * max value) = 1 - (0.25 * 9.0) = -1.25

還有許多其他辦法執行線性變換的數值輸入值,但這裡介紹的方法是簡單的和一個好的起點,在大多數情況下。

已經改變了四個數字輸入的值後,將原始資料檔案中的顏色值進行編碼使用 1 N 編碼。 當原始資料檔案中的所有值都已經計算出來並放置在 allData 矩陣時,該矩陣已重新排列隨機的傭工類中使用 ShuffleRows 實用程式方法其行。 AllData 中的行的順序拖著腳步後,分配的空間用於矩陣 trainMatrix 和 testMatrix,然後 allData 的第一個 numTrain 行複製到 trainMatrix 和 allData 的剩餘的 numTest 行複製到 testMatrix。

火車測試方法的重要設計替代方法是將原始資料分成三組: 培訓、 驗證和測試。 這個想法是使用列車資料來確定最佳的一組中的驗證資料,用來知道何時停止訓練結合神經網路權重。 還有其他的辦法,統稱為交叉驗證技術。

Softmax 啟動功能

當執行的分類的神經網路輸出變數在哪裡分類,有的神經網路輸出啟動功能,在培訓和計算的神經網路預測準確性期間計算誤差相當棘手關係。 時斷然輸出資料 (如顏色值紅色、 綠色或藍色) 使用 1 N 編碼進行編碼的 — — 例如,(1.0 0.0 0.0) 紅 — — 你想要的神經網路,發出三個數字值,所以訓練網路時,您可以確定錯誤。 因為不這樣做,但是,想要三個任意的數位值,然後它不是完全清楚如何計算錯誤術語。 但是,假設的神經網路發出所有介於 0.0 和 1.0 和那筆為 1.0 的三個數字值。 排放的值可以被解釋為概率,使,事實證明,它容易訓練時和計算錯誤的詞,然後計算精度測試時。 Softmax 啟動函數發出此表單中的輸出值。 Softmax 函數接受神經隱藏-輸出的款項,並返回最終神經輸出值 ; 它可以像這樣實施:

private static double[] Softmax(double[] hoSums)
{
  double max = hoSums[0];
  for (int i = 0; i < hoSums.Length; ++i)
    if (hoSums[i] > max) max = hoSums[i];
  double scale = 0.0;
    for (int i = 0; i < hoSums.Length; ++i)
      scale += Math.Exp(hoSums[i] - max);
  double[] result = new double[hoSums.Length];
    for (int i = 0; i < hoSums.Length; ++i)
      result[i] = Math.Exp(hoSums[i] - max) / scale;
  return result;
}

原則上,Softmax 函數通過採取的每個隱藏輸出筆進出口、 總結他們,然後將每個值的 Exp 除以縮放因數計算縮放因數。 例如,假設三個隱藏輸出款項是 2.0、-1.0 4.0)。 縮放因數會 Exp(2.0) + Exp(-1.0) + Exp(4.0) = 7.39 + 0.37 + 54.60 = 62.36。 Softmax 的輸出值便 Exp (2.0) / 62.36,Exp(-1.0)/62.36、 Exp(4.0)/62.36) = 0.87 0.01 0.12)。 請注意,最後的輸出 0.0 和 1.0 之間都是和做事實上總和為 1.0,進一步的最大的隱藏輸出款項 (4.0) 具有最大輸出/概率 (0.87) 和關係是類似的第二和第三大的值。

不幸的是,Softmax 的一天真實現可能經常失敗,因為 Exp 函數非常迅速地變得非常大,可產生算術溢出。 前執行使用這一事實的 Exp (a-b) = Exp(a) / Exp(b) 來計算的一種方法,以避免溢出的輸出。 如果你跟蹤的執行情況作為投入使用 4.0-1.0 2.0) 執行,您將獲得相同 0.87 0.01 0.12) 輸出在前一節中所述。

交叉熵錯誤

訓練的神經網路的本質是要查找的權重產生最小錯誤中訓練集的資料集。 例如,假設一個正常化、 編碼的培訓資料行是 (0.75-0.25 0.25-0.50 0.00 1.00 0.00)。 記得前四個值都歸一化的投入的最後三個值表示 1 N 編碼中的綠色。 現在假設投入美聯儲通過神經網路,其中已載入某些組的權重,與 Softmax 輸出 (0.20 0.70 0.10)。 此測試向量的計算錯誤的傳統方法是使用的平方的差異,在這種情況下會總和 (0.00-0.20) ^2 + (1.00-0.70) ^2 + (0.00-0.10) ^2 = 0.14。 但假設 Softmax 輸出很 (0.30 0.70 0.00)。 此向量和以前的向量預測輸出是綠色的因為綠色的概率是 0.70。 然而,偏差平方和的這第二個向量是 0.18,這是不同的從第一次的錯誤術語。 雖然可以使用偏差平方和的計算培訓錯誤,但一些研究結果表明使用替代措施稱為跨­熵的錯誤是更可取。

原則上,一個給定的神經網路的交叉熵錯誤輸出向量 v 和測試輸出向量 t 由負數額的 v 向量的每個元件和 t 向量的相應元件的產品確定計算的。 和往常一樣,這是最容易理解的例子。 如果測試向量 (0.75-0.25 0.25-0.50 0.00 1.00 0.00) 和相應的 Softmax 神經網路輸出是 (0.20 0.70 0.10),交叉熵的錯誤代碼是-1,然後 * (0.00 * Log(0.20)) + (1.00 * Log(0.70)) + (0.00 * Log(0.10)) =-1 * (0 +-0.15 + 0) = 0.15。 除了一個總和中的條款將始終為零,1 N 編碼使用時的通知。 整個訓練集的交叉熵錯誤可以作為交叉熵的所有測試向量或每個測試向量的平均交叉熵的總和計算。 交叉熵錯誤的一種實現上市的圖 4

圖 4 交叉熵錯誤

private double CrossEntropy(double[][] trainData, 
  double[] weights)
{
  this.SetWeights(weights);
  double sce = 0.0; // sum of cross entropies
  for (int i = 0; i < trainData.Length; ++i)
  {
    double[] currInputs = new double[4];
    currInputs[0] = trainData[i][0];
    currInputs[1] = trainData[i][1];
    currInputs[2] = trainData[i][2];
    currInputs[3] = trainData[i][3];
    double[] currExpected = new double[3];
    currExpected[0] = trainData[i][4];
    currExpected[1] = trainData[i][5];
    currExpected[2] = trainData[i][6];
    double[] currOutputs = this.ComputeOutputs(currInputs);
    double currSum = 0.0;
    for (int j = 0; j < currOutputs.Length; ++j)
    {
      if (currExpected[j] != 0.0)
        currSum += currExpected[j] * Math.Log(currOutputs[j]);
    }
    sce += currSum; // accumulate
  }
  return -sce;
}

訓練神經網路

有很多方法來訓練神經網路分類器要查找的一組重量最佳匹配的訓練資料 (或相等,收益率的最小交叉熵錯誤)。 在更高層次的抽象,訓練的神經網路看起來如下:

create an empty neural network
loop
  generate a candidate set of weights
  load weights into neural network
  foreach training vector
    compute the neural output
    compute cross-entropy error
    accumulate total error
  end foreach
  if current weights are best found so far
    save current weights
  end if
until some stopping condition
return best weights found

 

到目前為止,最常用的技術用來訓練神經網路被稱為反向傳播。這項技術是一大批研究文章的主題 — — 因此,很多,事實上,如果你是新到的神經網路類別欄位,很容易可以為主導,相信這回繁殖是用於訓練的唯一技術。估計的權重的一種神經網路最佳的一組是一個數值最小化問題。兩個常見使用反向傳播監外教養辦法是使用一個實數型遺傳演算法 (也稱為一種進化優化演算法),和使用粒子群優化。每個估計技術有長處和短處。中顯示的程式圖 1 使用粒子群優化。描述了在 2012 年 6 月發行的 MSDN 雜誌進化優化演算法 (msdn.microsoft.com/magazine/jj133825) 和粒子群優化在 2011 年 8 月一期 (msdn.microsoft.com/magazine/hh335067)。

有很多技術,以確定何時停止訓練的神經網路。雖然你可能只是讓一種運行,直到交叉熵的錯誤是非常接近于零的訓練演算法,指示近乎完美適合,危險是由此產生的權重將 over-fit 的訓練資料權重將創建任何不在訓練集的資料不佳分類的神經網路。此外,模型 over-fitting 可以很容易導致要培訓,直至不有交叉熵錯誤中的任何變化。一種簡單的方法,和一個示常式序,使用的是限制到固定的反覆運算次數的培訓。在大多數情況下,很多更好的策略,以避免 over-fitting 是拆分成火車驗證測試集的源資料集。通常,這些三個資料集使用的 60%、 20%和 20%的來源資料,分別。技術計算按前面所述,該培訓集上的錯誤,但在主迴圈中的每次反覆運算後技術計算驗證資料集上的交叉熵錯誤。當上驗證組的交叉熵錯誤開始顯示錯誤中的一致的增加時,有一個好的機會,訓練演算法已開始 over-fit 資料和培訓應該制止。有很多其他停車技術可能。

評價的神經網路分類器

神經網路分類器已受過訓練,並產生了一組最佳權重和偏見之後下, 一步是確定如何準確生成的模型 (模型意味著具有最佳權重的集的神經網路) 是對測試資料。雖然可以用筆偏差平方或交叉熵錯誤等措施,合理檢測是準確性的只需正確預測模型所作的百分比。再說一遍,有幾種方法測定的準確性,但簡單的技術是使用了所謂的贏家全時間的方法。而且,像往常一樣,技術最好的解釋的示例。假設一個測試向量 (-1.00 1.00 0.25-0.50 1.00 0.00 0.00),第一組中的預測資料中所示圖 1。最佳權重設置後,神經網路生成預測的 Softmax 輸出 (0.9 0.1 0.0)。如果每個輸出被解釋為一個概率,那麼高的概率是 0.9 預測的輸出可以被認為是 (1 0 0),所以數學模型所做的正確的預測。提出另一種方式,贏家全帶技術確定神經網路輸出元件具有最大值,使該元件 1 和所有其他元件 0,並比較,結果與培訓載體中的實際資料。在第三組中精度分析資料的圖 1,測試向量是 (-0.50 0.75 0.25-0.75 0.0 0.0 1.0)。實際輸出的資料 (0.0 0.0 1.0),它對應為藍色。預測的輸出是 (0.3 0.6 0.1)。最大組成部分是 0.6,所以模型預測是 (0 1 0),它對應于綠色。該模型作出不正確的預測。

總結

用神經網路分類是一個重要的、 令人著迷的和複雜的話題。這裡介紹的示例應該給你的神經網路分類的試驗品了堅實的基礎。然而,這篇文章描述了只有一個非常具體的神經網路分類方案 — — 數位輸入的變數與分類輸出變數 — — 是只是一個起點。其他情形需要稍有不同的技術。例如,如果輸入的資料包含一個分類的變數,您可能期望將編碼使用 1 N 編碼就像一個分類輸出變數。在這種情況下,然而,輸入的資料應使用進行編碼的 1-of-(N-1) 技術。如果您想要瞭解更多有關基於神經網路的分類,我推薦神經網路可在 faqs.org 七常見問題解答的集。該連結到這些常見問題往往左右移動,但您應該能夠很容易地找到他們的互聯網搜索。

Dr. James McCaffrey 為伏資訊科學 Inc.,凡他管理技術培訓工作在華盛頓雷德蒙的微軟校園軟體工程師的工作。他曾經參與過多項 Microsoft 產品的研發,包括 Internet Explorer 和 MSN Search。麥卡弗裡是作者的".NET 測試自動化食譜"(很,2006年)。他可以在達到 jmccaffrey@volt.comjammc@microsoft.com

由於以下 Microsoft 技術專家,檢討這篇文章: Matthew Richardson