Frente a las redes neuronales en la universidad, la red Hopfield se convirtió en una de mis favoritas. Me sorprendió que fuera el último de la lista de laboratorios, porque su trabajo se puede demostrar claramente mediante imágenes y no es tan difícil de implementar.
Este artículo demuestra cómo resolver el problema de restaurar imágenes distorsionadas utilizando una red neuronal de Hopfield, previamente entrenada en imágenes de referencia.
Traté de describir paso a paso y lo más simple posible el proceso de implementación de un programa que te permite jugar con una red neuronal directamente en el navegador, entrenar la red usando mis propias imágenes dibujadas a mano y probar su funcionamiento en distorsiones. imágenes.
Fuentes en Github y demostración .
Para la implementación necesitará:
Navegador
Comprensión básica de las redes neuronales.
Conocimientos básicos de JavaScript / HTML
Un poco de teoría
La red neuronal de Hopfield es una red neuronal completamente conectada con una matriz simétrica de conexiones. Dicha red se puede utilizar para organizar la memoria asociativa, como filtro, y también para resolver algunos problemas de optimización.
- . , . , , .
, , . (, ), . , , «» ( ).
:
:
—
— - - - .
. :
( ):
— ;
— ;
— .
. — 3, , , . , .
.
Canvas , ( ) . , Canvas ( «» ).
, 10×10 . , , 100 ( 100 ). — , −1 1, −1 — , 1 — .
- , .
// 10
const gridSize = 10;
//
const squareSize = 45;
// (100)
const inputNodes = gridSize * gridSize;
// ,
//
let userImageState = [];
//
let isDrawing = false;
//
for (let i = 0; i < inputNodes; i += 1) {
userImageState[i] = -1;
}
// :
const userCanvas = document.getElementById('userCanvas');
const userContext = userCanvas.getContext('2d');
const netCanvas = document.getElementById('netCanvas');
const netContext = netCanvas.getContext('2d');
, .
//
// 100 (gridSize * gridSize)
const drawGrid = (ctx) => {
ctx.beginPath();
ctx.fillStyle = 'white';
ctx.lineWidth = 3;
ctx.strokeStyle = 'black';
for (let row = 0; row < gridSize; row += 1) {
for (let column = 0; column < gridSize; column += 1) {
const x = column * squareSize;
const y = row * squareSize;
ctx.rect(x, y, squareSize, squareSize);
ctx.fill();
ctx.stroke();
}
}
ctx.closePath();
};
«» , .
//
const handleMouseDown = (e) => {
userContext.fillStyle = 'black';
// x, y
// squareSize squareSize (4545 )
userContext.fillRect(
Math.floor(e.offsetX / squareSize) * squareSize,
Math.floor(e.offsetY / squareSize) * squareSize,
squareSize, squareSize,
);
// ,
//
const { clientX, clientY } = e;
const coords = getNewSquareCoords(userCanvas, clientX, clientY, squareSize);
const index = calcIndex(coords.x, coords.y, gridSize);
//
if (isValidIndex(index, inputNodes) && userImageState[index] !== 1) {
userImageState[index] = 1;
}
// ( )
isDrawing = true;
};
//
const handleMouseMove = (e) => {
// , .. ,
if (!isDrawing) return;
// , handleMouseDown
// isDrawing = true;
userContext.fillStyle = 'black';
userContext.fillRect(
Math.floor(e.offsetX / squareSize) * squareSize,
Math.floor(e.offsetY / squareSize) * squareSize,
squareSize, squareSize,
);
const { clientX, clientY } = e;
const coords = getNewSquareCoords(userCanvas, clientX, clientY, squareSize);
const index = calcIndex(coords.x, coords.y, gridSize);
if (isValidIndex(index, inputNodes) && userImageState[index] !== 1) {
userImageState[index] = 1;
}
};
, , getNewSquareCoords, calcIndex isValidIndex. .
//
//
const calcIndex = (x, y, size) => x + y * size;
// ,
const isValidIndex = (index, len) => index < len && index >= 0;
//
// , 0 9
const getNewSquareCoords = (canvas, clientX, clientY, size) => {
const rect = canvas.getBoundingClientRect();
const x = Math.ceil((clientX - rect.left) / size) - 1;
const y = Math.ceil((clientY - rect.top) / size) - 1;
return { x, y };
};
. .
const clearCurrentImage = () => {
// ,
//
drawGrid(userContext);
drawGrid(netContext);
userImageState = new Array(gridSize * gridSize).fill(-1);
};
«» .
— . ( ).
...
const weights = []; //
for (let i = 0; i < inputNodes; i += 1) {
weights[i] = new Array(inputNodes).fill(0); // 0
userImageState[i] = -1;
}
...
, , inputNodes . 100 , 100 .
( ) . . .
const memorizeImage = () => {
for (let i = 0; i < inputNodes; i += 1) {
for (let j = 0; j < inputNodes; j += 1) {
if (i === j) weights[i][j] = 0;
else {
// , userImageState
// -1 1, -1 - , 1 -
weights[i][j] += userImageState[i] * userImageState[j];
}
}
}
};
, , , . :
// - html lodash:
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
...
const recognizeSignal = () => {
let prevNetState;
// .
//
// (2 ),
const currNetState = [...userImageState];
do {
// ,
// ..
prevNetState = [...currNetState];
// 3
for (let i = 0; i < inputNodes; i += 1) {
let sum = 0;
for (let j = 0; j < inputNodes; j += 1) {
sum += weights[i][j] * prevNetState[j];
}
// ( - )
currNetState[i] = sum >= 0 ? 1 : -1;
}
//
// - isEqual
} while (!_.isEqual(currNetState, prevNetState));
// ( ),
drawImageFromArray(currNetState, netContext);
};
isEqual lodash.
drawImageFromArray. .
const drawImageFromArray = (data, ctx) => {
const twoDimData = [];
//
while (data.length) twoDimData.push(data.splice(0, gridSize));
//
drawGrid(ctx);
// ( )
for (let i = 0; i < gridSize; i += 1) {
for (let j = 0; j < gridSize; j += 1) {
if (twoDimData[i][j] === 1) {
ctx.fillStyle = 'black';
ctx.fillRect((j * squareSize), (i * squareSize), squareSize, squareSize);
}
}
}
};
HTML .
HTML
const resetButton = document.getElementById('resetButton');
const memoryButton = document.getElementById('memoryButton');
const recognizeButton = document.getElementById('recognizeButton');
//
resetButton.addEventListener('click', () => clearCurrentImage());
memoryButton.addEventListener('click', () => memorizeImage());
recognizeButton.addEventListener('click', () => recognizeSignal());
//
userCanvas.addEventListener('mousedown', (e) => handleMouseDown(e));
userCanvas.addEventListener('mousemove', (e) => handleMouseMove(e));
// ,
userCanvas.addEventListener('mouseup', () => isDrawing = false);
userCanvas.addEventListener('mouseleave', () => isDrawing = false);
//
drawGrid(userContext);
drawGrid(netContext);
, :
:
! .
, , ( — ). , , , , , .
Fuentes en Github y demostración .
En lugar de literatura, las conferencias fueron utilizadas por un excelente maestro sobre redes neuronales: Sergei Mikhailovich Roshchin , por lo que muchas gracias a él.