Операции над тензорами
Урок про операции над тензорами: сложение, умножение поэлементно и матрично, агрегации.
Поэлементная операция применяется к каждой паре соответствующих элементов независимо, а матричное умножение комбинирует строки и столбцы по правилам линейной алгебры.
Нейросеть — это по сути цепочка операций над тензорами: умножить вход на матрицу весов, прибавить смещение, применить активацию, повторить. Поэтому стоит твёрдо различать два вида умножения, которые легко спутать.
Поэлементно против матрично
Поэлементное умножение (*) перемножает элементы один к одному и требует одинаковой формы. Матричное (tf.matmul или @) перемножает по правилу «строка на столбец». Проиллюстрируем оба на чистом Python:
a = [[1, 2], [3, 4]]
b = [[5, 6], [7, 8]]
# поэлементно
elem = [[a[i][j] * b[i][j] for j in range(2)] for i in range(2)]
# матрично (строка на столбец)
mat = [[sum(a[i][k] * b[k][j] for k in range(2)) for j in range(2)] for i in range(2)]
print("поэлементно:", elem)
print("матрично: ", mat)Вывод:
поэлементно: [[5, 12], [21, 32]] матрично: [[19, 22], [43, 50]]
Видно: результаты разные. В нейросетях вход на веса умножают именно матрично.
Агрегации и редукции
Редукции «схлопывают» тензор: сумма, среднее, максимум по оси. Они нужны постоянно — например, чтобы усреднить ошибку по батчу. Посчитаем среднее и сумму на stdlib:
import statistics
values = [4, 8, 15, 16, 23, 42]
print("сумма:", sum(values))
print("среднее:", statistics.mean(values))
print("максимум:", max(values))Вывод:
сумма: 108 среднее: 18 максимум: 42
Те же операции в TensorFlow
В TF это выглядит так (требует TF, в браузере не идёт):
import tensorflow as tf
a = tf.constant([[1.0, 2.0], [3.0, 4.0]])
b = tf.constant([[5.0, 6.0], [7.0, 8.0]])
print(a * b) # поэлементно
print(tf.matmul(a, b)) # матрично, то же что a @ b
print(tf.reduce_sum(a)) # 10.0
print(tf.reduce_mean(a, axis=0)) # среднее по столбцамКак работает под капотом
TF не выполняет операции в Python-цикле — он вызывает оптимизированные C++/CUDA-ядра, которые обрабатывают миллионы чисел параллельно (это называется векторизацией). Поэтому никогда не пишите ручные циклы по элементам тензора: один tf.matmul на тысячи раз быстрее эквивалентного Python-цикла.
Частые ошибки
- Путать
*иtf.matmul. Звёздочка — поэлементно, для линейного слоя нужно матричное умножение. - Несогласованные формы при
matmul. Внутренние размерности обязаны совпадать: (m,k)·(k,n). - Ручные циклы по тензору. Это медленно; используйте векторные операции TF.
Итог
- Поэлементные операции (
*) работают над одинаковыми формами элемент к элементу. - Матричное умножение (
tf.matmul,@) комбинирует строки и столбцы — основа линейных слоёв. - Редукции (
reduce_sum,reduce_mean) схлопывают тензор по оси. - Все операции векторизованы — ручные циклы по элементам не нужны и вредны для скорости.