deeplearn.js:在瀏覽器上訓練神經網絡
最近我寫了一些文章,介紹如何使用JavaScript實現基礎的機器學習算法。我基于Node的math.js從頭開始實現了這些算法,但我仍然覺得使用JavaScript實現機器學習算法是一件非常復雜的事情。目前我正在自學神經網絡,想找一些現成的庫可以幫我完成一些任務。谷歌最近發布了deeplearn.js,可以用來訓練神經網絡,于是我就試用了一把。在這篇文章里,我將分享如何使用deeplearn.js訓練神經網絡,并用它解決真實世界的Web訪問性問題。
神經網絡有什么用?
這篇文章里實現的神經網絡可用于改進Web訪問性,它會根據背景色選擇恰當的字體顏色。比如,深藍色背景的字體應該是白色的,而淺黃色背景的字體應該是黑色的。你或許會想:為什么要在這里使用神經網絡?通過編程的方式來給字體設置恰當的顏色并不難啊,不是嗎?我很快從Stack Overflow上找到了一個解決方案,并根據我的需求進行了修改。
function?getAccessibleColor(rgb)?{?let?[?r,?g,?b?]?=?rgb;? let?colors?=?[r?/?255,?g?/?255,?b?/?255];? let?c?=?colors.map((col)?=>?{??if?(col?<=?0.03928)? {???return?col?/?12.92; ??}??return?Math.pow((col?+?0.055)?/?1.055,?2.4); ?});?let?L?=?(0.2126?*?c[0])?+?(0.7152?*?c[1])?+?(0.0722?*?c[2]);?return?(L?>?0.179) ????[?0,?0,?0?] ??:?[?255,?255,?255?]; }
因為已經可以通過編程的方式來解決這個問題,所以似乎就沒有必要使用神經網絡。不過,既然已經有了編程的解決方案,那為什么不將它與神經網絡解決方案的性能進行比較?GitHub上有一個動畫演示了最終效果,從動畫上你可以很直觀地看到這篇文章將會教你做一個什么樣的東西。
如果你熟悉機器學習,可能已經注意到,這其實是一個分類問題。它需要一個算法,根據輸入數據(背景色)輸出一個二元結果(字體顏色:白色或黑色)。在使用神經網絡對算法進行訓練之后,最終會根據輸入的背景色輸出恰當的字體顏色。
下面將告訴你如何從頭開始搭建這個神經網絡。
使用JavaScript生成數據集
機器學習的訓練包括輸入數據點和輸出數據點(標簽)。它們用來訓練算法,而算法會預測輸入數據點對應的輸出是什么。在訓練階段,算法會調整它的權重,對輸入數據點的標簽進行預測。簡單地說,被訓練的算法就是一個函數,它接收數據點,并預測輸出標簽。
通過神經網絡訓練得到的算法將會為新的背景色輸出對應的字體顏色,而這些字體顏色值并不存在于訓練數據集中。在后續可以使用測試數據集來驗證算法的準確性。因為我們處理的是顏色值,所以可以很容易為神經網絡生成樣本數據。
function?generateRandomRgbColors(m)?{ ?const?rawInputs?=?[];?for?(let?i?=?0;?i?
generateRandomRgbColors()函數生成指定大小(m)的數據集,數據集中的數據點都是RGB顏色值。矩陣中的每一行表示一種顏色,每一列表示顏色的一個特征,特征就是R、G或B的數值。數據集目前還沒有任何標簽,所以還不完整(沒有標簽的數據集也被稱為無標簽訓練集),因為它只有輸入值而沒有輸出值。
之前已經使用編程的方式來生成字體顏色,現在我們對它稍作調整,用來為訓練集生成標簽。我們要解決的是一個分類問題,標簽對應的是RGB里的白色或黑色。所以,標簽要么是表示黑色的[0,1],要么是表示白色的[1,0]。
function?getAccessibleColor(rgb)?{?let?[?r,?g,?b?]?=?rgb;? let?color?=?[r?/?255,?g?/?255,?b?/?255];? let?c?=?color.map((col)?=>?{??if?(col?<=?0.03928)?{???return?col?/?12.92; ??}??return?Math.pow((col?+?0.055)?/?1.055,?2.4); ?});?let?L?=?(0.2126?*?c[0])?+?(0.7152?*?c[1])?+?(0.0722?*?c[2]);?return?(L?>?0.179) ????[?0,?1?]?//?black ??:?[?1,?0?];?//?white}
現在就可以用它來生成隨機的數據集。
function?generateColorSet(m)?{?const?rawInputs?=?generateRandomRgbColors(m);? const?rawTargets?=?rawInputs.map(getAccessibleColor);?return?{?rawInputs,?rawTargets?}; }
我們還可以進行特征縮放(Feature Scaling),我們把RGB值固定在0到1之間。因為我們都知道它的最大值是多少,所以可以直接規范化每個RGB值。
function?normalizeColor(rgb)?{?return?rgb.map(v?=>?v?/?255); }
在JavaScript中搭建神經網絡模型
現在進入最激動人心的部分,我們將要使用JavaScript來實現一個神經網絡。在開始之前,需要先安裝deeplearn.js庫。deeplearn.js是一個JavaScript神經網絡框架,據官方的說法,“它是一個開源的框架,將機器學習帶到Web上,可以在瀏覽器上訓練神經網絡或者在推理模式下運行預訓練的模型”。這個框架為我們帶來了兩大好處:
首先,它利用了本地機器的GPU來加速機器學習算法的矢量運算。這些運算與圖形計算類似,所以使用GPU來計算效率更高。
其次,deeplearn.js與TensorFlow類似,它們都是由谷歌開發,不過后者使用了Python。所以,就算以后你要轉向Python機器學習,deeplearn.js也會是一個很好的起點。
現在讓我們回到項目上。如果你使用了npm,那么可以直接通過命令行來安裝deeplearn.js,否則的話請參考deeplearn.js的官方文檔,按照文檔指示來安裝它。
npm?install?deeplearn
因為我也沒有太多創建神經網絡的經驗,所以我就按照常規的做法,使用面向對象的方式。在JavaScript里,我們可以使用ES6里的類。我們通過類的屬性和方法來定義神經網絡,比如規范化顏色值的函數其實就是類的一個方法:
class?ColorAccessibilityModel?{ ?normalizeColor(rgb)?{ ??return?rgb.map(v?=>?v?/?255); ?} }export?default?ColorAccessibilityModel;
你也可以在這個方法里生成數據集,不過我只用它來規范化數據,而把數據集生成邏輯放在這個類之外。你可能會說,生成數據集的方式有很多,所以可以不在神經網絡模型里事先定義。不過不管怎樣,這只是個實現細節的問題。
在機器學習里,訓練階段和推理階段都是在一個會話(session)里進行的。首先,從deeplearn.js中導入NDArrayMathGPU類,這個類可用于在GPU上執行高效的數學運算。
import?{ ?NDArrayMathGPU, }?from?'deeplearn';const?math?=?new?NDArrayMathGPU();class?ColorAccessibilityModel?{ ?... }export?default?ColorAccessibilityModel;
然后聲明用于創建會話的方法,這個方法接收訓練集作為參數。接下來,會話會初始化一個空的圖,這個圖會在后面反映出你的神經網絡結構。至于要不要定義所有的屬性,完全取決于你自己。
import?{ ?Graph, ?NDArrayMathGPU, }?from?'deeplearn';class?ColorAccessibilityModel?{ ?setupSession(trainingSet)?{??const?graph?=?new?Graph(); ?} ?.. }export?default?ColorAccessibilityModel;
第四步,以張量的形式定義輸入數據點和輸出數據點的形狀。張量是一組數值(或數值數組),這些數值可以有多個維度。張量可以是一個vector、一個矩陣或一個多維矩陣。神經網絡將這些張量作為輸入和輸出。我們的這個例子有三個輸入單元(每種顏色通道就是一個輸入單元)和兩個輸出單元(二元分類:白顏色或黑顏色)。
class?ColorAccessibilityModel?{ ?inputTensor; ?targetTensor; ?setupSession(trainingSet)?{??const?graph?=?new?Graph();?? ?this.inputTensor?=?graph.placeholder('input?RGB?value',?[3]);?? ?this.targetTensor?=?graph.placeholder('output?classifier',?[2]); ?} ?... }export?default?ColorAccessibilityModel;
第五步,神經網絡包含了隱藏層,這里就是施展魔法的地方。一般來說,神經網絡經過訓練會得到自己的計算參數。不過,如何定義隱藏層的維度(每個單元的層數)也取決于你自己。
class?ColorAccessibilityModel?{ ?inputTensor; ?targetTensor; ?setupSession(trainingSet)?{??const?graph?=?new?Graph();?? ?this.inputTensor?=?graph.placeholder('input?RGB?value',?[3]);?? ?this.targetTensor?=?graph.placeholder('output?classifier',?[2]); ??let?connectedLayer?=?this.createConnectedLayer(graph,?this.inputTensor,?0,?64); ??connectedLayer?=?this.createConnectedLayer(graph,?connectedLayer,?1,?32); ??connectedLayer?=?this.createConnectedLayer(graph,?connectedLayer,?2,?16); ?} ?createConnectedLayer( ??graph, ??inputLayer, ??layerIndex, ??units, ?)?{ ??... ?} ?... } export?default?ColorAccessibilityModel;
用于創建連接層的方法以圖、變形層、新層索引和單元數量作為參數。圖的層屬性可用于返回一個帶有名字的張量。
class?ColorAccessibilityModel?{ ?inputTensor; ?targetTensor; ?setupSession(trainingSet)?{??const?graph?=?new?Graph();?? ?this.inputTensor?=?graph.placeholder('input?RGB?value',?[3]);?? ?this.targetTensor?=?graph.placeholder('output?classifier',?[2]); ??let?connectedLayer?=?this.createConnectedLayer(graph,?this.inputTensor,?0,?64); ??connectedLayer?=?this.createConnectedLayer(graph,?connectedLayer,?1,?32); ??connectedLayer?=?this.createConnectedLayer(graph,?connectedLayer,?2,?16); ?} ?createConnectedLayer( ??graph, ??inputLayer, ??layerIndex, ??units, ?)?{??return?graph.layers.dense( ???`fully_connected_${layerIndex}`, ???inputLayer, ???units ??); ?} ?... } export?default?ColorAccessibilityModel;
神經網絡的每個神經元都要有個激活函數,它可以是邏輯激活函數,你可能已經從邏輯回歸中知道這個函數是什么,它也就是神經網絡的邏輯單元。在我們的例子里,神經網絡默認使用了修正線性單元。
class?ColorAccessibilityModel?{ ?inputTensor; ?targetTensor; ?setupSession(trainingSet)?{??const?graph?=?new?Graph();?? ?this.inputTensor?=?graph.placeholder('input?RGB?value',?[3]);?? ?this.targetTensor?=?graph.placeholder('output?classifier',?[2]);?? ?let?connectedLayer?=?this.createConnectedLayer(graph,?this.inputTensor,?0,?64); ??connectedLayer?=?this.createConnectedLayer(graph,?connectedLayer,?1,?32); ??connectedLayer?=?this.createConnectedLayer(graph,?connectedLayer,?2,?16); ?} ?createConnectedLayer( ??graph, ??inputLayer, ??layerIndex, ??units, ??activationFunction ?)?{??return?graph.layers.dense(???`fully_connected_${layerIndex}`, ???inputLayer, ???units, ???activationFunction???activationFunction?:?(x)?=>?graph.relu(x) ??); ?} ?... }export?default?ColorAccessibilityModel;
第六步,創建輸出二分分類的層。它有兩個輸出單元,每個單元對應一個離散值(白或黑)。
class?ColorAccessibilityModel?{ ?inputTensor; ?targetTensor; ?predictionTensor; ?setupSession(trainingSet)?{??const?graph?=?new?Graph();?? ?this.inputTensor?=?graph.placeholder('input?RGB?value',?[3]);?? ?this.targetTensor?=?graph.placeholder('output?classifier',?[2]); ??let?connectedLayer?=?this.createConnectedLayer(graph,?this.inputTensor,?0,?64); ??connectedLayer?=?this.createConnectedLayer(graph,?connectedLayer,?1,?32); ??connectedLayer?=?this.createConnectedLayer(graph,?connectedLayer,?2,?16);?? ??this.predictionTensor?=?this.createConnectedLayer(graph,?connectedLayer,?3,?2); ?} ?... } export?default?ColorAccessibilityModel;
第七步,聲明一個成本張量。在這里,它就是均方誤差。它使用訓練集的目標張量(標簽)和來自已訓練算法的預測張量進行成本計算,以此來優化算法。
class?ColorAccessibilityModel?{ ?inputTensor; ?targetTensor; ?predictionTensor; ?costTensor; ?setupSession(trainingSet)?{??const?graph?=?new?Graph();?? ?this.inputTensor?=?graph.placeholder('input?RGB?value',?[3]);?? ?this.targetTensor?=?graph.placeholder('output?classifier',?[2]); ??let?connectedLayer?=?this.createConnectedLayer(graph,?this.inputTensor,?0,?64); ??connectedLayer?=?this.createConnectedLayer(graph,?connectedLayer,?1,?32); ??connectedLayer?=?this.createConnectedLayer(graph,?connectedLayer,?2,?16);?? ??this.predictionTensor?=?this.createConnectedLayer(graph,?connectedLayer,?3,?2);?? ??this.costTensor?=?graph.meanSquaredCost(this.targetTensor,?this.predictionTensor); ?} ?... } export?default?ColorAccessibilityModel;
最后,使用之前架構好的圖來創建會話。接下來就可以進入訓練階段了。
import?{ ?Graph, ?Session, ?NDArrayMathGPU, }?from?'deeplearn';class?ColorAccessibilityModel?{ ?session; ?inputTensor; ?targetTensor; ?predictionTensor; ?costTensor; ?setupSession(trainingSet)?{??const?graph?=?new?Graph();?? ?this.inputTensor?=?graph.placeholder('input?RGB?value',?[3]);?? ?this.targetTensor?=?graph.placeholder('output?classifier',?[2]); ??let?connectedLayer?=?this.createConnectedLayer(graph,?this.inputTensor,?0,?64); ??connectedLayer?=?this.createConnectedLayer(graph,?connectedLayer,?1,?32); ??connectedLayer?=?this.createConnectedLayer(graph,?connectedLayer,?2,?16);?? ??this.predictionTensor?=?this.createConnectedLayer(graph,?connectedLayer,?3,?2);?? ??this.costTensor?=?graph.meanSquaredCost(this.targetTensor,?this.predictionTensor);?? ??this.session?=?new?Session(graph,?math);??this.prepareTrainingSet(trainingSet); ?} ?prepareTrainingSet(trainingSet)?{ ??... ?} ?... } export?default?ColorAccessibilityModel;
在準備好訓練數據集之前還無法創建會話。首先,我們可以加入一個回調函數,讓它在GPU的上下文里執行。當然,這不是必需的,不用回調函數也能完成計算。
import?{ ?Graph, ?Session, ?NDArrayMathGPU, }?from?'deeplearn'; const?math?=?new?NDArrayMathGPU();class?ColorAccessibilityModel?{ ?session; ?inputTensor; ?targetTensor; ?predictionTensor; ?costTensor; ?... ?prepareTrainingSet(trainingSet)?{ ??math.scope(()?=>?{ ???... ??}); ?} ?... }export?default?ColorAccessibilityModel;
其次,你可以將輸入和輸出(標簽,也叫作目標)從訓練集中抽取出來,把它們映射成神經網絡可理解的格式。deeplearn.js使用自己的NDArrays完成這些數學運算。最后它們會變成多維度的矩陣或矢量。另外,輸入數組中的顏色值會被規范化,用以提升神經網絡的性能。
import?{ ?Array1D, ?Graph, ?Session, ?NDArrayMathGPU, }?from?'deeplearn';const?math?=?new?NDArrayMathGPU();class?ColorAccessibilityModel?{ ?session; ?inputTensor; ?targetTensor; ?predictionTensor; ?costTensor; ?... ?prepareTrainingSet(trainingSet)?{ ??math.scope(()?=>?{???const?{?rawInputs,?rawTargets?}?=?trainingSet;??? ??const?inputArray?=?rawInputs.map(v?=>?Array1D.new(this.normalizeColor(v)));??? ??const?targetArray?=?rawTargets.map(v?=>?Array1D.new(v)); ??}); ?} ?... }export?default?ColorAccessibilityModel;
第三,輸入和輸出數組被攪亂。deeplearn.js提供的攪亂器(Shuffler)在攪亂數組時會保持它們之間的同步。每次訓練迭代都會進行攪亂操作,輸入被分批填充進神經網絡。我們通過攪亂來改進訓練算法,因為這更像是通過避免過擬合來實現泛化。
import?{ ?Array1D, ?InCPUMemoryShuffledInputProviderBuilder, ?Graph, ?Session, ?NDArrayMathGPU, }?from?'deeplearn';const?math?=?new?NDArrayMathGPU();class?ColorAccessibilityModel?{ ?session; ?inputTensor; ?targetTensor; ?predictionTensor; ?costTensor; ?... ?prepareTrainingSet(trainingSet)?{ ??math.scope(()?=>?{???const?{?rawInputs,?rawTargets?}?=?trainingSet;??? ??const?inputArray?=?rawInputs.map(v?=>?Array1D.new(this.normalizeColor(v)));??? ??const?targetArray?=?rawTargets.map(v?=>?Array1D.new(v));??? ??const?shuffledInputProviderBuilder?=?new?InCPUMemoryShuffledInputProviderBuilder([ ????inputArray, ????targetArray ???]);???const?[ ????inputProvider, ????targetProvider, ???]?=?shuffledInputProviderBuilder.getInputProviders(); ??}); ?} ?... }export?default?ColorAccessibilityModel;
最后,填充項就成為神經網絡訓練階段的最終輸入。
import?{ ?Array1D, ?InCPUMemoryShuffledInputProviderBuilder ?Graph, ?Session, ?NDArrayMathGPU, }?from?'deeplearn';const?math?=?new?NDArrayMathGPU();class?ColorAccessibilityModel?{ ?session; ?inputTensor; ?targetTensor; ?predictionTensor; ?costTensor; ?feedEntries; ?... ?prepareTrainingSet(trainingSet)?{ ??math.scope(()?=>?{???const?{?rawInputs,?rawTargets?}?=?trainingSet;??? ??const?inputArray?=?rawInputs.map(v?=>?Array1D.new(this.normalizeColor(v)));??? ??const?targetArray?=?rawTargets.map(v?=>?Array1D.new(v));??? ??const?shuffledInputProviderBuilder?=?new?InCPUMemoryShuffledInputProviderBuilder([ ????inputArray, ????targetArray ???]);???const?[ ????inputProvider, ????targetProvider, ???]?=?shuffledInputProviderBuilder.getInputProviders();???this.feedEntries?=?[ ????{?tensor:?this.inputTensor,?data:?inputProvider?}, ????{?tensor:?this.targetTensor,?data:?targetProvider?}, ???]; ??}); ?} ?... }export?default?ColorAccessibilityModel;
到此,神經網絡的搭建階段就完成了。神經網絡包含了所有需要的層和單元,訓練集也準備完畢。現在只差兩個超參數了,它們將會在訓練階段用到。
import?{ ?Array1D, ?InCPUMemoryShuffledInputProviderBuilder, ?Graph, ?Session, ?SGDOptimizer, ?NDArrayMathGPU, }?from?'deeplearn';const?math?=?new?NDArrayMathGPU();class?ColorAccessibilityModel?{ ?session; ?optimizer; ?batchSize?=?300; ?initialLearningRate?=?0.06; ?inputTensor; ?targetTensor; ?predictionTensor; ?costTensor; ?feedEntries;?constructor()?{??this.optimizer?=?new?SGDOptimizer(this.initialLearningRate); ?} ?... }export?default?ColorAccessibilityModel;
第一個參數是學習速率(learning rate),它在線性回歸或邏輯回歸的梯度下降中有用到,主要用來確定算法以多快的速度收斂到最小成本。它的值可以設置得很高,不過不是必需的。否則的話,梯度下降將無法收斂,因為找不到局部最優狀態。
第二個參數是批次大小(batch size),它定義了每個迭代周期有多少數據點會經過神經網絡。每個周期時間點(epoch)都包含一個前向和一個后向的數據點批次。以批次的形式來訓練神經網絡有兩個好處。首先,它不會造成密集的計算,因為訓練算法使用更少的數據點。其次,權重是根據批次來調整的,而不是根據整個訓練集來調整。
訓練階段
搭建階段已經完成了,接下來是訓練階段。首先,可以在類的一個方法里對訓練階段進行定義。訓練將會在deeplearn.js的math上下文中運行。另外,我們將會使用神經網絡實例所有的預定義屬性來訓練算法。
class?ColorAccessibilityModel?{ ?... ?train()?{ ??math.scope(()?=>?{??? ??this.session.train(???? ??this.costTensor,???? ??this.feedEntries,???? ??this.batchSize,???? ??this.optimizer ???); ??}); ?} } export?default?ColorAccessibilityModel;
訓練方法只能運行在某個時間點上,所以需要在外部進行多次迭代調用才能進行神經網絡訓練,而且它進行的是批次訓練。為了進行多個批次的算法訓練,需要多次迭代運行這個訓練方法。
基本的訓練就是這樣的,不過我們可以通過調整學習速率來改進訓練。學習速率在剛開始時可以設置得高一些,不過隨著算法不斷收斂,可以逐步降低學習速率。
class?ColorAccessibilityModel?{ ?... ?train(step)?{ ??let?learningRate?=?this.initialLearningRate?*?Math.pow(0.90,?Math.floor(step?/?50));?? ??this.optimizer.setLearningRate(learningRate); ??math.scope(()?=>?{??? ??this.session.train(???? ??this.costTensor,???? ??this.feedEntries,???? ??this.batchSize,???? ??this.optimizer ???); ??} ?} } export?default?ColorAccessibilityModel;
在我們的例子里,學習速率每50步會降低10%。接下來,通過獲取訓練成本來驗證它會隨著時間而下降是一件很有趣的事情。我們可以在每次迭代之后把它返回,但這樣會影響計算性能,因為每次發出獲取成本的請求,都需要訪問GPU。所以,我們只在需要驗證它的時候才去獲取。如果不需要獲取成本,成本損失常量就設置為NONE(之前的默認值)。
import?{ ?Array1D, ?InCPUMemoryShuffledInputProviderBuilder, ?Graph, ?Session, ?SGDOptimizer, ?NDArrayMathGPU, ?CostReduction, }?from?'deeplearn';class?ColorAccessibilityModel?{ ?... ?train(step,?computeCost)?{ ??let?learningRate?=?this.initialLearningRate?*?Math.pow(0.90,?Math.floor(step?/?50));?? ??this.optimizer.setLearningRate(learningRate); ??let?costValue; ??math.scope(()?=>?{??? ??const?cost?=?this.session.train(????this.costTensor,???? ??this.feedEntries,???? ??this.batchSize,???? ??this.optimizer, ????computeCost???CostReduction.MEAN?:?CostReduction.NONE, ???);???if?(computeCost)?{ ????costValue?=?cost.get(); ???} ??});??return?costValue; ?} } export?default?ColorAccessibilityModel;
這樣,訓練階段也差不多了。在會話建立起來之后,迭代執行訓練方法就可以了。
推理階段
最后是推理階段。在這個階段,我們使用一個測試集來驗證算法的性能。輸入的是一個RGB背景色,輸出要么是[0,1],要么是[1,0],分別代表黑色或白色。因為輸入數據點需要經過規范化,所以不要忘了在這一步對顏色值進行規范化。
class?ColorAccessibilityModel?{ ?... ?predict(rgb)?{ ??let?classifier?=?[]; ??math.scope(()?=>?{???const?mapping?=?[{ ????tensor:?this.inputTensor,????data:?Array1D.new(this.normalizeColor(rgb)), ???}]; ???classifier?=?this.session.eval(this.predictionTensor,?mapping).getValues(); ??});??return?[?...classifier?]; ?} } export?default?ColorAccessibilityModel;
最終,我們完成了神經網絡的搭建、訓練和推理。
使用JavaScript可視化神經網絡
因為這個例子是關于顏色預測,而且使用了deeplearn.js,所以如果能夠對訓練階段和推理階段進行可視化會很有意思。
可視化的方式有很多,可以使用canvas和requestAnimationFrame API來實現,不過在這里,我選擇使用React.js。
先創建一個create-react-app項目,在項目里導入神經網絡類和用于生成數據集的函數。另外還可以增加幾個常量,如訓練集大小、測試集大小和訓練迭代次數。
import?React,?{?Component?}?from?'react';import?'./App.css';import?generateColorSet?from?'./data'; import?ColorAccessibilityModel?from?'./neuralNetwork';const?ITERATIONS?=?750;const?TRAINING_SET_SIZE?=?1500; const?TEST_SET_SIZE?=?10;class?App?extends?Component?{ ?... }export?default?App;
在App Component的構造函數里生成數據集(訓練集和測試集),啟動神經網絡會話,傳入訓練集,并定義組件的初始狀態。隨著訓練的進行,成本值和迭代次數會在某個地方展示出來。
import?React,?{?Component?}?from?'react';import?'./App.css';import?generateColorSet?from?'./data'; import?ColorAccessibilityModel?from?'./neuralNetwork';const?ITERATIONS?=?750; const?TRAINING_SET_SIZE?=?1500;const?TEST_SET_SIZE?=?10;class?App?extends?Component?{ ?testSet; ?trainingSet; ?colorAccessibilityModel;?constructor()?{??super();??this.testSet?=?generateColorSet(TEST_SET_SIZE);?? ?this.trainingSet?=?generateColorSet(TRAINING_SET_SIZE);?? ?this.colorAccessibilityModel?=?new?ColorAccessibilityModel();?? ?this.colorAccessibilityModel.setupSession(this.trainingSet);?? ?this.state?=?{???currentIteration:?0,???cost:?-42, ??}; ?} ?... }export?default?App;
接下來,在構造函數里啟動神經網絡會話,并開始訓練。簡單的做法是直接在React的掛載組件生命周期鉤子里循環調用訓練方法。
class?App?extends?Component?{ ?... ?componentDidMount?()?{??for?(let?i?=?0;?i?<=?ITERATIONS;?i++)?{???this.colorAccessibilityModel.train(i); ??} ?}; } export?default?App;
不過,在訓練過程中無法對輸出進行渲染,因為運行中的訓練會阻塞JavaScript線程,導致React組件無法重新渲染。這個時候可以使用requestAnimationFrame。我們不需要定義循環語句,因為每個動畫幀請求都可以用來運行訓練方法。
class?App?extends?Component?{ ?... ?componentDidMount?()?{ ??requestAnimationFrame(this.tick); ?}; ?tick?=?()?=>?{??this.setState((state)?=>?({ ???currentIteration:?state.currentIteration?+?1 ??}));??if?(this.state.currentIteration?
另外,我們可以每5步計算一次成本。
class?App?extends?Component?{ ?... ?componentDidMount?()?{ ??requestAnimationFrame(this.tick); ?}; ?tick?=?()?=>?{??this.setState((state)?=>?({ ???currentIteration:?state.currentIteration?+?1 ??}));??if?(this.state.currentIteration??0)?{????this.setState(()?=>?({?cost?})); ???} ??} ?}; }export?default?App;
在組件被掛載之后,訓練就開始了。現在可以渲染測試集了,可以顯示出之前通過編程方式得到的輸出和神經網絡的預測輸出。到最后,預測結果應該和通過編程方式得出的結果是一樣的。訓練集本身不會被可視化。
class?App?extends?Component?{ ?... ?render()?{ ??const?{?currentIteration,?cost?}?=?this.state; ??return?(???
Neural?Network?for?Font?Color?Accessibility
?????Iterations:?{currentIteration}
?????Cost:?{cost}
????Programmatically?Computed
?Neural?Network?Computed
?測試集包含了輸入顏色(背景色)和輸出顏色(字體顏色)。因為輸出顏色被分為黑色[0,1]和白色[1,0],所以它們需要被轉換成實際的顏色值。
const?ActualTable?=?({?testSet?})?=> ?
Programmatically?Computed
??{Array(TEST_SET_SIZE).fill(0).map((v,?i)?=> ???ColorBox組件接收輸入顏色(背景色)和目標顏色(字體顏色)作為參數,它會顯示一個矩形(顏色與輸入顏色一樣),矩形里面是輸入顏色的RGB值,字體顏色與目標顏色一樣。
const?ColorBox?=?({?rgbInput,?rgbTarget?})?=> ?
最好玩的是推理表格里的預測顏色值。它也使用了上述的ColorBox,只是顯示的屬性不一樣。
const?InferenceTable?=?({?testSet,?model?})?=> ?
Neural?Network?Computed
??{Array(TEST_SET_SIZE).fill(0).map((v,?i)?=> ???輸入顏色仍然是在測試集中定義的顏色,但目標顏色不是。目標顏色是使用神經網絡預測出來的,它接收輸入顏色,并經過訓練預測出目標顏色。
最后,啟動應用程序,就可以看到神經網絡是怎么運行的。React部分的動畫也可以在GitHub倉庫上看到。
本文轉載自異步社區。
智能數據 高性能計算 人工智能
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。