一切都是无常的。 “世界在寻找最佳解决方案。神经网络也不例外,作者:Biwa Hoshi。”
平安时代末期的军事编年史《平家物语》的开头是“万物无常的回声”,“祗园的钟声” Shosha提醒我们,这个世界上的所有现象都在不断变化。”它有“”的声音。
“正行无常”是佛教基本思想的三法印之一,它的意思是世间一切事物都在变化,重复生灭的命运,没有什么是永远不变的。
短篇故事:“世界都在寻找最佳解决方案。”
Kazuya Sanada 是一位居住在东京的程序员,每天都致力于优化 AI 模型。他最新的挑战是训练神经网络来发现数据中隐藏的模式并得出最佳的模型参数。然而,训练神经网络并不容易,寻找最优解的过程有时就像迷宫一样。
和也又想道。 “神经网络只不过是优化权重矩阵或权重函数来映射所需的输出和输入的问题。”梯度下降用于调整该权重函数以减少误差,尽管这种方法的目标是实现收敛。有时会达到局部最低点,往往无法达到真正的最优解。令他感到有点沮丧的是,他花费了大量时间和技能培养的模型最终只是一个“近似解决方案”。
和也突然想到了这一点。 “如果有一种创新的方法来优化这个权重函数……”在他的脑海中,他意识到需要一种更灵活、更具创造性的优化过程来取代当前的梯度下降方法。
如果误差停止减小后我们反转梯度方向会怎样?通过反转梯度下降计算的符号并沿着暂时分散误差的方向前进,有可能发现以前不可见的新路线。然后,将梯度的符号返回到其原始值,并再次收敛。在扩散阶段,它被拉回到更高的水平并再次收敛。这种交替的过程是脱离局部解,在更广阔的空间寻找最优解的新尝试。
当误差停止减小时,采用另一种方法,即反转梯度方向,暂时扩散,然后再次趋于收敛。
Kazuya 受到激励去探索进一步的方法。
因此,他开始寻找新的方法来推进权重函数的优化。这是一个动态的、探索性的优化之旅,就好像网络正在寻找自己的“答案”,而不仅仅是调整数字。和也决定踏上无尽的挑战,寻找前方未知的最佳解决方案。
他盯着电脑屏幕,小声嘀咕道。 “优化神经网络可能就像生活一样,不断寻找答案并不断进化,那可能才是真正意义上的最优解。”
将代码粘贴到文本编辑器(例如记事本)中,并将文件名保存为“index.html”。当您在浏览器中打开保存的文件时,代码将运行。
解释
生成矩阵:
使用TensorFlow.js的tf.randomUniform生成A、B_normal、B_reversal、C_target。
训练循环:
在每次迭代时使用 tf.matMul 计算当前输出。
计算误差矩阵、损失、更新权重并翻转符号。
防止内存泄漏:
通过 dispose 释放不必要的张量。
绘制图表:
使用 Chart.js 显示 lossHistoryNormal 和 lossHistoryReversal。
当您运行此代码时,您可以在浏览器中以图表形式查看随时间变化的损失,并比较梯度下降的正常版本和否定版本。
“平家物语”的代号。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Gradient Descent: Normal vs Reversal</title>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>
</head>
<body>
<h1>勾配降下法のシミュレーション: 通常 vs 符号反転</h1>
<canvas id="chart" width="600" height="400"></canvas>
<script>
// 行列の次元
const n = 10;
const learningRate = 0.001;
const iterations = 3000;
const switchInterval = 300;
let sign = 1;
// 行列の初期化
const A = tf.randomUniform([n, n]);
let B_normal = tf.randomUniform([n, n]);
let B_reversal = tf.randomUniform([n, n]);
const C_target = tf.randomUniform([n, n]);
// ロスの保存リスト
let lossHistoryNormal = [];
let lossHistoryReversal = [];
async function train() {
for (let i = 0; i < iterations; i++) {
// 通常のCと符号反転Cを計算
const C_current_normal = tf.matMul(A, B_normal);
const C_current_reversal = tf.matMul(A, B_reversal);
// 誤差行列を計算
const error_normal = C_current_normal.sub(C_target);
const error_reversal = C_current_reversal.sub(C_target);
// 損失(MSE)を計算して記録
const lossNormal = error_normal.square().mean().dataSync()[0];
const lossReversal = error_reversal.square().mean().dataSync()[0];
lossHistoryNormal.push(lossNormal);
lossHistoryReversal.push(lossReversal);
// 勾配を計算してBを更新
const grad_B_normal = tf.matMul(A.transpose(), error_normal);
const grad_B_reversal = tf.matMul(A.transpose(), error_reversal);
B_normal = B_normal.sub(grad_B_normal.mul(learningRate));
B_reversal = B_reversal.sub(grad_B_reversal.mul(sign * learningRate));
// インターバルごとに符号を反転
if ((i + 1) % switchInterval === 0) {
sign *= -1;
console.log(`Iteration ${i + 1}: Loss (reversal) = ${lossReversal} (sign reversed)`);
} else if (i % 100 === 0) {
console.log(`Iteration ${i}: Loss (normal) = ${lossNormal}, Loss (reversal) = ${lossReversal}`);
}
// メモリリークを防ぐためにテンソルを削除
C_current_normal.dispose();
C_current_reversal.dispose();
error_normal.dispose();
error_reversal.dispose();
grad_B_normal.dispose();
grad_B_reversal.dispose();
await tf.nextFrame(); // 画面を更新
}
plotLoss(); // 学習が完了したらロスのプロットを実行
}
function plotLoss() {
const ctx = document.getElementById('chart').getContext('2d');
const chart = new Chart(ctx, {
type: 'line',
data: {
labels: Array.from({ length: iterations }, (_, i) => i + 1),
datasets: [
{
label: 'Normal Gradient Descent',
data: lossHistoryNormal,
borderColor: 'blue',
fill: false
},
{
label: 'Reversal Gradient Descent',
data: lossHistoryReversal,
borderColor: 'red',
fill: false
}
]
},
options: {
responsive: true,
scales: {
x: { type: 'linear', title: { display: true, text: 'Iteration' } },
y: { title: { display: true, text: 'Loss' } }
}
}
});
}
// 学習を実行
train();
</script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</body>
</html>