近日,來自德國的 Robin Wieruch 發布了一系列使用 JavaScript 構建機器學習的教程,本文將主要介紹使用 JavaScript 實現神經網絡的方法。
JavaScript 是一種流行的高級編程語言,它被世界上的絕大多數網站所使用,也被所有主流瀏覽器所支持。隨著深度學習的火熱,越來越多開發者開始探索使用 JavaScript 實現人工智能與機器學習算法。近日,來自德國的 Robin Wieruch 發布了一系列使用 JavaScript 構建機器學習的教程,本文將主要介紹使用 JavaScript 實現神經網絡的方法。
近期,原作者發表了一系列有關在 JavaScript 上實現人工智能和機器學習算法的文章,其中包括:
線性回歸和梯度下降
正規方程線性回歸
邏輯回歸和梯度下降
這些機器學習算法的實現是基于 math.js 庫的線性代數(如矩陣運算)和微分的,你可以在 GitHub 上找到所有這些算法:
如果你發現其中存在任何缺陷,歡迎對這個資源提出自己的改進,以幫助后來者。我希望不斷為 web 開發者們提供更多、更豐富的機器學習算法。
就我個人來說,我發現實現這些算法在某種程度上是一個非常具有挑戰性的任務。特別是當你需要在 JavaScript 上實現神經網絡的前向和反向傳播的時候。由于我自己也在學習神經網絡的知識,我開始尋找適用于這種工作的庫。希望在不久的將來,我們能夠輕松地在 GitHub 上找到相關的基礎實現。然而現在,以我使用 JavaScript 的閱歷,我選擇了谷歌發布的 deeplearn.js 來進行此項工作。在本文中,我將分享使用 deeplearn.js 和 JavaScript 實現神經網絡從而解決現實世界問題的方式——在 web 環境上。
首先,我強烈推薦讀者先學習一下深度學習著名學者吳恩達的《機器學習》課程。本文不會詳細解釋機器學習算法,只會展示它在 JavaScript 上的用法。另一方面,該系列課程在算法的細節和解釋上有著令人驚嘆的高質量。在寫這篇文章之前,我自己也學習了相關課程,并試圖用 JavaScript 實現來內化課程中的相關知識。
神經網絡的目的是什么?
本文實現的神經網絡需要通過選擇與背景顏色相關的適當字體顏色來改善網頁可訪問性。比如,深藍色背景中的字體應該是白色,而淺黃色背景中的字體應該是黑色。你也許會想:首先你為什么需要一個神經網絡來完成任務?通過編程的方式根據背景顏色計算可使用的字體顏色并不難,不是嗎?我很快在 Stack Overflow 找到了該問題的解決辦法,并根據我的需求做了調整,以適應 RGB 空間中的顏色。
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 庫(https://github.com/javascript-machine-learning/color-accessibility-neural-network-deeplearnjs)中的動圖,了解它最終表現如何,以及本教程中你將構建什么。如果你熟悉機器學習,也許你已經注意到這個任務是一個分類問題。算法應根據輸入(背景顏色)決定二進制輸出(字體顏色:白色或黑色)。在使用神經網絡訓練算法的過程中,最終會根據輸入的背景顏色輸出正確的字體顏色。
下文將從頭開始指導你設置神經網絡的所有部分,并由你決定把文件/文件夾設置中的部分合在一起。但是你可以整合以前引用的 GitHub 庫以獲取實現細節。
JavaScript 中的數據集生成
? ? ? ? 機器學習中的訓練集由輸入數據點和輸出數據點(標簽)組成。它被用來訓練為訓練集(例如測試集)之外的新輸入數據點預測輸出的算法。在訓練階段,由神經網絡訓練的算法調整其權重以預測輸入數據點的給定標簽。總之,已訓練算法是一個以數據點作為輸入并近似輸出標簽的函數。
? ? ? ?該算法經過神經網絡的訓練后,可以為不屬于訓練集的新背景顏色輸出字體顏色。因此,稍后你將使用測試集來驗證訓練算法的準確率。由于我們正在處理顏色,因此為神經網絡生成輸入顏色的樣本數據集并不困難。
function generateRandomRgbColors(m) {
const rawInputs = [];
for (let i = 0; i < m; i++) {
rawInputs.push(generateRandomRgbColor());
}
return rawInputs;
}
function generateRandomRgbColor() {
return [
randomIntFromInterval(0, 255),
randomIntFromInterval(0, 255),
randomIntFromInterval(0, 255),
];
}
function randomIntFromInterval(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
generateRandomRgbColors() 函數創建給定大小為 m 的部分數據集。數據集中的數據點是 RGB 顏色空間中的顏色。每種顏色在矩陣中被表征為一行,而每一列是顏色的特征。特征是 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 };
}
使神經網絡中底層算法更好的另一步操作是特征縮放。在特征縮放的簡化版本中,你希望 RGB 通道的值在 0 和 1 之間。由于你知道最大值,因此可以簡單地推導出每個顏色通道的歸一化值。
function normalizeColor(rgb) {
return rgb.map(v => v / 255);
}
你可以把這個功能放在你的神經網絡模型中,或者作為單獨的效用函數。下一步我將把它放在神經網絡模型中。
JavaScript 神經網絡模型的設置階段
現在你可以使用 JavaScript 實現一個神經網絡了。在開始之前,你需要先安裝 deeplearn.js 庫:一個適合 JavaScript 神經網絡的框架。官方宣傳中說:「deeplearn.js 是一個開源庫,將高效的機器學習構造塊帶到 web 中,允許在瀏覽器中訓練神經網絡或在推斷模式下運行預訓練模型。」本文,你將訓練自己的模型,然后在推斷模式中運行該模型。使用該庫有兩個主要優勢:
首先,它使用本地電腦的 GPU 加速機器學習算法中的向量計算。這些機器學習計算與圖解計算類似,因此使用 GPU 的計算比使用 CPU 更加高效。
其次,deeplearn.js 的結構與流行的 TensorFlow 庫類似(TensorFlow 庫也是谷歌開發的,不過它使用的是 Python 語言)。因此如果你想在使用 Python 的機器學習中實現飛躍,那么 deeplearn.js 可提供通向 JavaScript 各領域的捷徑。
現在回到你的項目。如果你想用 npm 來設置,那么你只需要在命令行中安裝 deeplearn.js。也可以查看 deeplearn.js 項目的官方安裝說明文檔。
npm install deeplearn
我沒有構建過大量神經網絡,因此我按照構建神經網絡的一般實踐進行操作。在 JavaScript 中,你可以使用 JavaScript ES6 class 來推進它。該類可以通過定義神經網絡特性和類方法為你的神經網絡提供完美的容器。例如,你的顏色歸一化函數可以在類別中找到一個作為方法的點。
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;
第二,聲明分類方法類設置會話。其函數簽名使用訓練集作為參數,成為從先前實現的函數中生成訓練集的完美 consumer。
第三步,會話初始化空的圖。之后,圖將反映神經網絡的架構。你可以隨意定義其特性。
import {
Graph,
NDArrayMathGPU,
} from 'deeplearn';
class ColorAccessibilityModel {
setupSession(trainingSet) {
const graph = new Graph();
}
..
}
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]);
}
...
}
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;
根據層的數量,你可以變更圖來擴展出更多層。創建連接層的分類方法需要圖、變異連接層(mutated connected layer)、新層的索引,以及單元數量。圖的層屬性可用于返回由名稱確定的新張量。
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;
神經網絡中的每一個神經元必須具備一個定義好的激活函數。它可以是 logistic 激活函數。你或許已經從 logistic 回歸中了解到它,它成為神經網絡中的 logistic 單元。在我們的案例中,神經網絡默認使用修正線性單元。
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;
第七步,聲明一個代價張量(cost tensor),以定義損失函數。在這個案例中,代價張量是均方誤差。它使用訓練集的目標張量(標簽)和訓練算法得到的預測張量來計算代價。
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 數學計算環境中使用回調函數(callback function)來支持計算,但這并不是強制性的,可自主選擇。
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;
第三,shuffle 輸入和目標陣列。shuffle 的時候,deeplearn.js 提供的 shuffler 將二者保存在 sync 中。每次訓練迭代都會出現 shuffle,以饋送不同的輸入作為神經網絡的 batch。整個 shuffle 流程可以改善訓練算法,因為它更可能通過避免過擬合來實現泛化。
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;
最后,饋送條目(feed entries)是訓練階段中神經網絡前饋算法的最終輸入。它匹配數據和張量(根據設置階段的形態而定義)。
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(迭代)里有多少個訓練集的數據點通過神經網絡。一個 epoch 等于一批數據點的一次正向傳播和一次反向傳播。以批次的方式訓練神經網絡有兩個好處:第一,這樣可以防止密集計算,因為算法訓練時使用了內存中的少量數據點;第二,這樣可以讓神經網絡更快地進行批處理,因為每個 epoch 中權重會隨著每個批次的數據點進行調整——而不是等到整個數據集訓練完之后再進行改動。
訓練階段
設置階段結束后就到了訓練階段了。不需要太多實現,因為所有的基礎都已在設置階段完成。首先,訓練階段可以用分類方法來定義。然后在 deeplearn.js 的數學環境中再次執行。此外,它還使用神經網絡實例所有的預定義特性來訓練算法。
class ColorAccessibilityModel {
...
train() {
math.scope(() => {
this.session.train(
this.costTensor,
this.feedEntries,
this.batchSize,
this.optimizer
);
});
}
}
export default ColorAccessibilityModel;
訓練方法是 1 個 epoch 的神經網絡訓練。因此,從外部調用時,調用必須是迭代的。此外,訓練只需要 1 個 epoch。為了多批次訓練算法,你必須將該訓練方法進行多次迭代運行。
這就是基礎的訓練階段。但是根據時間調整學習率可以改善訓練。學習率最初很高,但是當算法在每一步過程中逐漸收斂時,學習率會出現下降趨勢。
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 中可視化學習神經網絡
現在是時候使用神經網絡進行訓練和驗證/測試了。簡單的過程為建立一個神經網絡,使用一個訓練集運行訓練階段,代價函數取得最小值之后,使用一個測試集進行預測。所有的過程只需要使用網頁瀏覽器上的開發者控制臺的幾個 console.log statements 就可以完成。然而,由于該神經網絡是關于顏色預測的,并且 deeplearn.js 是在瀏覽器上運行,從而可以輕松地對神經網絡的訓練階段和測試階段進行可視化。
至此,你可以自主決定你運行中的神經網絡的可視化方式。使用一個 canvas 和 repuestAnimationFrame API 可以使 JavaScript 代碼更簡單。但就這篇文章來說,我會使用 React.js 進行展示,因為我在博客上寫過 React.js。
因此在使用 create-react-app 設置完項目后,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 的組件包括生成數據集(訓練集和測試集)、通過傳遞訓練集建立神經網絡會話、定義組件的初始狀態。在訓練階段的時間內,代價函數的值和迭代次數會在控制臺上顯示,它也表示了組件的狀態。
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 的一個 for 循環就可以了。
class App extends Component {
...
componentDidMount () {
for (let i = 0; i <= ITERATIONS; i++) {
this.colorAccessibilityModel.train(i);
}
};
}
export default App;
然而,以上代碼不會在 React 的訓練階段提供(render)輸出,因為組件不會在神經網絡阻塞單個 JavaScript 線程的時候 reRender。這也正是 React 使用 requestAnimationFrame 的時候。與其自己定義一個 for 循環,每一個請求的瀏覽器的動畫幀都可以被用于運行一次訓練迭代。
class App extends Component {
...
componentDidMount () {
requestAnimationFrame(this.tick);
};
tick = () => {
this.setState((state) => ({
currentIteration: state.currentIteration + 1
}));
if (this.state.currentIteration < ITERATIONS) {
requestAnimationFrame(this.tick);
this.colorAccessibilityModel.train(this.state.currentIteration);
}
};
}
export default App;
此外,代價函數可以每 5 步進行一次計算。如前所述,需要訪問 GPU 來檢索代價函數。因此需要防止神經網絡訓練過快。
class App extends Component {
...
componentDidMount () {
requestAnimationFrame(this.tick);
};
tick = () => {
this.setState((state) => ({
currentIteration: state.currentIteration + 1
}));
if (this.state.currentIteration < ITERATIONS) {
requestAnimationFrame(this.tick);
let computeCost = !(this.state.currentIteration % 5);
let cost = this.colorAccessibilityModel.train(
this.state.currentIteration,
computeCost
);
if (cost > 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}
Cst: {cost}
/>
testSet={this.testSet}
/>
);
}
}
const ActualTable = ({ testSet }) =>
Programmatically Computed
const InferenceTable = ({ testSet, model }) =>
Neural Network Computed
export default App;
實際的表格會隨著測試集的不斷輸入不斷地展示每一個輸入和輸出的顏色。測試集包括輸入顏色(背景顏色)和輸出顏色(字體顏色)。由于生成數據集的時候輸出顏色被分類為黑色 [0,1] 和白色 [1,0] 向量,它們需要再次被轉換為真實的顏色。
const ActualTable = ({ testSet }) =>
Programmatically Computed
{Array(TEST_SET_SIZE).fill(0).map((v, i) =>rgbInput={testSet.rawInputs[i]}
rgbTarget={fromClassifierToRgb(testSet.rawTargets[i])}
/>
)}
const fromClassifierToRgb = (classifier) =>
classifier[0] > classifier[1]
? [ 255, 255, 255 ]
: [ 0, 0, 0 ]
ColorBox 組件是一個通用組件,以輸入顏色(背景顏色)和目標顏色(字體顏色)為輸入。它能簡單地用一個矩形展示輸入顏色的類型、輸入顏色的 RGB 代碼字符串,并用字體的 RGB 代碼將給定的目標顏色上色。
const ColorBox = ({ rgbInput, rgbTarget }) =>
const RgbString = ({ rgb }) =>
`rgb(${rgb.toString()})`
const getRgbStyle = (rgb) =>
`rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`
最后但重要的是,在推理表格中可視化預測顏色的激動人心的部分。它使用的也是 color box,但提供了一些不同的小道具。
const InferenceTable = ({ testSet, model }) =>
Neural Network Computed
{Array(TEST_SET_SIZE).fill(0).map((v, i) =>rgbInput={testSet.rawInputs[i]}
rgbTarget={fromClassifierToRgb(model.predict(testSet.rawInputs[i]))}
/>
)}
輸入顏色仍然是測試集中定義的顏色,但目標顏色并不是測試集中的目標色。任務的關鍵是利用神經網絡的預測方法預測目標顏色——它需要輸入的顏色,并應在訓練階段預測目標顏色。
最后,當你開啟應用時,你需要觀察神經網絡是否被啟用。而實際的表格從開始就在使用固定測試集,在訓練階段推理表格應該改變它的字體顏色。事實上,當 ActualTable 組件顯示實際測試集時,InferenceTable 顯示測試集的輸入數據點,但輸出是使用神經網絡預測的。
?
評論