前端AI實戰——告訴世界前端也能作AI

轉自IMWeb社區,做者:jerryOnlyZRJ,原文連接html

我想大多數人和我同樣,第一次聽見「人工智能」這個詞的時候都會以爲是一個很高大上、高不可攀的概念,特別像我這樣一個平凡的前端,和大部分人同樣,都以爲人工智能其實離咱們很遙遠,咱們對它的印象老是停留在各類各樣神奇而又複雜的算法,這些彷彿都是那些技術專家或者海歸博士纔有能力去作的工做。我也曾一度覺得本身和這個行業沒有太多緣分,但自從Tensorflow發佈了JS版本以後,這一領域又引發了個人注意。在python壟斷的時代,發佈JS工具庫不就是意味着咱們前端工程師也能夠參與其中?前端

當我決定開始投身這片領域作一些本身感興趣的事情的時候,卻發現身邊的人投來的都是鄙夷的目光,他們對前端的印象,還老是停留在上個年代那些只會寫寫頁面腳本的切圖仔,只有身處這片領域的咱們才知道大前端時代早已發生了翻天覆地的變革。python

今天,我就帶領你們從原理開始,儘量用最通俗易懂的方式,讓JS的愛好者們快速上手人工智能。git

具體項目可參照:github.com/jerryOnlyZR…github

本文就單拿人工智能下的一塊小領域——「圖像識別」做一些簡單介紹和實戰指引,固然這些都只是這片大領域下的冰山一角,還有不少不少知識等着你去發掘。web

1.CNN卷積神經網絡原理剖析

若是我不講解這部份內容,而是直接教大家怎麼使用一個現成的庫,那這篇文章就沒什麼價值了,看完以後給大家留下的也必定都會是「開局一張圖,過程全靠編」的錯覺。所以,要真正瞭解人工智能,就應該進入這個黑盒,裏面的思想纔是精華。算法

1.1.圖像灰度級與灰度圖

1.1.1.基本概念

要作圖像識別,咱們確定要先從圖像下手,你們先理解一個概念——圖像灰度級。後端

衆所周知,咱們的圖片都是由許多像素點組成的,就好像一張100*100像素的圖片,就表示它是由10000個像素點呈現的。但你可曾想過,這些像素點能夠由一系列的數字表示嘛?數組

就先不拿彩色的圖片吧,彩色太複雜了,咱們就先拿一張黑白的圖片爲例,假設咱們以黑色像素的深淺爲基準,將白色到黑色的漸變過程分爲不一樣的等級,這樣,圖片上每個像素點都能用一個最爲臨近的等級數字表示出來:網絡

若是咱們用1表示白色,用0表示黑色,將圖像二值化,最後以矢量(數字)的形式呈現出來,結果大概就是這樣:(下圖是一張5*5的二值化圖像,沒有具體表示含義,只做示例)

同理,若是是彩色的圖像,那咱們是否是能夠把R、G、B三個維度的像素單獨提取出來分別處理呢?這樣,每個維度不就能夠單獨視爲一張灰度圖。

1.1.2.平滑圖像與特徵點

若是一張圖像沒有什麼像素突變,好比一張全白的圖片,若是以數字表示,天然都是0,那咱們能夠稱這張圖片的像素點是平滑的。再好比這張全白的圖片上有一個黑點,天然,灰度圖上就會有一個突兀的數值,咱們就把它稱做特徵點,一般來講,圖像的特徵點有多是噪聲、邊緣或者圖片的實際特徵。

1.2.神經網絡與模型訓練

tensorflow在發佈了JS版本的工具庫後,也同時製做了一個Tensorflow遊樂場,打開以後,引入眼簾的網頁中央這個東西即是神經網絡:

從圖中,咱們能夠看到神經網絡有不少不一樣的層級,就是圖中的Layers,每一層都是前一層通過濾波器計算後的結果,越多的層級以及越多的「神經元」通過一次計算過程計算出來的結果偏差越小,同時,計算的時間也會增長。神經網絡正是模仿了咱們人類腦殼裏的神經元通過了一系列計算而後學習事物的過程。這裏推薦阮一峯的《神經網絡入門》這篇文章,可以幫助你們更加淺顯地瞭解神經網絡是什麼。

在咱們的卷積神經網絡中,這些層級都有不一樣的名字:輸入層、卷積層、池化層以及輸出層。

  • 輸入層:咱們輸入的矢量化以後的圖像
  • 卷積層:通過濾波器卷積計算以後的圖像
  • 池化層:通過池化濾波器卷積計算以後的圖像
  • 輸出層:輸出數據

Features就是咱們的算子,也稱爲濾波器,可是每種不一樣的濾波器對最後的輸出結果都會有不一樣的影響,進過訓練以後,機器會經過咱們賦予的算法(好比激活函數等等)計算出哪些濾波器會對輸出結果形成較大的偏差,哪些濾波器對輸出結果壓根沒有影響(原理很簡單,第一次計算使用全部濾波器,第二次計算拿掉某一個濾波器,而後觀察偏差值(Training loss)就能夠知道這個被拿掉的濾波器所起到的做用了),機器會爲比較重要的濾波器賦予較高的權重,咱們將這樣一個過程稱爲「訓練」。最終,咱們將獲得的整個帶有權重的神經網絡稱爲咱們經過機器訓練出的「模型」,咱們能夠拿着這個模型去讓機器學習事物。

這就是機器學習中「訓練模型」的過程,Tensorflow.js就是爲咱們提供的訓練模型的工具庫,當你真正掌握了模型訓練的奧義以後,Tensorflow對你而言就像JQuery用起來通常簡單。

你們看完這些介紹以後確定仍是一臉茫然,什麼是濾波器?什麼又是卷積計算?不着急,下一個版塊的內容將會爲你們揭開全部謎題。

1.3.卷積算法揭祕

1.3.1.卷積算法

還記得咱們在1.1.1裏說到一張圖片能夠用矢量的形式表示每一個像素點嘛?卷積計算就是在這基礎上,使用某些算子對這些像素點進行處理,而這些算子,就是咱們剛剛提到的濾波器(好比左邊,就是一張通過二值化處理的5*5的圖片,中間的就是咱們的濾波器):

那計算的過程又是怎樣的呢?卷積這東西聽起來感受很複雜,但實際上就是把咱們的濾波器套到圖像上,乘積求和,而後將圖像上位於濾波器中心的值用計算結果替換,大概的效果就是下面這張動圖這樣:

對,所謂高大上的卷積就是這樣一個過程,咱們的濾波器每次計算以後就向右移動一個像素,因此咱們能夠稱濾波器的步長爲1,以此類推。不過咱們發現,通過濾波器處理後的圖像,好像「變小了」!原來是5*5的圖片這下變成了3*3,這是卷積運算帶來的必然反作用,若是不想讓圖片變小,咱們能夠爲原圖像加上必定像素且值均爲0的邊界(padding)去抵消反作用,就像下面這樣:

1.3.2.池化算法

其實在平時訓練模型的過程當中,咱們輸入的圖像確定不僅有5*5像素這麼小,咱們最常常見到的圖片許多都是100*100像素以上的,這樣使用咱們的機器去計算起來確定是比較複雜的,所以,咱們經常會使用池化算法進行特徵提取或者圖像平滑處理,池化的過程其實就是按照某種規律將圖片等比縮小,過程就像下面這樣:

而池化算法最經常使用的有兩大類:取均值算法和取最大值算法,顧名思義,取均值算法就是取濾波器中的平均值做爲結果,取最大值算法就是取濾波器中的最大值做爲輸出結果:

上圖就是取最大值算法的處理過程,你們也能很直觀的看出,在池化層中,濾波器的步長大都是等於濾波器自身大小的(比較方便控制縮放比例)。而且,取最大值算法確定是很容易取到濾波器中的特徵點(還記得特徵點嘛?忘記的話快回去1.1.2看看哦~),因此咱們能夠講取最大值算法的池化處理稱爲特徵提取;同理,取均值算法由於把全部的像素點的灰度級都平均了,因此咱們能夠稱之爲平滑處理。

關於卷積神經網絡的知識,能夠具體參照這篇文章:《卷積神經網絡(1)卷積層和池化層學習》。瞭解了這些知識以後,就能夠開始咱們的實戰啦~

2.圖像識別實戰

說了那麼多理論,也不比實操來得有感受。在你們瞭解了卷積神經網絡的基本原理以後,就可使用咱們的工具庫來幫助咱們完成相關工做,這裏我推薦ConvNetJS。這款工具庫的本質就是咱們在1.2中提到的別人訓練好的模型,咱們只須要拿來「學習」便可。

2.1.使用ConvNetJS

咱們能夠看到在ConvNetJS的README裏有這樣一段官方demo,具體的含義我已經用註釋在代碼裏標註:

// 定義一個神經網絡
var layer_defs = [];
// 輸入層:便是32*32*3的圖像
layer_defs.push({type:'input', out_sx:32, out_sy:32, out_depth:3}); 
// 卷積層 
// filter:用16個5*5的濾波器去卷積
// stride:卷積步長爲1
// padding:填充寬度爲2(爲保證輸出的圖像大小不會發生變化)
// activation:激活函數爲relu(還有Tanh、Sigmoid等等函數,功能不一樣)
layer_defs.push({type:'conv', sx:5, filters:16, stride:1, pad:2, activation:'relu'});
// 池化層
// 池化濾波器的大小爲2*2
// stride:步長爲2
// 在這裏咱們沒法看出這個框架池化是使用的Avy Pooling仍是Max Pooling算法,先視爲後者
layer_defs.push({type:'pool', sx:2, stride:2});
// 反覆卷積和池化減少模型偏差
layer_defs.push({type:'conv', sx:5, filters:20, stride:1, pad:2, activation:'relu'});
layer_defs.push({type:'pool', sx:2, stride:2});
layer_defs.push({type:'conv', sx:5, filters:20, stride:1, pad:2, activation:'relu'});
layer_defs.push({type:'pool', sx:2, stride:2});
// 輸出層
// 分類器:輸出10中不一樣的類別
layer_defs.push({type:'softmax', num_classes:10});

// 實例化一個神經網絡
net = new convnetjs.Net();
net.makeLayers(layer_defs);

// 模型訓練
const trainer = new convnetjs.SGDTrainer(net, { learning_rate: 0.01, momentum: 0.9, batch_size: 5, l2_decay: 0.0 });
trainer.train(imgVol, classIndex);

// 使用訓練好的模型進行圖像識別
var x = convnetjs.img_to_vol(document.getElementById('some_image'))
var output_probabilities_vol = net.forward(x)
複製代碼

若是想要更形象點,上述過程能夠用這樣一幅圖表示:

中間的「卷積-池化-卷積-池化……「就是咱們定義並訓練的神經網絡,咱們輸入矢量化處理後的圖像後,先進行卷積運算,不一樣的濾波器獲得了不一樣的結果,官方demo裏是使用了16個不一樣的濾波器(PS:這裏給你們留一個思考的問題,一個3*3的二值化濾波器,能寫出多少種可能?),天然能卷積出16種不一樣的結果,再拿着這些結果池化處理,不斷重複這個過程,最終得出圖像識別結果:

2.2.實戰項目解析

來,咱們一塊兒詳細梳理一下使用ConvNetJS這個工具庫完成整個圖像識別的具體流程,

(PS:項目代碼具體參照:github.com/jerryOnlyZR…

首先,咱們必須先有數據供咱們的模型去學習,至少你該讓這個模型知道啥是啥對吧,在項目裏的 net 文件夾裏的 car.js 文件,存放的就是咱們的學習數據,若是大家感興趣能夠打開看看,裏面的數據就是告訴機器什麼樣的車標對應的是車的什麼品牌。

在咱們的項目裏,是經過這樣一段代碼完成機器學習的:

const trainer = new convnetjs.SGDTrainer(net, { learning_rate: 0.01, momentum: 0.9, batch_size: 5, l2_decay: 0.0 });
let imageList = [];
const loadData = i => {
    return function () {
        return new Promise(function (resolve, reject) {
    		let image = new Image();
		    image.crossOrigin = "anonymous";
 		    image.src = carList[i].url;
		    image.onload = function () {
        		let vol = convnetjs.img_to_vol(image);
                // 逐張訓練圖片
        		trainer.train(vol, i);
       		 	resolve();
    		};
   		  	image.onerror = reject;
		})
    }
}
// 遍歷圖片資源
for (let j = 0; j < carList.length; j++) {
    imageList.push(loadData(j));
}
var testBtn = document.getElementById("test")
function training(){
    testBtn.disabled = true
    return new Promise((resolve, reject) => {
        Promise.all(imageList.map(imageContainer => imageContainer())).then(() => {
    		console.log("模型訓練好了!!!👌")
    		testBtn.disabled = false
    		resolve()
		})
    })
}
複製代碼

咱們試着去打印一下圖像識別的輸出結果,獲得的是這樣一個東西:

從識別結果中咱們能夠看到,咱們獲得的是一個數組,這就是通過分類器分類的10個不一樣類別,對應的天然是咱們的車的品牌,值就是每一個類別對應的機率。因此,咱們只要拿到機率的最大值,就是預測得出的最傾向的結果。

3.結語

隨着JS引擎的計算能力不斷加強,人工智能領域的不斷髮展,能夠預見的是,在不久的未來,確定能有一些簡單的算法能夠被移植到用戶前端執行,這樣既能減小請求,又能分擔後端壓力。這一切並非無稽之談,爲何tensorflow.js會應運而生,正是由於JS的社區在不斷壯大,JS這款便捷的語言也在獲得更爲廣泛的使用。因此,請對你所從事的這份前端事業,有足夠的信心!

仍是那句老話:

技術歷來不會受限於語言,受限你的,永遠只是思想。

我並非什麼算法工程師,我也不是CS專業出來的科班生,我只是一枚普普統統的前端,和絕大多數人同樣,沒有多深厚的基礎,但我願意去學,我享受克服困難的過程,而那份對人工智能的執着,只是來源於那份不知足於現狀的倔性和對這片領域一成不變的初心。

若是您以爲這篇文章對您有幫助,還請麻煩您爲文章提供的示例demo項目點個star;若是您對個人其餘項目感興趣,也歡迎follow哦~

相關文章
相關標籤/搜索