Skip to content

13. Индексация 2D matrix и разделение табличных датасетов ​

Двумерная индексация и срезы ​

Синтаксис двумерного обращения по индексам ​

При переходе от векторов к матрицам NumPy использует лаконичный и производительный синтаксис индексации внутри одних квадратных скобок, где параметры осей разделяются запятой: matrix[row_index, column_index]

В отличие от стандартного Python, где для вложенных списков пришлось бы писать list[row][col] (что заставляет интерпретатор дважды выполнять поиск в памяти), NumPy на C-уровне вычисляет точный сдвиг в едином буфере данных по формуле strides (смещение) и извлекает элемент за один такт. Обратная индексация (с отрицательными значениями) работает по обеим осям абсолютно так же, как и в 1D-векторах.

Двумерные срезы (2D Slicing) и подматрицы ​

Чтобы извлечь из матрицы не одно число, а целую подматрицу (sub-matrix), используется оператор двоеточия : для каждой из осей: matrix[row_start:row_stop:row_step, col_start:col_stop:col_step]

Если по одной из осей указать одиночное двоеточие :, это означает «взять все элементы по данному направлению». Например:

  • matrix[0, :] — извлечь полностью самую первую строку в виде 1D-вектора.
  • matrix[:, 2] — извлечь полностью третий столбец.
python
import numpy

# Simulating a small dataset matrix (4 rows, 3 columns)
matrix_data = numpy.array([
    [10, 11, 12],
    [20, 21, 22],
    [30, 31, 32],
    [40, 41, 42]
])

# Extracting a specific scalar
print(matrix_data[1, 2])  # Row index 1, column index 2 -> Outputs: 22
print(matrix_data[1][2])  # Also outputs: 22 (but works slower under the hood)

# Extracting a sub-matrix (top-left 2x2 corner)
print(matrix_data[0:2, 0:2])
# Outputs:
# [[10 11]
#  [20 21]]

⚠️ Опасность логической ошибки: matrix[ , ] против matrix[][] ​

Раздельный синтаксис matrix[row][col] пришел из чистого Python и допустим в NumPy только для извлечения одиночных скаляров (хоть и работает медленнее из-за создания временного объекта-строки). Однако использовать его для срезов (Slicing) категорически запрещено, так как это ломает логику выборки данных.

Рассмотрим разницу на примере попытки извлечь подматрицу 2x2:

  1. Правильный подход: matrix_data[0:2, 0:2] NumPy одновременно применяет фильтр к строкам и столбцам за один проход по памяти. Вырезаются строки 0, 1 и столбцы 0, 1. Результат: Матрица размером (2, 2).

  2. Ошибочный подход: matrix_data[0:2][0:2] Поскольку скобки раздельные, они выполняются как цепочка независимых шагов, и обе операции применяются строго к первой оси (строкам):

    • Первые скобки [0:2] отрезают от исходного датасета первые две строки. Создается временная матрица размером (2, 3).
    • Вторые скобки [0:2] берут этот новый временный массив и снова возвращают его первые две строки. Столбцы при этом вообще не фильтруются.
    • Результат: Матрица размером (2, 3) вместо ожидаемой (2, 2).
python
import numpy

# Creating a fresh matrix space for validation
matrix_data = numpy.array([
    [10, 11, 12],
    [20, 21, 22],
    [30, 31, 32],
    [40, 41, 42]
])

# CORRECT: Cuts rows and columns at the same time (Fast & safe)
sub_matrix_correct = matrix_data[0:2, 0:2]
print("Correct slice (2x2):\n", sub_matrix_correct)
# Outputs:
# [[10 11]
#  [20 21]]

# BUG: Cuts rows twice, columns are completely ignored
sub_matrix_wrong = matrix_data[0:2][0:2]
print("Wrong slice (2x3):\n", sub_matrix_wrong)
# Outputs:
# [[10 11 12]
#  [20 21 22]]

Разделение данных и мини-батчинг ​

Изоляция признаков (Features) и целевой переменной (Target) ​

В машинном обучении табличные датасеты обычно хранятся в виде единой матрицы, где последний столбец содержит правильные ответы (таргет), а все предыдущие столбцы — входные признаки (фичи).

Перед подачей данных в модель их необходимо разделить на матрицу X и вектор y. С помощью двумерных срезов NumPy это делается в две строчки без копирования данных в памяти (создаются высокоскоростные представления Views):

python
import numpy

# Simulating a dataset where the last column is the target label
dataset = numpy.array([
    [1.5, 2.3, 0],
    [3.1, 0.8, 1],
    [0.5, 4.1, 0]
])

# X: Take all rows, and all columns except the last one
X = dataset[:, :-1]

# y: Take all rows, but only the very last column
y = dataset[:, -1]

print("Features X:\n", X)
# [[1.5 2.3]
#  [3.1 0.8]
#  [0.5 4.1]]

print("Target y:", y)  # [0. 1. 0.]

Нарезка мини-порции (Mini-batching) ​

При обучении нейросетей методом градиентного спуска (Mini-batch Gradient Descent) датасет подается в модель не целиком, а небольшими пачками (порциями) — мини-батчами.

Выделение конкретной пачки по строкам реализуется с помощью шаговых срезов по первой оси (axis=0). Например, если размер порции равен 2, то первый порция вычленяется как dataset[0:2, :], второй — dataset[2:4, :] и так далее.

python
import numpy

# Creating a simulated dataset directly as a 10x2 matrix
training_data = numpy.array([
    [0, 1], [2, 3], [4, 5], [6, 7], [8, 9],
    [10, 11], [12, 13], [14, 15], [16, 17], [18, 19]
])

batch_size = 2

# Extracting the third batch (rows 4 and 5) directly
start_index = 2 * batch_size  # index 4
end_index = start_index + batch_size  # index 6

third_batch = training_data[start_index:end_index, :]

print("Third batch data:\n", third_batch)   # Outputs: [[8 9][10 11]]

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

1. Извлечение конкретного признака объекта ​

Задача: Дана матрица датасета samples = numpy.array([[1.1, 2.2, 3.3], [4.4, 5.5, 6.6], [7.7, 8.8, 9.9]]). Извлеките элемент, находящийся в третьей строке (индекс 2) и втором столбце (индекс 1). Выведите его на экран.

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

samples = numpy.array([[1.1, 2.2, 3.3], [4.4, 5.5, 6.6], [7.7, 8.8, 9.9]])

# Direct 2D array coordination lookup
target_value = samples[2, 1]
print(target_value)  # Outputs: 8.8

2. Выделение изолированного вектора строки ​

Задача: Из матрицы samples из предыдущего упражнения извлеките целиком вторую строку (индекс 1) в виде одномерного вектора. Выведите полученный вектор.

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

samples = numpy.array([[1.1, 2.2, 3.3], [4.4, 5.5, 6.6], [7.7, 8.8, 9.9]])

# Slicing all columns for row index 1
second_row = samples[1, :]
print(second_row)  # Outputs: [4.4 5.5 6.6]

3. Разделение матрицы на фичи X и таргет y ​

Задача: Дан тренировочный мини-датасет: numpy.array([[0.25, 0.85, 1.0], [0.44, 0.12, 0.0]]). Используя отрицательные срезы, разделите его на матрицу признаков matrix_X (все столбцы, кроме последнего) и вектор ответов vector_y (только последний столбец). Выведите оба объекта.

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

raw_table = numpy.array([[0.25, 0.85, 1.0], [0.44, 0.12, 0.0]])

# Feature/Target boundary separation
matrix_X = raw_table[:, :-1]
vector_y = raw_table[:, -1]

print("Features:\n", matrix_X)
print("Target:", vector_y)

4. Извлечение правого нижнего блока матрицы ​

Задача: Создайте матрицу размером 4x4, содержащую числа от 0 до 15. С помощью двумерного среза извлеките из нее подматрицу размером 2x2, находящуюся в правом нижнем углу (последние две строки и последние два столбца). Выведите подматрицу на экран.

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

# Generating 4x4 matrix representation
grid_matrix = numpy.arange(16).reshape(4, 4)

# Slicing the bottom right corner block
corner_block = grid_matrix[-2:, -2:]
print(corner_block)
# Outputs:
# [[10 11]
#  [14 15]]

5. Нарезка первого мини-батча данных ​

Задача: Дан датасет matrix_data = numpy.array([[1, 2], [3, 4], [5, 6], [7, 8]]). Выделите из него первый мини-батч размером в 2 объекта (первые две строки матрицы, включая все столбцы). Выведите его.

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

matrix_data = numpy.array([[1, 2], [3, 4], [5, 6], [7, 8]])

# Slicing the first batch from index 0 to 2 (not inclusive)
first_batch = matrix_data[0:2, :]
print(first_batch)
# Outputs:
# [[1 2]
#  [3 4]]

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

6. Проверка метаданных и связи памяти для 2D-среза ​

Задача: Создайте матрицу source_matrix размером (4, 4) из нулей типа numpy.float32 (Урок 4 / Урок 11). Выделите из нее центральный блок размером (2, 2) (строки с 1 по 3, столбцы с 1 по 3, не включая правые границы) в переменную block_view (Урок 13). Проверьте объем памяти среза в байтах через .nbytes (Урок 2) и выведите логическое выражение block_view.base is source_matrix, чтобы доказать природу представлений (Урок 6).

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

# Allocating parent 2D layout (Lesson 4 & Lesson 11)
source_matrix = numpy.zeros((4, 4), dtype=numpy.float32)

# Extracting sub-matrix slice (Lesson 13)
block_view = source_matrix[1:3, 1:3]

# Verifying properties and memory tracking (Lesson 2 & Lesson 6)
print("Slice memory metrics:", block_view.nbytes, "bytes") # 4 elements * 4 bytes = 16 bytes
print("Is it linked to parent memory?", block_view.base is source_matrix) # Outputs: True

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

Задача: Инициализируйте матрицу весов из одних единиц размером (3, 3) типа numpy.float16 (Урок 11). С помощью двумерного среза извлеките из нее последний столбец (Урок 13). Умножьте элементы этого столбца на месте (in-place) на коэффициент 5.0 через оператор *= (Урок 8). Распечатайте исходную матрицу целиком и убедитесь, что мутация через представление изменила оригинальный буфер данных (Урок 6).

Посмотреть restriction
python
import numpy

# Defining 2D workspace matrix (Lesson 11)
weights_matrix = numpy.ones((3, 3), dtype=numpy.float16)

# Extracting the last column view (Lesson 13)
last_column_view = weights_matrix[:, -1]

# Mutating data buffer in-place to optimize allocations (Lesson 8)
last_column_view *= 5.0

# Verifying side-effects on the original array (Lesson 6)
print(weights_matrix)
# Outputs:
# [[1. 1. 5.]
#  [1. 1. 5.]
#  [1. 1. 5.]]

8. Слайсинг мини-батча со сложной ufuncs-активацией ​

Задача: Сгенерируйте упорядоченную матрицу логитов размером (4, 2) из чисел от -1 до 2 с помощью numpy.linspace и метода .reshape() (Урок 3 / Урок 11). Выделите из нее мини-батч, содержащий средние две строки (Урок 13). Примените к элементам этого батча векторизованную функцию активации Sigmoid по формуле (1 / (1 + e^{-x})) с помощью numpy.exp() (Урок 9). Выведите результат.

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

# Generating input matrix coordinates (Lesson 3 & Lesson 11)
logits_matrix = numpy.linspace(-1.0, 2.0, 8).reshape(4, 2)

# Extracting mini-batch slice rows 1 and 2 (Lesson 13)
batch_slice = logits_matrix[1:3, :]

# Executing complex element-wise activation math_oge (Lesson 9)
activated_probabilities = 1 / (1 + numpy.exp(-batch_slice))
print(activated_probabilities)

9. Булево маскирование и зануление аномалий внутри подматрицы ​

Задача: Дан зашумленный мини-батч признаков: numpy.array([[1.5, -99.0], [-99.0, 4.2]]) (Урок 11). Создайте его полную независимую копию через метод .copy() (Урок 6). Напишите булеву маску для поиска элементов, которые строго меньше нуля copy_matrix < 0 (Урок 10). Занулите эти аномальные элементы внутри изолированной копии (эмуляция ReLU). Выведите очищенный батч.

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

raw_batch = numpy.array([[1.5, -99.0], [-99.0, 4.2]])

# Securing allocations via explicit copying (Lesson 6 & Lesson 11)
clean_batch_copy = raw_batch.copy()

# Applying boolean condition mask assignment (Lesson 10)
clean_batch_copy[clean_batch_copy < 0] = 0.0

print("Sanitized independent matrix batch:\n", clean_batch_copy)
# Outputs:
# [[1.5 0. ]
#  [0.  4.2]]

10. Удаление строки из матрицы признаков ​

Задача: На основе вложенного списка создайте матрицу X_data = numpy.array([[10, 20], [30, 40], [999, 999]]) (Урок 11). Известно, что последняя строка содержит ошибочный лог. Используя функцию numpy.delete(), удалите полностью третью строку (индекс 2). В качестве параметра оси удалении укажите axis=0, чтобы NumPy понимал, что нужно убрать именно строку, а не столбец. Выведите полученную очищенную матрицу (Урок 6).

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

# Creating source 2D layout (Lesson 11)
X_data = numpy.array([[10, 20], [30, 40], [999, 999]])

# Deleting an element along axis=0 (rows deletion layout) (Lesson 6)
sanitized_X = numpy.delete(X_data, 2, axis=0)

print("Sanitized features matrix:\n", sanitized_X)
# Outputs:
# [[10 20]
#  [30 40]]