以下がTensorFlow.jsで実装した論理ゲートの実行画面です。結果は、概ね良好のように思えます。
全コードを以下に示します。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>論理ゲート</title>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@latest"></script>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<script src="js/graph.js"></script>
<style>
.graph-area{
margin: 10px;
width: 500px;
height:500px;
border: 1px solid black;
}
.container{
display: flex;
width: 1100px;
text-align: center;
margin: 30px;
}
input {
margin: 2px;
height: 30px;
width: 80px;
}
</style>
</head>
<body>
<h3>論理ゲート モデル、レイヤー使用</h3>
<div class="container">
<div id="and">
<img src="images/and.png" width="118" height="155">
<input type="button" id="and-button" value="AND">
</div>
<div id="nand">
<img src="images/nand.png" width="118" height="155">
<input type="button" id="nand-button" value="NAND">
</div>
<div id="or">
<img src="images/or.png" width="118" height="155">
<input type="button" id="or-button" value="OR">
</div>
<div id="xor">
<img src="images/xor.png" width="118" height="155">
<input type="button" id="xor-button" value="XOR">
</div>
<div>
<div id="chart" class="graph-area"></div>
</div>
</div>
</body>
<script>
document.addEventListener('DOMContentLoaded',async()=>{
let xs = null;
let ys = null;
let model = null;
const buildModel = async()=>{
console.log('モデルを定義');
const model = tf.sequential();
// units数をいろいろ変えてみる
model.add(tf.layers.dense({ units: 12, inputShape: [2], activation:'elu' }));
model.add(tf.layers.dense({ units: 1,activation:'sigmoid'}));
const learningRate = 0.01;
// optimizerをいろいろ変えてみる
const optimizer = tf.train.rmsprop(learningRate);
model.compile({loss: 'meanSquaredError', optimizer: optimizer});
return model;
}
const clear =()=>{
xData = [];
yData = [];
xs = null;
ys = null;
model = null;
}
const start = async(func, logic)=>{
// モデルの使いまわしはだめ。毎回未訓練のものを作る。
model = await buildModel();
// 渡された関数を呼び出して、データを得る
[xs, ys] = await func();
xs.print();
ys.print();
// 訓練開始
train(xs, ys, logic);
}
// 各ボタンのクリックで、それぞれに対応したデータを返す
// ANDボタン
const getAND =()=>{
const trainData = tf.tensor2d([[0,0],[1,0],[0,1],[1,1]], [4,2]);
return [trainData, tf.tensor2d( [[0],[0],[0],[1]],[4,1])];
}
// NANDボタン
const getNAND =()=>{
const trainData = tf.tensor2d([[0,0],[1,0],[0,1],[1,1]], [4,2]);
return [trainData, tf.tensor2d( [[1],[1],[1],[0]],[4,1])];
}
// ORボタン
const getOR =()=>{
const trainData = tf.tensor2d([[0,0],[1,0],[0,1],[1,1]], [4,2]);
return [trainData, tf.tensor2d([[0],[1],[1],[1]],[4,1])];
}
// XORボタン
const getXOR =()=>{
const trainData = tf.tensor2d([[0,0],[1,0],[0,1],[1,1]], [4,2]);
return [trainData, tf.tensor2d([[0],[1],[1],[0]],[4,1])];
}
document.getElementById('and-button').addEventListener('click', ()=>{
clear();
start( getAND, 'and');
}, false);
document.getElementById('nand-button').addEventListener('click', ()=>{
clear();
start( getNAND, 'nand');
}, false);
document.getElementById('or-button').addEventListener('click', ()=>{
clear();
start( getOR, 'or');
}, false);
document.getElementById('xor-button').addEventListener('click', ()=>{
clear();
start( getXOR, 'xor');
}, false);
const train=(xs, ys, logic)=>{
const epochs = 200;
console.log('訓練開始');
model.fit(xs, ys,{
batchSize: 4,
epochs: epochs,
callbacks: {
onEpochEnd: async (epoch, logs) => {
const lossData = [epoch,logs.loss];
//console.log(lossData[0], lossData[1]);
// グラフに学習の進捗状況を描く
plot(lossData[0], lossData[1]);
await tf.nextFrame();
}
}
}).then(() => {
// tf.Tensorが占める、GPUのメモリを解放
tf.tidy(()=>{
console.log('訓練終了');
// モデルが内部で計算した重み
console.log(model.trainableWeights[0].read().mean().dataSync())
console.log(model.trainableWeights[1].read().mean().dataSync())
console.log(model.trainableWeights[2].read().mean().dataSync())
console.log(model.trainableWeights[3].read().mean().dataSync())
// 推論を行う
const div = document.getElementById(logic);
// 0,0 の場合
const test1 = tf.tensor2d([0,0],[1,2]);
const test1Pred = model.predict(test1);
const p1 = appendResult(test1Pred);
div.appendChild(p1);
// 1,0の場合
const test2 = tf.tensor2d([1,0],[1,2]);
const test2Pred = model.predict(test2);
const p2 = appendResult(test2Pred);
div.appendChild(p2);
// 0,1の場合
const test3 = tf.tensor2d([0,1],[1,2]);
const test3Pred = model.predict(test3);
const p3 = appendResult(test3Pred);
div.appendChild(p3);
// 1,1の場合
const test4 = tf.tensor2d([1,1],[1,2]);
const test4Pred = model.predict(test4);
const p4 = appendResult(test4Pred);
div.appendChild(p4);
});
// tf.Tensorが占める、GPUのメモリを解放
xs.dispose();
ys.dispose();
});
};
// 推論の結果を<p>要素で返す
const appendResult =(pred)=>{
const p = document.createElement('p');
const tNode = document.createTextNode(pred);
p.appendChild(tNode);
return p;
}
},false);
</script>
</html>
graph.js
let xData = [];
let yData = [];
const layout = {
xaxis: {
range: [0, 200],
title: 'epochs'
},
yaxis: {
range: [0, 0.5],
title: 'Loss'
},
title: '損失値'
};
const plot = (x, y) => {
xData.push(x);
yData.push(y);
const trace = {
x: xData,
y: yData,
mode: 'lines',
type: 'scatter',
};
Plotly.newPlot('chart', [trace], layout, { displayModeBar: false });
}
やっていることは基本的に、モデルを作成して、それに訓練用データ(tf.tensor2d([[0,0],[1,0],[0,1],[1,1]], [4,2]))と、教師用データ(ANDゲートならtf.tensor2d( [[0],[0],[0],[1]],[4,1]))を与えて訓練し、推論時にtf.tensor2d([0,0],[1,2])とtf.tensor2d([1,0],[1,2])、tf.tensor2d([0,1],[1,2])、tf.tensor2d([1,1],[1,2])を与え、その結果を、4つのゲートの下に表示する、ということです。
ポイントは、モデルをどう定義するか、つまり論理ゲートという問題に適したモデルはどのようなものなのかを探ることにあります。モデルには正解がありません。このモデルはだめだ、前のモデルより少しましだ、ずいぶん良くなった、と試行錯誤することになります。
次は、異なるモデルをいくつか試していきます。