用瀏覽器訓練Tensorflow.js模型的18個技巧(下)

摘要: 送你18個訓練Tensorflow.js模型的小技巧!

用瀏覽器訓練Tensorflow.js模型的18個技巧(上)

8.隨機你的輸入!

訓練神經網絡的一個常見建議是通過在每個時期開始時對輸入進行混洗來隨機化訓練樣本。我們可以使用tf.utils.shuffle來實現這個目的:

/** Shuffles the array using Fisher-Yates algorithm. */
export function shuffle(array: any[]|Uint32Array|Int32Array|Float32Array): void

9.使用FileSaver.js保存模型檢查點

由於我們在瀏覽器中訓練我們的模型,你現在可能會問自己:我們如何在訓練時自動保存模型權重的檢查點?我們可以使用FileSaver.js,該腳本公開了一個名爲saveAs的函數,我們可以使用它來存儲任意類型的文件,這些文件最終會出現在我們的下載文件夾中。

這樣我們就可以保存模型權重:

const weights = new Float32Array([... model weights, flat array])
saveAs(new Blob([weights]), 'checkpoint_epoch1.weights')

甚至是json文件:

const losses = { totalLoss: ... }
saveAs(new Blob([JSON.stringify(losses)]), 'loss_epoch1.json')

排除故障

在花費大量時間訓練模型之前,你需要確保你的模型實際上要學習是什麼,並消除任何潛在的錯誤來源。如果你不考慮以下提示,你可能會浪費你的時間在訓練垃圾上:

10.檢查輸入數據,預處理和後處理邏輯!

如果你將垃圾傳遞到你的網絡,它定會把垃圾扔回你身邊。因此,請確保你的輸入數據標記正確,並確保你的網絡輸入符合你的預期。特別是如果你已經規定了一些預處理邏輯,如隨機裁剪、填充、平方、居中、平均減法或其他什麼,請確保預處理後進行可視化輸入。此外,我強烈建議單元測試這些步驟。

這聽起來像是一項繁瑣的額外工作,但它很重要!

11.檢查你的損失函數!

現在在大多數情況下,tensorflow.js爲你提供了你需要的損失函數。但是,如果你需要實現自己的損失函數,你絕對需要進行單元測試!不久前,我從頭開始使用tfjs-core API實現了Yolo v2 dropout函數,以便爲網絡訓練yolo對象檢測器。

12.先裝上一個小型數據集!

通常,最好是在訓練數據的一小部分上過度擬合,以驗證損失是否正在收斂,以及你的模型實際上是否在學習一些有用的東西。因此,你應該只選擇10到20張訓練數據的圖像並訓練一些時期。一旦損失收斂,對這10到20張圖像進行推斷並可視化結果:

這是一個非常重要的步驟,它將幫助你消除網絡實施中的各種錯誤來源、前後處理邏輯。

特別是,如果你正在實現自己的損失函數,你肯定要確保,你的模型能夠在開始訓練之前收斂!

性能

最後,我想給你一些建議,通過考慮一些基本原則,這將有助於你儘可能地減少訓練時間並防止瀏覽器因內存泄漏而崩潰。

13.防止明顯的內存泄漏

除非你是tensorflow.js的新手,否則你可能已經知道,我們必須手動處理未使用的張量來釋放內存,方法是調用tensor.dispose()或將我們的操作包裝在tf.tidy塊中。確保由於未正確處理張量而導致沒有此類內存泄漏,否則你的應用程序遲早會耗盡內存。

識別這些類型的內存泄漏非常簡單,只需記錄tf.memory()幾次迭代即可驗證,每次迭代時張量的數量不會無意中增長:

14.調整Canvases大小而不是你的張量!

注意,以下語句僅在tfjs-core的當前狀態時有效,直到最終得到修復。

這可能聽起來有點奇怪:爲什麼不使用tf.resizeBilinear、tf.pad等將輸入張量重塑爲所需的網絡輸入形狀?tfjs目前有一個未解決的問題,說明了這個問題。

TLDR:在調用tf.fromPixels之前,要將Canvaes轉換爲張量,請調整Canvaes的大小,使其具有網絡接受的大小,否則你將快速耗盡GPU內存,具體取決於各種不同的輸入大小。如果你的訓練圖像大小都相同,那麼這個問題就不那麼嚴重了,但是如果你必須明確調整它們的大小,你可以使用下面的代碼片段:

export function imageToSquare(img: HTMLImageElement | HTMLCanvasElement, inputSize: number): HTMLCanvasElement {
  const dims = img instanceof HTMLImageElement 
    ? { width: img.naturalWidth, height: img.naturalHeight }
    : img 
  const scale = inputSize / Math.max(dims.height, dims.width)
  const width = scale * dims.width
  const height = scale * dims.height

  const targetCanvas = document.createElement('canvas')
  targetCanvas .width = inputSize
  targetCanvas .height = inputSize
  targetCanvas.getContext('2d').drawImage(img, 0, 0, width, height)
  return targetCanvas
}

15.確定最佳批量大小

不要過分批量輸入!嘗試不同的批量大小並測量反向傳播所需的時間。最佳批量大小顯然取決於你的GPU統計信息,輸入大小以及網絡的複雜程度。在某些情況下,你根本不想批量輸入。

如果有疑問的話,我會一直使用1的批量大小。我個人認爲,在某些情況下,增加批量大小對性能沒有任何幫助,但在其他情況下,我可以看到整體加速的因素通過創建大小爲1624的批次,在相當小的網絡尺寸下輸入圖像大小爲112x112像素,大約1.5-2.0左右。

16.緩存、離線存儲、Indexeddb

我們的訓練圖像可能相當大,可能高達1GB甚至更大,具體取決於圖像的大小和數量。由於我們不能簡單地在瀏覽器中從磁盤讀取圖像,我們將使用文件代理(可能是一個簡單的快速服務器)來託管我們的訓練數據,瀏覽器將獲取每個數據項。

顯然,這是非常低效的,但是在瀏覽器中進行訓練時我們必須記住這一點,如果你的數據集足夠小,你可能會嘗試將整個數據保存在內存中,但這顯然也不是很有效。最初,我試圖增加瀏覽器緩存大小以簡單地將整個數據緩存在磁盤上,但這在以後的Chrome版本中似乎不再起作用,而且我也沒有運氣FireFox。

最後,我決定只使用Indexeddb,這是一個瀏覽器數據庫,你可能不太熟悉,我們可以利用它來存儲我們的整個訓練和測試數據集。Indexeddb入門非常簡單,因爲我們基本上只需幾行代碼即可將整個數據存儲和查詢爲鍵值存儲。使用Indexeddb,我們可以方便地將標籤存儲爲普通的json對象,將我們的圖像數據存儲爲blob。看看這篇博文很好地解釋瞭如何在Indexeddb中保存圖像數據和其他文件。

查詢Indexeddb是非常快的,至少我發現查詢每個數據項的速度要快一些,而不是一遍又一遍地從代理服務器中獲取文件。此外,在將數據移動到Indexeddb之後,技術上的訓練現在完全脫機,這意味着我們可能不再需要代理服務器了。

17.異步丟失報告

這是一個簡單但非常有效的提示,它幫助我減少了訓練時的迭代次數。主要的作用是,如果我們想要檢索由optimizer.minimize返回的損失張量的值,我們肯定會這樣做,因爲我們想要在訓練時跟蹤我們的損失,我們希望避免等待損失返回的lose.data()及防止等待CPU和GPU在每次迭代時同步。相反,我們想要執行類似以下的操作來報告迭代的損失值:

const loss = optimizer.minimize(() => {
  const out = net.predict(someInput)
  const loss = tf.losses.meanSquaredError(
    groundTruth,
    out,
    tf.Reduction.MEAN
  )
  return loss
}, true)
loss.data().then(data => {
  const lossValue = data[0]
  window.lossValues[epoch] += (window.lossValues[epoch] || 0) + lossValue
  loss.dispose()
})

我們只需記住,我們的損失現在是異步報告的,所以如果我們想在每個epoch的末尾將整體損失保存到文件中,我們將不得不等待最後的解決方案。我通常只是通過使用setTimeout在一個epoch完成後10秒左右保存整體損失值來解決這個問題:

if (epoch !== startEpoch) {
  // ugly hack to wait for loss datas for that epoch to be resolved
  const previousEpoch = epoch - 1
  setTimeout(() => storeLoss(previousEpoch, window.losses[previousEpoch]), 10000)
}

成功訓練模型後

18.權重量化

一旦我們完成了對模型的訓練並且我們對它的性能感到滿意,我建議通過應用權重量化來縮小模型大小。通過量化我們的模型權重,我們可以將模型的大小減小到原始大小的1/4!儘可能減小模型的大小對於將模型權重快速傳遞到客戶端應用程序至關重要,特別是如果我們基本上可以免費獲得它。

本文作者:【方向】

閱讀原文

本文爲雲棲社區原創內容,未經允許不得轉載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章