JS,一門從瀏覽器興起,卻不止於瀏覽器的腳本,個人一直認為其是最有潛力的腳本語言。不只是因為ES6優雅的語法,更重要的是其易上手,跨平台的優點。
Node將JS從browser帶去了client是革命性的,使得常常被冠以“瀏覽器腳本”的JS成為一門足以和PHP,PY匹敵的通用性腳本。
關於tensorflow,這里就不多做介紹,簡而言之就是一個深度學習的框架,而為眾人所知的是他對python的支持性非常高,幾乎可以說到tf,那就是python的天下,以至於說到深度學習,眾人都會聯想到py,Google也是首推python在深度學習領域的使用,這和python早期與Google的淵源有關。不過,筆者多次和py的交手后,對py的這種偏自然的語法及其不適應,很難接受這么“優秀的語言”(寧願ruby,亦不python),這樣的感受始於筆者最早一次使用深度學習做金融數據分析的畢業設計,python可把我害苦了。那時的我對JS愛不釋手,曾企圖使用JS自己構建神經網絡。
終於,tfjs還是來了(在做畢設那會我就預言了要深度學習可以完全用JS開發),然而,Google最早對tfjs的態度(其實是在Google大腦工作的開源開發者)似乎還是停留在“JS要在瀏覽器上跑”的這種觀念,所以筆者使用0.0.x版本都是基於瀏覽器開發的,@tensorflow/tfjs這個項目,其實在vue或者react等項目上非常的合適,但是對於client的node來說就不是那么友好,很多接口是不支持的。
下面來說說,這兩個項目的區別,打開 npmjs.com,搜索tfjs
可以看到,tfjs和tfjs-node,下面來說說這個兩個項目有什么不同,tfjs是為瀏覽器端而設計的,而tfjs-node是為node端設計的。
存儲
個人認為,這兩個項目最大的區別就是存儲model的差別,早期的tfjs是基於瀏覽器的,故而可以將訓練后的model存儲在localstorage,indexedb,當然還可以通過formdata上傳至http服務器。下面我來測試一下:
localStorage存儲Model
瀏覽器代碼
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs/dist/tf.min.js"> </script>
<script>
(async ()=>{
const model = tf.sequential();
model.add(tf.layers.dense({units: 1, inputShape: [1]}));
model.compile({loss: 'meanSquaredError', optimizer: 'sgd'});
const xs = tf.tensor2d([1, 2, 3, 4], [4, 1]);
const ys = tf.tensor2d([1, 3, 5, 7], [4, 1]);
model.fit(xs, ys).then(() => {
model.predict(tf.tensor2d([5], [1, 1])).print();
});
let save = await model.save('localstorage://model-1')
})()
</script>
</head>
<body>
</body>
</html>
在chrome的瀏覽器application中可以看到已經存儲的localstorage,tfjs一共存儲了5個key-value,目前不懂里面的意義是什么
indexdb存儲Model
事實上我之前做為前端也沒有使用過indexdb,主要還是因為indexdb相對於webstorage過於龐大復雜,復雜的數據一般都丟給了后端用MySQL,筆者非常喜歡使用localstorage。以下嘗試使用indexdb存儲訓練模型
瀏覽器代碼
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs/dist/tf.min.js"> </script>
<script>
(async ()=>{
let model = tf.sequential();
model.add(tf.layers.dense({units: 1, inputShape: [1]}));
model.compile({loss: 'meanSquaredError', optimizer: 'sgd'});
//目標y=2x-1
const xs = tf.tensor2d([1, 2, 3, 4], [4, 1]);
const ys = tf.tensor2d([1, 3, 5, 7], [4, 1]);
console.log('開始訓練')
for(let i=0;i<2000;i++) await model.fit(xs, ys)
console.log('訓練完畢')
let save = await model.save('indexeddb://model-1')
model = await tf.loadLayersModel('indexeddb://model-1');
model.predict(tf.tensor2d([10],[1,1])).print();
})()
</script>
</head>
<body>
</body>
</html>
可以看到訓練模型被存儲再indexdb數據庫中
Model上傳至服務器
前端代碼,訓練的過程是一毛一樣的,上述所有瀏覽器的深度學習法唯獨就是最后一步存儲不太一樣,這里需要用到browserHTTPRequest的request請求,特別注意browserHTTPRequest不可以再node下跑,會提示要求再瀏覽器中使用,傳輸的文件形式應該是通過form-data到達服務器的。
前端代碼
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs/dist/tf.min.js"> </script>
<script>
(async ()=>{
let model = tf.sequential();
model.add(tf.layers.dense({units: 1, inputShape: [1]}));
model.compile({loss: 'meanSquaredError', optimizer: 'sgd'});
//目標y=2x-1
const xs = tf.tensor2d([1, 2, 3, 4], [4, 1]);
const ys = tf.tensor2d([1, 3, 5, 7], [4, 1]);
console.log('開始訓練')
for(let i=0;i<2000;i++) await model.fit(xs, ys)
console.log('訓練完畢')
let save = await model.save(tf.io.browserHTTPRequest('http://localhost:3000/Upload/test', {method: 'PUT'}))
})()
</script>
</head>
<body>
</body>
</html>
后端node實現文件接受,轉儲
static async test(req,res){
let json = req.files.model.weights.bin.path
let path = req.files.model.json.path
const r = fs.createReadStream(json)
const w = fs.createWriteStream('./test.json')
r.pipe(w)
}
上述代碼是一個代碼塊,由於我用到了自己構建的一個node框架,是根據TP的MVC模式設計的,關鍵代碼如上,從req的files中取出文件,用fs模塊轉儲到特定目錄中。
以上所述均是tfjs框架的存儲方式,有很大的缺點,那就是在瀏覽器端訓練,訓練后的模型只能存儲在瀏覽器中,無法做大數據的收集,即使可以上傳到服務器,訓練的模型依然不能得到安全,可持續化,完整的保證,當然也有其一些優點,節省了大量服務器的資源,畢竟深度學習訓練如果涉及到圖形,音頻,語言文字的話是相當復雜和消耗資源的,將訓練過程合理的分壓到客戶端,最后再收集到服務器卻是一種很“雞賊”的手法,在這里我只能說妙啊妙啊,不過這滿足不了程序員的控制欲,下面介紹一下tfjs-node的存儲,由於tfjs-node的安裝比tfjs復雜一些,如何安裝tfjs-node請查看筆者上一篇博客。
Node的文件系統fs存儲方式
欲用文件系統,請先安裝tfjs-node。
node代碼
const tf = require("@tensorflow/tfjs-node");
const model = tf.sequential();
//定義網絡結構,層數,單元數,輸入
model.add(tf.layers.dense({units: 1, inputShape: [1]}));
//定義優化器
model.compile({loss: 'meanSquaredError', optimizer: 'sgd'});
//目標:y=2x+1;
const xs = tf.tensor2d([1,2,3,5], [4,1]);
const ys = tf.tensor2d([3,5,7,11], [4,1]);
//使用async是因為訓練中有異步操作,需要用到await
(async ()=>{
//訓練1000次
for(let i=0;i<1000;i++) {
await model.fit(xs,ys);//等待訓練的異步操作
console.log(`第${i}次`);
}
model.predict(tf.tensor2d([5,3,99], [3, 1])).print();
let save = await model.save('file://./model')
})();
上述代碼中,其實訓練過程和存儲過程與browser大同小異,且存儲的API都是model的save方法。
使用上述代碼后,js所在的當前目錄會產生一個model目錄,並多出兩個記憶文件模型。
至此,tfjs-node了卻了我半年的顧慮,為js做深度學習保存模型尋找一個文件系統的方式,這樣才能完全使用js進行深度學習的開發了。