Индексация и срезы

TensorFlow.js предоставляет мощные средства для работы с многомерными массивами данных — тензорами. Эффективное использование индексации и срезов позволяет манипулировать данными без копирования, оптимизируя производительность и упрощая код.

Структура тензоров

Тензор — это многомерный массив с фиксированным типом данных (float32, int32, bool). Каждый тензор имеет:

  • Форму (shape) — массив, задающий количество элементов по каждой оси, например [3, 4] для матрицы 3×4.
  • Размерность (rank) — число осей тензора.
  • Тип данных (dtype) — определяет, какой тип значений хранится в тензоре.

Примеры создания тензоров:

const t1 = tf.tensor([1, 2, 3, 4]);          // Вектор
const t2 = tf.tensor([[1, 2], [3, 4]]);      // Матрица 2x2
const t3 = tf.tensor([[[1],[2]],[[3],[4]]]); // Тензор 3D

Индексация

Индексация в TensorFlow.js работает схоже с JavaScript-массивами, но с некоторыми отличиями:

  • Тензоры неизменяемы. Операции возвращают новый тензор, не изменяя исходный.
  • Для доступа к отдельному элементу используется метод arraySync() или dataSync() для синхронного получения массива или array() и data() для асинхронного.

Пример доступа к элементу:

const t = tf.tensor([[10, 20], [30, 40]]);
const arr = t.arraySync();
console.log(arr[0][1]); // 20

Непосредственная индексация через get:

console.log(t.get(1, 0)); // 30

Метод get принимает количество аргументов, равное рангу тензора.

Срезы (slice)

Срезы позволяют извлекать подмассивы тензоров без явного копирования данных:

const t = tf.tensor([[1, 2, 3], [4, 5, 6]]);
const slice1 = t.slice([0, 1], [2, 2]); // начиная с [0,1], размер 2x2
slice1.print();
  • Первый аргумент — начальные индексы для каждой оси.
  • Второй аргумент — размер среза по каждой оси.
  • Возвращается новый тензор с указанными элементами.

Примеры срезов:

// Срез по строкам
const rows = t.slice([1, 0], [1, 3]); // Вторая строка
rows.print();

// Срез по столбцам
const cols = t.slice([0, 0], [2, 2]); // Первые два столбца
cols.print();

Использование tf.tensor1d и tf.tensor2d для индексации

Для одномерных и двумерных данных полезно применять специализированные методы:

const vec = tf.tensor1d([10, 20, 30, 40]);
const subVec = vec.slice(1, 2); // элементы с индекса 1, длина 2
subVec.print(); // [20, 30]
const mat = tf.tensor2d([[1,2,3],[4,5,6]]);
const subMat = mat.slice([0,1],[2,2]); // срез 2x2
subMat.print(); // [[2,3],[5,6]]

Индексация с булевыми масками

Для выбора элементов на основе условий используется tf.booleanMaskAsync:

const t = tf.tensor([1, 2, 3, 4, 5]);
const mask = tf.tensor([true, false, true, false, true]);
tf.booleanMaskAsync(t, mask).then(result => result.print()); // [1,3,5]

Особенности:

  • Маска должна быть того же размера, что и тензор вдоль оси выборки.
  • Возвращается новый одномерный тензор с выбранными элементами.

Индексация с gather и gatherND

Методы gather и gatherND позволяют извлекать элементы по индексам:

const t = tf.tensor([[10, 20], [30, 40], [50, 60]]);
const indices = tf.tensor1d([0,2], 'int32');
const result = tf.gather(t, indices);
result.print(); // [[10,20],[50,60]]

gatherND используется для выборки элементов или подтензоров по многомерным индексам:

const t = tf.tensor([[1,2],[3,4]]);
const indices = tf.tensor2d([[0,0],[1,1]], [2,2], 'int32');
const result = tf.gatherND(t, indices);
result.print(); // [1,4]

Разрезание с split и unstack

Методы split и unstack позволяют разбивать тензор на несколько частей:

const t = tf.tensor2d([[1,2,3],[4,5,6]]);
const [a, b] = tf.split(t, 2, 1); // разрез по оси 1 на 2 части
a.print(); // [[1,2],[4,5]]
b.print(); // [[3],[6]]

unstack превращает тензор в массив тензоров вдоль указанной оси:

const t = tf.tensor2d([[1,2],[3,4]]);
const unstacked = tf.unstack(t, 0);
unstacked[0].print(); // [1,2]
unstacked[1].print(); // [3,4]

Ключевые моменты индексации и срезов

  • Тензоры неизменяемы — все операции создают новые объекты.
  • slice используется для извлечения подтензоров по позициям и размерам.
  • get позволяет получить отдельные элементы.
  • Булевы маски и gather обеспечивают выборку по условиям и индексам.
  • split и unstack удобны для разбиения данных на части.

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