Appearance
8. Векторизованная арифметика: операции со скалярами и поэлементная математика
Базовая векторная арифметика и типы операндов
Математические операции в режиме «вектор со скаляром»
Когда вы применяете стандартные арифметические операторы (+, -, *, /, **) к массиву NumPy и одиночному числу (скаляру), библиотека выполняет операцию векторизованно. NumPy автоматически проецирует скаляр на каждый отдельный элемент массива. Вычисления происходят на уровне скомпилированного C-кода, полностью минуя медленные циклы Python.
python
import numpy
# Simulating initial model weights
weights = numpy.array([0.5, -1.2, 3.0])
# Array and scalar operations
scaled_weights = weights * 2.0
shifted_weights = weights + 0.1
print("Scaled:", scaled_weights) # Outputs: [ 1. -2.4 6. ]
print("Shifted:", shifted_weights) # Outputs: [ 0.6 -1.1 3.1]Математические операции в режиме «вектор с вектором» (Поэлементная математика)
Если арифметическая операция выполняется между двумя массивами NumPy, вычисления по умолчанию происходят поэлементно (element-wise). Элемент с индексом 0 первого массива взаимодействует с элементом с индексом 0 второго массива и так далее.
Требование идентичности форм (shapes)
Чтобы выполнить поэлементную операцию между двумя векторами, они обязательно должны иметь одинаковую длину (одинаковую форму .shape). Если вы попытаетесь сложить или умножить векторы разной длины, NumPy не сможет сопоставить элементы поэлементно и выбросит критическую ошибку ValueError: operands could not be broadcast together.
python
import numpy
# Simulating prediction tokens and target tokens
predictions = numpy.array([1.0, 2.0, 3.0])
targets = numpy.array([1.0, 0.0, 3.0])
# Element-wise multiplication and subtraction
differences = predictions - targets
print("Differences:", differences) # Outputs: [0. 2. 0.]
multiplied = predictions * targets
print("Multiplied:", multiplied) # Outputs: [1. 0. 9.]Приведение типов при операциях (Upcasting к большему)
Автоматическое расширение типов данных
При выполнении математических операций над массивами и скалярами разных типов данных NumPy автоматически применяет правила безопасного расширения типов (Upcasting). Результат операции всегда приводится к наиболее емкому (большему) типу данных из участвующих в выражении, чтобы предотвратить потерю точности.
Если к целочисленному вектору (int32) прибавить дробное число (тип которого в Python по умолчанию трактуется как float64), NumPy автоматически создаст новый массив, расширив тип каждого элемента до вещественного float64.
python
import numpy
# Creating a 32-bit integer array
input_data = numpy.array([1, 2, 3], dtype=numpy.int32)
# Adding a float scalar forces an automatic upcast
transformed_data = input_data + 0.5
print(input_data.dtype) # Outputs: int32
print(transformed_data.dtype) # Outputs: float64
print(transformed_data) # Outputs: [1.5 2.5 3.5]Встроенные математические константы
NumPy содержит встроенные математические константы высокой точности, которые хранятся как стандартные вещественные числа типа float64:
numpy.e— число Эйлера (база натурального логарифма), приблизительно равное2.718281828459045. Используется при расчете экспоненциального сглаживания и функций активации.numpy.pi— число Пи, приблизительно равное3.141592653589793. Используется для тригонометрических преобразований и генерации волновых сигналов.
python
import numpy
# Printing high-precision constants
print("Euler's number (e):", numpy.e)
print("Pi constant (pi):", numpy.pi)Упражнения базовые (5 шт) (только с опорой на объясненную в данном уроке теорию)
1. Поэлементный расчет ошибки отклонения
Задача: Даны два вектора одинаковой длины: истинные значения gold_targets = numpy.array([10.0, 20.0, 30.0]) и ответы модели model_outputs = numpy.array([9.5, 21.0, 30.0]). Вычислите вектор их поэлементных отклонений по формуле gold_targets - model_outputs без циклов. Выведите результат.
Посмотреть решение
python
import numpy
gold_targets = numpy.array([10.0, 20.0, 30.0])
model_outputs = numpy.array([9.5, 21.0, 30.0])
# Element-wise subtraction between two arrays
absolute_errors = gold_targets - model_outputs
print(absolute_errors) # Outputs: [ 0.5 -1. 0. ]2. Отслеживание автоматического Upcasting типа
Задача: Создайте целочисленный массив indices = numpy.array([10, 20, 30], dtype=numpy.int32). Разделите этот массив на скалярное число 2.0 (тип float). Выведите в консоль тип данных получившегося массива (.dtype), чтобы подтвердить автоматический каст к большему вещественному типу.
Посмотреть решение
python
import numpy
indices = numpy.array([10, 20, 30], dtype=numpy.int32)
# Dividing by a float scalar triggers automatic upcasting
scaled_indices = indices / 2.0
print("Resulting dtype:", scaled_indices.dtype) # Outputs: float64
print("Resulting array:", scaled_indices) # Outputs: [ 5. 10. 15.]3. Векторизованное масштабирование признаков
Задача: Создайте массив исходных признаков из списка [1.0, 2.0, 3.0]. Возведите все элементы массива в квадрат (в степень 2) одной векторизованной операцией с числом и выведите результат.
Посмотреть решение
python
import numpy
raw_features = numpy.array([1.0, 2.0, 3.0])
# Array and scalar power operation
squared_features = raw_features ** 2
print(squared_features) # Outputs: [1. 4. 9.]4. Инициализация вектора с константой Pi
Задача: Передайте в функцию создания массива numpy.array() список, содержащий одно число — константу numpy.pi. Умножьте этот массив на скаляр 2 и выведите результат в консоль.
Посмотреть решение
python
import numpy
# Array creation with a math_oge constant
pi_vector = numpy.array([numpy.pi])
# Vectorized multiplication by scalar
doubled_pi = pi_vector * 2
print(doubled_pi) # Outputs: [6.28318531]Упражнения смешанные (5 шт) (включают материал прошлых уроков)
5. Поэлементная арифметика над сгенерированной сеткой
Задача: Сгенерируйте массив из 4 элементов от 0 до 3 с помощью функции numpy.arange (Урок 3). Сгенерируйте второй линейный массив из 4 элементов от 10 до 20 включительно с помощью numpy.linspace (Урок 3). Сложите эти два массива поэлементно (Урок 7) и выведите итоговый результат.
Посмотреть решение
python
import numpy
# Generating vectors (Lesson 3)
first_vector = numpy.arange(0, 4) # Outputs: [0 1 2 3]
second_vector = numpy.linspace(10, 20, 4) # Outputs: [10. 13.333 16.666 20.]
# Element-wise addition (Lesson 7)
combined_array = first_vector + second_vector
print(combined_array) # Outputs: [10. 14.33333333 18.66666667 23.]6. Конвертация типов и оценка занимаемой памяти
Задача: Создайте массив вещественных чисел от 0.5 до 4.5 длины 5 (используя numpy.linspace из Урока 3). Проверьте объем занимаемой памяти в байтах с помощью .nbytes (Урок 2). Принудительно приведите массив к типу numpy.int32 через метод .astype() (Урок 7). Выведите объем занимаемой памяти обновленного массива и посчитайте в уме, во сколько раз он сжался.
Посмотреть решение
python
import numpy
# Generating initial array (Lesson 3)
float_grid = numpy.linspace(0.5, 4.5, 5) # Default is float64 (8 bytes per item)
print("Initial memory footprint:", float_grid.nbytes, "bytes") # 40 bytes
# Explicit conversion to int32 (4 bytes per item) (Lesson 7)
int_grid = float_grid.astype(numpy.int32)
print("Truncated elements:", int_grid) # [0 1 2 3 4]
print("Reduced memory footprint:", int_grid.nbytes, "bytes") # 20 bytes (2 times less)7. Математическая операция над срезом квантованного массива
Задача: Создайте вектор квантованных параметров из 6 элементов [10, 20, 30, 40, 50, 60] типа numpy.int8 (Урок 4). Возьмите срез из первых 3-х элементов (Урок 5). Прибавьте к полученному срезу скалярный коэффициент 1.5 (Урок 7). Выведите результат и проверьте его итоговый .dtype.
Посмотреть решение
python
import numpy
# Creating quantized array (Lesson 4)
quantized_tensor = numpy.array([10, 20, 30, 40, 50, 60], dtype=numpy.int8)
# Extracting a slice view (Lesson 5)
target_slice = quantized_tensor[:3]
# Scalar math_oge operation causes automatic upcasting to float64 (Lesson 7)
activated_slice = target_slice + 1.5
print("Resulting values:", activated_slice) # [11.5 21.5 31.5]
print("Final inferred dtype:", activated_slice.dtype) # float648. Арифметика над изолированной копией зашумленного вектора
Задача: Инициализируйте массив вероятностей с пропущенным значением: [0.1, numpy.nan, 0.4] типа numpy.float32 (Урок 4). Чтобы защитить исходный лог от изменений, сделайте его полную независимую копию через .copy() (Урок 6). В полученной копии замените элемент с индексом 1 (NaN) на рабочее значение 0.0 (Урок 5). Умножьте всю очищенную копию на скаляр 10.0 (Урок 7) и выведите результат.
Посмотреть решение
python
import numpy
# Safe float32 initialization with a NaN marker (Lesson 4)
corrupted_vector = numpy.array([0.1, numpy.nan, 0.4], dtype=numpy.float32)
# Deep copying to secure memory (Lesson 6)
clean_vector_copy = corrupted_vector.copy()
# Updating elements by index (Lesson 5)
clean_vector_copy[1] = 0.0
# Vectorized math_oge multiplication by a scalar (Lesson 7)
final_scaled_vector = clean_vector_copy * 10.0
print("Processed independent vector copy:", final_scaled_vector) # [ 1. 0. 4.]9. Поэлементное умножение тригонометрических векторов
Задача: Сгенерируйте вектор first_factor из 3 элементов, заполненный единицами, с помощью numpy.ones (Урок 3). Прибавьте к нему константу numpy.e (Урок 7). Создайте второй вектор second_factor = numpy.array([2.0, 2.0, 2.0]). Перемножьте эти два вектора поэлементно (Урок 7) и выведите результат.
Посмотреть решение
python
import numpy
# Generating base components (Lesson 3 & Lesson 7)
first_factor = numpy.ones(3) + numpy.e # Yields: [3.71828, 3.71828, 3.71828]
second_factor = numpy.array([2.0, 2.0, 2.0])
# Element-wise array multiplication (Lesson 7)
resulting_product = first_factor * second_factor
print(resulting_product) # Outputs: [7.43656366 7.43656366 7.43656366]