前言
之前介绍的神经网路这篇参考文献,里面他做出一个动态更新的神经网路训练,觉得相当有趣,因此尝试利用canvas来实现动态更新的效果。
这次介绍主要都是座标绘製也无用到特别公式,所以文字部分比较少几乎都是程式码。
[笔记]深度学习(Deep Learning)-神经网路学习
绘製
整体绘製
神经网路。二维座标系。运用方法
线条。箭头。圆形。文字。流程
神经网路资料。绘製函数。绘製神经网路。绘製二维坐标系。更新绘製。取得神经网路资料(Javacript)
继上次神经网路学习转Javascript版本,如下。
// network.jsconst Network = function () { this.TAG = "Network";};Network.prototype = { sigmoid: function (x) { return 1.0 / (1.0 + Math.exp(-x)); }, fun: function (x, w, b) { return x * w + b; }, loss: function (x, w, b) { const a = this.fun(x, w, b); const y = this.sigmoid(a); return 0.5 * (y * y); }, getGrad: function (x, w, b) { const h = 1e-2; let grad = []; let temp = 0.0; let fun1 = 0.0; let fun2 = 0.0; temp = w; w = temp + h; fun1 = this.loss(x, w, b); w = temp - h; fun2 = this.loss(x, w, b); w = temp; grad.dW = (fun1 - fun2) / (2.0 * h); temp = b; b = temp + h; fun1 = this.loss(x, w, b); b = temp - h; fun2 = this.loss(x, w, b); b = temp; grad.db = (fun1 - fun2) / (2.0 * h); return grad; }, tran: function (x, w, b) { const lr = 0.15; const times = 300; let history = []; for (let index = 0; index < 300; index++) { let a = this.fun(x, w, b); let y = this.sigmoid(a); let data = { y: y, w: w, b: b }; history.push(data); let grad = this.getGrad(x, w, b); w -= lr * grad.dW; b -= lr * grad.db; } return history },};
绘製函数(Javacript)
这次选择把函数拉出来处理,主要有绘製线条.圆形.箭头,canvas原函数这篇。
注:箭头45度所以直接加就好不用计算sin和cos。(45度性质1:1:sqrt(2))
// draw.js// 绘製箭头// drawArrow params:// ctx: 绘製图块// x: x位置// y: y位置// len: 绘製长度// type: 绘製方向function drawArrow(ctx, x, y, len, type) { ctx.beginPath(); switch (type) { case 'up': ctx.moveTo(x, y); ctx.lineTo(x + len, y + len); ctx.moveTo(x, y); ctx.lineTo(x - len, y + len); break; case 'down': ctx.moveTo(x, y); ctx.lineTo(x + len, y - len); ctx.moveTo(x, y); ctx.lineTo(x - len, y - len); break; case 'left': ctx.moveTo(x, y); ctx.lineTo(x + len, y + len); ctx.moveTo(x, y); ctx.lineTo(x + len, y - len); break; case 'right': ctx.moveTo(x, y); ctx.lineTo(x - len, y + len); ctx.moveTo(x, y); ctx.lineTo(x - len, y - len); break; } ctx.closePath(); ctx.stroke();}// 绘製线条// drawLine params:// ctx: 绘製图块// sX: x起始位置// sY: y起始位置// eX: x终点位置// eY: y终点位置// color: 绘製颜色function drawLine(ctx, sX, sY, eX, eY, color) { ctx.beginPath(); ctx.moveTo(sX, sY); ctx.lineTo(eX, eY); ctx.closePath(); ctx.stroke(); ctx.strokeStyle = color;}// 绘製圆形// drawRound params:// ctx: 绘製图块// x: 原心x位置// y: 原心y位置// radius: 半径大小function drawRound(ctx, x, y, radius) { ctx.beginPath(); ctx.arc(x, y, radius, 0, Math.PI * 2, true); ctx.closePath(); ctx.stroke();}
绘製神经网路.坐标系(Javacript)
神经网路绘製流程
1.输入的文字。
2.线条 + 箭头。
3.圆形。
4.线条 + 箭头。
6.输出的文字。
坐标系绘製流程
1.y轴线条 + 箭头。
2.x轴线条 + 箭头。
3.x.y文字。
// network-view.jsconst _startX = 10;const _startY = 100;const _endX = 350 + _startX;const _endY = 350 + _startY;// 绘製坐标系.神经网路function init() { const canvas = document.getElementById('stockGraph'); const ctx = canvas.getContext('2d'); netCanvasInit(ctx); ctx.fillText('y', _startX, _startY - 10); drawLine(ctx, _startX, _startY, _startX, _endY, '#000'); ctx.fillText('x', _endX + 10, _endY); drawLine(ctx, _startX, _endY, _endX, _endY, '#000'); drawArrow(ctx, _startX, _startY, 8, 'up'); drawArrow(ctx, _endX, _endY, 8, 'right');}// 绘製神经网路// netCanvasInit params:// ctx: 绘製图块function netCanvasInit(ctx) { const x = _startX / 2; const y = _startY / 2; const radius = y / 2.4; const fixY = (radius * 2 + y) / 2; const fixX = x + 2 * radius; ctx.font = '20px serif'; ctx.fillText('Input:', x, y); ctx.fillText('1.0', x + 50, y); drawLine(ctx, x + 100, fixY, x + 200, fixY, '#000'); drawArrow(ctx, x + 200, fixY, 8, 'right'); ctx.fillText('W:', x + 115, y * 1.5); ctx.fillText('0', x + 145, y * 1.5); drawRound(ctx, fixX + 200 - radius, fixY, radius); drawLine(ctx, fixX + 200, fixY, fixX + 300, fixY, '#000'); drawArrow(ctx, fixX + 300, fixY, 8, 'right'); ctx.fillText('b:', fixX + 220, y * 1.5); ctx.fillText('0', fixX + 240, y * 1.5); ctx.fillText('Output:', fixX + 310, y); ctx.fillText('0', fixX + 380, y);}
更新神经网路.坐标系(Javacript)
更新主要运用文章介绍的requestAnimationFrame定时更新。
主要流程
1.检查是否在运行中.输入资料。
2.添加线条对应的颜色图示。
3.取得神经网路资料。
4.绘製神经网路训练300次的坐标系(x = 次数,y = 训练结果)。
5.绘製神经网路的w和b的变化。
// network-view.jslet _drawRun = false;let _event = null;function run() { // 检查.初始化参数 if (_drawRun) { return; } const params = { w: parseFloat(document.getElementById('text_w').value), b: parseFloat(document.getElementById('text_b').value), color: document.getElementById('text_color').value }; const msg = checkMSG(params); if (msg != "Y") { alert(msg); return; } // 线条颜色标示 addMark(params); // 初始化资料 let network = {}; network.__proto__ = Network.prototype; Network.call(network); const datas = { data: network.tran(1.0, params.w, params.b), index: 0, lastY: _startY + 10 }; // 绘图 const canvas = document.getElementById('stockGraph'); const ctx = canvas.getContext('2d'); _drawRun = true; _event = window.requestAnimationFrame(function () { draw(ctx, params.color, datas) });}// 检查 w.b.color // checkMSG params:// params: 相关资料function checkMSG(params) { if (isNaN(params.w)) { return "w 格式错误"; } if (isNaN(params.b)) { return "b 格式错误"; } for (let index = 1; index < params.color.length; index++) { if (params.color[index] > 'f') { return "color 格式错误"; } } return "Y";}// 绘製二维和神经网路// draw params:// ctx: 绘製图块// color: 绘製颜色// datas: 神经网路资料function draw(ctx, color, datas) { if (datas.index >= datas.data.length) { _drawRun = false; window.cancelAnimationFrame(_event); return; } const nowY = (_endY - _startY) * (1 - datas.data[datas.index].y) + _startY + 10; drawLine(ctx, _startX + datas.index, datas.lastY, _startX + datas.index + 1, nowY, color); updateNet(ctx, datas.data[datas.index]); datas.lastY = nowY; datas.index++; _event = window.requestAnimationFrame(function () { draw(ctx, color, datas) });}// 绘製神经网路目前参数// updateNet params:// ctx: 绘製图块// data: 神经网路目前资料function updateNet(ctx, data) { const x = _startX / 2; const y = _startY / 2; const offset = 20; const radius = y / 2.4; const fixY = y * 1.5; const fixX = x + 2 * radius; ctx.clearRect(x + 145, fixY - offset, 50, offset * 2); ctx.fillText(data.w.toFixed(2), x + 145, y * 1.5); ctx.clearRect(fixX + 240, fixY - 20, 50, offset * 2); ctx.fillText(data.b.toFixed(2), fixX + 240, fixY); ctx.clearRect(fixX + 380, y - 20, 50, offset * 2); ctx.fillText(data.y.toFixed(2), fixX + 380, y);}// 绘製线条对应的图示// addMark params:// params: 神经网路输入资料function addMark(params) { const html = "<li><div style='background:" + params.color + "'></div> w:" + params.w.toFixed(2) + ", b:" + params.b.toFixed(2) + "</li>"; document.getElementById('mark_list').innerHTML += html; randomData();}// 更换随机输入资料function randomData() { document.getElementById('text_color').value = getRandomColor(); document.getElementById('text_w').value = (Math.random() * 2).toFixed(2); document.getElementById('text_b').value = (Math.random() * 2).toFixed(2); colorChange();}// 取得随机颜色function getRandomColor() { return "#" + Math.floor(Math.random() * 0xffffff).toString(16);}// 更换区块颜色function colorChange() { const color = document.getElementById('text_color').value; document.getElementById('color_div').style.background = color;}
Css
/* index.css */body { text-align: center;}.btn-style1 { border-radius: 5px; background: #5fc0fb; border: #5fc0fb 1px solid; color: #fff; cursor: pointer; padding: 5px;} .btn-style1:hover { background: #21abff; } .btn-style1:focus { outline: none; border-color: #21abff; box-shadow: 0 0 5px #21abff; }.text-style1 { border-radius: 5px; padding: 5px; border: solid 1px #bababa; vertical-align: middle;} .text-style1:focus { outline: none; border-color: #5eb6ff; box-shadow: 0 0 5px #5eb6ff; }#mark_list { display: inline-block; width: 150px; margin: 0;} #mark_list > li { list-style-type: none; } #mark_list > li > div { width: 10px; height: 10px; background: #2894ff; display: inline-block; vertical-align: middle; }
Html
<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml"><head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title></title> <script src="network.js"></script> <script src="draw.js"></script> <script src="network-view.js"></script> <link href="index.css" rel="stylesheet" /></head><body onload="init();"> <div style="padding:10px;"> <ul id="mark_list"> </ul> <canvas id="stockGraph" width="600px" height="460px"></canvas> </div> <div> <label>w:</label> <input type="text" id="text_w" class="text-style1" value="2" " /> <label>b:</label> <input type="text" id="text_b" class="text-style1" value="2" " /> <input type="text" id="text_color" class="text-style1" value="#2894ff" onchange="colorChange();" oninput="colorChange();" /> <div id="color_div" style="width:30px; height:30px; background:#2894ff; display:inline-block; vertical-align: middle;"></div> <input type="button" class="btn-style1" value="Run" onclick="run();" /> </div></body></html>
结果
测试网址
结论
用这样一个方式可以让人看到更新过程,这比较适合用来基础教学,因为若权重变为二维阵列神经网路图的部分就要绘製更多,主要是观看也不方便,但我想如果刚学神经网路看到这个动态图也会比较有兴趣继续学习下去。
参考文献
[1]Canvas函数。检自https://developer.mozilla.org/zh-TW/docs/Web/API/Canvas_API/Tutorial (2018.09.01)。