編者按:opencv4nodejs作者Vincent Mühler分享了基于tensorflow.js設(shè)計(jì)、訓(xùn)練面向web的神經(jīng)網(wǎng)絡(luò)模型的經(jīng)驗(yàn)。
在將現(xiàn)有的一些目標(biāo)檢測、臉部檢測、臉部識別的模型移植到tensorflow.js后,我發(fā)現(xiàn)有的模型在瀏覽器中的表現(xiàn)沒有達(dá)到最優(yōu),而另一些模型在瀏覽器上的表現(xiàn)相當(dāng)不錯(cuò)。瀏覽器內(nèi)的機(jī)器學(xué)習(xí)潛力巨大,tensorflow.js等庫給web開發(fā)者帶來了太多可能性。
然而,直接在瀏覽器中運(yùn)行的深度模型也帶來了新的挑戰(zhàn)和限制,畢竟一些現(xiàn)存的模型不是為在客戶端瀏覽器中運(yùn)行而設(shè)計(jì)的,更別說移動(dòng)端瀏覽器了。就拿當(dāng)前最先進(jìn)的目標(biāo)檢測模型來說:它們通常需要大量計(jì)算資源才能以合理的fps運(yùn)行,更別說實(shí)時(shí)運(yùn)行了。另外,對一個(gè)簡單的web應(yīng)用來說,分發(fā)100MB以上的模型權(quán)重至客戶端瀏覽器也不現(xiàn)實(shí)。
為web訓(xùn)練高效的深度學(xué)習(xí)模型
不過不要放棄希望!基于一些基本原則,我們可以創(chuàng)建和訓(xùn)練很不錯(cuò)的模型,為在web環(huán)境中運(yùn)行而優(yōu)化。信不信由你,實(shí)際上我們可以訓(xùn)練相當(dāng)不錯(cuò)的圖像分類模型,甚至是目標(biāo)檢測模型,大小不過幾兆,甚至幾百K:
這篇文章將給出一些通用的建議,幫助你開始訓(xùn)練自己的卷積神經(jīng)網(wǎng)絡(luò)(CNN),以及一些基于tensorflow.js為web應(yīng)用和移動(dòng)端瀏覽器訓(xùn)練CNN的建議。
你和我說要在瀏覽器里訓(xùn)練深度學(xué)習(xí)模型?
你也許會(huì)好奇:為什么要在瀏覽器里基于tensorflow.js訓(xùn)練我的模型,而不是直接在自己的機(jī)器上基于tensorflow訓(xùn)練模型?你當(dāng)然可以在自己的機(jī)器上訓(xùn)練,特別是如果你的機(jī)器配備了NVIDIA顯卡。tensorflow.js在底層使用了WebGL加速,所以在瀏覽器中訓(xùn)練模型的一個(gè)好處是可以利用AMD顯卡。另外,在瀏覽器中訓(xùn)練模型,可以更好地保護(hù)用戶隱私,更容易讓用戶信任。
網(wǎng)絡(luò)架構(gòu)
顯然,訓(xùn)練模型之間,首先需要實(shí)現(xiàn)模型。通常人們會(huì)建議挑選一個(gè)久經(jīng)考驗(yàn)的現(xiàn)有架構(gòu),比如YOLO、SSD、ResNet、MobileNet等。
我個(gè)人認(rèn)為,在設(shè)計(jì)自己的架構(gòu)時(shí)應(yīng)用現(xiàn)有架構(gòu)的一些概念是很有價(jià)值的,但直接在web應(yīng)用中引入這些現(xiàn)存架構(gòu)可能不妥,因?yàn)槲覀兿胍叽巛^小、推理較快(理想情況下能實(shí)時(shí)推理)、容易訓(xùn)練的模型。
不管你是打算引入一個(gè)現(xiàn)有架構(gòu),還是完全從頭開始,都可以參考下面的一些建議,在我自己為web設(shè)計(jì)高效CNN架構(gòu)時(shí),這些建議幫助很大:
1. 從小型架構(gòu)開始
牢記,能夠取得較好的精確度的網(wǎng)絡(luò)越小,它進(jìn)行推理的時(shí)間越短,用戶下載、緩存模型更容易。此外,較小的模型參數(shù)較少,因此在訓(xùn)練時(shí)收斂更快。
如果發(fā)現(xiàn)當(dāng)前的網(wǎng)絡(luò)架構(gòu)表現(xiàn)不是很好,或者沒有達(dá)到你想要的精確度,你仍然可以逐漸增加網(wǎng)絡(luò)的尺寸,例如,增加每層的卷積過濾器數(shù)量,或者直接堆疊更多網(wǎng)絡(luò)層,構(gòu)造更深的網(wǎng)絡(luò)。
2. 應(yīng)用深度可分離卷積
既然我們是要打造一個(gè)新模型,毫無疑問我們想要使用深度可分離卷積,而不是傳統(tǒng)的2D卷積。深度可分離卷積將常規(guī)的卷積操作分為兩部分,首先分頻道進(jìn)行卷積,接著應(yīng)用1x1卷積。和常規(guī)卷積操作相比,深度可分離卷積的參數(shù)更少,這意味著更少的浮點(diǎn)運(yùn)算,更容易并行化,推理更快(我曾經(jīng)碰到過,僅僅將常規(guī)卷積替換為深度可分離卷積后,推理就加速了10x),更少的資源占用(在移動(dòng)設(shè)備上意味著性能提升)。此外,因?yàn)閰?shù)更少,訓(xùn)練所需時(shí)間也更短。
MobileNet和Xception應(yīng)用了深度可分離卷積(tensorflow.js中的MobileNet和PoseNet)。深度可分離卷積是否導(dǎo)致精確度下降大概沒有定論,但就我的經(jīng)驗(yàn)而言,web和移動(dòng)端模型,毫無疑問應(yīng)該使用深度可分離卷積。
長話短說:我建議在第一層使用常規(guī)的conv2d,畢竟通常而言這一層也沒有多少參數(shù),這樣可以在提取特征中保留RGB通道之間的關(guān)系。
export type ConvParams = {
filter: tf.Tensor4D
bias: tf.Tensor1D
}
exportfunction convLayer(
x: tf.Tensor4D,
params: ConvParams,
stride: [number, number],
padding: string
): tf.Tensor4D {
return tf.tidy(() => {
let out = tf.conv2d(x, params.filter, stride, padding)
out = tf.add(out, params.bias)
return out
})
}
剩下的都用深度可分離卷積,用3 × 3 × channelsin × 1過濾器和1 × 1 × channelsin × channels_out過濾器替換單個(gè)核。
export type SeparableConvParams = {
depthwise_filter: tf.Tensor4D
pointwise_filter: tf.Tensor4D
bias: tf.Tensor1D
}
exportfunction depthwiseSeparableConv(
x: tf.Tensor4D,
params: SeparableConvParams,
stride: [number, number],
padding: string
): tf.Tensor4D {
return tf.tidy(() => {
let out = tf.separableConv2d(x, params.depthwise_filter: tf.Tensor4D, params.pointwise_filter, stride, padding)
out = tf.add(out, params.bias)
return out
})
}
所以,原本使用tf.conv2d,形狀為[3, 3, 32, 64]的核,現(xiàn)在改用tf.separableConv2d,一個(gè)形狀為[3, 3, 32, 1]的核加上一個(gè)[1, 1, 32, 64]的核。
3. 跳層連接和密集連接塊
一旦決定創(chuàng)建較深的網(wǎng)絡(luò),很快就會(huì)面臨訓(xùn)練神經(jīng)網(wǎng)絡(luò)最常遇到的問題:梯度消失問題。若干epoch后,損失以極其微小的幅度下降,導(dǎo)致慢得荒謬的訓(xùn)練,甚至是完全收斂不了。
ResNet和DenseNet使用的跳層連接可以緩解創(chuàng)建更深的架構(gòu)時(shí)遇到的梯度消失問題。我們只需將之前的網(wǎng)絡(luò)層的輸出,添加到網(wǎng)絡(luò)中較深層的輸入之中(在應(yīng)用激活函數(shù)之前):
跳層連接背后的直覺是,梯度不必僅僅通過卷積層(或全連接層)反向傳播(這導(dǎo)致梯度漸漸消失)。它們可以添加跳層連接操作,“跳過”一些網(wǎng)絡(luò)層。
顯然,跳層連接要工作,需要輸出和輸入之間的形狀互相匹配。假設(shè)我們想要跳層連接網(wǎng)絡(luò)層A和網(wǎng)絡(luò)層B,那么A的輸出形狀需要和B的輸入形狀匹配。如果你想要?jiǎng)?chuàng)建殘差塊或密集連接塊,只需確保卷積過濾器數(shù)目一致,步長為1,補(bǔ)齊方案相同。順便說下,還有其他方法,比如補(bǔ)齊A的輸出,使其滿足B的輸入的形狀,或是連接之前層的特征映射,使得相連的層的深度互相匹配。
剛開始,我擺弄了一下類似ResNet的方法,直接隔層引入跳層連接,如上圖所示。不過我很快發(fā)現(xiàn),密集連接塊的效果更好,并且應(yīng)用密集連接塊后,模型收斂的時(shí)間馬上下降了:
這里是一個(gè)密集連接塊實(shí)現(xiàn)的例子,我在face-api.js的68點(diǎn)臉部識別中將其作為基本構(gòu)件。這個(gè)構(gòu)件使用了4個(gè)深度可分離卷積層(注意,第一個(gè)密集塊的第一個(gè)卷積層用了常規(guī)卷積),每塊的第一個(gè)卷積操作步長為2,以縮小輸入:
export type DenseBlock4Params = {
conv0: SeparableConvParams | ConvParams
conv1: SeparableConvParams
conv2: SeparableConvParams
conv3: SeparableConvParams
}
exportfunction denseBlock4(
x: tf.Tensor4D,
denseBlockParams: DenseBlock4Params,
isFirstLayer: boolean = false
): tf.Tensor4D {
return tf.tidy(() => {
const out0 = isFirstLayer
? convLayer(x, denseBlockParams.conv0 as ConvParams, [2, 2], 'same')
: depthwiseSeparableConv(x, denseBlockParams.conv0 as SeparableConvParams, [2, 2], 'same')
as tf.Tensor4D
const in1 = tf.relu(out0) as tf.Tensor4D
const out1 = depthwiseSeparableConv(in1, denseBlockParams.conv1, [1, 1], 'same')
// 第一個(gè)連接
const in2 = tf.relu(tf.add(out0, out1)) as tf.Tensor4D
const out2 = depthwiseSeparableConv(in2, denseBlockParams.conv2, [1, 1], 'same')
// 第二個(gè)連接
const in3 = tf.relu(tf.add(out0, tf.add(out1, out2))) as tf.Tensor4D
const out3 = depthwiseSeparableConv(in3, denseBlockParams.conv3, [1, 1], 'same')
// 最后一個(gè)連接
return tf.relu(tf.add(out0, tf.add(out1, tf.add(out2, out3)))) as tf.Tensor4D
})
}
4. 使用ReLU系列激活函數(shù)
除非你有特別的理由,我建議直接使用tf.relu,因?yàn)镽eLU系列激活函數(shù)有助于緩解梯度消失問題。
你也可以試驗(yàn)ReLU的其他變體,例如leaky ReLU,YOLO架構(gòu)就用了這個(gè):
exportfunction leakyRelu(x: tf.Tensor, epsilon: number) {
return tf.tidy(() => {
const min = tf.mul(x, tf.scalar(epsilon))
return tf.maximum(x, min)
})
}
或者M(jìn)obilenet用的ReLU-6:
exportfunction relu6(x: tf.Tensor) {
return tf.clipByValue(x, 0, 6)
}
訓(xùn)練
有了初始架構(gòu)之后,就可以開始訓(xùn)練模型了。
5. 如果拿不準(zhǔn)主意,直接用Adam
剛開始訓(xùn)練自己的模型的時(shí)候,我想知道哪種優(yōu)化算法是最好的?我從原始的SGD開始,有時(shí)候會(huì)陷入局部極小值,甚至導(dǎo)致梯度爆炸——模型權(quán)重?zé)o限增長,逐漸變?yōu)镹aN.
我并不打算說,對所有問題而言,Adam都是最優(yōu)選擇,但我發(fā)現(xiàn)它是訓(xùn)練新模型最簡單也最健壯的方式,只需將初始學(xué)習(xí)率定為0.001,然后以默認(rèn)參數(shù)啟動(dòng)Adam:
const optimizer = tf.train.adam(0.001)
6. 調(diào)整學(xué)習(xí)率
一旦損失沒有明顯下降,那么我們的模型很可能收斂了(或陷入局部極小值),無法進(jìn)一步學(xué)習(xí)了。此時(shí)我們也許可以直接停止訓(xùn)練過程,以免模型過擬合(或者嘗試不同的架構(gòu))。
然而,也有可能還有一點(diǎn)壓榨的余地,通過調(diào)整(調(diào)低)學(xué)習(xí)率,讓模型進(jìn)一步學(xué)習(xí)。特別是在整個(gè)訓(xùn)練集上的總誤差開始震蕩(一會(huì)兒高一會(huì)兒低)的時(shí)候,試著降低學(xué)習(xí)率也許是個(gè)好主意。
下圖是個(gè)例子。從第46個(gè)epoch開始,損失值開始震蕩,從46個(gè)epoch后,將學(xué)習(xí)率從0.001調(diào)低至0.0001,再訓(xùn)練10個(gè)epoch,可以進(jìn)一步降低總誤差。
7. 權(quán)重初始化
如果你對如何恰當(dāng)?shù)爻跏蓟瘷?quán)重毫無頭緒(我剛開始的時(shí)候就是這樣),那么可以簡單地將所有偏置初始化為零(tf.zeros),將權(quán)重初始化為從某種正態(tài)分布中隨機(jī)取樣的非零值(比如使用tf.randomNormal)。不過我現(xiàn)在更喜歡用glorot正態(tài)分布:
const initializer = tf.initializers.glorotNormal()
const depthwise_filter = initializer.apply([3, 3, 32, 1])
const pointwise_filter = initializer.apply([1, 1, 32, 64])
const bias = tf.zeros([64])
8. 打亂輸入
訓(xùn)練神經(jīng)網(wǎng)絡(luò)的常見建議是在每個(gè)epoch前隨機(jī)化訓(xùn)練樣本的出現(xiàn)順序。我們可以使用tf.utils.shuffle:
exportfunction shuffle(array: any[]|Uint32Array|Int32Array|Float32Array): void
9. 使用FileSaver.js保存模型的快照
FileSaver.js提供了一個(gè)saveAs函數(shù),讓我們可以儲(chǔ)存任意類型的文件至下載文件夾。我們可以用它來保存模型權(quán)重:
const weights = newFloat32Array([... model weights, flat array])
saveAs(newBlob([weights]), 'checkpoint_epoch1.weights')
我們也可以存儲(chǔ)為json格式,例如保存epoch的累計(jì)損失:
const losses = { totalLoss: ... }
saveAs(newBlob([JSON.stringify(losses)]), 'loss_epoch1.json')
調(diào)錯(cuò)
在花費(fèi)大量時(shí)間訓(xùn)練模型之前,我們想要確保模型確實(shí)能夠?qū)W習(xí)我們預(yù)想的東西,并清除任何潛在的錯(cuò)誤和bug源。下面的幾條建議有助于你避免浪費(fèi)大量時(shí)間訓(xùn)練出一堆垃圾并懷疑人生:
10. 檢查輸入數(shù)據(jù)、預(yù)處理和后處理邏輯
如果你傳入網(wǎng)絡(luò)的是垃圾,網(wǎng)絡(luò)返還的也會(huì)是垃圾。因此,需要確保輸入數(shù)據(jù)標(biāo)注正確,網(wǎng)絡(luò)的輸入也符合預(yù)期。特別地,如果你實(shí)現(xiàn)了隨機(jī)剪裁、補(bǔ)齊、截成正方形、居中、減去均值之類的預(yù)處理邏輯,別忘了可視化預(yù)處理之后的輸入,以檢查輸入是否符合預(yù)期。另外,我強(qiáng)烈建議單元測試這些步驟。后處理也是一樣。
我知道這聽起來是一些乏味的額外工作,但它們無疑是有價(jià)值的!你不會(huì)相信,我花了多少小時(shí)嘗試搞明白為什么我的目標(biāo)檢測器完全學(xué)不會(huì)檢測面部,直到我逐漸發(fā)現(xiàn),由于錯(cuò)誤的剪裁和變形,我的預(yù)處理邏輯將輸入變成了垃圾。
11. 檢查損失函數(shù)
在大多數(shù)情況下,tensorflow.js都可以提供所需的損失函數(shù)。然而,在你需要實(shí)現(xiàn)自己的損失函數(shù)的情況下,你絕對需要單元測試!不久前我曾經(jīng)基于tfjs-core API從頭構(gòu)建YOLO v2的損失函數(shù),從頭實(shí)現(xiàn)損失函數(shù)很麻煩,除非你分解問題,并確保每一部分的計(jì)算結(jié)果符合預(yù)期。
12. 先過擬合小數(shù)據(jù)集
先過擬合訓(xùn)練數(shù)據(jù)的一個(gè)小子集,從而驗(yàn)證損失函數(shù)可以收斂,模型確實(shí)可以學(xué)到有用的東西。一般而言,這是一個(gè)好主意。例如,你可以直接從訓(xùn)練數(shù)據(jù)中選取10到20張圖像,并訓(xùn)練若干epoch。損失收斂后,在這10到20張圖像上運(yùn)行推理,并可視化結(jié)果:
這是非常重要的一步,有助于消除神經(jīng)網(wǎng)絡(luò)實(shí)現(xiàn)中各種造成bug的來源,包括預(yù)處理邏輯和后處理邏輯在內(nèi)。因?yàn)?,如果代碼中有不少bug,模型不太可能作出符合期望的預(yù)測。
特別是實(shí)現(xiàn)自己的損失函數(shù)的時(shí)候,毫無疑問,你希望在正式開始訓(xùn)練之前確保模型能夠收斂。
性能
最后,我想給出一些有助于降低訓(xùn)練時(shí)間,以及防止瀏覽器因內(nèi)存泄漏而奔潰的建議。
13. 避免明顯的內(nèi)存泄露
除非你在tensorflow.js方面完全是新手,你大概已經(jīng)知道你需要手動(dòng)丟棄未使用的張量,以釋放它們占用的內(nèi)存。你可以通過調(diào)用tensor.dispose()或?qū)⒉僮鞣庋b在tf.tidy塊中做到這一點(diǎn)。確保不要因?yàn)闆]有正確地丟棄張量而導(dǎo)致內(nèi)存泄露,否則你的應(yīng)用遲早會(huì)用盡內(nèi)存。
識別這類內(nèi)存泄露很容易。只需在若干次迭代中調(diào)用tf.memory()記錄日志,以驗(yàn)證張量的數(shù)量沒有隨著每次迭代而異常增長:
14. 調(diào)整canvas的尺寸,而不是張量的尺寸
注意,這條建議只適用于當(dāng)前的tfjs-core(我使用的版本是0.12.14),因?yàn)槲磥淼陌姹究赡軙?huì)修正這一問題。
我知道這也許聽起來有點(diǎn)奇怪:為什么不使用tf.resizeBilinear、tf.pad等來重整輸入張量的形狀,以匹配網(wǎng)絡(luò)輸入要求的形狀?這是因?yàn)楫?dāng)前tfjs有一個(gè)GPU顯存泄露的bug(#604)
長話短說,在調(diào)用tf.fromPixels將canvas轉(zhuǎn)換成張量前,首先調(diào)整canvas的尺寸,以匹配網(wǎng)絡(luò)輸入要求的形狀。不這么做的話,你會(huì)很快耗盡GPU顯存(取決于訓(xùn)練數(shù)據(jù)中圖像尺寸的不同程度)。如果所有訓(xùn)練圖像尺寸一致,不會(huì)有什么問題,但如果尺寸不一致,你可以使用以下代碼調(diào)整尺寸:
exportfunction 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. 找到最優(yōu)的batch大小
不要把batch大小設(shè)得過大!嘗試不同的batch大小,測量反向傳播所需的時(shí)間。最優(yōu)batch大小取決于GPU、輸入尺寸、網(wǎng)絡(luò)復(fù)雜度。甚至某些情況下,不使用batch,逐一輸入才是最好的。
如果拿不準(zhǔn),我會(huì)將batch大小設(shè)為1(也就是不使用batch)。我個(gè)人發(fā)現(xiàn)在一些情形下,增加batch大小對性能提升沒什么幫助,不過,在另一些情形下,我發(fā)現(xiàn),在相當(dāng)小的網(wǎng)絡(luò)上,為112 × 112的圖像創(chuàng)建16到24的batch能帶來大約1.5-2.0的速度提升。
16. 緩存、離線存儲(chǔ)、Indexeddb
訓(xùn)練圖像(和標(biāo)簽)也許很多,取決于圖像的尺寸和數(shù)量,可能達(dá)到1GB甚至更多。由于在瀏覽器下無法直接從磁盤讀取圖像,我們需要使用一個(gè)文件代理,比如一個(gè)簡單的express服務(wù)器,托管訓(xùn)練數(shù)據(jù),然后讓瀏覽器通過文件代理獲取數(shù)據(jù)。
顯然,這很不高效,不過,別忘了,在瀏覽器中訓(xùn)練時(shí),如果數(shù)據(jù)集足夠小,我們大概會(huì)把所有數(shù)據(jù)放在內(nèi)存中,這顯然也不怎么高效。剛開始,我嘗試增加瀏覽器的緩存大小,以直接將所有數(shù)據(jù)緩存到磁盤上,不過看起來新版的Chrome和FireFox都不再支持這個(gè)功能了。
我最終決定使用Indexddb,這是一個(gè)瀏覽器中的數(shù)據(jù)庫,可以用來存儲(chǔ)整個(gè)訓(xùn)練集和測試集。上手Indexeddb很容易,只需幾行代碼,就可以以鍵值存儲(chǔ)的格式儲(chǔ)存和查詢所有數(shù)據(jù)。在Indexeddb中,我們可以將標(biāo)簽存儲(chǔ)為json對象,將圖像數(shù)據(jù)存儲(chǔ)為blob。
查詢Indexeddb相當(dāng)快,至少比反復(fù)從文件代理查詢并獲取文件要快。另外,將數(shù)據(jù)轉(zhuǎn)移到Indexeddb之后,整個(gè)訓(xùn)練過程完全可以離線進(jìn)行,也就是說,之后的訓(xùn)練過程不再需要文件代理服務(wù)器了。
17. 異步損失報(bào)告
這個(gè)一個(gè)非常有效的簡單技巧,有助于大量減少訓(xùn)練的迭代次數(shù)。如果我們想要獲取optimizer.minimize返回的損失張量的值,以了解訓(xùn)練過程中的損失值,我們希望避免等待每次迭代的CPU和GPU同步數(shù)據(jù)。相反,我們希望迭代異步地報(bào)告損失:
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()
})
別忘了,現(xiàn)在損失是異步報(bào)告的,所以如果我們想要把每個(gè)epoch末的總體損失保存到一個(gè)文件,我們需要等待最后一個(gè)promise得到滿足。我通常直接使用setTimeout在每個(gè)epoch完成后的10秒之后再記錄總體損失。
if (epoch !== startEpoch) {
// 丑陋的等待最后一個(gè)promise滿足的方法
const previousEpoch = epoch - 1
setTimeout(() => storeLoss(previousEpoch, window.losses[previousEpoch]), 10000)
}
成功訓(xùn)練模型之后
18. 權(quán)重量化
完成模型訓(xùn)練并對模型的表現(xiàn)滿意后,我建議應(yīng)用權(quán)重量化以壓縮模型大小。通過量化模型權(quán)重,可以將模型大小壓縮至1/4!盡可能地壓縮模型大小是很關(guān)鍵的,因?yàn)樗兄诳焖俚貍鬏斈P蜋?quán)重至客戶端應(yīng)用,特別是這樣的壓縮幾乎沒有什么代價(jià)。
所以,別忘了參考我寫的tensorflow.js的權(quán)重量化指南:https://itnext.io/shrink-your-tensorflow-js-web-model-size-with-weight-quantization-6ddb4fcb6d0d
-
神經(jīng)網(wǎng)絡(luò)
+關(guān)注
關(guān)注
42文章
4810瀏覽量
102918 -
深度學(xué)習(xí)
+關(guān)注
關(guān)注
73文章
5555瀏覽量
122534
原文標(biāo)題:基于tensorflow.js在瀏覽器中設(shè)計(jì)訓(xùn)練神經(jīng)網(wǎng)絡(luò)模型的18條建議
文章出處:【微信號:jqr_AI,微信公眾號:論智】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
評論