本文章是由機器翻譯。

測試回合

扭曲 MNIST 影像資料集

James McCaffrey

下載代碼示例

混合的美國國家標準和技術 (MNIST) 資料集是 70,000 小圖像的手寫體數位的集合。創建的資料,作為基準的圖像識別演算法。雖然 MNIST 圖像很小 (28 x 28 圖元),和僅有 10 可能的數位 (0 到 9),承認和有 60,000 訓練圖像 (用 10000 幅伸出來測試模型的準確性) 創建的圖像識別模型,經驗表明認識到 MNIST 圖像是一個困難的問題。

對付困難模式分類問題,其中包括圖像識別的一個方法是使用更多的訓練資料。一個聰明的方法以程式設計方式生成更多的訓練資料是扭曲每個原始圖像。看一看該演示程式中圖 1。圖 4 中在左邊,原始的 MNIST 形式的數位和數位後使用右邊的彈性變形失真。中的演示應用程式右上角的參數指示失真取決於位移隨機種子、 內核的大小和標準差和強度值。


圖 1 扭曲 MNIST 圖像

不太可能你需要扭曲圖像中大多數的工作環境,但你可能有用的資訊在這篇文章有三個原因。第一,瞭解確切地圖像失真是如何通過看實際的代碼將説明您瞭解圖像識別的很多文章。第二,在圖像的失真中使用的程式設計技術的幾個可以在其他,更常見的程式設計方案中很有用。第三,你會發現圖像失真一個有趣的話題為其自身的緣故。

這篇文章假設你有高級程式設計技能,但不會假定你知道任何圖像失真。該演示程式在 C# 中的編碼和 Microsoft.NET 框架的廣泛利用,所以重構為非.NET 的語言將是具有挑戰性。演示了最正常的錯誤檢查去除,以保持較小的代碼大小和主要思路明確。因為是由Visual Studio創建一個 Windows 表單應用程式的演示,很多代碼與 UI 相關,傳播到幾個檔。不過,我已經重構到單個 C# 原始程式碼檔,現已在演示代碼 msdn.microsoft.com/magazine/msdnmag0714。你可以在幾個地方在互聯網上找到的 MNIST 資料。主存儲庫是在 yann.lecun.com/exdb/mnist

程式的整體結構

要創建該演示程式我推出Visual Studio,並創建一個名為 MnistDistort 的 Windows 表單應用程式。使用者介面有八個 textbox 控制項的路徑到解壓縮後的 MNIST 資料檔案 (一個圖元檔、 一個標籤檔) ; 指數的當前顯示和下一個圖像 ; 和種子值、 內核大小、 內核標準差和變形過程的強度值。一個下拉清單控制項保存值以放大圖像視圖。有三個按鈕控制項載入到記憶體中的 60,000 的 MNIST 圖像和標籤、 顯示的圖像,和扭曲所顯示的圖像。有兩個 PictureBox 控制項來顯示經常和扭曲的圖像。最後,一個 ListBox 控制項用於顯示進度和日誌記錄消息。

頂部的原始程式碼,該代碼我刪除對不需要命名空間的引用,然後添加 System.IO 命名空間必須能夠讀取 MNIST 資料檔案的引用。

我添加了名為 trainImages,其中包含對程式定義的 DigitImage 物件和變數以保存兩個 MNIST 的資料檔案的位置的引用類範圍陣列:

DigitImage[] trainImages = null;
string pixelFile = @"C:\MnistDistort\train-images.idx3-ubyte"; // Edit
string labelFile = @"C:\MnistDistort\train-labels.idx1-ubyte"; // Edit

在表單建構函式中我添加了這些六行代碼:

textBox1.Text = pixelFile;
textBox2.Text = labelFile;
comboBox1.SelectedItem = "6"; // Magnification
textBox3.Text = "NA"; // Curr index
textBox4.Text = "0"; // Next index
this.ActiveControl = button1;

Button1 click 事件處理常式載入到記憶體中的 60,000 的圖像:

string pixelFile = textBox1.Text;
string labelFile = textBox2.Text;
trainImages = LoadData(pixelFile, labelFile);
listBox1.Items.Add("MNIST training images loaded into memory");

Button2 click 事件處理常式將顯示下一個圖像並更新 UI 控制項:

int nextIndex = int.Parse(textBox4.Text);
DigitImage currImage = trainImages[nextIndex];
int mag = int.Parse(comboBox1.SelectedItem.ToString());
Bitmap bitMap = MakeBitmap(currImage, mag);
pictureBox1.Image = bitMap;
textBox3.Text = textBox4.Text; // Update curr index
textBox4.Text = (nextIndex + 1).ToString(); // next index
textBox8.Text = textBox3.Text;  // Random seed
listBox1.Items.Add("Curr image index = " + textBox3.Text +
  " label = " + currImage.label);

你會發現在附帶的代碼下載中的 LoadData 和 MakeBitmap 的方法。 大部分失真的工作由由 Button3 click 事件處理常式,調用是在提出的方法圖 2

圖 2 圖像失真調用代碼

private void button3_Click(object sender, EventArgs e)
{
  int currIndex = int.Parse(textBox3.Text);
  int mag = int.Parse(comboBox1.SelectedItem.ToString());
  int kDim = int.Parse(textBox5.Text); // Kernel dimension
  double kStdDev = double.Parse(textBox6.Text); // Kernel std dev
  double intensity = double.Parse(textBox7.Text);
  int rndSeed = int.Parse(textBox8.Text);  // Randomization
  DigitImage currImage = trainImages[currIndex];
  DigitImage distorted = Distort(currImage, kDim, kStdDev,
    intensity, rndSeed);
  Bitmap bitMapDist = MakeBitmap(distorted, mag);
  pictureBox2.Image = bitMapDist;
}

扭曲了的方法調用的方法 MakeKernel (來創建一個平滑的矩陣),MakeDisplace (方向和變形圖像中的每個圖元的距離) 和置換 (以實際變形源映射)。説明器方法,MakeDisplace 調用子幫手 ApplyKernel 到光滑的位移值。小組的説明器方法,ApplyKernel 調用分一分説明器方法墊。

彈性變形

扭曲圖像使用彈性變形的基本思想是相當簡單的。如果是 MNIST 的圖像,您想要稍微移動現有的每個圖元。但細節不是那麼簡單。一個幼稚的做法,獨立移動的每個圖元會導致新的圖像出現破裂,而不是拉伸。例如,考慮中的概念圖像圖 3。這兩個圖像表示一個 5 x 5 部分的圖像的失真。每個箭頭指示的方向和一個移動的一個對應的圖元距離。左側的圖像顯示更多或更少的隨機向量,其中會分手,而不是扭曲圖像。右圖中都相互關聯的導致拉伸圖像的向量。


圖 3 隨機 vs。平滑的位移場

所以訣竅在於取代彼此接近圖元移動相對較相似,但不確切,相同、 方向和距離的一種中每個圖元。這可以通過使用稱為連續的高斯核函數矩陣。整體思路是可能最好的解釋使用的代碼。在演示此方法,請考慮:

private DigitImage Distort(DigitImage dImage, int kDim,
  double kStdDev, double intensity, int seed)
{
  double[][] kernel = MakeKernel(kDim, kStdDev);
  double[][] xField = MakeDisplace(dImage.width, dImage.height,
   seed, kernel, intensity);
  double[][] yField = MakeDisplace(dImage.width, dImage.height,
    seed + 1, kernel, intensity);
  byte[][] newPixels = Displace(dImage.pixels, xField, yField);
  return new DigitImage(dImage.width, dImage.height,
    newPixels, dImage.label);
}

扭曲了的方法接受一個 DigitImage 物件和相關到內核的四個數值參數。 類型 DigitImage 是一個程式 — —­定義類,表示彌補 MNIST 圖像的圖元 28 x 28 位元組。 該方法首先創建一個內核,內核和 kStdDev 的大小是影響位移的圖元為單位) 將是多麼的相似的值 kDim 在哪裡。

來取代一個圖元,是有必要知道如何遠左-右的方向,和上下方向移動。 此資訊存儲在陣列 xField 和 yField,分別,和使用 helper 方法 MakeDisplace 計算。 説明器方法置換接受 DigitImage 圖像的圖元值,然後使用位移場來生成新的圖元值。 新的圖元值然後喂 DigitImage 的建構函式,產生一個新的、 扭曲的形象。 總結,要扭曲圖像,您將創建一個內核。 內核用於生成 x 和 y 方向領域相關,而不是獨立。 方向場應用於源圖像,以生成該圖像的扭曲的版本。

高斯核

連續的高斯核函數是一個矩陣的值的總和為 1.0 ; 有的最大值在中心 ; 和是徑向對稱。 這裡是 1.0 的一個 5 x 5 高斯內核標準差:

0.0030   0.0133   0.0219   0.0133   0.0030
0.0133   0.0596   0.0983   0.0596   0.0133
0.0219   0.0983   0.1621   0.0983   0.0219
0.0133   0.0596   0.0983   0.0596   0.0133
0.0030   0.0133   0.0219   0.0133   0.0030

請注意相互靠近的值不同,但類似。 標準差值確定核心價值觀有多近。 較大的標準差給出緊密的值。 例如,使用標準差為 1.5 給與第一次行值的一個 5 x 5 內核:

0.0232   0.0338   0.0383   0.0338   0.0232

這似乎很奇怪在第一次因為標準差是衡量指標和更大的資料傳播一組資料的標準差值表示較大的價差。 但在高斯核函數的上下文,標準差用於生成的值,不是在生成內核中傳播的措施。 該演示程式用於生成高斯核的方法在圖 4

圖 4 的方法 MakeKernel

private static double[][] MakeKernel(int dim, double sd)
{
  if (dim % 2 == 0)
    throw new Exception("kernel dim must be odd");
  double[][] result = new double[dim][];
  for (int i = 0; i < dim; ++i)
    result[i] = new double[dim];
  int center = dim / 2; // Note truncation
  double coef = 1.0 / (2.0 * Math.PI * (sd * sd));
  double denom = 2.0 * (sd * sd);
  double sum = 0.0; // For more accurate normalization
  for (int i = 0; i < dim; ++i) {
    for (int j = 0; j < dim; ++j) {
      int x = Math.Abs(center - j);
      int y = Math.Abs(center - i);
      double num = -1.0 * ((x * x) + (y * y));
      double z = coef * Math.Exp(num / denom);
      result[i][j] = z;
      sum += z;
    }
  }
  for (int i = 0; i < dim; ++i)
    for (int j = 0; j < dim; ++j)
      result[i][j] = result[i][j] / sum;
  return result;
}

生成高斯核可以有些令人費解的任務,因為有很多演算法變化取決於內核打算如何使用,以及近似技術的幾個變化。 基本的數學定義為一個二維連續高斯內核中的值是:

z = (1.0 / (2.0 * pi^2)) * exp((-(x^2 + y^2)) / (2 * sd^2))

這裡 x 和 y 是 x 和 y 座標的相對於中心儲存格 ; 內核中的儲存格 pi 是數學常數 ; exp 函數是冪函數 ; 和 sd 為指定的標準差。係數的主項的 1.0 / (2.0 * Pi ^2) 是實際上一個歸一化處理期限為一維高斯函數的版本。但為 2D 的內核,您想要所有內核初步值都相加,然後除以每個初步的值以便最後的所有值將都添加到 1.0 (舍入錯誤)。在圖 4,這最後的正常化使用名為 sum 的變數來完成。因此,名為係數的變數是多餘的可以省略從代碼 ; 變數係數是包括在這裡,因為大多數研究論文描述了內核使用係數一詞。

位移場

要扭曲圖像,必須移動的每個圖元 (實際上,不能隨便) 一定的距離向左或向右,並向上或向下。在中定義的方法 MakeDisplace 圖 5。方法 MakeDisplace 返回一個陣列的陣列樣式矩陣對應到一半中的概念矩陣的圖 3。那就是,返回矩陣的儲存格中的值對應于一個方向,在 x 方向或 y 方向移動的圖元的大小。因為 MNIST 圖像的大小為 28 × 28 圖元,返回矩陣的 MakeDisplace 也將 28 x 28。

圖 5 製作位移場

private static double[][] MakeDisplace(int width, int height, int seed,
  double[][] kernel, double intensity)
{
  double[][] dField = new double[height][];
  for (int i = 0; i < dField.Length; ++i)
    dField[i] = new double[width];
  Random rnd = new Random(seed);
  for (int i = 0; i < dField.Length; ++i)
    for (int j = 0; j < dField[i].Length; ++j)
      dField[i][j] = 2.0 * rnd.NextDouble() - 1.0;
  dField = ApplyKernel(dField, kernel); // Smooth
  for (int i = 0; i < dField.Length; ++i)
    for (int j = 0; j < dField[i].Length; ++j)
      dField[i][j] *= intensity;
  return dField;
}

方法 MakeDisplace 生成一個矩陣與初始隨機值為-1 和 + 1 之間。 幫手 ApplyKernel 平滑如所建議的隨機值圖 3。 平滑的值是本質上的方向元件與 0 和 1 之間的距離。 然後所有的值都乘以一個強度參數,以增加拉伸的距離。

應用一個內核和位移

應用一個內核向的位移矩陣,然後使用由此產生平滑的位移來生成新的圖元值是相當棘手。 這個過程的第一部分所示圖 6。 左邊的部分的圖表示-1 和 + 1 在 x 方向為 8 x 8 的圖像之間的初步隨機位移值。 時行 [3],[6] (0.40) 列的值是使用一個 3 x 3 內核被平滑。 新的位移是加權的平均數的當前值和值的八個最接近的鄰居。 因為每個新的位移值在本質上是它的鄰居的平均數,淨效應是生成彼此相關的值。

平滑處理後, 的位移值乘以強度因數 (有時稱為研究文獻的阿爾法)。 例如,如果強度因數是 20,然後在中的最後 x 位移圖 6 為圖像的圖元在 (3,6) 將會為 0.16 * 20 = +3.20。 會有一個類似的 y 位移矩陣。 假設最終值在 (3,6) 在 y 位移矩陣是-1.50。 對應于在圖元的值 +3.20 和-1.50 (3,6) 現在應用到的源圖像產生失真的圖像,但不是完全明顯的方式。


圖 6 將內核應用於位移矩陣

第一,上限與下限確定。 為 +3.20 x-位移,這些都是 3 和 4。 為-1.50 y 位移,他們是-2,-1。 四個邊界生成四 (x,y) 位移對:(3, -2), (3, -1), (4, -2), (4, -1). 回憶起這些值對應于原始圖像的圖元值在指數 (3,6)。 圖元指標結合的四個位移對生成四個指標對:(6, 4), (6, 5), (7, 4), (7, 5). 最後,在的扭曲圖像的圖元值 (3,6) 指數 (6,4),在原始的圖元值的平均值是 5 3),(7,4) 和 (7,5)。

由於使用的幾何形狀,它是最常見的將內核限制為 3、 5 等奇數尺寸。 請注意將嘗試平滑位移矩陣的邊緣附近的任何初步的位移值,因為內核這麼說,會超出矩陣的邊緣問題。 有幾種方法來處理的邊緣問題。 一種方法是墊初步位移矩陣與虛擬的行和列。 要墊行或列的數目將等於二分之一 (使用整數截斷) 的維度的內核。

總結

在這篇文章描述的圖像彈性變形過程是很多可能的辦法之一。 最多,但不是全部,提出的失真演算法的這裡是改編的研究文章,"最佳做法為卷積神經網路應用於視覺文檔分析,"這是可用線上在 bit.ly/REzsnM。

演示程式產生變形的影像創造額外的培訓資料的圖像識別系統。 如果你實際上訓練圖像識別系統您可以重構的演示代碼來生成新的培訓資料的飛行,或您可以重構代碼以生成,然後將扭曲的圖像保存為文本或二進位檔案。

Dr. James McCaffrey 卡弗裡為在雷德蒙微軟研究院工作 他曾在幾個 Microsoft 產品,包括互聯網資源管理器和必應。聯繫到他在 jammc@microsoft.com。

由於以下的技術專家對本文的審閱:狼 Kienzle (微軟研究)