Skip to content

11. Фильтрация 1D массивов, булевы маски и функция ReLU ​

Булева индексация и фильтрация ​

Механизм работы булевых масок на C-уровне ​

Булева фильтрация (индексация) — это один из самых мощных инструментов в NumPy, позволяющий извлекать, изменять или анализировать элементы массива на основе логических условий полностью без использования циклов for и ветвлений if.

Когда вы применяете оператор сравнения (например, >, <, ==, !=) к массиву NumPy, библиотека выполняет векторизованную операцию сравнения на C-уровне. Вместо одного логического значения вы мгновенно получаете новый массив — булеву маску (boolean mask). Она имеет точно такую же форму (shape), как и исходный массив, но состоит исключительно из элементов типа numpy.bool.

python
import numpy

# Simulating confidence scores of a computer vision object detector
confidence_scores = numpy.array([0.22, 0.85, 0.41, 0.92, 0.15])

# Generating a boolean mask for high-confidence predictions
high_confidence_mask = confidence_scores > 0.5

print(high_confidence_mask)  # Outputs: [False  True False  True False]
print(high_confidence_mask.dtype)  # Outputs: bool

Извлечение данных по маске ​

Если передать полученную булеву маску внутрь квадратных скобок исходного массива array[mask], NumPy выполнит фильтрацию: он пройдется по памяти и соберет в новый массив только те элементы, напротив которых в маске стояло значение True.

python
import numpy

confidence_scores = numpy.array([0.22, 0.85, 0.41, 0.92, 0.15])
high_confidence_mask = confidence_scores > 0.5

# Filtering operation
filtered_predictions = confidence_scores[high_confidence_mask]
print(filtered_predictions)  # Outputs: [0.85 0.92]

Логическое комбинирование условий ​

Если вам нужно отфильтровать массив по нескольким условиям сразу, стандартные операторы Python and, or и not использовать нельзя, так как они пытаются вычислить логическое значение всего массива целиком. Вместо них в NumPy применяются побитовые операторы, которые работают поэлементно:

  • & — логическое И (AND)
  • | — логическое ИЛИ (OR)
  • ~ — логическое НЕ / инверсия (NOT)

Обязательные круглые скобки

При комбинировании булевых условий в NumPy каждое отдельное сравнение обязательно должно быть обернуто в круглые скобки. Побитовые операторы в Python имеют более высокий приоритет, чем операторы сравнения, и без скобок код выдаст критическую ошибку ValueError.

python
import numpy

features = numpy.array([-10, 5, 20, 35, -2])

# Correct syntax with parentheses and bitwise AND
valid_range_mask = (features >= 0) & (features <= 25)
print(features[valid_range_mask])  # Outputs: [ 5 20]

Условные операторы и функция ReLU в AI ​

Математическая логика и реализация ReLU ​

В глубоком обучении одной из самых популярных функций активации является ReLU (Rectified Linear Unit). Она работает как триггер: пропускает через себя без изменений все положительные сигналы, а все отрицательные — зануляет. Её математическая формула: f(x)=max(0,x).

В NumPy реализация ReLU на основе булевой маски выполняется путем модификации элементов «на месте» (in-place mutation), что критически важно для экономии памяти при обработке миллиардов скрытых параметров нейросети:

python
import numpy

# Hidden layer activations containing raw neural signals (logits)
layer_activations = numpy.array([-2.5, 4.1, -0.1, 1.8, -12.4])

# Applying ReLU via boolean assignment
layer_activations[layer_activations < 0] = 0.0

print(layer_activations)  # Outputs activated features: [0.  4.1 0.  1.8 0. ]

Векторный оператор numpy.where (Быстрая замена if-else) ​

Функция numpy.where(condition, x, y) — это векторизованный аналог тернарного оператора x if condition else y. Она проверяет булево условие condition и для каждой ячейки массива возвращает элемент из массива x, если условие истинно (True), или из массива y, если условие ложно (False). При этом вместо массивов x и y можно передавать одиночные числа-скаляры.

Этот метод идеален для задач бинарной классификации (перевод вероятностей в жесткие метки классов 0 или 1) и для точечной очистки датасетов от аномалий:

python
import numpy

predicted_probabilities = numpy.array([0.12, 0.88, 0.45, 0.61, 0.03])

# If probability > 0.5 -> class 1, else -> class 0
predicted_classes = numpy.where(predicted_probabilities > 0.5, 1, 0)
print(predicted_classes)  # Outputs: [0 1 0 1 0]

Функции быстрой проверки any и all (Кванторы всеобщности и существования) ​

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

  • numpy.any(condition) — возвращает True, если хотя бы один элемент массива удовлетворяет логическому условию. Часто используется для быстрого поиска критических ошибок или NaN в весах.
  • numpy.all(condition) — возвращает True только тогда, когда абсолютно все элементы массива соответствуют условию.
python
import numpy

weights = numpy.array([0.1, -0.5, numpy.nan, 0.4])

# Fast checking for presence of broken NaN data
has_nan = numpy.any(numpy.isnan(weights))
print("Are there any NaNs in weights?", has_nan)  # Outputs: True

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

1. Выделение аномально высоких температур ​

Задача: Дан вектор показаний датчиков температуры: [22.5, 45.1, 18.9, 52.4, 21.0]. Сгенерируйте булеву маску для температур, строго превышающих критическую отметку в 40.0 градусов. Извлеките эти аномальные элементы в новый массив с помощью маски и выведите его.

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

sensor_temperatures = numpy.array([22.5, 45.1, 18.9, 52.4, 21.0])

# Step 1: Create a boolean mask
critical_mask = sensor_temperatures > 40.0

# Step 2: Extract filtered records
extreme_temperatures = sensor_temperatures[critical_mask]
print(extreme_temperatures)  # Outputs: [45.1 52.4]

2. Фильтрация признаков по двойному условию ​

Задача: Дан вектор численных признаков: [-5, 12, 0, 18, 40, -1]. С помощью побитовых логических операторов извлеките элементы, которые одновременно больше или равны 0 и строго меньше 20. Выведите отфильтрованный вектор.

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

raw_features = numpy.array([-5, 12, 0, 18, 40, -1])

# Utilizing bitwise AND with strict parenthesis syntax
filtered_features = raw_features[(raw_features >= 0) & (raw_features < 20)]
print(filtered_features)  # Outputs: [12  0 18]

3. Ручная имплементация ReLU-активации ​

Задача: Инициализируйте массив сырых выходных сигналов нейросети raw_logits = numpy.array([-1.5, 2.3, -0.8, 0.0, 4.5]). Примените к нему логику функции активации ReLU: найдите все отрицательные элементы с помощью булевой маски и перезапишите их значение на 0.0. Выведите измененный массив.

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

raw_logits = numpy.array([-1.5, 2.3, -0.8, 0.0, 4.5])

# In-place substitution of negative logits to replicate ReLU trigger
raw_logits[raw_logits < 0] = 0.0
print(raw_logits)  # Outputs: [0.  2.3 0.  0.  4.5]

4. Квантование предсказаний через numpy.where ​

Задача: Модель предсказала массив вероятностей оттока пользователей: [0.15, 0.72, 0.49, 0.95]. Используя функцию numpy.where, преобразуйте этот массив в вектор строгих текстовых меток: если вероятность больше 0.5, ячейка должна получить строку "churn", в противном случае — "loyal". Выведите результат.

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

churn_probabilities = numpy.array([0.15, 0.72, 0.49, 0.95])

# Vectorized ternary mapping using numpy.where
customer_status = numpy.where(churn_probabilities > 0.5, "churn", "loyal")
print(customer_status)  # Outputs: ['loyal' 'churn' 'loyal' 'churn']

5. Экспресс-проверка градиентов на затухание ​

Задача: Дан вектор вычисленных градиентов весов нейросети: [0.001, 0.000, 0.002, 0.000]. Напишите логическое выражение с использованием numpy.any(), которое проверит, есть ли в данном векторе хотя бы один градиент, который строго равен 0.0 (проблема затухающих градиентов). Выведите результат логической проверки.

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

weight_gradients = numpy.array([0.001, 0.000, 0.002, 0.000])

# Check for presence of absolute zero signals
is_gradient_vanishing = numpy.any(weight_gradients == 0.0)
print("Are there vanishing gradients?", is_gradient_vanishing)  # Outputs: True

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

6. Фильтрация и подсчет памяти сгенерированной сетки ​

Задача: Сгенерируйте линейную последовательность из 10 элементов в диапазоне от -10 до 10 включительно (Урок 3). Извлеките из нее все положительные числа (строго больше 0) с помощью булевой маски (Урок 10). Выведите полученный массив и объем памяти в байтах, который занимают его элементы (Урок 2).

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

# Generating the source grid (Lesson 3)
base_grid = numpy.linspace(-10, 10, 10)

# Boolean filtering (Lesson 10)
positive_elements = base_grid[base_grid > 0]

print("Filtered array:", positive_elements)
print("Memory footprint in bytes:", positive_elements.nbytes)

7. Точечная замена пропусков NaN в представлении массива ​

Задача: Инициализируйте массив сырых данных [1.2, numpy.nan, 4.8, numpy.nan] типа numpy.float32 (Урок 4). Сделайте срез первых 3-х элементов в переменную data_view (Урок 5), которая является представлением оригинала (Урок 6). Используя функцию numpy.isnan(data_view) в качестве булева условия для numpy.where, замените все NaN внутри этого представления на дефолтное число 0.0. Выведите измененный исходный массив.

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

# Correct float32 setup with NaN markers (Lesson 4)
raw_signals = numpy.array([1.2, numpy.nan, 4.8, numpy.nan], dtype=numpy.float32)

# Slicing a subset view (Lesson 5 & Lesson 6)
data_view = raw_signals[:3]

# Using numpy.where to patch NaNs inside the view slice (Lesson 10)
data_view[:] = numpy.where(numpy.isnan(data_view), 0.0, data_view)

print("Mutated source array:", raw_signals)  # Outputs: [1.2 0.  4.8 nan]

8. Очистка изолированной копии квантованных меток от выбросов ​

Задача: Создайте вектор квантованных ID классов [1, 2, 99, 3, -99] с явным типом данных numpy.int8 (Урок 4). Чтобы не повредить исходный лог, сделайте полную независимую копию этого вектора через .copy() (Урок 6). В этой копии с помощью комбинированного побитового условия (Урок 10) найдите элементы, которые либо строго меньше 0, либо строго больше 10, и занулите их. Выведите исходный вектор и очищенную копию.

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

# Initial 8-bit integer setup (Lesson 4)
corrupted_labels = numpy.array([1, 2, 99, 3, -99], dtype=numpy.int8)

# Safe memory isolation (Lesson 6)
clean_labels_copy = corrupted_labels.copy()

# Applying bitwise OR operation to locate anomalies (Lesson 10)
anomaly_mask = (clean_labels_copy < 0) | (clean_labels_copy > 10)
clean_labels_copy[anomaly_mask] = 0

print("Source logs untouched:", corrupted_labels) # [  1   2  99   3 -99]
print("Sanitized batch copy:", clean_labels_copy) # [1 2 0 3 0]

9. Векторизованный расчет штрафов для отфильтрованного сабсета ​

Задача: Дан вектор вероятностей правильных ответов модели: [0.8, 0.1, 0.9, 0.4]. Сделайте срез последних 3-х элементов (Урок 5). Из этого среза отфильтруйте только те вероятности, которые строго меньше 0.5 (слабые предсказания) (Урок 10). Примените к полученным слабым вероятностям универсальную функцию логарифмического штрафа -numpy.log(x) (Урок 9). Выведите итоговый массив штрафов.

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

prediction_probabilities = numpy.array([0.8, 0.1, 0.9, 0.4])

# Step 1: Slice subset (Lesson 5)
target_slice = prediction_probabilities[-3:]  # [0.1, 0.9, 0.4]

# Step 2: Apply mask condition (Lesson 10)
weak_predictions = target_slice[target_slice < 0.5]  # [0.1, 0.4]

# Step 3: Run vectorized mathematical ufunc (Lesson 9)
incurred_penalties = -numpy.log(weak_predictions)
print("Penalties for weak predictions:", incurred_penalties)

10. Проверка полной валидности сгенерированной логарифмической сетки ​

Задача: Сгенерируйте логарифмическую сетку из 5 элементов в диапазоне степеней от -2 до 2 (Урок 3). Напишите логическую проверку с использованием numpy.all(), которая проверяет, правда ли, что абсолютно все элементы полученного массива строго больше коэффициента отсечения 0.005 (Урок 10). Выведите результат (True/False).

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

# Generating logarithmic spacing vector (Lesson 3)
hyperparams_grid = numpy.logspace(-2, 2, 5)  # [0.01, 0.1, 1.0, 10.0, 100.0]

# Validating the entire matrix using .all() (Lesson 10)
are_all_above_threshold = numpy.all(hyperparams_grid > 0.005)
print("Are all values safe?", are_all_above_threshold)  # Outputs: True

11. Сито Эратосфена: векторизованная проверка простых чисел ​

Задача: Создайте булев вектор длины 100 (Урок 4), в котором True означает «число простое». Изначально пометьте все 100 позиций как True и сбросьте первые две позиции (числа 0 и 1 не являются простыми). Затем, используя единственный цикл for и шаговый срез с булевым присваиванием mask[2*i::i] = False (Урок 5), «вычеркните» все кратные для каждого кандидата-делителя (Урок 11). Выведите первые 20 флагов, общее количество простых чисел до 100 и значения флагов для позиций 17 (простое) и 25 (составное).

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

# Initial boolean mask: assume all 100 numbers are prime
is_prime_mask = numpy.ones(100, dtype=numpy.bool_)

# 0 and 1 are not prime numbers
is_prime_mask[:2] = False

# Sieve of Eratosthenes: cross out multiples of each divisor
limit = int(numpy.sqrt(100))
for i in range(2, limit):
    if is_prime_mask[i]:
        is_prime_mask[2*i::i] = False  # start at 2*i with step i

print("First 20 flags:", is_prime_mask[:20])
# Outputs: [False False  True  True False  True False  True False False False
#           True False  True False False False  True False  True]

print("Number of primes below 100:", numpy.count_nonzero(is_prime_mask))  # 25
print("Is 17 prime?", is_prime_mask[17])  # Outputs: True
print("Is 25 prime?", is_prime_mask[25])  # Outputs: False