Skip to content

12. Двумерные матрицы (2D): Создание и базовые атрибуты ​

Конструирование 2D матриц ​

Конвертация вложенных двумерных списков ​

Для перехода от одномерных векторов к двумерным пространствам в NumPy используется тот же конструктор numpy.array(), но на вход ему передается упорядоченная структура — вложенный список Python (список списков). В этот момент библиотека аллоцирует единый непрерывный блок оперативной памяти на C-уровне, куда копирует числовые элементы, выстраивая сетку строк и столбцов.

python
import numpy

# Converting a nested Python list into a 2D matrix
nested_list = [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]
dataset_matrix = numpy.array(nested_list)

print(dataset_matrix)
# Outputs:
# [[1. 2. 3.]
#  [4. 5. 6.]]

Геометрия и оси матриц (Axis 0 и Axis 1) ​

В двумерных массивах NumPy вводится строгое понятие направлений — осей вычислений (axes):

  • axis=0 — это вертикальная ось (строки). Движение вдоль этой оси означает переход от строки к строке внутри одного столбца.
  • axis=1 — это горизонтальная ось (столбцы). Движение вдоль нее означает переход от столбца к столбцу внутри одной строки.

Понимание осей критически важно для ИИ-инженеров, так как все последующие операции (агрегации, нормализации, свертки) будут требовать явного указания направления вычислений.

Проверка ключевых параметров и метаданных матрицы ​

Фундаментальные атрибуты ndarray в 2D пространстве ​

Для контроля геометрии и структуры матриц на практике используются встроенные свойства объекта:

  • .ndim — размерность массива. Для любой плоской матрицы этот показатель строго равен 2.
  • .shape — кортеж (tuple) геометрии формата (rows, columns). Для матрицы из 2 строк и 3 столбцов он вернет (2, 3). Соответствие .shape весов и входных тензоров — главное условие успешной работы слоев нейросети.
  • .size — общее число элементов. Рассчитывается как произведение строк на столбцы (rows * columns).
  • .nbytes — физический вес элементов матрицы в оперативной памяти (в байтах). Позволяет рассчитать, сколько VRAM займет табличный датасет или слой весов.
  • strides — кортеж (tuple) шагов в байтах, необходимых для перехода к следующему элементу вдоль каждой оси. Для 2D-матрицы формат имеет вид (row_stride, column_stride). Например, шаг по строкам показывает, сколько байт нужно «перешагнуть» в памяти, чтобы переместиться строго на одну строку вниз. Это свойство объясняет, как NumPy мгновенно выполняет транспонирование и создание срезов без реального копирования данных в RAM (изменяются только strides и .shape).
python
import numpy

matrix = numpy.array([[10, 20], [30, 40], [50, 60]], dtype=numpy.float32)

print("Dimensions (ndim):", matrix.ndim)   # Outputs: 2
print("Geometry (shape):", matrix.shape)   # Outputs: (3, 2) -> 3 rows, 2 columns
print("Total elements (size):", matrix.size) # Outputs: 6
print("Memory cost (nbytes):", matrix.nbytes) # Outputs: 24 bytes (6 items * 4 bytes)

Физическое устройство 2D-массива: Strides и C_CONTIGUOUS ​

Оперативная память компьютера физически является линейной (одномерной). Чтобы хранить двумерную матрицу, NumPy укладывает строки одну за другой в виде единого непрерывного буфера. Такой порядок называется C-style (Row-major) order и отмечается во флагах массива как C_CONTIGUOUS: True.

Из-за линейной укладки атрибут .strides (шаги в байтах) для 2D-матрицы возвращает кортеж из двух чисел: (шаг_по_строкам, шаг_по_столбцам).

  • Если матрица (3, 2) имеет тип int64 (8 байт), её .strides будет равен (16, 8). Это значит: чтобы сдвинуться на один столбец вправо (вдоль axis=1), процессору нужно перешагнуть 8 байт. А чтобы сдвинуться на одну строку вниз (вдоль axis=0), нужно перешагнуть целых 16 байт (пропустить всю текущую строку).

Ручное конструирование матриц с приведением к AI-типам данных ​

При инициализации матриц весов скрытых слоев или эмбеддингов, хорошим тоном в промышленной разработке является принудительное выставление энергоэффективных низкобитных вещественных типов (таких как numpy.float16). Это экономит видеопамять графического ускорителя (GPU) в 4 раза по сравнению со стандартным форматом двойной точности numpy.float64.

Вы также можете использовать специализированные двумерные конструкторы быстрого заполнения на базе изученных ранее методов: numpy.zeros((rows, cols)), numpy.ones((rows, cols)) и их аналоги _like для копирования структуры.

python
import numpy

# Initializing a low-precision 2D weight matrix for a neural network layer
# Note: dimensions must be passed as a tuple inside the function argument
layer_weights = numpy.ones((4, 2), dtype=numpy.float16)

print(layer_weights)        # Outputs: [[1. 1.] [1. 1.] [1. 1.] [1. 1.]]
print(layer_weights.dtype)  # Outputs: float16
print(layer_weights.shape)  # Outputs: (4, 2)

Специализированные конструкторы 2D-матриц и координатные сетки ​

Единичная матрица (numpy.eye) ​

Функция numpy.eye(N) мгновенно аллоцирует квадратную матрицу размера N x N с единицами на главной диагонали и нулями во всех остальных ячейках. Единичная матрица I — это нейтральный элемент матричного умножения: для любой матрицы A выполняется тождество A⋅I=A. В AI-практике она используется для построения диагональных структур весов, инициализации и маскирования.

python
import numpy

# Allocating a 4x4 identity matrix
identity_matrix = numpy.eye(4)
print(identity_matrix)
# Outputs:
# [[1. 0. 0. 0.]
#  [0. 1. 0. 0.]
#  [0. 0. 1. 0.]
#  [0. 0. 0. 1.]]

Диагональные матрицы: функция numpy.diag ​

Функция numpy.diag работает в двух направлениях:

  1. Вектор → матрица: numpy.diag(v) превращает 1D-вектор в квадратную диагональную матрицу, разместив элементы вектора на главной диагонали.
  2. Матрица → вектор: numpy.diag(matrix) извлекает главную диагональ 2D-матрицы в виде 1D-вектора.
python
import numpy

weights_vector = numpy.array([0.5, 0.2, 0.1])

# Vector -> diagonal matrix
diagonal_matrix = numpy.diag(weights_vector)
print("Diagonal matrix:\n", diagonal_matrix)
# Outputs:
# [[0.5 0.  0. ]
#  [0.  0.2 0. ]
#  [0.  0.  0.1]]

# Matrix -> main diagonal vector
extracted_diagonal = numpy.diag(diagonal_matrix)
print("Extracted diagonal:", extracted_diagonal)  # Outputs: [0.5 0.2 0.1]

След матрицы (numpy.trace) ​

След (trace) — это сумма всех элементов главной диагонали квадратной матрицы. Функция numpy.trace(matrix) вычисляет эту метрику одной командой на C-скорости. След широко применяется в линейной алгебре и при отладке диагональных преобразований весов.

python
import numpy

matrix = numpy.array([[1.0, 2.0, 3.0],
                      [0.0, 5.0, 6.0],
                      [0.0, 0.0, 7.0]])

print("Matrix trace:", numpy.trace(matrix))  # Outputs: 13.0

Координатные сетки: numpy.meshgrid ​

В задачах построения графиков, генерации координатных карт и обработки изображений часто нужны двумерные массивы, зависящие только от одного индекса: значения строк копируются из вектора (xij=uj), а значения столбцов — из другого вектора (yij=vi). Функция numpy.meshgrid(u, v) за один проход по памяти строит пару таких сеток:

  • grid_x — каждая строка равна вектору u (зависимость только от столбцового индекса);
  • grid_y — каждый столбец равен вектору v (зависимость только от строкового индекса).
python
import numpy

u_axis = numpy.linspace(1.0, 2.0, 2)   # [1. 2.]
v_axis = numpy.linspace(4.0, 8.0, 3)   # [4. 6. 8.]

grid_x, grid_y = numpy.meshgrid(u_axis, v_axis)
print("Grid X:\n", grid_x)
# Outputs:
# [[1. 2.]
#  [1. 2.]
#  [1. 2.]]

print("Grid Y:\n", grid_y)
# Outputs:
# [[4. 4.]
#  [6. 6.]
#  [8. 8.]]

Так как сетка физически содержит лишь копии исходных 1D-векторов, grid_x можно «выпрямить» обратно в вектор через reshape (Урок 18): grid_x.reshape(6) вернет [1. 2. 1. 2. 1. 2.].

Упражнения базовые (5 шт) (только с опорой на объясненную в данном уроке теорию) ​

1. Ручное создание матрицы признаков ​

Задача: Создайте двумерную матрицу из вложенного списка Python, содержащую следующие данные: первая строка [1, 2, 3], вторая строка [4, 5, 6]. Выведите матрицу на экран.

Посмотреть решение
python
import numpy

raw_data = [[1, 2, 3], [4, 5, 6]]

# Creating a basic 2D array matrix
features_matrix = numpy.array(raw_data)
print(features_matrix)

2. Извлечение геометрических параметров слоя ​

Задача: Инициализируйте нулевую матрицу весов размерностью 5 строк и 3 столбца с помощью функции numpy.zeros. Выведите в консоль два её атрибута: размерность (.ndim) и кортеж формы (.shape).

Посмотреть решение
python
import numpy

# Shape dimensions must be packed inside a tuple argument
weights_tensor = numpy.zeros((5, 3))

print("Dimensions count:", weights_tensor.ndim)         # Outputs: 2
print("Shape geometry tuple:", weights_tensor.shape)    # Outputs: (5, 3)

3. Оценка физического объема VRAM для датасета ​

Задача: Представьте, что вы загрузили матрицу признаков, содержащую 1 000 строк (объектов) и 50 столбцов (параметров). Создайте такую матрицу из одних единиц, принудительно выставив тип данных numpy.float32. Выведите на экран общее количество элементов (.size) и точный вес матрицы в байтах (.nbytes).

Посмотреть решение
python
import numpy

# Initializing simulated matrix structure
dataset_matrix = numpy.ones((1000, 50), dtype=numpy.float32)

print("Total elements inside the matrix:", dataset_matrix.size)  # 50000
print("Total physical weight in bytes:", dataset_matrix.nbytes)  # 200000 bytes (~195 KB)

4. Исследование шагов памяти (Strides) по двум осям ​

Задача: Создайте матрицу размером (2, 4) из целых чисел, явно указав тип данных dtype=numpy.int64. Выведите значение её атрибута .strides. Запишите в комментарии к коду обоснование полученных чисел.

Посмотреть решение
python
import numpy

# Creating row-major aligned matrix block
strict_matrix = numpy.ones((2, 4), dtype=numpy.int64)

print("Matrix strides:", strict_matrix.strides)  # Outputs: (32, 8)
# Meaning: Each int64 takes 8 bytes. To move 1 column right, we skip 8 bytes. 
# To move 1 row down, we must skip an entire row of 4 items (4 * 8 = 32 bytes).

5. Инициализация FP16 матрицы весов скрытого слоя ​

Задача: Создайте матрицу из одних единиц размером 3 строки на 3 столбца. Задайте ей тип данных полуточности numpy.float16. Выведите матрицу и её тип элементов на экран.

Посмотреть решение
python
import numpy

# Initializing half-precision tensor representation
low_prec_weights = numpy.ones((3, 3), dtype=numpy.float16)

print(low_prec_weights)
print("Data type verification:", low_prec_weights.dtype) # float16

Упражнения смешанные (6 шт) (включают материал прошлых уроков) ​

6. Создание 2D-матрицы на базе структуры сгенерированного вектора ​

Задача: Сгенерируйте одномерный вектор из 4 элементов типа numpy.float32 (Урок 4). Используя специализированный метод _like (Урок 3), создайте на его основе новую пустую двумерную матрицу размерностью (2, 2), которая автоматически унаследует тип данных float32 исходного вектора (Урок 11). Выведите .dtype и .shape новой матрицы.

Посмотреть решение
python
import numpy

# Creating template 1D vector (Lesson 4)
vector_template = numpy.array([0.1, 0.2, 0.3, 0.4], dtype=numpy.float32)

# Using zeros_like or ones_like requires matching the exact shape, 
# so we explicitly pass shape into a standard allocation constructor for a clean 2D layout:
target_matrix = numpy.zeros((2, 2), dtype=vector_template.dtype)

print("Inferred dtype from template:", target_matrix.dtype)  # float32
print("Geometric matrix shape configuration:", target_matrix.shape)  # (2, 2)

7. In-place масштабирование двумерной FP16 матрицы ​

Задача: Инициализируйте матрицу весов из одних единиц размером (2, 5) типа numpy.float16 (Урок 11). Умножьте эту матрицу на месте (in-place) на скалярный коэффициент 0.25 через оператор *=, чтобы оптимизировать потребление RAM (Урок 8). Выведите итоговый вес матрицы в байтах (Урок 2).

Посмотреть решение
python
import numpy

# Initializing 2D matrix layout with low precision (Lesson 11)
weights_fp16 = numpy.ones((2, 5), dtype=numpy.float16)

# Modifying elements buffer directly inside memory (Lesson 8)
weights_fp16 *= 0.25

print(weights_fp16)
print("Physical RAM footprints cost:", weights_fp16.nbytes, "bytes") # Outputs: 20 bytes

8. Провокация аномалий внутри матрицы и проверка метаданных ​

Задача: Создайте две матрицы размером (2, 2): первую с элементами [[1.0, 1.0], [0.0, 0.0]] и вторую с элементами [[1.0, 0.0], [0.0, 0.0]]. Разделите первую матрицу на вторую поэлементно (Урок 7). В результате возникнут математические аномалии: деление ненулевого числа на ноль даст inf, а деление нуля на ноль даст nan (Урок 9). Выведите полученную матрицу с аномалиями и проверьте, остались ли неизменными её характеристики формы .shape и размерности .ndim (Урок 11).

Посмотреть решение
python
import numpy

# Creating dividend and divisor matrices (Lesson 11)
matrix_a = numpy.array([[1.0, 1.0], [0.0, 0.0]])
matrix_b = numpy.array([[1.0, 0.0], [0.0, 0.0]])

# Element-wise division triggers IEEE 754 warnings (Lesson 7 & Lesson 9)
# [1/1, 1/0] -> [1.0, inf]
# [0/0, 0/0] -> [nan, nan]
anomalous_matrix = matrix_a / matrix_b

print("Anomalous matrix data:\n", anomalous_matrix)             # [[ 1. inf] [nan nan]]
print("Shape geometry verification:", anomalous_matrix.shape)   # Still (2, 2)
print("Dimensions count verification:", anomalous_matrix.ndim)  # Still 2

9. Поэлементная арифметика над матрицами признаков ​

Задача: Создайте матрицу matrix_A = numpy.array([[1, 2], [3, 4]]) (Урок 11). Создайте матрицу matrix_B = numpy.array([[10, 20], [30, 40]]). Выполните поэлементное умножение матриц через оператор * (Урок 7). К результату прибавьте скалярную константу numpy.e (Урок 7). Выведите итоговую матрицу.

Посмотреть решение
python
import numpy

matrix_A = numpy.array([[1, 2], [3, 4]])
matrix_B = numpy.array([[10, 20], [30, 40]])

# Element-wise multiplication followed by scalar addition upcast (Lesson 7)
processed_output = (matrix_A * matrix_B) + numpy.e
print(processed_output)     # [[ 12.71828183  42.71828183] [ 92.71828183 162.71828183]]

10. Явное преобразование типов вещественной матрицы и оценка шагов в памяти ​

Задача: Создайте линейную числовую сетку по оси X из 6 элементов от 1.5 до 6.5 через linspace (Урок 3). Принудительно приведите этот вектор к типу numpy.int32 с помощью метода .astype() (Урок 7). Представьте, что вы вручную сконструировали из этого новую матрицу признаков размером (2, 3) (Урок 11). Выведите полученную матрицу и .strides сколько байт теперь занимает переход на одну строку вниз.

Посмотреть решение
python
import numpy

# Symmetrical vector generation (Lesson 3)
raw_grid = numpy.linspace(1.5, 6.5, 6)

# Cast truncation to integers (Lesson 7)
int_grid = raw_grid.astype(numpy.int32) # [1 2 3 4 5 6]

# Manual transformation mimicking a 2D dataset space layout (Lesson 11)
dataset_matrix = numpy.array([[int_grid[0], int_grid[1], int_grid[2]], 
                              [int_grid[3], int_grid[4], int_grid[5]]])

print("Matrix configuration:\n", dataset_matrix)                        # [[1 2 3] [4 5 6]]
print("Calculated strides byte offset paths:", dataset_matrix.strides)  # Outputs: (12, 4)
# Verification: Each int32 is 4 bytes. Moving down 1 row means skipping 3 elements (3 * 4 = 12 bytes).

11. Составная диагональная матрица: главная и побочная диагонали ​

Задача: Создайте квадратную матрицу размера 8x8, на главной диагонали которой идет арифметическая прогрессия с шагом 3 (начиная с 3: 3, 6, 9, ..., 24), на побочной диагонали (от левого нижнего угла к правому верхнему) стоят элементы -1, а все остальные элементы равны 0 (Уроки 3, 12). Подсказка: побочная диагональ получается из единичной матрицы разворотом строк: numpy.eye(8)[::-1] (Урок 13). Выведите итоговую матрицу.

Посмотреть решение
python
import numpy

# Flipped identity matrix: the diagonal moves to the anti-diagonal
anti_diagonal = -1 * numpy.eye(8)[::-1]

# Arithmetic progression 3, 6, 9, ... 24 on the main diagonal
main_diagonal = numpy.diag(numpy.arange(3, 27, 3))

# Superposition of two sparse diagonal structures
composed_matrix = anti_diagonal + main_diagonal
print(composed_matrix)
# Outputs:
# [[ 3.  0.  0.  0.  0.  0.  0. -1.]
#  [ 0.  6.  0.  0.  0.  0. -1.  0.]
#  [ 0.  0.  9.  0.  0. -1.  0.  0.]
#  [ 0.  0.  0. 12. -1.  0.  0.  0.]
#  [ 0.  0.  0. -1. 15.  0.  0.  0.]
#  [ 0.  0. -1.  0.  0. 18.  0.  0.]
#  [ 0. -1.  0.  0.  0.  0. 21.  0.]
#  [-1.  0.  0.  0.  0.  0.  0. 24.]]