天天看點

AI應用開發實戰 - 手寫識别應用入門

手寫體識别的應用已經非常流行了,如輸入法,圖檔中的文字識别等。但對于大多數開發人員來說,如何實作這樣的一個應用,還是會感覺無從下手。本文從簡單的MNIST訓練出來的模型開始,和大家一起入門手寫體識别。

在本教程結束後,會得到一個能用的AI應用,也許是你的第一個AI應用。雖然離實際使用還有較大的距離(具體差距在文章後面會分析),但會讓你對AI應用有一個初步的認識,有能力逐漸搭建出能夠實際應用的模型。

建議和回報,請發送到

https://github.com/Microsoft/vs-tools-for-ai/issues

聯系我們

[email protected]

使用win10 64位作業系統的計算機

參考上一篇部落格AI應用開發實戰 - 從零開始配置環境。在電腦上訓練并導出MNIST模型。

通過上一篇文章搭建環境的介紹後,就能得到一個能識别單個手寫數字的模型了,并且識别的準确度會在98%,甚至99%以上了。那麼我們要怎麼使用這個模型來搭建應用呢?

大緻的步驟如下:

實作簡單的界面,将使用者用滑鼠或者觸屏的<code>輸入變成圖檔</code>。

将生成的模型<code>包裝</code>起來,成為有公開資料接口的類。

将輸入的圖檔進行<code>規範化</code>,成為資料接口能夠使用的格式。

最後通過模型來推理(inference)出圖檔應該是哪個數字,并顯示出來。

是不是很簡單?

提問:那我們要怎麼擷取手寫的數字呢?

回答:我們可以寫一個簡單的WinForm畫圖程式,讓我們可以用滑鼠手寫數字,然後把圖檔儲存下來。

首先,我們打開Visual Studio,選擇<code>檔案-&gt;建立-&gt;項目</code>。

AI應用開發實戰 - 手寫識别應用入門

在彈出的視窗裡選擇<code>Visual C#-&gt;Windows窗體應用</code>,項目名稱不妨叫做<code>DrawDigit</code>,解決方案名稱不妨叫做<code>MnistForm</code>,點選确定。

AI應用開發實戰 - 手寫識别應用入門

此時,Visual Studio也自動彈出了一個視窗的設計圖。

AI應用開發實戰 - 手寫識别應用入門

在DrawDigit項目上點選右鍵,選擇屬性,在生成一欄将平台目标從<code>Any CPU</code>改為<code>x64</code>。

AI應用開發實戰 - 手寫識别應用入門

否則,DrawDigit(首選32位)與它引用的MnistForm(64位)的編譯平台不一緻會引發<code>System.BadImageFormatException</code>的異常。

然後我們對這個視窗做一些簡單的修改:

首先我們打開VS視窗左側的工具箱,這個視窗程式需要以下三種元件:

PictureBox:用來手寫數字,并且把數字儲存成圖檔

Label:用來顯示模型的識别結果

Button:用來清理PictureBox的手寫結果

那經過一些簡單的<code>選擇與拖動還有調整大小</code>,這個視窗現在是這樣的:

AI應用開發實戰 - 手寫識别應用入門

一些注意事項

這些元件都可以通過<code>右鍵-&gt;檢視屬性</code>,在屬性裡修改它們的設定

為了友善把PictureBox裡的圖檔轉化成Mnist能識别的格式,PictureBox的需要是正方形

可以給這些控件起上有意義的名稱。

可以調整一下label控件大小、字型等,讓它更美觀。

經過一些簡單的調整,這個視窗現在是這樣的:

AI應用開發實戰 - 手寫識别應用入門

現在來讓我們愉快地給這些元件添加事件!

還是在屬性視窗,我們選擇某個元件,右鍵-&gt;檢視屬性,點選閃電符号,給元件綁定對應的事件。每次綁定後,會跳到代碼部分,生成一個空函數。點回設計視圖繼續操作即可。

元件類型

事件

pictureBox1

在<code>Mouse</code>下輕按兩下<code>MouseDown</code>、<code>MouseUp</code>、<code>MouseMove</code>來生成對應的響應事件函數。

button1

如上,在<code>Action</code>下輕按兩下<code>Click</code>。

Form1

如上,在<code>Behavior</code>下輕按兩下<code>Load</code>。

然後我們開始補全對應的函數體内容。

注意,如果在上面改變了控件的名稱,下面的代碼需要做對應的更改。

廢話少說上代碼!

将模型包裝成一個C#是整個過程中比較麻煩的一步。所幸的是,Tools for AI對此提供了很好的支援。進一步了解,可以看這裡。

首先,我們在解決方案MnistForm下點選滑鼠右鍵,選擇<code>添加-&gt;建立項目</code>,在彈出的視窗裡選擇<code>AI Tools-&gt;Inference-&gt;模型推理類庫</code>,名稱不妨叫做<code>MnistModel</code>,點選确定,于是我們又多了一個項目,

AI應用開發實戰 - 手寫識别應用入門

然後自己配置好這個項目的名稱、位置,點選<code>确定</code>。

然後彈出一個模型推理類庫建立向導,這個時候就需要我們選擇自己之前訓練好的模型了~

AI應用開發實戰 - 手寫識别應用入門

首先在模型路徑裡選擇儲存的模型檔案的路徑。這裡我們使用在AI應用開發實戰 - 從零開始配置環境部落格中訓練并導出的模型

note:模型可在<code>/samples-for-ai/examples/tensorflow/MNIST</code>目錄下找到,其中<code>output</code>檔案夾儲存了檢查點檔案,<code>export</code>檔案夾儲存了模型檔案。

對于TensorFlow,我們可以選擇檢查點的<code>.meta</code>檔案,或者是儲存的模型的<code>.pb</code>檔案

這裡我們選擇在AI應用開發實戰 - 從零開始配置環境這篇部落格最後生成的<code>export</code>目錄下的檢查點的<code>SavedModel.pb</code>檔案,這時程式将自動配置好配置推理接口,見下圖:

AI應用開發實戰 - 手寫識别應用入門

類名可以自己定義,因為我們用的是MNIST,那麼類名就叫<code>Mnist</code>好了,然後點選确定。

這樣,在<code>解決方案資料總管</code>裡,在解決方案<code>MnistForm</code>下,就多了一個<code>MnistModel</code>:

AI應用開發實戰 - 手寫識别應用入門

輕按兩下<code>Mnist.cs</code>,我們可以看到項目自動把模型進行了封裝,生成了一個公開的<code>infer</code>函數。

然後我們在<code>MnistModel</code>上右擊,再選擇<code>生成</code>,等待一會,這個項目就可以使用了~

這一步差不多就是這麼個感覺:

I have an apple , I have a pen. AH~ , Applepen

首先,我們來給DrawDigit添加引用,讓它能使用MnistModel。在DrawDigit項目的引用上點選滑鼠右鍵,點選<code>添加引用</code>,在彈出的視窗中選擇<code>MnistModel</code>,點選确定。

AI應用開發實戰 - 手寫識别應用入門
AI應用開發實戰 - 手寫識别應用入門

然後,由于MNIST的模型的輸入是一個28×28的白字黑底的灰階圖,是以我們首先要對圖檔進行一些處理。

首先将圖檔轉為28×28的大小。

然後将RGB圖檔轉化為灰階圖,将灰階标準化到[-0.5,0.5]區間内,轉換為黑底白字。

最後将圖檔用mnist模型要求的格式包裝起來,并傳送給它進行推理。

于是,我們在<code>pictureBox1_MouseUp</code>中添加上這些代碼,并且在檔案最初添加上<code>using MnistModel;</code>:

最後讓我們嘗試一下運作~

現在我們就有了一個簡單的小程式,可以識别手寫的數字了。

趕緊試試效果怎麼樣~

AI應用開發實戰 - 手寫識别應用入門

路徑中不能有中文字元,否則可能找不到模型。

我們已經支援了單個手寫數字的識别,那能不能支援多個手寫數字的識别呢?同時寫下多個數字,正是現實中更為常見的情形。相比之下,如果隻能一次識别一個手寫數字,應用就會有比較大的局限性。

首先,我們可以嘗試在現有的應用裡一次寫下兩個數字,看看識别效果(為了更好的展示效果,将筆畫的寬度由40調整為20。這一改動對單個數字的識别并無大的影響):

AI應用開發實戰 - 手寫識别應用入門

識别效果不盡人意。

右上角展示的結果準确地反應了模型對我們手寫輸入的推理結果(即<code>result.First().First().ToString()</code>),然而這一結果并不像我們期望的那樣是“42”。

了解MNIST資料集的讀者們可能已經意識到了,這是“理所當然”的。歸根結底,這一問題的症結在于:作為我們AI應用核心的AI模型,本身并不具備識别多個數字的能力——目前案例中我們使用的AI模型是基于MNIST資料集訓練的(訓練過程請回顧我們之前的部落格AI應用開發實戰 - 從零開始配置環境),而MNIST資料集隻覆寫了單個的手寫數字;并且,我們并未對筆迹圖形作額外的處理。

結果是在寫下多個數字的情況下,我們實際上在“強行”讓AI模型做超出其适應性範圍的判斷。這屬于AI模型的誤用。其結果自然難以令人滿意。

那麼,為了增強應用的可用性,我們能不能改善它、讓它能識别多個數字呢?我們很自然地想到,既然MNIST模型已經能很好地識别單個數字,那我們隻需要把多個數字分開,一個一個地讓MNIST模型進行識别就好了。這樣,我們就引入了一個新的子問題,即是“多個手寫數字的分割”。

我們注意到本文介紹的應用有一個特點,那就是最終用作輸入的圖形,是使用者當場寫下的,而非通過圖檔檔案導入的靜态圖檔,即我們擁有筆畫産生過程中的全部動态資訊,比如筆畫的先後順序,筆畫的重疊關系等等。考慮到這些資訊,我們可以設計一種基本的分割規則:在水準面上的投影相重疊的筆畫,我們就認為它們同屬于一個數字。

筆畫和水準方向上投影的關系示意如下圖:

AI應用開發實戰 - 手寫識别應用入門

是以書寫時,就要求不同的數字之間盡量隔開。當然為了盡可能處理不經意的重疊,我們還可以為重疊部分相對每一筆畫的位置設定一個門檻值,如至少進入筆畫一端的10%以内。

應用這樣的規則後,我們就能比較好的把多個手寫數字分割開,并能利用Visual Studio Tools for AI提供的批量推理功能,一次性對所有分割出的圖形做推理。

多個手寫數字識别的最終效果如圖:

AI應用開發實戰 - 手寫識别應用入門

當然,我們對問題的定義還是非常理想化,分割算法也比較簡單。在實際應用中,我們還經常要考慮非二值圖形、噪點、非數字的判别等等。并且對手寫數字的分割可能比我們設定的規則要複雜,因為在現實場景中,水準方向上的重疊可能會影響圖形的涵義。

将兩個手寫數字分割開這一問題,實際上和經典的圖像分割問題非常類似。雖然本文示例中的圖像非常簡單,但仍然可能具有相當複雜的語義需要處理。為此,我們可能需要引入更多的模型,或者擴充現有的模型來正确判斷多個圖形之間的關系。

那麼,如果要識别多個連寫的數字,或支援字母該怎麼做呢?大家多用用也會發現,如果數字寫得很小,或者沒寫到正中,識别起來正确率也會不高。要解決這些問題,做成真正的産品,就不止這一個模型了。比如在多個數字識别中,可能要根據經驗來切分圖,或者訓練另一個模型來檢測并分割數字。要支援字母,則需要重新訓練一個包含手寫字母的模型,并準備更多的字母的資料。要解決字太小的問題,還要檢測一下字的大小,做合适的放大等等。

我們可以看到,一個訓練出來的模型本身到一個實際的應用之間還有不少的功能要實作。希望我們這一系列的介紹,能夠幫助大家将機器學習的概念帶入到傳統的程式設計領域中,做出更聰明的産品。

繼續閱讀