Программирование искусственного интеллекта на JavaScript

Раздел: Программирование -> AI

Машинное обучение в браузере: основные решения

JavaScript давно применяют для создания интерфейсов, но его возможности в области искусственного интеллекта не ограничиваются простыми анимациями. Для обучения нейронных сетей в браузере чаще всего обращаются к библиотеке TensorFlow.js. Она переносит вычисления на графический процессор через WebGL и позволяет обучить модель на основе данных пользователя без отправки их на сервер.

Основным подходом для задачи обучения сети становится создание последовательной модели, добавление слоёв и запуск цикла обучения.

Как обучить нейросеть для классификации четырёх признаков?

import * as tf from '@tensorflow/tfjs';

const model = tf.sequential();
model.add(tf.layers.dense({
  units: 32,
  activation: 'relu',
  inputShape: [4]
}));
model.add(tf.layers.dense({
  units: 3,
  activation: 'softmax'
}));
model.compile({
  optimizer: 'adam',
  loss: 'categoricalCrossentropy',
  metrics: ['accuracy']
});

const xs = tf.randomNormal([100, 4]);
const ys = tf.randomNormal([100, 3]);

await model.fit(xs, ys, { epochs: 10 });
model.predict(tf.randomNormal([2, 4])).print();

Ai js (javascript: искусственный интеллект)

Код создаёт три слоя. Первый слой принимает вектор из четырёх чисел и содержит тридцать два нейрона с активацией relu. Второй слой возвращает вероятности трёх классов. Модель компилируется с оптимизатором adam и функцией потерь categoricalCrossentropy.

Процесс обучения запускается методом fit. Данные xs являются случайными признаками, ys случайными метками. После завершения обучения predict получает новый набор данных и выводит предсказанные классы.

Типичная проблема при работе с TensorFlow.js

Ошибка несоответствия размерности появляется, когда количество признаков в данных не совпадает с inputShape. Способ решения состоит в контроле массива признаков: длина каждой строки должна быть равна четырём. Если данные содержат строку с пятью элементами, модель не сможет обработать такой тензор.

Как построить простой перцептрон без библиотек?

Если задача ограничивается линейно разделимыми данными, к примеру логической операцией OR, модель можно создать на чистом JavaScript. Этот вариант используется для обучения базовым принципам нейронных сетей и для небольших проектов без внешних зависимостей.

const data = [[0, 0, 0], [0, 1, 1], [1, 0, 1], [1, 1, 1]];
let weights = [Math.random(), Math.random()];
let bias = Math.random();

function train(epochs = 100, learningRate = 0.1) {
  for (let epoch = 0; epoch < epochs; epoch++) {
    data.forEach((input) => {
      const [x1, x2, expected] = input;
      const total = x1 * weights[0] + x2 * weights[1] + bias;
      const prediction = total > 0 ? 1 : 0;
      const error = expected - prediction;
      weights[0] += error * x1 * learningRate;
      weights[1] += error * x2 * learningRate;
      bias += error * learningRate;
    });
  }
}

train();
console.log(data.map((row) => {
  const [x1, x2] = row;
  return x1 * weights[0] + x2 * weights[1] + bias > 0 ? 1 : 0;
}));

Каждый пример проходит через сеть. Если предсказание не совпадает с ожидаемым значением, веса и смещение корректируются на величину ошибки. После ста повторений сеть выводит все значения OR.

Ограничение одного нейрона

Простой перцептрон не способен обучиться логике XOR, потому что классы нельзя разделить прямой линией. Для такой задачи требуется скрытый слой и нелинейная функция активации. В противном случае обучение заканчивается на локальном минимуме.

Как распознавать объекты на фотографиях в браузере?

Готовая модель COCO-SSD решает задачу обнаружения объектов на изображении. Модель обучена на наборе COCO и умеет находить людей, автомобили, животных и другие сущности.

<script type='module'>
import * as cocoSsd from 'https://cdn.jsdelivr.net/npm/@tensorflow-models/coco-ssd';

const image = document.querySelector('img');
const model = await cocoSsd.load();
const predictions = await model.detect(image);

console.log(predictions);
</script>

Модель detect возвращает массив объектов с координатами ограничивающей рамки, названием класса и вероятностью. Входное изображение должно находиться в том же документе, либо быть загружено через объект HTMLImageElement.

Ошибка загрузки модели в Node.js

Модель COCO-SSD предназначена для работы в браузере. Попытка выполнить её в Node.js без полифилов DOM приводит к ошибке document is not defined. Решением становится запуск в браузерном окружении или применение отдельного модуля Node.js с обходом DOM.

Как определить токсичность текста без сервера?

Модель Toxicity из состава TensorFlow Models оценивает наличие оскорбительных выражений, угроз, непристойностей, сексуальных домогательств и других негативных форм общения.

import * as toxicity from '@tensorflow-models/toxicity';

const threshold = 0.8;
const model = await toxicity.load(threshold);

const results = await model.classify(['This comment contains no toxicity']);
console.log(results);

Параметр threshold задаёт порог вероятности, при котором текст считается токсичным. Чем выше порог, тем меньше ложных срабатываний.

Медленная работа на низкопроизводительных устройствах

Анализ текста выполняется постепенно, и на смартфоне можно наблюдать задержку. Способ решения состоит в загрузке модели до начала работы с интерфейсом и в использовании более коротких предложений для классификации.

Расширенные примеры интеллектуальных вычислений

Как восстановить зависимость между значениями с помощью линейной регрессии?

Модель обучается на паре массивов и предсказывает выходное значение для нового входа.

Пример
const tf = require('@tensorflow/tfjs');

const xs = tf.tensor2d([0, 1, 2, 3, 4], [5, 1]);
const ys = tf.tensor2d([-1, 1, 3, 5, 7], [5, 1]);

const model = tf.sequential();
model.add(tf.layers.dense({ units: 1, inputShape: [1] }));
model.compile({ optimizer: 'sgd', loss: 'meanSquaredError' });

async function trainAndPredict() {
  await model.fit(xs, ys, { epochs: 300 });
  const result = model.predict(tf.tensor2d([5, 6], [2, 1]));
  result.print();
}

trainAndPredict();

Пара xs содержит числа от нуля до четырёх, ys содержит значения, соответствующие формуле 2x - 1. После обучения модель выдаёт результат, близкий к девяти и одиннадцати.

Tensor
    [[9.0103 ],
     [10.9812]]

Числа после десятичной точки зависят от случайной инициализации весов.

Ошибка потери метрик во время обучения

Если loss становится нечисловым значением NaN, причиной часто является слишком большой learning rate. Уменьшение параметра обучения или нормализация входных данных возвращает процесс к стабильной сходимости.

Как сгруппировать точки без обучающей выборки?

Алгоритм k-means применяется для кластеризации и не требует размеченных данных. Каждая точка относится к ближайшему центру.

Пример
function kMeans(points, k, iterations = 100) {
  let centroids = points.slice(0, k);

  for (let iter = 0; iter < iterations; iter++) {
    const groups = Array.from({ length: k }, () => []);

    points.forEach((point) => {
      let minDistance = Infinity;
      let clusterIndex = 0;

      centroids.forEach((centroid, index) => {
        const distance = Math.hypot(
          point[0] - centroid[0],
          point[1] - centroid[1]
        );
        if (distance < minDistance) {
          minDistance = distance;
          clusterIndex = index;
        }
      });

      groups[clusterIndex].push(point);
    });

    centroids = centroids.map((centroid, index) => {
      const group = groups[index];
      if (group.length === 0) return centroid;
      const sumX = group.reduce((sum, point) => sum + point[0], 0);
      const sumY = group.reduce((sum, point) => sum + point[1], 0);
      return [sumX / group.length, sumY / group.length];
    });
  }

  return centroids;
}

const points = [
  [1, 1],
  [1.2, 1.4],
  [1.5, 1.1],
  [8, 8],
  [8.1, 7.9],
  [7.8, 8.2]
];

console.log(kMeans(points, 2));
[ [1.2333333333333334, 1.1666666666666667],
  [7.966666666666667, 8.033333333333333] ]

В результате две группы получают центры вблизи скоплений точек. Метод не требует предварительного обучения, поэтому подходит для анализа данных без меток.

Неустойчивое количество кластеров

Алгоритм k-means зависит от выбранного значения k и начальных положений центров. В малых наборах данных центры могут попадать в пустые кластеры. Повторный запуск с другими начальными точками или выбор k через оценку силуэта помогает получить устойчивый результат.

Как найти маршрут в графе с эвристикой?

Алгоритм A* выбирает путь, используя стоимость перехода и эвристическую оценку расстояния до цели. Это классический метод планирования движения.

Пример
function aStar(graph, start, goal, heuristic) {
  const open = [{ id: start, g: 0, f: heuristic(start), path: [start] }];
  const closed = new Set();

  while (open.length > 0) {
    open.sort((a, b) => a.f - b.f);
    const current = open.shift();

    if (current.id === goal) return current.path;

    closed.add(current.id);

    graph[current.id].forEach((neighbor) => {
      if (closed.has(neighbor.id)) return;

      const gScore = current.g + neighbor.cost;
      const existing = open.find((node) => node.id === neighbor.id);

      if (!existing || gScore < existing.g) {
        const fScore = gScore + heuristic(neighbor.id);
        const newPath = current.path.concat(neighbor.id);

        if (existing) {
          existing.g = gScore;
          existing.f = fScore;
          existing.path = newPath;
        } else {
          open.push({ id: neighbor.id, g: gScore, f: fScore, path: newPath });
        }
      }
    });
  }

  return null;
}

const graph = {
  A: [{ id: 'B', cost: 1 }, { id: 'C', cost: 4 }],
  B: [{ id: 'C', cost: 1 }, { id: 'D', cost: 5 }],
  C: [{ id: 'D', cost: 1 }],
  D: []
};

const heuristic = (node) => ({ A: 3, B: 2, C: 1, D: 0 }[node]);

console.log(aStar(graph, 'A', 'D', heuristic));
[ 'A', 'B', 'C', 'D' ]

Путь из A в D через B и C оказывается короче прямого перехода из A в C из-за весов рёбер. Эвристика позволяет алгоритму проверять перспективные вершины раньше остальных.

Бесконечный цикл при недостижимой цели

Если граф не содержит пути, алгоритм переберёт все доступные вершины и вернёт null. Для больших графов неконтролируемое расширение открытого списка вызывает рост памяти. Ограничение глубины поиска или применение итеративного углубления помогает избежать чрезмерного потребления ресурсов.

Как применить генетический алгоритм для поиска строки?

Популяция кандидатов улучшается через отбор, скрещивание и мутацию. Такой подход применим к задачам оптимизации, где нет прямой функции обучения.

Пример
const target = 'hello';
const populationSize = 200;
const mutationRate = 0.05;
const symbols = 'abcdefghijklmnopqrstuvwxyz ';

function randomSymbol() {
  return symbols[Math.floor(Math.random() * symbols.length)];
}

function fitness(individual) {
  let score = 0;
  for (let i = 0; i < target.length; i++) {
    if (individual[i] === target[i]) score++;
  }
  return score;
}

function createChild(parent) {
  let child = '';
  for (let i = 0; i < target.length; i++) {
    if (Math.random() < mutationRate) child += randomSymbol();
    else child += parent[i];
  }
  return child;
}

let population = Array.from({ length: populationSize }, () =>
  Array.from({ length: target.length }, randomSymbol).join('')
);

let bestSolution = '';

for (let generation = 0; generation < 200; generation++) {
  population.sort((a, b) => fitness(b) - fitness(a));
  bestSolution = population[0];
  if (fitness(bestSolution) === target.length) break;

  const next = [];
  for (let i = 0; i < populationSize; i++) {
    const parent = population[Math.floor(Math.random() * 50)];
    next.push(createChild(parent));
  }
  population = next;
}

console.log(bestSolution);
hello

Каждое поколение сохраняет лучшие решения, а новые потомки получают мутации. Алгоритм находит целевую строку в большинстве запусков за несколько десятков поколений.

Преждевременная сходимость

Популяция может потерять разнообразие и застрять в локальном оптимуме. Увеличение вероятности мутации или использование турнирного отбора с большим размером группы сокращает такой риск.

JavaScript: искусственный интеллект - comments

En
Ai js (javascript)