Construyendo una red neuronal Hopfield en JavaScript

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.





-  .     ,   .   ,   , .





Diagrama de bloques de la red neuronal de Hopfield

, , . (, ), .   ,  , «» ( ).





:







  1. :



    w_ {ij} = \ left \ {\ begin {matrix} \ sum_ {k = 1} ^ {m} x_ {i} ^ {k} * x_ {j} ^ {k} & i \ neq j \\ 0 , & i = j \ end {matriz} \ right.



    metro—  

    x_ {i} ^ {k}, x_ {j} ^ {k}I- j- k- .





  2.   . :

    y_ {j} (0) = x_ {j}





  3. (   ):



    y_ {j} (t + 1) = f \ left (\ sum_ {i = 1} ^ {n} w_ {ij} * y_ {i} (t) \ right)



    F —   [-1; 1];

    t — ;

    j = 1 ... n;  norte —  .





  4.   .  —   3, , , . ,   .





.





Demostración del programa

    Canvas   . HTML  CSS ,     (  ).





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);
      
      



, :





Imágenes de referencia para la formación en red

:





Tratando de reconocer la imagen distorsionada de la letra H
Intentando reconocer una imagen distorsionada de la letra T

! .





, metro  , 0,15 * n( norte—   ). , , , ,   ,               .





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.








All Articles