每當(dāng)談起機(jī)器學(xué)習(xí)钠导、深度學(xué)習(xí)骡澈,大家自然就會(huì)想到 python c++ GPU tensorflow python 這些詞。似乎距離前端人員比較遙遠(yuǎn)的事 毅人,今天我們就用 js 來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單分類(lèi)的算法吭狡。
一切從簡(jiǎn)單開(kāi)始,從一個(gè)分類(lèi)問(wèn)題開(kāi)始吧丈莺。任務(wù)就是一個(gè)二分類(lèi)的問(wèn)題划煮,通過(guò)既有的數(shù)據(jù)訓(xùn)練得到一個(gè)模型,然后用模型來(lái)對(duì)一個(gè)數(shù)據(jù)預(yù)測(cè)其屬于哪一個(gè)類(lèi)別缔俄,來(lái)了解是如何使用 javascript 進(jìn)行機(jī)器學(xué)習(xí)弛秋。
const R = require('ramda');
randomPoints = R.range(0,5);
這里引入了 ramda 庫(kù)幫助生成隨機(jī)數(shù),實(shí)現(xiàn)起來(lái)很簡(jiǎn)單俐载。
[ 0, 1, 2, 3, 4 ]
var rand = (high,low)=> Math.random()*(high - low) + low
randomPoints = R.range(0,100).map(_=> rand(-1,1));
生成在 -1 到 1 之間的隨機(jī)數(shù)蟹略,這里使用 map 方法來(lái)實(shí)現(xiàn)返回在某個(gè)區(qū)間的隨機(jī)數(shù)、
[ -0.9515869798311445,
-0.884506721474116,
0.7849131001955243,
-0.8271371680230728,
0.6015755023017424,
上面隨機(jī)數(shù)瞎疼,然后我們用這些隨機(jī)數(shù)生成隨機(jī)點(diǎn)科乎,x 和 y 都是 -1 到 1 之間隨機(jī)數(shù)
randomPoints = R.range(0,100).map(_=> (
{
x:rand(-1,1),
y:rand(-1,1)
}));
我們將這些隨機(jī)點(diǎn)通過(guò) svg 以繪制在寬和高同為 400 的區(qū)域來(lái)顯示,讓你更直觀(guān)地觀(guān)察機(jī)器學(xué)習(xí)的過(guò)程贼急。
// const R = require('ramda');
var rand = (high,low)=> Math.random()*(high - low) + low
const X_MAX = 400;
const Y_MAX = 400;
randomPoints = R.range(0,100).map(_=> (
{
x:rand(0,X_MAX),
y:rand(0,Y_MAX)
}));
console.log(randomPoints)
var html=`
<svg width="${X_MAX}" height="${Y_MAX}">
${randomPoints.map(point=>
`<circle
cx="${point.x}"
cy="${point.y}"
r="5"
/>`
)}
<line x1="0" x2="${X_MAX}" y1="0" y2="${Y_MAX}" stroke="purple"/>
</svg>
`;
document.getElementById("app").innerHTML = html;
并且將這些隨機(jī)點(diǎn)顯示出來(lái)茅茂,這些點(diǎn)散落在寬高都是 400 的個(gè)方形區(qū)域,然后繪制一條線(xiàn)太抓,這條線(xiàn)就是我們隨后擬合分割線(xiàn)空闲,這條線(xiàn)將這些樣本點(diǎn)分成了兩個(gè)類(lèi)別,位于線(xiàn)上部分是一個(gè)類(lèi)別走敌,位于線(xiàn)下面又是一個(gè)類(lèi)別碴倾。
其實(shí)問(wèn)題很簡(jiǎn)單,只要某一個(gè)點(diǎn) y 坐標(biāo)大于 x 坐標(biāo)掉丽,或者 y 坐標(biāo)小于 x 坐標(biāo)屬于另一個(gè)類(lèi)別跌榔,如果某一個(gè)點(diǎn) x 坐標(biāo)大于 y 輸入 1 否則 -1 也就是用 1 和 -1 表示兩個(gè)類(lèi)別
var team = point => point.x > point.y ? 1 : -1;
接下來(lái)通過(guò)顏色來(lái)表示兩個(gè)不同類(lèi)別,類(lèi)別為 -1 的點(diǎn)用藍(lán)色來(lái)表示捶障,而 1 則用紅色點(diǎn)來(lái)表示僧须。
var html=`
<svg width="${X_MAX}" height="${Y_MAX}">
${randomPoints.map(point=>
`<circle
cx="${point.x}"
cy="${point.y}"
r="5"
fill="${team(point)===-1?'blue':'red'}"
/>`
)}
<line x1="0" x2="${X_MAX}" y1="0" y2="${Y_MAX}" stroke="purple"/>
</svg>
`;
team 函數(shù)通過(guò)簡(jiǎn)單邏輯(規(guī)則)對(duì)隨機(jī)點(diǎn)進(jìn)行分類(lèi),通過(guò)點(diǎn)的 x 和 y 值大小進(jìn)行判斷可以進(jìn)行判斷项炼。
開(kāi)始寫(xiě)模型
好接下來(lái)我們就開(kāi)始構(gòu)建 AI 來(lái)模擬機(jī)器學(xué)習(xí)過(guò)程担平。初始一個(gè)隨機(jī)參數(shù)模型參數(shù)比較有意思是兩個(gè)取值從 -1 到 1 的隨機(jī)數(shù),這里這樣理解我們通過(guò) x 和 y 之間比值來(lái)劃分 ax + by
var randomWeights = ({
x:rand(-1,1),
y:rand(-1,1)
})
這里 guess 函數(shù)會(huì)輸出一個(gè)類(lèi)別锭部,也就是我們函數(shù)模型暂论,這里有連個(gè)參數(shù),或者叫做權(quán)重拌禾,權(quán)重負(fù)責(zé)根據(jù) x 和 y 給出一個(gè)該點(diǎn)所屬類(lèi)別 1 * x + -1*y > 0 這個(gè)關(guān)系取胎。
var guess =(weights,point) => {
const sum =
point.x * weights.x +
point.y * weights.y
const team = sum >= 0 ? 1 : -1
return team
}
testGuess = guess(randomWeights,{x:300,y:400})
這里 weights 是權(quán)重,point 是輸入湃窍,sum 為輸入和權(quán)重乘積的和扼菠,這個(gè)神經(jīng)元有兩個(gè)輸入 x 和 y(兩個(gè)特征)然后輸出為 1 或 -1
var html=`
<svg width="${X_MAX}" height="${Y_MAX}">
${randomPoints.map(point=>
`<circle
cx="${point.x}"
cy="${point.y}"
r="5"
fill="${guess(randomWeights,point)===-1?'blue':'red'}"
/>`
)}
<line x1="0" x2="${X_MAX}" y1="0" y2="${Y_MAX}" stroke="purple"/>
</svg>
`;
以為 weight 是隨機(jī)數(shù)摄杂,我們每次刷新會(huì)得到不同結(jié)果。
創(chuàng)建我們訓(xùn)練函數(shù)循榆,訓(xùn)練接收 weights 和 point 輸入析恢,以及期望值 actualTeam 通過(guò)對(duì)比判斷結(jié)果和期望值對(duì)比來(lái)反饋到訓(xùn)練,進(jìn)行優(yōu)化調(diào)整weight 獲取正確計(jì)算模型
function train(weights,point, actualTeam){
//loss
//otimizer
}
function train(weights,point, actualTeam){
//loss
const guessResult = guess(weights,point) //1
const error = actualTeam - guessResult;
return error
//otimizer
}
var testTrain = () => {
const point = {x:200,y:400}
return train(randomWeights,point,team(point))
}
console.log(`result ${testTrain()}`)
上面代碼可以簡(jiǎn)單測(cè)試我們計(jì)算結(jié)果和實(shí)際期望值的差距秧饮。
function train(weights,point, actualTeam){
//loss
const guessResult = guess(weights,point) //1
const error = actualTeam - guessResult;
return {
x: weights.x + (point.x * error),
y: weights.y + (point.y * error)
}
//otimizer
}
在 trainedWeights 方法中映挂,通過(guò)給定點(diǎn)來(lái)訓(xùn)練出權(quán)重,我們通過(guò)返回訓(xùn)練 weights 做為下一次參數(shù)傳入到 train 不斷調(diào)整 weight盗尸。
var trainedWeights =()=> {
const p1 = {x:721, y:432}
const p2 = {x:211, y:122}
const p3 = {x:328, y:833}
const p4 = {x:900, y:400}
let trainedWeights;
trainedWeights = train(randomWeights,p1,team(p1))
trainedWeights = train(trainedWeights,p2,team(p2))
trainedWeights = train(trainedWeights,p3,team(p3))
trainedWeights = train(trainedWeights,p4,team(p4))
return trainedWeights;
}
得到結(jié)果并不在我們weight(-1柑船,1)取值范圍內(nèi),
trainedWeights = 785.6063038318143, -801.4601438564098
var html=`
<svg width="${X_MAX}" height="${Y_MAX}">
${randomPoints.map(point=>
`<circle
cx="${point.x}"
cy="${point.y}"
r="5"
fill="${guess(trainedWeights(),point)===-1?'blue':'red'}"
/>`
)}
<line x1="0" x2="${X_MAX}" y1="0" y2="${Y_MAX}" stroke="purple"/>
</svg>
`;
document.getElementById("app").innerHTML = html;
將我們訓(xùn)練好的結(jié)果 trainedWeights() 代替隨機(jī)權(quán)重返回到圖泼各,我們發(fā)現(xiàn)圖中點(diǎn)分布接近我們期望結(jié)果鞍时,藍(lán)色和紅色點(diǎn)大致都分布在線(xiàn)兩側(cè)。