Skip to content

18. Изменение формы и сплющивание многомерных тензоров ​

Изменение размерности тензоров ​

Механика работы метода .reshape() ​

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

Главное правило изменения формы на C-уровне: общее количество элементов в массиве (.size) до трансформации и после неё должно оставаться строго неизменным. Метод .reshape() является чрезвычайно быстрым, поскольку по умолчанию он не копирует элементы в новые ячейки памяти, а возвращает высокоскоростное представление (View). Он просто меняет метаданные в заголовке массива: кортеж формы .shape и шаги в памяти .strides.

python
import numpy

# Creating a flat 1D vector of 6 features
flat_vector = numpy.array([10, 20, 30, 40, 50, 60])

# Reshaping 1D vector into a 2D matrix (2 rows, 3 columns)
matrix_2d = flat_vector.reshape(2, 3)
print(matrix_2d)
# Outputs:
# [[10 20 30]
#  [40 50 60]]

Автоматический подсчет размерности через параметр -1 ​

Если вы трансформируете огромный массив и знаете точную длину только одной или нескольких осей, вам не обязательно вручную высчитывать финальный размер оставшейся оси. В NumPy для этого реализован специальный параметр-джокер — -1.

Вы можете указать -1 в качестве значения только для одной из осей в кортеже формы. В этот момент NumPy на C-уровне автоматически разделит общее количество элементов .size на произведение длин всех остальных указанных осей и мгновенно вычислит нужный размер.

python
import numpy

large_stream = numpy.arange(12)

# Automatically calculate the number of columns by specifying 3 rows
auto_matrix = large_stream.reshape(3, -1)
print(auto_matrix.shape)  # Outputs strictly: (3, 4)

Прямое присваивание атрибуту .shape (исторический способ) ​

Помимо метода .reshape(), геометрию массива можно изменить прямым присваиванием кортежа атрибуту .shape:

python
import numpy

flat_vector = numpy.arange(12)
flat_vector.shape = (3, 4)   # In-place header rewrite, data buffer untouched
print(flat_vector)
# Outputs:
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]]

Механика идентична .reshape(): в заголовке массива меняются только метаданные (форма и strides), а физический буфер данных не пересобирается и не копируется. Два ограничения:

  • если произведение новых размеров не равно общему .size, NumPy выбросит ValueError (например, flat_vector.shape = (3, 5));
  • в актуальных версиях NumPy (2.5+) прямое присваивание атрибуту объявлено deprecated (генерируется DeprecationWarning) — в промышленном коде используйте numpy.reshape(array, (rows, cols)) или метод array.reshape(...).

Сплющивание (Flattening) для классификаторов ​

Разница между методами np.flatten() и np.ravel() ​

Когда данные выходят из многомерных скрытых слоев (например, из сверточных слоев сверточной нейросети CNN, где картинка хранится как 3D-тензор), их необходимо превратить в один плоский 1D-вектор признаков для последующей передачи в полносвязный классификатор. Этот процесс называется сплющиванием (Flattening).

В NumPy для этого существуют два метода, обладающие принципиально разным поведением на уровне оперативной памяти:

  1. .flatten() — производит глубокое копирование (Deep Copy). Метод выделяет новый независимый буфер памяти в RAM, полностью дублирует туда элементы и разворачивает их в 1D. Любые последующие изменения этого вектора не затронут оригинал.
  2. .ravel() — возвращает представление (View) везде, где это технически возможно на физическом уровне непрерывности памяти. Он работает мгновенно без выделения лишней оперативной памяти. Однако изменение элементов массива, полученного через .ravel(), неявно мутирует исходный многомерный тензор.
python
import numpy

tensor_space = numpy.array([[1, 2], [3, 4]])

# Method 1: Allocation copy setup
flat_copy = tensor_space.flatten()

# Method 2: High-speed view setup
flat_view = tensor_space.ravel()

Сравнение с концепциями PyTorch (torch.view и torch.reshape) ​

При переходе к глубокому обучению на PyTorch вы столкнетесь со стопроцентным повторением этой логики:

  • Метод torch.view() в PyTorch является полным аналогом быстрого решейпа в NumPy и требует строгой непрерывности буфера памяти, возвращая View.
  • Метод torch.reshape() в PyTorch работает гибко: он возвращает View, если память непрерывна, или автоматически делает физическую копию, если структура фрагментирована.

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

1. Трансформация вектора в квадратную матрицу ​

Задача: Создайте одномерный массив из 9 элементов от 1 до 9 с помощью numpy.arange(1, 10). Переформатируйте его в двумерную квадратную матрицу размером 3x3 с помощью метода .reshape(). Выведите полученную матрицу.

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

vector_data = numpy.arange(1, 10)

# Reshaping 1D sequence into 3x3 matrix space
square_matrix = vector_data.reshape(3, 3)
print(square_matrix)
# Outputs:
# [[1 2 3]
#  [4 5 6]
#  [7 8 9]]

2. Применение автоматического джокера размерности -1 ​

Задача: Дан одномерный вектор признаков из 8 элементов: numpy.array(). Переведите его в матрицу, у которой количество столбцов строго равно 2, а количество строк пускай NumPy рассчитает автоматически с помощью параметра -1. Выведите итоговую форму .shape.

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

raw_features = numpy.array()

# Forcing 2 columns layout, rows calculated implicitly
aligned_matrix = raw_features.reshape(-1, 2)

print("Inferred dynamic shape configuration:", aligned_matrix.shape)  # Outputs: (4, 2)
print(aligned_matrix)

3. Глубокое сплющивание матрицы через .flatten() ​

Задача: Создайте матрицу weights_matrix = numpy.array([[0.1, 0.2], [0.3, 0.4]]). Сплющите её в одномерный вектор flat_weights с помощью метода .flatten(). Измените первый элемент вектора flat_weights на 99.9 и докажите выводом на экран, что исходная матрица weights_matrix не подверглась скрытой мутации.

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

weights_matrix = numpy.array([[0.1, 0.2], [0.3, 0.4]])

# Safe independent deep copy flattening
flat_weights = weights_matrix.flatten()
flat_weights = 99.9

print("Original matrix (must stay safe):")
print(weights_matrix)  # Outputs original values without modifications

4. Высокоскоростное разворачивание через .ravel() ​

Задача: Для матрицы weights_matrix из предыдущего упражнения примените метод .ravel(), сохранив результат в переменную linked_vector. Выведите логическое сравнение linked_vector.base is weights_matrix, чтобы программно подтвердить создание View-структуры.

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

weights_matrix = numpy.array([[0.1, 0.2], [0.3, 0.4]])

# Fast flattening via view representation
linked_vector = weights_matrix.ravel()

# Verifying reference origins
print("Is it linked to parent memory allocations?", linked_vector.base is weights_matrix)  # True

5. Превращение матрицы-строки в вектор-столбец ​

Задача: Дан вектор-строка признаков: row_matrix = numpy.array([[1, 2, 3, 4]]) (форма 1x4). Трансформируйте её в вертикальный вектор-столбец формы (4, 1) с помощью метода .reshape(). Выведите результат.

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

row_matrix = numpy.array([[1, 2, 3, 4]])

# Converting horizontal matrix to vertical column block
column_matrix = row_matrix.reshape(4, 1)
print(column_matrix)
# Outputs:
# [[1]
#  [2]
#  [3]
#]

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

6. Решейп сгенерированной линейной сетки и аудит метаданных памяти ​

Задача: Сгенерируйте одномерный вектор из 12 элементов в диапазоне от -5.0 до 5.0 включительно через numpy.linspace (Урок 3). Переформатируйте его в 2D-матрицу с формой (4, 3) с помощью метода .reshape() (Урок 18). Выведите на экран характеристики полученного объекта: саму матрицу, размерность .ndim (Урок 11) и точные шаги перемещения в байтах через атрибут .strides (Урок 2 / Урок 11).

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

# Step 1: Linear grid sequence creation (Lesson 3)
flat_grid = numpy.linspace(-5.0, 5.0, 12)

# Step 2: Reshaping into matrix view buffer (Lesson 18)
grid_matrix = flat_grid.reshape(4, 3)

# Step 3: Architecture diagnostics (Lesson 2 & Lesson 11)
print("Matrix:\n", grid_matrix)
print("Dimensions count:", grid_matrix.ndim)  # 2
print("RAM byte strides configurations:", grid_matrix.strides)  # Outputs: (24, 8)
# Verification: float64 is 8 bytes. 1 column step = 8 bytes. 1 row step = 3 items * 8 bytes = 24 bytes.

7. Сплющивание многомерного батча изображений для полносвязного слоя ​

Задача: Сгенерируйте 4D-тензор, имитирующий мини-батч из 2 цветных картинок размером 3x3 пикселя: batch_images = numpy.ones((2, 3, 3, 3), dtype=numpy.float16) (Урок 4 / Урок 16). Представьте, что вам нужно передать эти данные в полносвязный классификатор. С помощью метода .reshape(batch_size, -1) превратите этот 4D-тензор в плоскую 2D-матрицу, где первая ось сохранит размер батча (2), а все пиксели и каналы каждой картинки вытянутся в одну строку автоматически (Урок 18). Выведите полученную форму .shape.

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

# 4D images batch container block allocation (Lesson 4 & Lesson 16)
batch_images = numpy.ones((2, 3, 3, 3), dtype=numpy.float16)

# Flattening images while protecting batch sizes axis alignment (Lesson 18)
# Dynamic joker dimension collapses (3 * 3 * 3) = 27 elements into a single feature row vector
flattened_batch = batch_images.reshape(2, -1)

print("Flattened matrix geometry shape layout:", flattened_batch.shape)  # Outputs: (2, 27)

8. Случайная инициализация весов с последующим решейпом под слой ​

Задача: С помощью современного Generator API (rng.normal) создайте одномерный вектор из 12 случайных весов со средним 0.0 и отклонением 0.01 (Урок 17). Переформатируйте этот вектор в матрицу весов W размером (4, 3) (Урок 18). Выполните матричное умножение входной детерминированной матрицы признаков X = numpy.array([[1.0, 2.0, 3.0, 4.0]]) (форма 1x4) на полученную матрицу весов W (Урок 14). Выведите результат.

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

rng = numpy.random.default_rng(seed=42)

# Step 1: Generate random flat vector stream (Lesson 17)
flat_weights = rng.normal(loc=0.0, scale=0.01, size=12)

# Step 2: Reshaping parameters to fit linear layer dimensions matrix (Lesson 18)
W_weights = flat_weights.reshape(4, 3)

X_input = numpy.array([[1.0, 2.0, 3.0, 4.0]])  # Shape (1, 4)

# Step 3: Matrix dot product mapping calculation (Lesson 14)
layer_output = X_input @ W_weights  # (1, 4) @ (4, 3) -> (1, 3)
print("Layer Output activations:\n", layer_output)

9. Нарезка батча со сплющиванием и вычислением Argmax предсказаний ​

Задача: Дан 3D-тензор скрытых финальных логов нейросети: network_outputs = numpy.array([[[0.1, 0.8], [0.9, 0.2]], [[0.3, 0.4], [0.1, 0.7]]]) (форма 2x2x2) (Урок 16). Выделите из него срез, содержащий только второй объект батча (индекс 1) (Урок 13). Сплющите полученную подматрицу в 1D-вектор с помощью быстрого метода .ravel() (Урок 18). Найдите индекс позиции максимального элемента в этом векторе с помощью метода .argmax() (Урок 15) и выведите его.

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

network_outputs = numpy.array([[[0.1, 0.8], [0.9, 0.2]], 
                               [[0.3, 0.4], [0.1, 0.7]]])

# Step 1: Extract tensor slice block (Lesson 13 & Lesson 16)
sample_matrix = network_outputs[1, :, :]  # Extracts matrix: [[0.3, 0.4], [0.1, 0.7]]

# Step 2: Instant flat view conversion (Lesson 18)
flat_logits = sample_matrix.ravel()  # Yields 1D vector: [0.3, 0.4, 0.1, 0.7]

# Step 3: Argmax index evaluation (Lesson 15)
winning_index = flat_logits.argmax()
print("Winning prediction configuration index:", winning_index)  # Outputs index position: 3

10. In-place модификация отслайсенного батча после изменения формы ​

Задача: Сгенерируйте пустую базовую матрицу из 8 нулей размером (4, 2) типа numpy.float32 (Урок 11). Сделайте решейп этой матрицы в одномерный плоский вектор flat_view с помощью метода .reshape(-1) (Урок 18). Возьмите срез из первых 4 элементов этого вектора (Урок 5) и прибавьте к ним на месте (in-place) скаляр 7.7 через оператор += (Урок 8). Выведите исходную базовую матрицу размером (4, 2) и посмотрите, как изменения сквозь цепочку решейпов и срезов отразились на оригинале благодаря механизму Views (Урок 6).

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

# Allocating parent workspace matrix (Lesson 11)
root_matrix = numpy.zeros((4, 2), dtype=numpy.float32)

# Transforming into a flat 1D view layer tracking original storage (Lesson 18)
flat_view = root_matrix.reshape(-1)

# Extracting vector slice boundaries (Lesson 5)
target_slice = flat_view[:4]

# Modifying structural memory buffers directly in-place (Lesson 8)
target_slice += 7.7

# Inspecting side-effects on original array references (Lesson 6)
print("Mutated root matrix layout:\n", root_matrix)
# Outputs the updated grid array showing perfect View chain propagation:
# [[7.7 7.7]
#  [7.7 7.7]
#  [0.  0. ]
#  [0.  0. ]]