1. 簡介
在本教學課程中,我們會建構 TensorFlow.js 模型,透過卷積類神經網路辨識手寫數字。首先,我們要訓練分類器的「樣式」大量手寫數字與標籤接著,我們會使用模型從未見過的測試資料,評估分類器的準確度。
這項工作視為分類工作,因為我們正在訓練模型,將類別 (出現在圖片中的數字) 指派給輸入圖片。為了訓練模型,我們會提供許多輸入內容範例和正確輸出內容。這項功能稱為「監督式學習」。
建構目標
您將建立一個使用 TensorFlow.js 在瀏覽器中訓練模型的網頁。如果圖片有特定大小的黑白圖片,系統會將其分類。您需要執行的步驟包括:
- 載入資料。
- 定義模型的架構。
- 訓練模型並在訓練期間監控成效。
- 進行預測以評估訓練過的模型。
課程內容
- TensorFlow.js 語法,使用 TensorFlow.js Layers API 建立卷積模型。
- 在 TensorFlow.js 中公式分類工作
- 如何使用 tfjs-vis 程式庫監控瀏覽器內訓練。
軟硬體需求
- 最新版本的 Chrome,或其他支援 ES6 模組的新版瀏覽器。
- 文字編輯器,可在您的電腦本機或透過 Codepen 或 Glitch 等網路執行。
- 具備 HTML、CSS、JavaScript 和 Chrome 開發人員工具 (或您偏好的瀏覽器開發人員工具) 的知識。
- 大致瞭解類神經網路。如需相關簡介或複習,請觀看這部 3blue1brown 前的影片,或是這部 Ashi Krishnan 推出的「Deep Learning in JavaScript」影片。
您也應該已經熟悉我們的第一個訓練教學課程中的教材。
2. 做好準備
建立 HTML 網頁並加入 JavaScript
將下列程式碼複製到名為「 」的 HTML 檔案。
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TensorFlow.js Tutorial</title>
<!-- Import TensorFlow.js -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@1.0.0/dist/tf.min.js"></script>
<!-- Import tfjs-vis -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-vis@1.0.2/dist/tfjs-vis.umd.min.js"></script>
<!-- Import the data file -->
<script src="data.js" type="module"></script>
<!-- Import the main script file -->
<script src="script.js" type="module"></script>
</head>
<body>
</body>
</html>
建立資料和程式碼的 JavaScript 檔案
- 在與上述 HTML 檔案相同的資料夾中,建立一個名稱為 data.js 的檔案,然後將這個連結的內容複製到該檔案中。
- 在步驟 1 的資料夾中,建立一個名為 script.js 的檔案,然後將下列程式碼加入其中。
console.log('Hello TensorFlow');
測試
HTML 和 JavaScript 檔案建立完畢後,請測試這些檔案。在瀏覽器中開啟 index.html 檔案,然後開啟開發人員工具控制台。
如果一切運作正常,應該建立兩個全域變數。tf
是 TensorFlow.js 程式庫的參照,tfvis
是 tfjs-vis 程式庫的參照。
系統應該會顯示 Hello TensorFlow
, 訊息,表示您已經準備好進行下一個步驟。
3. 載入資料
在這個教學課程中,您會訓練模型來學習辨識圖片中的數字,如下所示。這些圖片是 MNIST 資料集中的 28x28 像素灰階圖片。
我們提供了程式碼,以從我們為您建立的特殊 Sprite 檔案 (約 10 MB) 載入這些圖片,以便我們專注於訓練部分。
歡迎您自行研究 data.js
檔案,瞭解資料的載入方式。完成本教學課程後,請自行建立資料載入方法。
提供的程式碼包含具有兩個公開方法的類別 MnistData
:
nextTrainBatch(batchSize)
:傳回訓練集內的隨機一批圖片及其標籤。nextTestBatch(batchSize)
:從測試集傳回一批圖片及其標籤
MnistData 類別也會執行重組與正規化資料的重要步驟。
共有 65,000 張圖片,我們會使用最多 55,000 張圖片訓練模型,儲存 10,000 張圖片,以便在完成後用來測試模型成效。我們要在瀏覽器中進行所有操作!
我們就載入資料並測試是否已正確載入。
將下列程式碼加進 Script.js 檔案中。
import {MnistData} from './data.js';
async function showExamples(data) {
// Create a container in the visor
const surface =
tfvis.visor().surface({ name: 'Input Data Examples', tab: 'Input Data'});
// Get the examples
const examples = data.nextTestBatch(20);
const numExamples = examples.xs.shape[0];
// Create a canvas element to render each example
for (let i = 0; i < numExamples; i++) {
const imageTensor = tf.tidy(() => {
// Reshape the image to 28x28 px
return examples.xs
.slice([i, 0], [1, examples.xs.shape[1]])
.reshape([28, 28, 1]);
});
const canvas = document.createElement('canvas');
canvas.width = 28;
canvas.height = 28;
canvas.style = 'margin: 4px;';
await tf.browser.toPixels(imageTensor, canvas);
surface.drawArea.appendChild(canvas);
imageTensor.dispose();
}
}
async function run() {
const data = new MnistData();
await data.load();
await showExamples(data);
}
document.addEventListener('DOMContentLoaded', run);
重新整理頁面,幾秒後你應該會在左側看到含有多張圖片的面板。
4. 瞭解工作的概念
輸入資料如下所示。
我們的目標是訓練一個將一張圖片訓練的模型,並學習預測每個圖片可能所屬的 10 個類別 (數字 0 到 9) 的分數。
每張圖片的高度都是 28px,高 28px,有 1 個色彩頻道,因為它是灰階圖片。因此每張圖片的形狀都是 [28, 28, 1]
。
請注意,我們會執行一對十的對應,以及每個輸入範例的形狀,因為這對下一節很重要。
5. 定義模型架構
在本節中,我們將撰寫程式碼來說明模型架構。模型架構可讓您清楚表示「模型在執行時會執行哪些函式」,或「Google 模型會使用何種演算法計算答案」。
在機器學習技術中,我們會定義架構 (或演算法),並讓訓練程序學習該演算法的參數。
將下列函式加入
script.js
檔案定義模型架構
function getModel() {
const model = tf.sequential();
const IMAGE_WIDTH = 28;
const IMAGE_HEIGHT = 28;
const IMAGE_CHANNELS = 1;
// In the first layer of our convolutional neural network we have
// to specify the input shape. Then we specify some parameters for
// the convolution operation that takes place in this layer.
model.add(tf.layers.conv2d({
inputShape: [IMAGE_WIDTH, IMAGE_HEIGHT, IMAGE_CHANNELS],
kernelSize: 5,
filters: 8,
strides: 1,
activation: 'relu',
kernelInitializer: 'varianceScaling'
}));
// The MaxPooling layer acts as a sort of downsampling using max values
// in a region instead of averaging.
model.add(tf.layers.maxPooling2d({poolSize: [2, 2], strides: [2, 2]}));
// Repeat another conv2d + maxPooling stack.
// Note that we have more filters in the convolution.
model.add(tf.layers.conv2d({
kernelSize: 5,
filters: 16,
strides: 1,
activation: 'relu',
kernelInitializer: 'varianceScaling'
}));
model.add(tf.layers.maxPooling2d({poolSize: [2, 2], strides: [2, 2]}));
// Now we flatten the output from the 2D filters into a 1D vector to prepare
// it for input into our last layer. This is common practice when feeding
// higher dimensional data to a final classification output layer.
model.add(tf.layers.flatten());
// Our last layer is a dense layer which has 10 output units, one for each
// output class (i.e. 0, 1, 2, 3, 4, 5, 6, 7, 8, 9).
const NUM_OUTPUT_CLASSES = 10;
model.add(tf.layers.dense({
units: NUM_OUTPUT_CLASSES,
kernelInitializer: 'varianceScaling',
activation: 'softmax'
}));
// Choose an optimizer, loss function and accuracy metric,
// then compile and return the model
const optimizer = tf.train.adam();
model.compile({
optimizer: optimizer,
loss: 'categoricalCrossentropy',
metrics: ['accuracy'],
});
return model;
}
我們來詳細看看
卷積
model.add(tf.layers.conv2d({
inputShape: [IMAGE_WIDTH, IMAGE_HEIGHT, IMAGE_CHANNELS],
kernelSize: 5,
filters: 8,
strides: 1,
activation: 'relu',
kernelInitializer: 'varianceScaling'
}));
這裡使用依序模型
我們使用的是 conv2d
層,而非稠密層。我們無法詳細介紹「卷積」的運作方式,以下列出一些解釋基礎作業的資源:
現在讓我們細分 conv2d
設定物件中的每個引數:
inputShape
。會流入模型第一層的資料形狀。在本例中,MNIST 範例為 28x28 像素的黑白圖片。圖片資料的標準格式是[row, column, depth]
,所以我們要設定[28, 28, 1]
的形狀。每個尺寸的像素數量都達到 28 列和 28 欄,深度為 1,因為圖片只有 1 個色通道。請注意,我們並未在輸入形狀中指定批量。圖層的設計是不受批次大小的影響,因此您可以在推論期間傳遞任何批量的張量。kernelSize
。要套用至輸入資料的滑動卷積篩選視窗大小。這裡,我們設定5
的kernelSize
,指定 5x5 卷積窗口。filters
。要套用至輸入資料的篩選器視窗數量 (大小為kernelSize
)。這樣我們就會對資料套用 8 個篩選器。strides
。「步距」也就是滑動視窗隨著圖片移動的像素數量我們在此指定的平方值是 1,表示濾鏡會在圖片上滑動 1 像素的步數。activation
。卷積後要套用至資料的啟用函式。在這個例子中,我們會套用「Rectified Linear Unit (ReLU)」函式,這是機器學習模型中很常見的活化函式。kernelInitializer
。用於隨機初始化模型權重的方法,這對訓練動態來說相當重要。這裡不會詳細說明初始化的細節,但VarianceScaling
(在此使用) 通常是不錯的初始化器選項。
壓平合併資料表示法
model.add(tf.layers.flatten());
圖片是高維度資料,卷積運算往往會增加擷取圖片的資料量。將這些資料傳送到最終分類層之前,我們必須將資料整併成一個長陣列。稠密層 (我們用做最後一層) 只會使用 tensor1d
,因此這個步驟在許多分類工作中很常見。
計算最終機率分佈
const NUM_OUTPUT_CLASSES = 10;
model.add(tf.layers.dense({
units: NUM_OUTPUT_CLASSES,
kernelInitializer: 'varianceScaling',
activation: 'softmax'
}));
我們會使用搭配 softmax 啟動的稠密層,計算 10 個可能類別的機率分佈情形。得分最高的類別將是預測位數。
選擇最佳化工具和損失函式
const optimizer = tf.train.adam();
model.compile({
optimizer: optimizer,
loss: 'categoricalCrossentropy',
metrics: ['accuracy'],
});
與第一個教學課程相比,這裡使用 categoricalCrossentropy
做為損失函式。顧名思義,就是在模型的輸出結果為機率分佈。categoricalCrossentropy
會測量模型最後一層產生的機率分佈情形,以及真實標籤提供的機率分佈情形之間的錯誤。
例如,如果我們的數字確實代表 7,就可能得到以下結果
索引 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
實際標籤 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
預測 | 0.1 | 0.01 | 0.01 | 0.01 | 0.20 | 0.01 | 0.01 | 0.60 | 0.03 | 0.02 |
類別交叉熵會產生單一數字,指出預測向量與真實標籤向量的相似程度。
這裡使用的資料表示法稱為 one-hot 編碼,常見於分類問題。每個類別都有一個與其相關的機率。當我們確切知道目標該設為多少時,可以將機率設為 1,其他機率則設為 0。如要進一步瞭解 one-hot 編碼,請參閱這個頁面。
我們還要監控的另一個指標是 accuracy
,針對分類問題,則是在所有預測的正確預測結果中,所佔的百分比。
6. 訓練模型
將下列函式複製到 Script.js 檔案。
async function train(model, data) {
const metrics = ['loss', 'val_loss', 'acc', 'val_acc'];
const container = {
name: 'Model Training', tab: 'Model', styles: { height: '1000px' }
};
const fitCallbacks = tfvis.show.fitCallbacks(container, metrics);
const BATCH_SIZE = 512;
const TRAIN_DATA_SIZE = 5500;
const TEST_DATA_SIZE = 1000;
const [trainXs, trainYs] = tf.tidy(() => {
const d = data.nextTrainBatch(TRAIN_DATA_SIZE);
return [
d.xs.reshape([TRAIN_DATA_SIZE, 28, 28, 1]),
d.labels
];
});
const [testXs, testYs] = tf.tidy(() => {
const d = data.nextTestBatch(TEST_DATA_SIZE);
return [
d.xs.reshape([TEST_DATA_SIZE, 28, 28, 1]),
d.labels
];
});
return model.fit(trainXs, trainYs, {
batchSize: BATCH_SIZE,
validationData: [testXs, testYs],
epochs: 10,
shuffle: true,
callbacks: fitCallbacks
});
}
然後將以下程式碼加到您的
run
函式。
const model = getModel();
tfvis.show.modelSummary({name: 'Model Architecture', tab: 'Model'}, model);
await train(model, data);
重新整理頁面,幾秒後您會看到一些報告訓練進度的圖表。
讓我們一起來一探究竟。
監控指標
const metrics = ['loss', 'val_loss', 'acc', 'val_acc'];
您可以在這裡決定要監控的指標。我們會監控訓練集的損失和準確率,以及驗證集 (分別 val_loss 和 val_acc) 的損失和準確率。以下將進一步說明驗證集。
以張量形式準備資料
const BATCH_SIZE = 512;
const TRAIN_DATA_SIZE = 5500;
const TEST_DATA_SIZE = 1000;
const [trainXs, trainYs] = tf.tidy(() => {
const d = data.nextTrainBatch(TRAIN_DATA_SIZE);
return [
d.xs.reshape([TRAIN_DATA_SIZE, 28, 28, 1]),
d.labels
];
});
const [testXs, testYs] = tf.tidy(() => {
const d = data.nextTestBatch(TEST_DATA_SIZE);
return [
d.xs.reshape([TEST_DATA_SIZE, 28, 28, 1]),
d.labels
];
});
我們在這裡建立兩個資料集、用來訓練模型的訓練集,以及在每個訓練週期結束時測試模型的驗證集,不過在訓練期間,驗證集中的資料絕不會向模型顯示。
我們提供的資料類別可讓您輕鬆從圖片資料中取得張量。但我們仍會將張量重塑為模型預期的形狀 [num_examples, image_width, image_height, channels],然後提供給模型。每個資料集都有輸入內容 (X) 和標籤 (Y)。
return model.fit(trainXs, trainYs, {
batchSize: BATCH_SIZE,
validationData: [testXs, testYs],
epochs: 10,
shuffle: true,
callbacks: fitCallbacks
});
我們呼叫 model.fit 來開始訓練迴圈。我們也會傳遞 VerifyData 屬性,以指出模型在每個訓練週期後應使用哪些資料來自我測試 (但不得用於訓練)。
如果我們的訓練資料依舊有效,但驗證資料沒有問題,就表示模型可能過度配適於訓練資料,因此便無法妥善一般化,以便輸入先前沒看過的內容。
7. 評估模型
驗證準確率提供了準確的估算值,可讓您評估模型對於先前未見過的資料表現 (只要資料與特定驗證集的相似程度即可)。不過,我們可能會請您針對不同課程,提供更詳細的成效分析資料。
tfjs-vis 中的幾種方法可協助您達成此目標。
將下列程式碼加進 Script.js 檔案底部
const classNames = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine'];
function doPrediction(model, data, testDataSize = 500) {
const IMAGE_WIDTH = 28;
const IMAGE_HEIGHT = 28;
const testData = data.nextTestBatch(testDataSize);
const testxs = testData.xs.reshape([testDataSize, IMAGE_WIDTH, IMAGE_HEIGHT, 1]);
const labels = testData.labels.argMax(-1);
const preds = model.predict(testxs).argMax(-1);
testxs.dispose();
return [preds, labels];
}
async function showAccuracy(model, data) {
const [preds, labels] = doPrediction(model, data);
const classAccuracy = await tfvis.metrics.perClassAccuracy(labels, preds);
const container = {name: 'Accuracy', tab: 'Evaluation'};
tfvis.show.perClassAccuracy(container, classAccuracy, classNames);
labels.dispose();
}
async function showConfusion(model, data) {
const [preds, labels] = doPrediction(model, data);
const confusionMatrix = await tfvis.metrics.confusionMatrix(labels, preds);
const container = {name: 'Confusion Matrix', tab: 'Evaluation'};
tfvis.render.confusionMatrix(container, {values: confusionMatrix, tickLabels: classNames});
labels.dispose();
}
此程式碼的功用為何?
- 進行預測。
- 計算準確度指標。
- 顯示指標
接下來會進一步說明各個步驟。
進行預測
function doPrediction(model, data, testDataSize = 500) {
const IMAGE_WIDTH = 28;
const IMAGE_HEIGHT = 28;
const testData = data.nextTestBatch(testDataSize);
const testxs = testData.xs.reshape([testDataSize, IMAGE_WIDTH, IMAGE_HEIGHT, 1]);
const labels = testData.labels.argMax(-1);
const preds = model.predict(testxs).argMax(-1);
testxs.dispose();
return [preds, labels];
}
首先,我們需要進行一些預測在此,我們要拍攝 500 張圖片,並預測其中的數字 (您之後可以提高這個數字,以便對更多圖像進行測試)。
值得注意的是,argmax
函式提供了機率最高的類別索引。請注意,模型會輸出每個類別的機率。我們可以找出機率最高的,並以該數字進行預測。
您可能也會發現,我們可以一次對全部 500 個樣本進行預測。這就是 TensorFlow.js 提供的向量化能力。
顯示各課程的準確性
async function showAccuracy() {
const [preds, labels] = doPrediction();
const classAccuracy = await tfvis.metrics.perClassAccuracy(labels, preds);
const container = { name: 'Accuracy', tab: 'Evaluation' };
tfvis.show.perClassAccuracy(container, classAccuracy, classNames);
labels.dispose();
}
我們透過一組預測和標籤計算出每個類別的準確率。
顯示混淆矩陣
async function showConfusion() {
const [preds, labels] = doPrediction();
const confusionMatrix = await tfvis.metrics.confusionMatrix(labels, preds);
const container = { name: 'Confusion Matrix', tab: 'Evaluation' };
tfvis.render.confusionMatrix(container, {values: confusionMatrix, tickLabels: classNames});
labels.dispose();
}
混淆矩陣與各類別的準確率很類似,但會進一步細分,顯示分類錯誤模式。讓您瞭解模型是否不清楚任何特定類別組合。
顯示評估結果
將下列程式碼新增至執行函式底部,以顯示評估作業。
await showAccuracy(model, data);
await showConfusion(model, data);
畫面應如下所示。
恭喜!您剛剛訓練了卷積類神經網路!
8. 重點整理
預測輸入資料的類別稱為分類工作。
分類工作需要適當的標籤資料表示法
- 常見的標籤表示法包括類別的 one-hot 編碼
準備資料:
- 除了模型在訓練期間看不到的資料外,您還可以保留一些資料來評估模型成效。這就是驗證集。
建構並執行模型:
- 研究顯示,卷積模型可在圖片工作上發揮良好成效。
- 分類問題通常會使用類別交叉熵來處理損失函式。
- 監控訓練,看看損失是否減少,準確性也會上升。
評估模型
- 決定模型訓練完畢後,要評估模型在最初問題解決方面的成效。
- 比起只評估整體準確率,依類別準確率與混淆矩陣相較,您可以更精細地分析模型成效。