利用一點機器學習來加速你的網站
在生活中,我有 73% 的時間在考慮 web 性能-在低配手機上達到 60 FPS、 有序加載資源、離線緩存任何能緩存的資源。還有一些其他的優化。
最近,我發現自己對 web 性能的定義可能太狹隘了,從用戶的角度上來說,這些只是 web 性能中的一些小插曲。
所以我打開了我經常去的網站,嘗試了所有的用戶可能的操作,并記錄操作所花費的時間。(我們需要一些用戶操作時光軸工具)
之后,我發現了一個可行的提升性能的方案。
下面的文章內容聚焦在某個具體網站的具體操作步驟。但是我覺得這個解決方案(嗯,沒錯!就是機器學習)可以應用到很多其他類型的網站上去。
問題,如何才能節約時間
這個網站,用于賣家出售沒用的東西,買家通過購買這些東西來淘一些有價值的東西。
當賣家要在網站上出售東西時候,要先選擇分類, 再選擇對應的模版,然后填寫細節信息,預覽,最后發布。
然而第一步 — 選擇分類 — 就把我帶進了一條彎路
首先,一共有674個類別,我根本不知道我你破舊的皮劃艇屬于哪個類別( Steve Krug 說的好, 不要讓用戶去思考 )
第二步,即使我知道商品所屬的類別 — 子類別 — 子子類別,我也要至少花費12秒的時間。
如果我跟你說,我能把你的頁面的加載時間減少12秒,你一定覺得我瘋了。那么為什么不在一些別的地方來節約這12秒呢。
正如凱撒大帝所說,時間很寶貴的呢。
我一直認為用戶無知是福。我如果把商品的標題、描述、價格放到機器學習的模型里面,系統應該能自動計算出商品所屬的分類。
這樣子用戶選類別的時間就能省下來了。他們就可以開心的把這些時間拿來去 reddit 找 DIY 的雙層床了。
機器學習-你不該逃避它,你要去擁抱它
一開始的時候,我對機器學習一點概念都沒有。我是在游戲 AI ,以及 Alpha 狗戰勝人類頂級圍棋棋手之后才有所了解的。
因此我打算開始去了解它,下面的幾步一個小時都不需要。
- Google 搜索 'machine learning'
- 查看大量的關于機器學習的文章
- 發現了亞馬遜發布的 機器學習 相關的服務
- 我開始意識到我不需要知道太多的關于機器學習的東西
- 嗯。好開心
(作者注: 因為沒有去系統的學習機器學習,所以文章的一些專業術語可能被亂用。。)
一個簡單的實現流程
亞馬遜發布了他的機器學習文檔 。如果你不是對這個文檔很感興趣,打算花5個小時去閱讀,那么就來看下我寫的一些總結吧。
整理如下:
- 獲取一些 CSV 數據文件,每行都是一個商品項(^_^我的皮劃艇),列名是標題、描述、價格、所屬分類。
- 把數據傳送到亞馬遜的 AWS S3 bucket 里面
- 用數據去訓練機器。這樣子,這個小小云機器人就能通過商品的標題,描述和價格去預測他的分類了。
- 在前端頁面上,寫一些代碼,獲取用戶輸入的 標題/描述/價格,發給這個云機器人,經過計算,就能向預測這個商品所屬的分類了。
實戰模擬
下面是我寫的一個表單,模擬了賣家發布信息的幾個關鍵流程。
下面的結果一定會讓你對機器學習保持興趣。你只要相信我,建議類別是由深度學習模擬預測出來的。
讓我們去賣一個冰箱
再來試一下賣個水族館:
這個云機器人居然能識別出水族館!
當我看到這個結果的時候,手舞足蹈,是不是棒棒噠?
(我偷偷的告訴你我是怎么實現的:React, Redux, JQuery, Mox, RxJs, BlueBird, Bootstrap, Sass, Compass, NodeJs, Express, Loadsh。項目是使用 webpack 打包。最后生成的文件在1M左右)
嗯。不 BB 了。開始講正經事。
一開始為了拿到機器學習用的數據。我也是想破了頭。我大概需要10K條數據。后來是在一個當地的交易網站上面發現有這些數據??戳艘幌?URL 和 DOM 結構之后,我用 Google Scraper 插件提取了一些數據。導出成 CSV 文件。在這些數據上我大概花費了四個小時。將近整個項目時間的一半了。
數據整理好之后,上傳到了 Amazon S3 上,配置了一下機器學習的參數,設置了數據模型。整個學習的 CPU 耗時才3分鐘。
界面上還有一個實時預測功能,所以我打算用一些參數測試一下。
嗯。還挺好用的。
為了不在瀏覽器里面暴露出我的 Amazon API ,所以我把 API 放到了 Node 服務器上。
后臺代碼(Node)
使用方式很簡單。接口參數為 modelId, 服務器返回一個 prediction :
- const AWS = require('aws-sdk');
- const machineLearning = new AWS.MachineLearning();
- const params = {
- MLModelId: 'some-model-id',
- PredictEndpoint: 'some-endpoint',
- Record: {},
- };
- machineLearning.predict(params, (err, prediction) => { // we have a prediction!});
這里參數用大寫字母開頭,本來打算改掉的。后來想想還是算了。
Record, 是一個JSON對象。屬性值是(title, description, price)
我不想只提供一些代碼片段。為了幫助大家更好的理解。我把所有的服務端代碼都貼上來了。
server.js:
- const express = require('express');
- const bodyParser = require('body-parser');
- const AWS = require('aws-sdk');
- const app = express();
- app.use(express.static('public'));
- app.use(bodyParser.json());
- AWS.config.loadFromPath('./private/aws-credentials.json');
- const machineLearning = new AWS.MachineLearning();
- app.post('/predict', (req, res) => { const params = {
- MLModelId: 'my-model-id',
- PredictEndpoint: 'https://realtime.machinelearning.us-east-1.amazonaws.com',
- Record: req.body,
- };
- machineLearning.predict(params, (err, data) => { if (err) {
- console.log(err);
- } else {
- res.json({ category: data.Prediction.predictedLabel });
- }
- });
- });
- app.listen(8080);
aws-credentials.json:
- {
- "accessKeyId": "my-access-key-id",
- "secretAccessKey": "shhh-secret-squirrel", "region": "us-east-1"
- }
(在.gitignore 中忽略 /private 文件夾)
上面就是所有的后臺代碼。
前端代碼
表單里面的代碼功能比較簡單。
- 監聽幾個輸入框的 blur 事件
- 讀取表單里面的字段值
- POST 給 API 端
- 把 API 端返回的 prediction 顯示在頁面上
- (function() {
- const titleEl = document.getElementById('title-input');
- const descriptionEl = document.getElementById('desc-input');
- const priceEl = document.getElementById('price-input');
- const catSuggestionsEl = document.getElementById('cat-suggestions');
- const catSuggestionEl = document.getElementById('suggested-category');
- function predictCategory() {
- const fetchOptions = {
- method: 'POST',
- headers: { 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- title: titleEl.value,
- description: descriptionEl.value,
- price: priceEl.value,
- })
- };
- fetch('/predict', fetchOptions)
- .then(response => response.json())
- .then(prediction => {
- catSuggestionEl.textContent = prediction.category;
- catSuggestionsEl.style.display = 'block';
- });
- }
- document.querySelectorAll('.user-input').forEach(el => {
- el.addEventListener('blur', predictCategory);
- });
- })();
上面就是全部的前端代碼了。
啊啊啊……云服務還要收費呢
別忙著收起你的帽子,魔術表演怎么可能是免費呢。
我上面用到的 model 數據(10K行/4列)有6.3MB. 云端在等待接受請求的時候,消耗了6.3MB的內存。這些資源的開銷是每小時0.0001刀?;蛘呙磕?刀。 我在手套上面花的錢都比它多。
每次進行 prediction 的時候,也要0.0001刀。所有就不要隨隨便便就調用這個 API 了。
雖然目前不僅僅是 Amazon 提供了這個服務,但是我還是沒有找到另外兩個大廠家的價目表。
Google 有 TensorFlow , 但是我看了一下 入門教程 就跑了。
Microsoft 有 Machine Learning offering , 但是IE6還是讓我有點耿耿于懷 (可能不久后,Amazon 和 Microsoft 之間會有一場大戰吧)。
一些總結
或許只是我感到有些許驚訝(我還記得當我意識到‘news’是‘new’的復數的時候),我認為這些都十分讓人驚訝。它允許像你我這樣的普通人(對發展影響的程度較小的人)在機器學習中進行挖掘,可能會促成那些用戶很大的改進。
下一步在哪?
上面的例子顯然是進行過設計的,并且,我承認,我省略了一些話題。
如果我可以的話,我應該列出所有問題,但要是你自己去做你自己發現問題那也是很有趣的。
因此,去做吧,如果你取得了一些成功,我將樂于在評論中看到它們。