JS做深度學習2——導入訓練模型
改進項目
前段時間,我做了個RNN預測金融數據的畢業設計(華爾街),當時TensorFlow.js還沒有發布,我不得已使用了keras對數據進行了訓練,並且擬合好了不同期貨的模型,因為當時畢設的網站是用node.js寫的,為了可以在網站中預測,我采取的方案是:用python進行訓練和預測,然后使用node.js運行python命令,最終在瀏覽器上可視化出來,這也算的上是黑科技了!
不過這樣通過一個解釋器調用另一個解釋器,語言之間互相通信其實不是什么好的設計方式,且不說維護兩門語言的困難,調用其他語言過程會產生的錯誤和性能問題較多,而且顯得整個項目很混亂,強迫症受不了。
如今,Google開始官推TensorFlow的JS API又是前端福音。但根據官網的介紹,TensorFlow.js目前尚不成熟,JS方面尚未實現像Python那么豐富的學習API。所以各種基於TF的深度學習項目如果需要使用JS重構也需要慢慢過渡。
更多關於TensorFlow.js的目前支持狀況請參閱:https://www.linpx.com/p/you-want-to-know-that-everything-about-tensorflowjs-is-here.html
如上所述,TensorFlow.js尚不能導出訓練文件,但可以導入訓練文件,今天根據官網提供的文檔,將我畢設項目的預測功能重構為純JS實現。方法是通過導入python訓練好的文件,依靠TensorFlow.js調用進行預測。
官方文檔
本文是對TensorFlow官網文檔的學習和實用,記錄了筆者用tfjs導入模型進行預測的過程,參考資料:https://js.tensorflow.org/tutorials/import-keras.html
接下來就開始翻譯~~哦不,是上手coding。
文件格式
如tfjs官方所述,我們通常在keras中訓練后導出的是H5格式的文件,tfjs不能直接理解h5文件,故需要先將h5轉換格式。
准備插件
安裝tensorflowjs:
pip install tensorflowjs

可以看到tensorflowjs版本還很年幼,看來發布不久。
手動轉換測試
注意下面這些操作依然是基於python做的,我們先嘗試手動轉換文件格式。
pip安裝完tensorflowjs后,進入cmd嘗試轉換H5文件
tensorflowjs_converter --input_format keras D:\pro\WallStreet\tf_modules\models\20.h5 D:\pro\WallStreet\tf_modules\models\20
對上面的命令進行一下解釋:
input_format這個option后面跟着的是原始文件格式來源(keras),然后緊跟着h5文件的地址,然后是轉化后保存的目標目錄。
這里注意一下,h5最終會被轉化為多個文件,所以目標是個目錄而不是文件,目錄里有:

其中model.json是js中需要調用的文件,另外兩個是訓練后的二進制文件。
The target TensorFlow.js Layers format is a directory containing a model.json file and a set of sharded weight files in binary format. The model.json file contains both the model topology (aka "architecture" or "graph": a description of the layers and how they are connected) and a manifest of the weight files.
這是Google的原文,翻譯過來是tensorflowjs最終格式是個目錄 ,包含了一個model.json,還有一些碎片的權重(神經網絡中的名詞:訓練過程優化所得的w值)文件(二進制格式)。json文件則記錄神經網絡的拓撲結構,一種對神經網絡不同層,不同神經元之間連接的狀態的記錄。大意就是保存了訓練后的模型結構!
這段話比較玄學,以我多年對計算機各種理論的融匯貫通后的理解是,神經網絡成型后的模型(也就是訓練過后的文件),由兩部分組成,一個是神經元本身的內部權重(一些數據),還有事神經元之間連接的橋梁(一些結構),綜合起來還是——“數據結構”,這個數據結構類似圖,有節點和連接,節點還是一些多維的權值。
由此可見,一個真正的程序員應當好好學習基本的專業課包括數學知識,而不是停留在語言層面,否則涉及到高深的技術時,終將也會茫然。
模型保存時轉換
廢話多了,在實際項目中,我們不可能手動轉換,考慮直接在keras訓練后生成tensorflowjs文件,這應是自動化的過程
如下:
import tensorflowjs as tfjs
...
# 創建RNN,並訓練
model = Sequential()
model.add(LSTM(128, input_shape=(1, window)))
model.add(Dense(1))
model.compile(loss='mean_squared_error', optimizer='adam')
model.fit(trainX, trainY, epochs=100, batch_size=100, verbose=2)
#保存訓練模型
#model.save('tf_modules/models/%s.h5'%g_id)
tfjs.converters.save_keras_model(model,'tf_modules/models/%s'%g_id)
這只是我改進項目中的python訓練代碼的片段,可以看到原來的model.save是保存為h5,被我注釋后改成了保存為tensorflowjs文件集,使用的是tensorflowjs下的 converters.save_keras_model 方法。
導入模型
萬事俱備,只欠東風,可以通過JavaScript導入模型預測了。
當到達這一步時,以為要大功告成了,too young!
你可能會說很簡單啊,tfjs一定提供了讀取模型的方法,沒錯,確實提供了,不過很可惜不支持node.js,筆者在寫篇博客期間,不停翻閱各種國外文檔,耗費了整整一下午,官方給出的例子太過簡單了:
import * as tf from '@tensorflow/tfjs';
const model = await tf.loadModel('https://foo.bar/tfjs_artifacts/model.json');
const example = tf.fromPixels(webcamElement); // for example
const prediction = model.predict(example);
就這么短短幾行,一個例子,意思是:“你用loadmodel+predict方法就行了!”
這對於tensorflow新手來說無疑又是巨大災難。
最關鍵的是,筆者注意到loadModel()方法傳入的是一個http地址,而研究了一下午,在嘗試了node.js下各種文件讀取,http訪問后發現原來這貨也只支持瀏覽器端的實現,一句話概括就是目前的tfjs導入模型不支持node.js!
以下就是結論的由來,如果有成功使用Node.js讀取model的請留言幫助我,感激不盡。

那怎么辦呢,硬着頭皮在瀏覽器下運行咯。具體如下:
第一步:保存模型到靜態目錄下
為了browser端可以訪問到model.json以及另外兩個權重的二進制文件,在python代碼訓練完成后保存模型到靜態可訪問目錄下,這對於node.js來說十分重要,因為node.js通過express中間件可以給定一個靜態資源目錄(就是存放css,js,img的目錄)
例如:
//配置靜態文件為assets目錄
app.use(express.static(__dirname + "/assets"))
這是指定的靜態文件的根目錄,訓練后的模型也放到這個目錄中,瀏覽器才能訪問到。
我的方法是在這個目錄下新建一個models目錄專門存放model,並且不同商品訓練出來的模型都是一個獨立目錄(前面說了,tensorflowjs轉化后形成的是一個目錄),目錄名字是商品在數據庫的主鍵ID。
這樣保存模型的代碼就變成這樣了
tfjs.converters.save_keras_model(model,'assets/tf_models/%s'%g_id
第二步:瀏覽器script引入tfjs
script(src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@0.11.4",type="text/javascript")
以上是筆者使用了jade引擎,可以自行轉化為html的script標簽
第三步:預測
以下的JavaScript代碼都是在瀏覽器中運行的:
async function get_predict_data(g_id){
//預測
let model = await tf.loadModel(`/tf_models/${g_id}/model.json`)
let response = await fetch(`/futures/data/${g_id}/30`);
let data = (await response.json()).data;
let max_price = [];
for(let i in data){
max_price.push(data[i].max_price);
}
scaler = new min_max_scale(max_price);
max_price = scaler.fit(max_price);
let tf_price = tf.tensor3d(max_price,[1,1,30]);
let prediction = model.predict(tf_price);
let a = (await prediction.data())[0];
a = scaler.inverse(a);
}
//數據歸一化
function min_max_scale(data){
this.max = Math.max(...data);
this.min = Math.min(...data);
this.fit = function(){
for(i in data){
data[i] = (data[i]-this.min)/(this.max-this.min);
}
return data;
}
this.inverse = (to_inverse) => to_inverse*(this.max-this.min)+this.min
}
最后這個a即為瀏覽器下跑模型預測的值。
別看代碼很簡短,大致用了兩個方法,一個是預測方法,另一個歸一化數據方法,這里面坑坑可多了。
數據標准化,這里使用的是歸一化,在python下使用的是sklearn的MinMaxScaler對象,而npm庫中翻遍了也找不到相似的功能模塊,只好我自己實現了。
什么是歸一化,算法是什么?請參考:https://blog.csdn.net/Jiaach/article/details/79484990
可以看到,get_predict_data(預測函數)中除了官方給出的loadModel和predict函數外還有許多陌生的函數(均來自於tensorflow),這些函數的API對於新手來說十分陌生,官網給的也不是很明確,而且還是英文的,今天先不介紹,這些函數留給今后《JS做深度學習博客系列》一個個介紹。
tensorflow.js導入模型到此告一段落。
完整代碼參考我的github項目:https://github.com/devilyouwei/WallStreet
