Основные математические вычисления
Эта глава обсуждает типичные научные вычисления с использованием кодов на Python. Мы сосредоточимся на том, как числовые данные представлены математически, как они структурированы или организованы, хранятся, манипулируются, обрабатываются или вычисляются эффективными способами. Будут рассмотрены тонкости этих операций в Python. Все коды представленные в этом учебнике и в этой главе в частности можно найти по ссылке https://sohoware.ru/SohoBook/. Наше обсуждение начнётся с некоторых базовых операций линейной...
Ключевые идеи
- Линейная алгебра
- Интерполяция
- Разложение на Одночлены
- Анализ главных компонент
- Нахождение численных корней.
- Численное интегрирование в контексте машинного обучения
Практическое задание
Возьмите небольшой список или словарь данных, преобразуйте его в NumPy-массив и посчитайте две простые статистики. Свяжите результат с главой "Основные математические вычисления".
Основные математические вычисления
Эта глава обсуждает типичные научные вычисления с использованием кодов на Python. Мы сосредоточимся на том, как числовые данные представлены математически, как они структурированы или организованы, хранятся, манипулируются, обрабатываются или вычисляются эффективными способами. Будут рассмотрены тонкости этих операций в Python. Все коды представленные в этом учебнике и в этой главе в частности можно найти по ссылке https://sohoware.ru/SohoBook/. Наше обсуждение начнётся с некоторых базовых операций линейной алгебры над данными со структурой вектора, матрицы и тензора.
3.1 Линейная алгебра
Линейная алгебра крайне важна для любых вычислений, связанных с большими данными, таких как машинное обучение. Мы планируем кратко рассмотреть основные операции линейной алгебры с использованием программирования на Python и модулей, которые уже были разработаны в широком сообществе Python. Мы пройдемся по основным концепциям, математической нотации, структуре данных и процедуре вычислений. Читатели могут пропустить эту главу, если вы уже уверены в базовых вычислениях линейной алгебры. Наше обсуждение начнется со структуры данных. Сначала мы импортируем необходимые модули и функции.
Import numpy as np
# импортируем пакет Numpy и присваиваем ему псевдоним np
# Далее мы также будем использовать пакет TensorFlow.
# Если это еще не сделано, мы установим TensorFlow с помощью команды: pip install tensorflow.
# После установки импортируем TensorFlow.
import tensorflow as tf #Импортируем TensorFlow и даем ему псевдоним tf3.1.1 Скалярные числа
В предыдущей главе мы уже коснулись темы скалярных чисел в контексте математических вычислений. Эти числа делятся на три основные категории: целые числа, вещественные числа и комплексные числа. В языке программирования Python каждому такому числу присваивается уникальное имя и адрес в памяти. Это позволяет нам обращаться к числу, используя его имя, а также обновлять его значение и использовать в качестве аргумента в функциях, будь то встроенные функции, функции, определенные пользователем в коде, или функции из импортированных модулей.
Операции с числами в Python выполнены таким образом, чтобы быть интуитивно понятными и прямолинейными. Однако в вычислениях часто возникает проблема, связанная с превышением допустимого диапазона чисел, что может привести к переполнению (over-flow) или потере точности (under-flow). Это означает, что число становится слишком большим или слишком маленьким для корректной обработки с учетом ограничений, накладываемых числовыми типами и архитектурой компьютера.
В Python предполагается, что числовые значения способны охватывать весь диапазон вещественных чисел с точностью, ограниченной характеристиками компьютера. Это важно в, где точность и способность обрабатывать большие числовые диапазоны могут иметь важное значение для эффективности и точности моделей.
3.1.2 Векторы
Под вектором понимается упорядоченный набор чисел, расположенных в одном измерении. Эти числа называются компонентами вектора. В физическом контексте каждая компонента вектора может представлять собой величину в определенном направлении. Например, вектор силы в трехмерном пространстве содержит три компоненты, каждая из которых относится к силе, действующей вдоль соответствующей оси координат.
В машинном обучении векторы используются для представления наборов признаков, где каждый элемент вектора соответствует одному признаку. Такие векторы могут быть очень большими, особенно в случае использования методов, таких как метод конечных элементов, где объекты дискретизируются на множество элементов и узлов, что приводит к векторам с миллионами компонентов.
Давайте рассмотрим векторы на примере программирования с использованием языка Python и его библиотек для научных вычислений, таких как TensorFlow и NumPy.
Пример создания вектора в TensorFlow:
import tensorflow as tf # Импортируем TensorFlow и даем ему псевдоним tf
p = 15 # Длина вектора, представляющая количество компонентов
# Создаем одномерный тензор из последовательных чисел от 0 до p-1
x = tf.range(p) # Используем функцию range библиотеки TensorFlow
# Выводим тензор и его форму
print(x) # Тензор x теперь содержит 15 компонентов
print(x.shape) # Форма тензора в Python обозначается как (p,)
tf.Tensor([0 1 2 3 4 5 6 7 8 9 10 11 12 13 14], shape=(15,), dtype=int32)
(15,)В результате получим вектор x длиной 15 элементов. В математике такой вектор часто представляют в виде столбца, но в Python по умолчанию он представлен как строка.
Теперь, если мы хотим транспонировать вектор (преобразовать вектор-строку в вектор-столбец), нам нужно сначала изменить его форму:
import tensorflow as tf # Импортируем TensorFlow и даем ему псевдоним tf
# Изменяем форму тензора, чтобы сделать его двумерным (вектор-столбцом)
x_reshaped = tf.reshape(x, (p, 1))
# Транспонируем двумерный тензор
x_transposed = tf.transpose(x_reshaped)
# Выводим транспонированный тензор и его форму
print(x_transposed) # Транспонированный вектор
print(x_transposed.shape) # Форма транспонированного вектора
tf.Tensor([[0 1 2 3 4 5 6 7 8 9 10 11 12 13 14]], shape=(1, 15), dtype=int32)
(1, 15)В TensorFlow, транспонирование вектора не обязательно приводит к физическому перемещению данных в памяти, что делает операцию эффективной. Однако, TensorFlow может не сохранять одинаковые адреса в памяти для исходного и транспонированного тензоров, особенно при использовании операций, меняющих форму тензора.
3.1.3 Матрицы
Матрица — это структура данных, состоящая из чисел, упорядоченных по двум измерениям: строкам и столбцам. Это расширение понятия вектора на два и более измерений. Каждый столбец матрицы может быть рассмотрен как вектор. В таком случае матрицу можно воспринимать как набор векторов, сложенных бок о бок. Они используются для представления и обработки наборов данных, где строки соответствуют отдельным наблюдениям (например, различным изображениям), а столбцы — различным признакам (например, пикселям).
В программировании на Python с использованием библиотеки TensorFlow операции с матрицами могут быть выполнены следующим образом:
import tensorflow as tf # Импортируем TensorFlow и даем ему псевдоним tf
p = 15 # Длина вектора, представляющая количество компонентов
# Создаем одномерный тензор из последовательных чисел от 0 до p-1
x = tf.range(p) # Используем функцию range библиотеки TensorFlow
# Предполагая, что 'x' - это одномерный тензор, изменяем его форму,
# чтобы получить двумерный тензор размером 3 строки на 5 столбцов
A = tf.reshape(x, (3, 5))
# Выводим тензор 'A' и его транспонированную версию
print('@'*50)
print("A =", A)
print("A.T =", tf.transpose(A))
# Рассмотрим пример, где мы определяем форму тензора 'A'
# и его транспонированной версии
print('@'*50)
print(A.shape)
print(tf.transpose(A).shape)
# Доступ к элементам тензора можно получить
# с помощью индексации и срезов:
print('@'*50)
# Индексация элемента на пересечении первой строки и второго столбца
print('A[0, 1] =', A[0, 1])
print('row 2', A[1,:]) # Получаем все элементы второй строки
print('column 1', A[:, 1]) # Получаем все элементы второго столбца@@@@@@@@@@@@@@@@@@@@
A = tf.Tensor(
[[0 1 2 3 4]
[5 6 7 8 9]
[10 11 12 13 14]], shape=(3, 5), dtype=int32)
A.T = tf.Tensor(
[[0 5 10]
[1 6 11]
[2 7 12]
[3 8 13]
[4 9 14]], shape=(5, 3), dtype=int32)@@@@@@@@@@@@@@@@@@@@
(3, 5)
(5, 3)@@@@@@@@@@@@@@@@@@@@
A[0, 1] = tf.Tensor(1, shape=(), dtype=int32)
row 2 tf.Tensor([5 6 7 8 9], shape=(5,), dtype=int32)
column 1 tf.Tensor([1 6 11], shape=(3,), dtype=int32)3.1.4 Тензоры
В математике и физике тензор — это структурированный набор данных, который преобразуется по определенным правилам при изменении системы координат. Тензоры могут быть различных порядков: скаляр (единственное число) является тензором нулевого порядка, вектор — первого порядка, матрица — второго порядка, и так далее, с увеличением числа измерений.
Однако в контексте машинного обучения (ML) "тензор" обычно обозначает любой массив данных, размерность которого превышает два. В этой сфере тензоры часто ассоциируются с большими данными, которые необходимо структурировать в высокой размерности. Например, изображение в формате RGB представляет собой трехмерный тензор, где оси соответствуют высоте, ширине и цветовым каналам. В библиотеке NumPy для Python тензоры представлены в виде многомерных массивов.
В машинного обучения обычно не используются тензорные преобразования, применяемые в физике, поэтому для целей этого учебника мы будем использовать термин "тензор" для обозначения многомерных массивов без учета математических правил тензорных преобразований.
Для создания тензора в библиотеке TensorFlow мы можем использовать следующий код:
import tensorflow as tf # Импортируем TensorFlow и даем ему псевдоним tf
# Создаем трехмерный тензор с помощью tf.range, затем изменяем его форму
X = tf.range(24)
X = tf.reshape(X, (2, 3, 4))
# Выводим размерность тензора и сам тензор
print('X.shape =', X.shape)
print('X =', X)
X.shape = (2, 3, 4)
X = tf.Tensor(
[[[0 1 2 3]
[4 5 6 7]
[8 9 10 11]]
[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]], shape=(2, 3, 4), dtype=int32)
Результатом будет трехмерный тензор X с формой (2, 3, 4), представляющий два блока, каждый из которых содержит три строки и четыре столбца.
В машинном обучении тензоры часто используются для операций, которые применяются поэлементно, как показано в следующем примере:
import tensorflow as tf # Импортируем TensorFlow и даем ему псевдоним tf
# Создаем двумерный тензор A и тензор B,
# содержащий единицы такой же формы, умноженные на 8
A = tf.reshape(tf.range(8), (2, 4))
B = tf.ones_like(A) * 8
# Выполняем поэлементное сложение и умножение тензоров A и B
print('A =', A, '\nB =', B)
print('A + B =', A + B, '\nA * B =', A * B)
A = tf.Tensor(
[[0 1 2 3]
[4 5 6 7]], shape=(2, 4), dtype=int32)
B = tf.Tensor(
[[8 8 8 8]
[8 8 8 8]], shape=(2, 4), dtype=int32)
A + B = tf.Tensor(
[[8 9 10 11]
[12 13 14 15]], shape=(2, 4), dtype=int32)
A * B = tf.Tensor(
[[0 8 16 24]
[32 40 48 56]], shape=(2, 4), dtype=int32)Здесь тензор A состоит из чисел от 0 до 7, а тензор B - из чисел 8, повторяющихся в каждом элементе. Операции сложения и умножения выполняются поэлементно, результатом чего является новый тензор той же формы.
В машинном обучении, часто требуется вычислить сумму или среднее значение элементов в тензоре. Тензор в этом случае можно рассматривать как многомерный массив данных. Действия над тензорами, такие как суммирование и нахождение среднего, позволяют получить обобщенные характеристики данных.
Рассмотрим, как выполняется эта операция на практике с использованием библиотеки TensorFlow в Python:
import tensorflow as tf # Импортируем TensorFlow и даем ему псевдоним tf
# Инициализируем одномерный тензор (вектор) с помощью функции range
x = tf.range(5)
print(x) # Выводим вектор
print(tf.reduce_sum(x)) # Вычисляем сумму элементов вектора
[tf.Tensor([0 1 2 3 4], shape=(5,), dtype=int32)
tf.Tensor(10, shape=(), dtype=int32)
Этот код создает одномерный тензор (вектор) x со значениями от 0 до 4. Вывод команды print(x) показывает этот вектор. Команда print(tf.reduce_sum(x)) возвращает сумму всех элементов вектора x, то есть 0+1+2+3+4, что равно 10.
Далее, создадим двумерный тензор (матрицу) и выполним операции:
import tensorflow as tf # Импортируем TensorFlow и даем ему псевдоним tf
# Создаем двумерный тензор, заполненный единицами
X = tf.ones((3, 5))
print(X) # Выводим матрицу
print(tf.reduce_sum(X)) # Вычисляем сумму элементов матрицы
# Вычисляем среднее значение элементов матрицы двумя способами
print(tf.reduce_mean(X), tf.reduce_sum(X) / tf.size(X).numpy())
tf.Tensor(
[[1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1.]], shape=(3, 5), dtype=float32)
tf.Tensor(15.0, shape=(), dtype=float32)
tf.Tensor(1.0, shape=(), dtype=float32) tf.Tensor(1.0, shape=(), dtype=float32)<NDArray 1 @cpu(0)>
[1.]
<NDArray 1 @cpu(0)>
Этот код формирует матрицу X размером 3 на 5, где каждый элемент равен 1. Вывод команды print(X) показывает эту матрицу. Команда print(tf.reduce_sum(X)) вычисляет сумму всех элементов матрицы X, что в данном случае равно 15, поскольку матрица содержит 15 единиц. Здесь tf.reduce_mean(X) вычисляет среднее значение всех элементов тензора X, которое в данном случае равно 1, поскольку все элементы тензора равны единице. Тот же результат можно получить, разделив сумму элементов на количество элементов в тензоре (tf.reduce_sum(X) / tf.size(X).numpy()).Эти операции лежат в основе анализа данных, позволяя сократить размерность информации до управляемых и интерпретируемых статистических значений.
3.1.5 Скалярное произведение двух векторов
Скалярное произведение наиболее часто используемая операция в машинном обучени. В данном разделе мы подробно обсудим его использование для векторов, которые могут иметь различные структуры данных, что влечет за собой некоторые тонкости.
Даны два вектора a и b. Их скалярное произведение в линейной алгебре часто записывается как a · b. По сути, это просто сумма произведений их соответствующих элементов, что приводит к скаляру. Это означает, что формы векторов a и b должны быть совместимы: они должны иметь одинаковую длину. Рассмотрим некоторые примеры.
import numpy as np # импортируем пакет Numpy и присваиваем ему псевдоним np
import tensorflow as tf # Импортируем TensorFlow и даем ему псевдоним tf
# Инициализируем два вектора 'a' и 'b'
a = tf.range(5)
b = tf.ones_like(a) * 2 # Это гарантирует их совместимость
# Выводим векторы и их формы
print('--------------------------------------')
print(f"a={a}, a.shape={a.shape}\nb={b}, b.shape={b.shape}")
# Вычисляем скалярное произведение векторов с помощью библиотеки TensorFlow
print('--------------------------------------')
print('tf.tensordot(a, b, axes=1)', tf.tensordot(a, b, axes=1))
# Выводим форму результата скалярного произведения
print('--------------------------------------')
print('tf.tensordot(a, b, axes=1).shape', tf.tensordot(a, b, axes=1).shape)
# Вычисляем скалярное произведение векторов с помощью библиотеки NumPy
print('--------------------------------------')
print(f"np.dot(a, b)={np.dot(a.numpy(), b.numpy())}")
--------------------------------------
a=[0 1 2 3 4], a.shape=(5,)
b=[2 2 2 2 2], b.shape=(5,)
--------------------------------------
tf.tensordot(a, b, axes=1) tf.Tensor(20, shape=(), dtype=int32)
--------------------------------------
tf.tensordot(a, b, axes=1).shape ()
--------------------------------------
np.dot(a, b)=20Скалярное произведение векторов `a` и `b` в tensorflow и NumPy дает одинаковый скалярный результат.
Транспонирование — это математическая операция, при которой строки матрицы превращаются в столбцы, и наоборот. Эта операция часто используется в математике и компьютерных вычислениях, особенно при работе с матрицами.
Однако, когда речь заходит о одномерных массивах (векторах) в NumPy, ситуация слегка отличается. В NumPy одномерный массив или вектор имеет форму (n,), где n — это количество элементов в массиве. Здесь указано только одно измерение, которое соответствует длине вектора. Поскольку нет второго измерения, операция транспонирования не имеет смысла, так как нет второй оси, которую можно было бы "перевернуть" с первой. Транспонирование предполагает изменение двумерной структуры, но одномерный массив уже находится в своей наиболее простой форме.
В более широком смысле, когда мы транспонируем двумерный массив в NumPy, мы меняем местами его оси. Если у нас есть массив формы (m, n), где m — это количество строк, а n — количество столбцов, транспонирование изменит его форму на (n, m). Это означает, что элементы, которые были в строке i и столбце j до транспонирования, окажутся в строке j и столбце i после транспонирования.
Для одномерного массива такое изменение осей выполнить нельзя, так как есть только одно измерение. Функция транспонирования `.T` или метод `transpose()` в NumPy возвращают исходный одномерный массив без изменений, потому что ось для перестановки отсутствует.
Вот простой пример для иллюстрации:importnumpyasnp
# Создаем одномерный массив в NumPy
a=np.array([1, 2, 3])
# Пытаемся транспонировать одномерный массив
a_transposed=a.T
# Результат транспонирования — тот же самый одномерный массив
print(a_transposed) # Выведет [1 2 3]
[1 2 3]Как видите, результатом транспонирования одномерного массива будет тот же одномерный массив, поскольку у него всего одна ось, и, следовательно, нет другой оси, с которой можно было бы её поменять местами. Одномерный массив NumPy имеет форму (n,) и не рассматривается как матрица.
Когда `b` является столбцовым вектором (особый случай двумерного массива), он имеет две оси, как и матрица. Скалярное произведение a · b такое же, как матричное произведение ab (где a определен как строковый вектор, а b — как столбцовый вектор) с точки зрения результирующего скалярного значения. Таким образом, в нашем изложении мы не делаем различий между ними с математической точки зрения и часто используем следующее равенство a · b = ab
Теперь приведем примеры кода, которые иллюстрируют упомянутые тонкости:
import numpy as np # импортируем пакет Numpy и присваиваем ему псевдоним np
import tensorflow as tf # Импортируем TensorFlow и даем ему псевдоним tf
# Инициализируем два вектора 'a' и 'b'
a = tf.range(5)
b = tf.fill([5], 2) # Это гарантирует их совместимость
# Преобразуем одномерный массив 'b' в столбцовый вектор
b_c = tf.reshape(b, [-1, 1])
# Выводим столбцовый вектор и его форму
print('-----------------------------------')
print(b_c, 'b_c.shape=', b_c.shape)
# Вычисляем скалярное произведение в NumPy (результат — скаляр)
print('-----------------------------------')
print('np.dot(a, b)', np.dot(a.numpy(), b.numpy()))
# Вычисляем скалярное произведение одномерного массива и столбцового вектора (результат — массив)
print('-----------------------------------')
print('np.dot(a, b_c)', np.dot(a.numpy(), b_c.numpy()))
# Выводим результаты операций скалярного произведения в TensorFlow
print('-----------------------------------')
print('tf.tensordot(a, b, axes=1)', tf.tensordot(a, b, axes=1))
print('-----------------------------------')
print('tf.tensordot(a, b_c, axes=1)', tf.tensordot(a, b_c, axes=1))
print('-----------------------------------')
print('tf.tensordot(a, b_c, axes=1).shape', tf.tensordot(a, b_c, axes=1).shape)
-----------------------------------
tf.Tensor(
[[2]
[2]
[2]
[2]
[2]], shape=(5, 1), dtype=int32) b_c.shape= (5, 1)
-----------------------------------
np.dot(a, b) 20
-----------------------------------
np.dot(a, b_c) [20]
-----------------------------------
tf.tensordot(a, b, axes=1) tf.Tensor(20, shape=(), dtype=int32)
-----------------------------------
tf.tensordot(a, b_c, axes=1) tf.Tensor([20], shape=(1,), dtype=int32)
-----------------------------------
tf.tensordot(a, b_c, axes=1).shape (1,)Как видно, все эти операции дают одно и то же скалярное значение.
Скалярное произведение является стандартной операцией в линейной алгебре и имеет особое значение, когда мы работаем с векторами в форме столбцов. Обычно векторы представляются в форме столбцов, когда мы имеем дело с линейной алгеброй, и в этом контексте скалярное произведение двух столбцовых векторов приводит к скаляру. Однако в математической нотации это произведение записывается с использованием транспонирования одного из векторов, чтобы показать, что мы выполняем суммирование произведений соответствующих элементов двух векторов.
Допустим, у нас есть два вектора, a и b каждый из которых имеет форму столбца и содержит одинаковое количество элементов. Скалярное произведение этих векторов обозначается как aTb или bTa, где символ T обозначает операцию транспонирования. Транспонирование вектора a превращает его из столбцового вектора в строковый вектор, после чего производится суммирование произведений соответствующих элементов вектора a и вектора b. Это произведение даёт нам скаляр, то есть одно число.
Важно отметить, что хотя результат является скалярным значением, в библиотекеNumPy, результат представлен в виде двумерного массива или матрицы с одним элементом, что технически делает его матрицей размером 1x1, а не просто числом. Это связано с тем, что в этих библиотеках векторы и матрицы представлены в виде массивов, и операции над ними следуют правилам массивов, даже если результатом является одиночное значение.
import numpy as np # импортируем пакет Numpy и присваиваем ему псевдоним np
import tensorflow as tf # Импортируем TensorFlow и даем ему псевдоним tf
# Инициализируем два вектора 'a' и 'b'
a = tf.range(5)
b = tf.fill([5], 2) # Это гарантирует их совместимость
# Преобразуем одномерный массив 'b' в столбцовый вектор
b_c = tf.reshape(b, [-1, 1])
# Преобразуем одномерный массив 'a' в столбцовый вектор
a_c = tf.reshape(a, [-1, 1])
# Вычисляем скалярное произведение транспонированного столбцового вектора 'a'
#и столбцового вектора 'b' (результат — скаляр в 2D массиве)
print('-------------------------------------')
print('tf.matmul(a_c, b_c, transpose_a=True)', tf.matmul(a_c, b_c, transpose_a=True))
# Чтобы получить скалярное значение из 2D массива формы (1, 1), используйте:
print('-------------------------------------')
print('scalar value', tf.reshape(tf.matmul(a_c, b_c, transpose_a=True), []).numpy())
# Используйте flatten() для преобразования столбцового вектора обратно в строковый вектор
print('-------------------------------------')
print('tf.tensordot(a_c, b_c, axes=1)', tf.tensordot(tf.reshape(a_c, [-1]), tf.reshape(b_c, [-1]), axes=1))
# Чтобы получить скалярное значение из одномерного массива формы (1,), используйте:
print('-------------------------------------')
print('scalar value', tf.tensordot(tf.reshape(a_c, [-1]), tf.reshape(b_c, [-1]), axes=1).numpy())
-------------------------------------
tf.matmul(a_c, b_c, transpose_a=True) tf.Tensor([[20]], shape=(1, 1), dtype=int32)
-------------------------------------scalar value 20
-------------------------------------
tf.tensordot(a_c, b_c, axes=1) tf.Tensor(20, shape=(), dtype=int32)
-------------------------------------scalar value 20
Таким образом, приведенный код демонстрирует, как выполняется операция скалярного произведения в TensorFlow и NumPy, а также как обрабатывать результаты этих операций в различных структурах данных.
3.1.6 Внешнее произведение двух векторов определение и применение
Внешнее произведение двух векторов a и b, обозначаемое как a · b, приводит к созданию матрицы. Эта операция всегда совместима независимо от формы векторов a и b.
Как это работает: элемент матрицы на позиции ij представляет собой произведение aibj. Например, если у нас есть вектор aс элементами [0, 1, 2] и вектор bс элементами [2, 2, 2, 2, 2], результатом внешнего произведения будет матрица, в которой каждый элемент aiумножается на каждый элемент bj.
Рассмотрим пример на языке программирования Python с использованием библиотеки NumPy:importnumpyasnp# импортируем пакет Numpy и присваиваем ему псевдоним np
# Определение векторов a и b
a=np.arange(3) # Создает вектор [0, 1, 2]
b=np.ones(5) *2# Создает вектор [2, 2, 2, 2, 2]
# Вывод векторов a и b
print('-------------------------------')
print(a, b)
# Расчет внешнего произведения a и b
# Функция np.outer() принимает два вектора и возвращает их внешнее произведение
outer_product=np.outer(a, b)
print('-------------------------------')
print('np.outer=\n', outer_product)
-------------------------------
[0 1 2] [2. 2. 2. 2. 2.]
-------------------------------
np.outer=
[[0. 0. 0. 0. 0.]
[2. 2. 2. 2. 2.]
[4. 4. 4. 4. 4.]]Также можно достичь аналогичного результата с использованием оператора @, преобразовав вектор a в столбец (форма (n, 1))) и вектор b в строку (форма (1,m)).
importnumpyasnp# импортируем пакет Numpy и присваиваем ему псевдоним np
# Определение векторов a и b
a=np.arange(3) # Создает вектор [0, 1, 2]
b=np.ones(5) *2# Создает вектор [2, 2, 2, 2, 2]
# Преобразуем одномерный массив 'a' в столбцовый вектор
a_c=a.reshape(-1, 1)
# Преобразуем одномерный массив 'b' в столбцовый вектор
b_c=b.reshape(-1, 1)
# Пытаемся транспонировать одномерный массив
b_transposed=b_c.T
# Выводвекторовa_cиb_transposed
print('-------------------------------')
print(a_c, b_transposed)
# Расчет внешнего произведения a и b
# result- результат внешнего произведения принимает два вектора и возвращает их
result=a_c@b_transposed
print('-------------------------------')
print('np.outer=\n', result)
-------------------------------
[[0]
[1]
[2]] [[2. 2. 2. 2. 2.]]
-------------------------------
np.outer=
[[0. 0. 0. 0. 0.]
[2. 2. 2. 2. 2.]
[4. 4. 4. 4. 4.]]
Однако использование встроенной функции `np.outer()` предпочтительнее, так как она работает быстрее и не требует дополнительных операций.3.1.7 Произведение матрицы на вектор
Произведение матрицы на вектор — это стандартная операция в линейной алгебре, которая выполняется, когда размерности матрицы и вектора совместимы. Это действие позволяет преобразовать данные, умножая их на веса признаков, представленные вектором.
Давайте рассмотрим, как эта операция реализуется на практике с использованием библиотеки TensorFlow в Python:
import tensorflow as tf # Импортируем TensorFlow и даем ему псевдоним tf
# Инициализируем матрицу 'A35' размером 3x5 с числами от 0 до 14
A35 = tf.reshape(tf.range(15), (3, 5))
# Инициализируем вектор 'd5' длиной 5, состоящий из единиц
d5 = tf.cast(tf.ones((A35.shape[1],)), tf.int32) # Преобразование d5 в int32
# Выводим матрицу 'A35', её форму, вектор 'd5' и его форму
print('--------------------')
print(A35.numpy(), A35.shape, d5.numpy(), d5.shape)
# Вычисляем произведение матрицы 'A35' на вектор 'd5'
f = tf.matmul(A35, tf.reshape(d5, [-1, 1]))
# Формы совместимы: [3, 5] x [5, 1] -> вектор длиной 3
print('--------------------')
print(f.numpy(), f.shape)
--------------------
[[0 1 2 3 4]
[5 6 7 8 9]
[10 11 12 13 14]] (3, 5) [1 1 1 1 1] (5,)
--------------------
[[10]
[35]
[60]] (3, 1)В этом примере мы умножаем матрицу `A35` размером 3x5 на вектор `d5` длиной 5. Поскольку количество столбцов в матрице `A35` соответствует длине вектора `d5`, операция произведения может быть выполнена, и результатом является вектор длиной 3.
Однако, если попробовать выполнить операцию в другом порядке, то есть умножить вектор `d5` на матрицу `A35`, мы получим ошибку, так как размерности не совместимы для такой операции:
Заменим переменную f
# Следующая операция приведет к ошибке из-за несовместимости форм
# f = tf.matmul(d5, A35) # Эта строка вызовет ошибку
Если мы хотим умножить вектор слева на матрицу, вектор должен иметь длину, равную количеству строк матрицы:
import tensorflow as tf # Импортируем TensorFlow и даем ему псевдоним tf
# Инициализируем матрицу 'A35' размером 3x5 с числами от 0 до 14
A35 = tf.reshape(tf.range(15), (3, 5))
# Инициализируем вектор 'd3' длиной 3, состоящий из единиц
# Используем первый элемент формы A35 для определения длины вектора
d3 = tf.cast(tf.ones((A35.shape[0],)), tf.int32)
# Выводим матрицу 'A35', её форму, вектор 'd3' и его форму
print('--------------------')
print(d3, d3.shape, A35, A35.shape)
# Вычисляем произведение вектора 'd3' на матрицу 'A35'
f = tf.matmul(tf.reshape(d3, [1, -1]), A35)
# Формы совместимы: [1, 3] x [3, 5] -> вектор длиной 5
print('--------------------')
print(f, f.shape)
--------------------
tf.Tensor([1 1 1], shape=(3,), dtype=int32) (3,) tf.Tensor(
[[0 1 2 3 4]
[5 6 7 8 9]
[10 11 12 13 14]], shape=(3, 5), dtype=int32) (3, 5)
--------------------
tf.Tensor([[15 18 21 24 27]], shape=(1, 5), dtype=int32) (1, 5)В этом случае результатом является вектор длиной 5, который получается путем суммирования элементов каждого столбца матрицы `A35`.
3.1.8 Умножение матрицы на матрицу
Для того чтобы выполнить умножение двух матриц, необходимо, чтобы их формы были совместимы. Это означает, что количество столбцов в первой матрице должно совпадать с количеством строк во второй матрице.
Рассмотрим пример умножения матрицы на матрицу с использованием библиотеки tensorflow в Python:
import tensorflow as tf # Импортируем TensorFlow и даем ему псевдоним tf
# Инициализируем две матрицы 'A23' и 'B35' совместимых форм
A23 = tf.ones(shape=(2, 3))
B35 = tf.ones(shape=(3, 5))
print('-'*20)
# Выводим обе матрицы
print(A23.numpy(), B35.numpy())
# Выполняем операцию умножения матрицы на матрицу
C25 = tf.matmul(A23, B35)
# Формы совместимы: [2, 3] x [3, 5] -> результат имеет форму [2, 5]
print('-'*20)
print(C25.numpy())
--------------------
[[1. 1. 1.]
[1. 1. 1.]] [[1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1.]]
--------------------
[[3. 3. 3. 3. 3.]
[3. 3. 3. 3. 3.]]В этом коде матрица `A23` имеет форму 2x3, а матрица `B35` — форму 3x5. После умножения мы получаем матрицу `C25` формы 2x5, где каждый элемент представляет собой сумму произведений элементов соответствующих строк первой матрицы на столбцы второй матрицы. В данном случае, поскольку обе матрицы заполнены единицами, каждый элемент результирующей матрицы `C25` будет равен числу 3.
Такая же операция в NumPy выполняется аналогично:importnumpyasnp# импортируем пакет Numpy и присваиваем ему псевдоним np
# Инициализируем две матрицы 'A23' и 'B35' совместимых форм
A23=np.ones(shape=(2, 3))
B35=np.ones(shape=(3, 5))
print('-'*20)
# Выводим обе матрицы
print(A23,'\n', B35)
print('-'*20)
# Используем функцию np.dot() для умножения матриц
print('np.dot():\n', np.dot(A23, B35))
print('-'*20)
# Используем оператор @ в NumPy для умножения матриц
print('numpy @ operator:\n', A23@B35)
--------------------
[[1. 1. 1.]
[1. 1. 1.]]
[[1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1.]
[1. 1. 1. 1. 1.]]
--------------------
np.dot():
[[3. 3. 3. 3. 3.]
[3. 3. 3. 3. 3.]]
--------------------
numpy @ operator:
[[3. 3. 3. 3. 3.]
[3. 3. 3. 3. 3.]]Оба способа в NumPy приведут к тому же результату, что и в tensorflow.
3.1.9 Нормы
В линейной алгебре и анализе данных, норма — это функция, которая назначает положительную длину или размерность каждому вектору в векторном пространстве, за исключением нулевого вектора, которому назначается длина ноль. В контексте программирования на Python и машинного обучения, нормы используются для определения "величины" векторов и матриц. Они помогают определить, насколько велики элементы вектора или матрицы по сравнению с нулем или друг с другом.
Существует несколько различных типов норм, каждый из которых имеет свои применения:- L2-норма (также известная как Евклидова норма) вектора — это квадратный корень из суммы квадратов его элементов. Это наиболее часто используемый тип нормы, особенно в машинном обучении, так как она связана с евклидовым расстоянием между точками. Для матриц L2-норма также известна как норма Фробениуса, которая является аналогом L2-нормы для матричных данных.
Пример использованияL1-нормыи L2-нормы в Python:- **L1-норма** вектора — это сумма абсолютных значений его элементов. Эта норма менее чувствительна к выбросам и часто используется в статистике и оптимизации. Для матриц L1-норма может быть определена как максимальное значение L1-норм среди столбцов матрицы. Пример использования L1-нормы и L2-нормы в Python, используя TensorFlow:
import tensorflow as tf # Импортируем TensorFlow и даем ему псевдоним tf
import numpy as np # импортируем пакет Numpy и присваиваем ему псевдоним np
# Создаем тензор (вектор) с единицами
d = tf.ones(9)
print('-'*20)
# Выводим вектор и сумму его элементов
print(d.numpy(), tf.reduce_sum(d).numpy())
print('-'*20)
# Вычисляем L2-норму вектора с помощью TensorFlow
print('tf.norm(d)', tf.norm(d).numpy())
print('-'*20)
# Вычисляем L2-норму вектора с помощью NumPy
print('np.linalg.norm(d)', np.linalg.norm(d.numpy()))
print('-'*20)
# Вычисляем L1-норму вектора с помощью TensorFlow
print('tf.norm(d, ord=1)', tf.norm(d, ord=1).numpy())
print('-'*20)
# Вычисляем L1-норму вектора с помощью NumPy
print('np.linalg.norm(d, 1)', np.linalg.norm(d.numpy(), 1))
--------------------
[1. 1. 1. 1. 1. 1. 1. 1. 1.] 9.0
--------------------
tf.norm(d) 3.0
--------------------
np.linalg.norm(d) 3.0
--------------------
tf.norm(d, ord=1) 9.0
--------------------
np.linalg.norm(d, 1) 9.0Нормы используется в регуляризации, которая предотвращает переобучения моделей машинного обучения, ограничивая величину параметров модели. Например, регуляризация L1 может привести к разреженным решениям, где некоторые параметры модели становятся равными нулю, что может быть полезно для отбора признаков. Регуляризация L2 стремится уменьшить все параметры модели одновременно, что помогает избежать слишком больших весов и уменьшает сложность модели.
3.1.10 Решение систем линейных алгебраических уравнений
В математике и машинном обучении часто возникает задача решения систем линейных уравнений, которые могут быть представлены в форме матричного уравнения:KD = F
Здесь K — это квадратная матрица, D — искомый вектор переменных, а F — вектор результатов или известных значений.
Матрица K должна быть положительно определенной, что означает, что все её собственные значения больше нуля. Это условие необходимо для обеспечения единственности решения. В контексте МКЭ(методе конечных элементов) матрица жесткости K обычно симметрична и положительно определена, что гарантирует, что уравнения системы соответствуют физически реальной и стабильной модели.
Функция numpy.linalg.solve предназначена для решения таких систем уравнений. Она принимает два аргумента: матрицу K и вектор F, и возвращает вектор D, который удовлетворяет исходному уравнению.
Давайте рассмотрим пример предположим, мы имеем систему уравнений, где матрица K имеет некоторые физические характеристики системы, а F — это внешние силы, действующие на систему. Мы хотим найти D.
importnumpyasnp# импортируем пакет Numpy и присваиваем ему псевдоним np
# Определим новую матрицу K
K=np.array([[3, 1], [1, 2]])
# Определим вектор F
F=np.array([9, 3])
# Решим систему уравнений и найдемD
D=np.linalg.solve(K, F)
# Выведем результаты на экран
print("Матрица K:\n", K)
print("Вектор F:", F)
print("ВекторD:", D)
Матрица жесткости K:
[[3 1]
[1 2]]
Вектор внешних нагрузок F: [9 3]
Вектор D: [3. 0.]В этом коде numpy.linalg.solve эффективно решает систему уравнений. Если бы матрица K не была квадратной или система была переопределена (то есть имела больше уравнений, чем неизвестных), мы бы использовали numpy.linalg.lstsq для нахождения решения методом наименьших квадратов, которое минимизирует сумму квадратов ошибок между данными и предсказаниями модели. Этот подход часто используется в машинном обучении для задач регрессии.
Стоит отметить, что решение больших систем уравнений может быть ресурсоемким процессом. Современные численные алгоритмы и развитие вычислительной техники позволяют справляться с этой задачей эффективно, особенно при использовании итеративных методов и методов на основе градиентного спуска, которые уменьшают ошибку решения до приемлемого уровня. Эти методы схожи с теми, что используются в машинном обучении для оптимизации функций потерь.
3.1.11 Инверсия матрицы
Инверсия матрицы — это процесс нахождения матрицы K-1 которая является обратной к данной матрице K. Матрица K-1уникальна для K и удовлетворяет условиям K×K-1=I и K-1×K=I, где I — единичная матрица соответствующего размера.
В предыдущих разделах мы обсуждали решение уравнений линейной алгебры. Давайте рассмотрим более детально инверсию матрицы. Рассмотрим уравнение KD = F, решение которого может быть представлено в виде: D=K-1Fгде K-1 является обратной матрицей для K. Таким образом, если мы можем вычислить K-1, то решение сводится к произведению матрицы на вектор. Для малых систем этот подход действительно работает и широко используется. Для вычисления обратной матрицы мы используем функцию `numpy.linalg.inv()`:
importnumpyasnp# импортируем пакет Numpy и присваиваем ему псевдоним np
fromnumpy.linalgimportinv
# Определимматрицу K
K=np.array([[3, 1], [1, 2]])
# Вычисляем обратную матрицу K
Kinv=inv(K)
print('-'*20)
print(Kinv)
print('-'*20)
# Проверяем, что произведение K и Kinv дает единичную матрицу
print(np.allclose(np.dot(K, Kinv), np.eye(2)))
print(np.allclose(np.dot(Kinv, K), np.eye(2)))
--------------------
[[0.4 -0.2]
[-0.2 0.6]]
--------------------True
True
Здесь `np.allclose` используется для проверки, что произведение K и K-1дает приблизительно единичную матрицу, что является признаком корректной инверсии.
Решение уравнения D=K-1Fможно получаеть следующим образом:
importnumpyasnp# импортируем пакет Numpy и присваиваем ему псевдоним np
fromnumpy.linalgimportinv
# Определимматрицу K
K=np.array([[3, 1], [1, 2]])
# Определим вектор F
F=np.array([9, 3])
# Вычисляем обратную матрицу K
Kinv=inv(K)
# Выведем результат D на экран
D=np.dot(Kinv, F)
print('D:', D)
D: [3. 0.]
Это решение совпадает с ранее полученным. Интересный момент: можно инвертировать несколько матриц одновременно:importnumpyasnp# импортируем пакет Numpy и присваиваем ему псевдоним np
fromnumpy.linalgimportinv
a=np.array([[[1., 2.], [3., 4.]], [[5, 6], [7, 8]]])
print('-'*20)
print(a)
print('-'*20)
print(inv(a))
--------------------
[[[1. 2.]
[3. 4.]]
[[5. 6.]
[7. 8.]]]
--------------------
[[[-2. 1.]
[1.5 -0.5]]
[[-4. 3.]
[3.5 -2.5]]]Инверсия больших матриц очень затратна с точки зрения вычислительных ресурсов, поэтому часто предпочтительнее решать алгебраические системы уравнений напрямую. Также в вычислениях, связанных с машинным обучением, можно столкнуться с сингулярными матрицами, которые не имеют обратных, что приводит к сбою в вычислениях. Часто матрицы бывают почти сингулярными, что может позволить продолжить вычисления, но привести к серьезным ошибкам, проявляющимся в неожиданном, странном поведении. При наблюдении такого поведения высока вероятность того, что системная матрица может быть "плохо обусловлена", и следует проверить возможные ошибки в данных или процедуре формулировки, которые могут привести к почти сингулярной системной матрице. Если проблема в самих данных, может потребоваться очистка данных или проверка на наличие ошибок в данных. После этого можно прибегнуть к математическим методам, одним из которых является использование сингулярного разложения (SVD) для получения наилучшей возможной информации из данных.
Основной момент, который мы хотим здесь подчеркнуть, заключается в том, что самым важным фактором, определяющим возможность решения системы алгебраических уравнений для получения качественного решения, является свойство (или характеристика, или условие) системной матрицы. Собственные значения (если они существуют) и соответствующие им собственные векторы являются характеристиками матрицы.
3.1.12 Разложение матрицы на собственные значения и собственные векторы
Основы разложения
Разложение матрицы на собственные значения и собственные векторы — это процесс, при котором диагонализируемая матрица представляется в виде произведения матрицы собственных векторов, диагональной матрицы собственных значений и транспонированной матрицы собственных векторов. Этот метод особенно эффективен для реальных симметричных матриц, так как собственные значения в таком случае будут действительными числами, а собственные векторы могут быть ортонормированными.
Матрица собственных векторов (V): собственные векторы матрицы представляют направления, в которых действие этой матрицы может быть выражено как простое масштабирование. Если матрица A имеет собственный вектор v, то при умножении A на v, результатом будет v, умноженный на некоторое скалярное значение. Это скалярное значение известно как собственное значение. V состоит из собственных векторов матрицы A, расположенных в качестве ее столбцов. Для симметричной матрицы эти векторы будут ортонормированными, что означает, что они имеют единичную длину и ортогональны друг другу.
Диагональная матрица собственных значений (Λ): этот компонент разложения представляет собой диагональную матрицу, где каждый элемент на главной диагонали является собственным значением, соответствующим собственному вектору из матрицы V. Собственные значения отражают фактор, на который собственный вектор увеличивается при умножении на матрицу A. В случае симметричной матрицы, все собственные значения будут действительными числами.
Транспонированная матрица собственных векторов (VT): последний компонент разложения — это транспонированная матрица V. Транспонирование матрицы включает в себя замену ее строк на столбцы (или наоборот). В случае ортонормированной матрицы V для симметричной матрицы A, транспонирование эквивалентно нахождению обратной матрицы V. Это связано с тем, что умножение V наVT (или наоборот) дает единичную матрицу.
Для положительно определенной симметричной матрицы A разложение выглядит следующим образом:A = VΛVT, где:
V — матрица собственных векторов,
Λ— диагональная матрица собственных значений, а
VT— транспонированная матрица V .
Так как V ортонормирована, выполняется:VV T = I и следовательно, обратная матрица V равна её транспонированию:
Вычисление обратной матрицы
После разложения матрицы, вычисление обратной становится простым. Исходя из определения обратной матрицы AA-1 = I и используя уравнения A = VΛVT и V-1=VT, получаем:
A-1= VΛ-1VT
Обратная матрица диагональной матрицы Λ состоит из обратных значений её диагональных элементов.
Рассмотрим примеры вычисления собственных значений и векторов на Python:importnumpyasnp# Импортируембиблиотеку NumPy.
fromnumpyimportlinalgaslg# Импортируеммодульlinalgизбиблиотеки NumPy подпсевдонимом lg.
A=np.array([[1, 0.4, 0.8], [0.4, 1, 0.5], [0.8, 0.5, 1]])# Создаем матрицу размера 3x3.
# Вызываем функцию eig из модуля linalg, которая вычисляет собственные значения (e)
# и собственные векторы (v) матрицы A.
e, v=lg.eig(A)
print('-'*20)
# Выводимматрицу A.
print('матрица A:\n', A)
print('-'*20)
# Выводим собственные значения матрицы A.
print('Собственные значения:', e)
print('-'*20)
# Выводим собственные векторы матрицы A.
print('Собственные векторы:\n', v)
--------------------
матрица A:
[[1. 0.4 0.8]
[0.4 1. 0.5]
[0.8 0.5 1.]]
--------------------
Собственные значения: [2.15226471 0.19102751 0.65670778]
--------------------
Собственные векторы:
[[-0.60615713 -0.66545116 0.43560106]
[-0.48421405 -0.12572504 -0.86586949]
[-0.63095982 0.73577712 0.24601167]]Здесь получаем три действительных собственных значения и ортонормированные собственные векторы.
Используя собственные значения и векторы, можно восстановить исходную матрицу:importnumpyasnp# Импортируембиблиотеку NumPy.
fromnumpyimportlinalgaslg# Импортируеммодульlinalgизбиблиотеки NumPy подпсевдонимом lg.
A=np.array([[1, 0.4, 0.8], [0.4, 1, 0.5], [0.8, 0.5, 1]])# Создаем матрицу размера 3x3.
# Вызываем функцию eig из модуля linalg, которая вычисляет собственные значения (e)
# и собственные векторы (v) матрицы A.
e, v=lg.eig(A)
# Создаем диагональную матрицу lamd из собственных значений e. Элементы lamd находятся на главной диагонали,
# а все остальные элементы равны нулю.
lamd=np.diag(e)
# Восстанавливаем исходную матрицу A, используя разложение на собственные значения и векторы.
# Умножаем матрицу собственных векторов v на диагональную матрицу собственных значений lamd и на транспонированную
# матрицу собственных векторов v.T. Оператор @ обозначает матричное умножение.
A_recovered=v@lamd@v.T
# Выводим восстановленную матрицу A.
print('Восстановленная матрица A:\n', A_recovered)
Восстановленная матрица A:
[[1. 0.4 0.8]
[0.4 1. 0.5]
[0.8 0.5 1.]]Это демонстрирует, что обратная матрица, вычисленная таким образом, совпадает с результатом, полученным с помощью функции `numpy.linalg.inv()`.
Также можно применять метод для асимметричных матриц, но в этом случае собственные значения могут быть комплексными.
Разложение на собственные значения и векторы является мощным инструментом, особенно когда дело касается анализа и преобразования матриц. Это одна из форм декомпозиции матриц, и она имеет особое значение для симметричных и положительно определенных матриц.
3.1.13 Число обусловленности матрицы
Число обусловленности: Это мера, которая оценивает, насколько "чувствительны" результаты системы уравнений к небольшим изменениям входных данных. В контексте матрицы, это число показывает, насколько изменится решение системы уравнений, если вы внесете небольшие изменения в коэффициенты уравнений (то есть в элементы матрицы). В реальных вычислениях часто встречаются ошибки округления и неточности в данных. Число обусловленности помогает понять, насколько сильно эти небольшие ошибки повлияют на конечный результат. Маленькое число обусловленности означает, что даже с ошибками результаты будут довольно точными. Высокое число обусловленности указывает на то, что система уравнений или математическая задача численно нестабильна. Это означает, что даже небольшие изменения в входных данных могут привести к значительным изменениям в результатах.
Представьте матрицу как набор линейных уравнений. Если у вас есть система линейных уравнений, вы используете матрицу для их представления и решения.Когда вы решаете эту систему, вы ожидаете получить определенные значения переменных.Если матрица имеет низкое число обусловленности, это означает, что небольшие изменения в коэффициентах (элементах матрицы) приведут к небольшим изменениям в решении.Например если у вас есть матрица с числом обусловленности, скажем, 2, это означает, что если вы измените входные данные на 1%, результаты изменятся примерно на 2%. И наоборот, если матрица имеет высокое число обусловленности, то небольшие изменения в коэффициентах могут привести к очень большим и непредсказуемым изменениям в решении.Например у матрицы число обусловленности 10000, то изменение входных данных на 1% может изменить результаты на 10000%.
Примеры на Python с использованием NumPy:importnumpyasnp# импортируем пакет Numpy и присваиваем ему псевдоним np
fromnumpyimportlinalgaslg
A=np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
print('-'*20)
# Вызываем функцию eig из модуля linalg, которая вычисляет собственные значения (e)
# и собственные векторы (v) матрицы A.
e, v=lg.eig(A)
print('Собственные значения:', e)
print('-'*20)
# Единичная матрица
# Очевидно, у неё 3 собственных значения по 1.0
print(A, '\n Число обусловленности A=', lg.cond(A))
--------------------
Собственные значения: [1. 1. 1.]
--------------------
[[1 0 0]
[0 1 0]
[0 0 1]]
Число обусловленности A= 1.0
Поскольку матрица A является единичной, мы получаем число обусловленности 1, как и ожидалось. Другой пример:importnumpyasnp# импортируем пакет Numpy и присваиваем ему псевдоним np
fromnumpyimportlinalgaslg
A=np.array([[1, 0, 0], [0, 1, 0], [0, 0, 0]])
print('-'*20)
# Вызываем функцию eig из модуля linalg, которая вычисляет собственные значения (e)
# и собственные векторы (v) матрицы A.
e, v=lg.eig(A)
print('Собственные значения:', e)
print('-'*20)
print(A, '\nЧислообусловленности A=', lg.cond(A))
--------------------
Собственные значения: [1. 1. 0.]
--------------------
[[1 0 0]
[0 1 0]
[0 0 0]]
Число обусловленности A= inf
Поскольку матрица A особенная (сингулярная), её число обусловленности равно inf, что в numpy означает бесконечность, как и ожидалось.importnumpyasnp# импортируем пакет Numpy и присваиваем ему псевдоним np
fromnumpyimportlinalgaslg
A=np.array([[1, 0, 0], [0, 1, 0], [0, 0, 10]])
print('-'*20)
# Вызываем функцию eig из модуля linalg, которая вычисляет собственные значения (e)
# и собственные векторы (v) матрицы A.
e, v=lg.eig(A)
print('Собственные значения:', e)
print('-'*20)
# Единичная матрица
# Очевидно, у неё 3 собственных значения по 1.0
print(A, '\n Число обусловленности A=', lg.cond(A))
--------------------
Собственные значения: [1. 1. 10.]
--------------------
[[1 0 0]
[0 1 0]
[0 0 10]]
Число обусловленности A= 10.0Число обусловленности этой матрицы A равно 10.0, что соответствует 10.0/1.0. Опять же, число обусловленности — это отношение наибольшего собственного значения к наименьшему. Число 10 указывает на то, что матрица менее стабильна, чем единичная матрица, но не является сингулярной.Мы можем сделать вывод, что если наибольшее собственное значение матрицы очень велико или наименьшее собственное значение матрицы очень мало, матрица, скорее всего, будет особенной (сингулярной), в зависимости от их соотношения.
Этотвывод подразумевает, что нормализация матрицы (часто используемая в машинном обучении) теоретически не изменит её число обусловленности. Это может помочь в уменьшении потери значащих цифр (из-за ограниченного представления чисел с плавающей точкой в компьютерном оборудовании).
3.1.14 Ранг матрицы
Ранг матрицы - это мера, определяющая максимальное количество линейно независимых строк или столбцов в матрице. Линейно независимые строки или столбцы - это такие, которые не могут быть выражены как линейная комбинация других строк или столбцов. Если квадратная матрица имеет полный ранг, это означает, что все её столбцы (или строки) взаимно линейно независимы. Такая матрица не является сингулярной. Для сингулярной матрицы (когда она не имеет обратной матрицы. В контексте линейной алгебры, это означает, что строки или столбцы матрицы не являются линейно независимыми.) её ранг должен быть меньше полного(это максимально возможное количество линейно независимых строк или столбцов), что называется дефицитом ранга. Представьте матрицу размером 3×3, у которой только два строки или столбца линейно независимы. Такая матрица будет иметь ранг 2. Поскольку полный ранг для квадратной матрицы размером 3×3 должен быть 3, а реальный ранг матрицы — 2, дефицит ранга составляет 3 - 2 =1.
Для неквадратной матрицы полный ранг — это количество её столбцов или строк, в зависимости от того, что меньше. Аналогично, она также может иметь дефицит ранга, если её ранг меньше полного ранга.
Теперь давайте рассмотрим это более подробно с использованием Numpy.
importnumpyasnp# импортируем пакет Numpy и присваиваем ему псевдоним np
fromnumpy.linalgimportmatrix_rank, eig
A=np.eye(4) # Единичнаяматрица
range=matrix_rank(A) # вычисляемранг
print('Ранг единичной матрицы=', range)
Ранг единичной матрицы= 4Здесь видно, что единичная матрица имеет размер 4 × 4 и полный ранг.
importnumpyasnp# импортируем пакет Numpy и присваиваем ему псевдоним np
fromnumpy.linalgimportmatrix_rank, eig
A=np.array([[1, -0.2, 0], [0.1, 1, -0.5], [0.1, 1, -0.5]]) # особеннаяматрица
range=matrix_rank(A) # вычисляемранг
print('Ранг Сингулярной матрицы=', range)
Ранг особеннойматрицы= 2Эта особенная матрица имеет два линейно независимых столбца и, следовательно, ранг 2. У неё дефицит ранга 1. Следовательно, она также должна иметь нулевое собственное значение, как показано ниже. Если у матрицы дефицит ранга n, у неё будет n нулевых собственных значений. Это легко проверить с помощью Numpy.
importnumpyasnp# импортируем пакет Numpy и присваиваем ему псевдоним np
fromnumpy.linalgimportmatrix_rank
fromnumpyimportlinalgaslg
# Сингулярная матрица
A=np.array([[1, -0.2, 0], [0.1, 1, -0.5], [0.1, 1, -0.5]])
# Вычисляемранг
range=matrix_rank(A)
# Вычисляем собственные значения
e, v=lg.eig(A)
# Выводим значения с 4 знаками после запятой
formatted_eigenvalues=np.around(e, 4)
print('собственные значений матрицы А:', formatted_eigenvalues)
print('Ранг Сингулярной матрицы=', range)
собственные значений матрицы А: [0.9562 0.5438 -0.]
Ранг Сингулярной матрицы= 2
Пример неквадратной матрицы:importnumpyasnp# импортируем пакет Numpy и присваиваем ему псевдоним np
fromnumpy.linalgimportmatrix_rank
# Сингулярная матрица
A=np.array([[1, -0.2, 2], [0.1, 1, -0.5]])
# Вычисляемранг
range=matrix_rank(A)
print('Ранг Неквадратной матрицы=', range)
Ранг Сингулярной матрицы= 2Эта матрица имеет только две строки и ранг 2. Она имеет полный ранг.
3.1.15 Матрица Поворота
В контексте двумерной системы координат, матрица поворота используется для изменения положения точек или векторов. Этот процесс важен, например, при вращении изображений или графиков. Матрица поворота T представлена в виде:
T=cosθ-sinθsinθcosθ
Здесьθ обозначает угол поворота. Этот угол определяет, насколько и в каком направлении мы хотим повернуть наш объект (например, вектор). Матрица состоит из косинуса и синуса этого угла, расположенных таким образом, что она поворачивает вектор на заданный угол в двумерном пространстве.
Вектор d, который мы хотим повернуть, представлен в виде двух компонент в системе координат: d = [u v]Теперь давайте рассмотрим, как это реализовано на Python с использованием библиотеки NumPy.
importnumpyasnp# импортируем пакет Numpy и присваиваем ему псевдоним np
theta=45# Угол поворота в градусах. Здесь 45 градусов.
# Преобразование угла из градусов в радианы,
# так как функции cos и sin в NumPy работают с радианами.
theta_rad=np.deg2rad(theta)
# Вычисление косинуса и синуса угла для элементов матрицы.
cos, sin=np.cos(theta_rad), np.sin(theta_rad)
# Создание матрицы поворота с использованием косинуса и синуса.
T=np.array([[cos, -sin], [sin, cos]])
print('-'*20)
print('Матрица преобразования T:\n', T)
d=np.array([1, 0]) # Исходный вектор направленный вдоль оси X.
print('-'*20)
print('Исходный вектор d:', d)
print('-'*20)
K=T@d# поворачиваем вектор d на угол theta.
print('вектор d поввернутый на угол theta\n', K)
print('-'*20)
K=T@ (T@d) # поворачиваем вектор d на угол 2*theta
print('вектор d поввернутый на угол 2*theta\n', K)
print('-'*20)
T_2=T@T# Комбинирование двух матриц поворота, что
K=T_2@d# эквивалентная запись T @ (T @ d)
print('вектор d поввернутый на угол 2*theta 2-й способ \n', K)
--------------------
Матрица преобразования T:
[[0.70710678 -0.70710678]
[0.70710678 0.70710678]]
--------------------
Исходный вектор d: [1 0]
--------------------вектор d поввернутый на угол theta
[0.70710678 0.70710678]
--------------------
вектор d поввернутый на угол 2*theta
[0. 1.]
--------------------
вектор d поввернутый на угол 2*theta 2-й способ
[0. 1.]Этот фрагмент кода демонстрирует, как последовательное применение матрицы поворота приводит к увеличению угла поворота. В данном случае, два применения матрицы поворота поворачивают вектор на угол, в два раза больший исходного.
Однако, интересный момент заключается в том, что если угол θ равен 45 градусам, то поворот на 8*45 = 360градусов (T @ (T @ (T @ (T @ (T @ (T @ (T @ T))))))) фактически вернет вектор в исходное положение, так как поворот на 360 градусов не изменяет положение объекта.
Эти примеры кода показывают, как можно использовать матрицу поворота для изменения ориентации векторов в двумерном пространстве. Этот инструмент применяется в машинном обучении и компьютерном зрении, где такие преобразования могут быть использованы для подготовки и аугментации данных, а также для улучшения восприятия и анализа изображений.
3.2 Интерполяция
Интерполяция - это часто используемая численная техника, которая позволяет получить приблизительные значения на основе известных данных. В некотором смысле, машинное обучение схоже с интерполяцией. В этом разделе рассматриваются общие вопросы, связанные с интерполяцией, с использованием библиотеки NumPy. Интерполяция также известна как подгонка кривых. Здесь мы покажем некоторые примеры интерполяции функций и аппроксимации, используя значения, заданные в дискретных точках пространства.
Первым примером будет использование `numpy.interp`.
importnumpyasnp# импортируем пакет Numpy и присваиваем ему псевдоним np
# Данные точки, для которых известны значения
x_known= [1, 2, 3, 4, 5]
y_known= [2.1, 2.9, 3.8, 5.1, 7.2]
# Точки, для которых мы хотим интерполировать значения
x_new= [1.5, 2.5, 3.5]
# Использование функции numpy.interp для интерполяции
y_new=np.interp(x_new, x_known, y_known)
print(y_new)
[2.5 3.35 4.45]
В этом коде `x_known` и `y_known` представляют собой известные данные. В данном случае, они задают функцию, значения которой нам известны в дискретных точках. `x_new` - это новые точки, для которых мы хотим вычислить значения, используя интерполяцию. Функция `np.interp` принимает новые точки `x_new`, а также массивы `x_known` и `y_known`, чтобы вычислить интерполированные значения `y_new`.Этот простой пример демонстрирует основную концепцию интерполяции: нахождение значений в новых точках, исходя из известных значений в других точках. Это применяется в машинном обучении и анализе данных, где необходимо работать с неполными или дискретными данными.
3.2.1 кусочно-линейная интерполяция
Кусочно-линейная интерполяция — это метод в математике и численном анализе, который используется для нахождения новых точек на основе дискретного набора известных точек. В одномерном случае это означает, что у вас есть некоторые известные точки на прямой, и вы хотите оценить значения в промежуточных точках.
importnumpyasnp# импортируем пакет Numpy и присваиваем ему псевдоним np
# Доступные данные
xn= [1, 15, 50] # данные: заданные координаты x
fn= [1, 20, 40] # данные: заданные значения функции в точках x
# Запрос/Предсказание f в новой точке x
x=25
f=np.interp(x, xn, fn) # получаем приблизительное значение в точке x
print(f'f({x:.3f})≈{f:.3f}') # выводим результат
print('-'*20)
x_2= [11, 21, 31, 19, 10]
f_2=np.interp(x_2, xn, fn) # запрос в более многочисленных точках
print(f_2)
f(25.000)≈25.714
--------------------
[14.57142857 23.42857143 29.14285714 22.28571429 13.21428571]На практике мы знаем, что интерполяция может быть довольно рискованной операцией, поэтому требуется особая осторожность, особенно при экстраполяции.
Экстраполяция - это процесс расширения оценок функции за пределы диапазона известных значений. В отличие от интерполяции, экстраполяция предполагает предсказание значений функции за пределами заданных точек данных.
Чтобы избежать или быть осведомленным об экстраполяции, можно установить предупреждение, когда интерполяция происходит вне области, которую покрывают данные.
importnumpyasnp# импортируем пакет Numpy и присваиваем ему псевдоним np
# Доступные данные
xn= [1, 15, 50] # данные: заданные координаты x
fn= [1, 20, 40] # данные: заданные значения функции в точках x
out_of_domain=-1111111.1# Используется число для предупреждения
print(np.interp(29, xn, fn, right=out_of_domain)) # выводчислаприэкстраполяции
print(np.interp(60, xn, fn, right=out_of_domain)) # Предупреждение28.0
-1111111.1Интерполяция с использованием полиномов более высокого порядка может быть более точной, но также может представлять большую проблему. Кусочно-линейная аппроксимация часто оказывается гораздо более безопасной и может быть очень эффективной, когда доступны 'плотные данные'. Ниже приведен пример использования кусочно-линейной интерполяции для аппроксимации синусоидальной функции.
importnumpyasnp# импортируем пакет Numpy и присваиваем ему псевдоним np
importmatplotlib.pyplotasplt# модуль для отображения результатов
x=np.linspace(0, 4*np.pi, 40) # данные: значения x
y=np.sin(x) # данные: значения функции в точках x
xvals=np.linspace(0, 4*np.pi, 50) # генерация плотных данных x, для которых значения
# получены путем интерполяции
yinterp=np.interp(xvals, x, y)
plt.plot(x, y, 'o') # отображение исходных точек данных
plt.plot(xvals, yinterp, '-x') # отображение интерполированных точек данных
plt.show() # показать графики
Здесь x — это массив значений от 0 до 4*pi, разделенный на 40 равных частей. y — это значения синусоидальной функции в этих точках. Затем xvals генерируется как более плотный набор точек, и np.interp используется для интерполяции значений синуса в этих новых точках. После интерполяции результаты визуализируются с помощью matplotlib. Исходные точки данных отображаются кружками ('o'), а интерполированные точки — крестиками ('x').
На следующим графике мы будем использовать в 2 раза меньше значений x изменим в коде строчку:
x=np.linspace(0, 4*np.pi, 40) # данные: значения xна
x=np.linspace(0, 4*np.pi, 20) # данные: значения x
Это позволяет наглядно увидеть, как интерполяция аппроксимирует исходную функцию.
3.2.2 Аппроксимация решения методом наименьших квадратов
Аппроксимация решения методом наименьших квадратов в одномерном случае — это процесс подгонки линейной модели к данным. В данном примере, мы будем аппроксимировать набор данных прямой линией на плоскости x-y.
y = wx + bЗдесь y— это значение, которое мы пытаемся предсказать, x — независимая переменная, w — градиент (или угловой коэффициент) прямой, а b — смещение (или точка пересечения с осью Y).
Мы определим градиент w и смещение b, используя пары данных xiyi. В этом примере уравнение может быть переписано как y = X · w
В этом уравнении X представляет собой матрицу данных, включающую значения [x, 1] (как константу для учета смещения b). w теперь представляет вектор параметров, содержащий градиент и смещение. Теперь мы можем использовать np.linalg.lstsq для решения w:importnumpyasnp# импортируем пакет Numpy и присваиваем ему псевдоним np
importmatplotlib.pyplotasplt
w_true, b_true=2.0, 0.0# Устанавливаются истинные значения для градиента и смещения
x=np.array([0, 1, 2, 3, 4, 5]) # Создается массив значений x
X=np.vstack([x, np.ones(len(x))]).T# Формируется матрица X
y=w_true*x+b_true+np.random.rand(len(x))/0.1# Генерация значений y с добавлением случайного шума
w, b=np.linalg.lstsq(X, y, rcond=None)[0] # Вычисление параметров w и b
plt.plot(x, y, 'o', label='Исходные данные', markersize=10)
plt.plot(x, w*x+b, 'r', label='Аппроксимированная линия')
plt.legend()
plt.show()
Полученная модель — это простейшая форма машинного обучения, известная как линейная регрессия. Она показывает, как можно аппроксимировать зависимости между переменными линейной функцией. Этот метод используется в машинном обучении для прогнозирования значений или для понимания отношений между переменными.
3.2.3 Одномерная интерполяция с использованием interp1d
Одномерная интерполяция это простой, но мощный инструмент. Рассмотрим метод интерполяции interp1d из библиотеки SciPy.
Функция interp1d позволяет проводить интерполяцию для одномерных данных. Это может быть линейная интерполяция, где новые точки создаются путем соединения соседних точек данных прямыми линиями, или более сложная, например, кубическая интерполяция, где используются кубические полиномы для гладкого соединения точек.
Рассмотрим Пример:importnumpyasnp# импортируем пакет Numpy и присваиваем ему псевдоним np
# Импортируется библиотека Matplotlib для визуализации данныхimportmatplotlib.pyplotasplt
# Импортируется функция interp1d для интерполяцииfromscipy.interpolateimportinterp1d
x0, xL=0, 20# Задаются начальное (x0) и конечное (xL) значения диапазона x
# Генерируется 15 равномерно распределенных точек между x0 и xL
x=np.linspace(x0, xL, num=11, endpoint=True)
# Вычисляются y значения как синус от куба значений x, масштабированные на 1/8
y=np.cos(-x**3/8.0)
print('Данные x:', x)
print('Данные y:', y)
print('x.shape:', x.shape, 'y.shape:', y.shape)# Выводятсяразмерымассивов x и y
f=interp1d(x, y) # Создается линейная интерполяционная функция
# Создается кубическая интерполяционная функция
f2=interp1d(x, y, kind='cubic')
# Генерируются новые точки для предсказания
xnew=np.linspace(x0, xL, num=41, endpoint=True)
# Визуализируются исходные данные и результаты интерполяции
plt.plot(x, y, 'o', xnew, f(xnew), '-', xnew, f2(xnew), '-')
# Добавляется легенда на график
plt.legend(['данные', 'линейная ', 'кубическая'], loc='best')
plt.show() # Показывается график
Данные x: [0. 2. 4. 6. 8. 10. 12. 14. 16. 18. 20.]
Данные y: [1. 0.54030231 -0.14550003 -0.29213881 0.39185723 0.78771451
-0.71798508 -0.84383778 -0.99683339 0.98869558 0.56237908]
x.shape: (11,) y.shape: (11,)
3.2.4 Представление двухмерного сплайна с использованием bisplrep
Двухмерный сплайн — это средство для создания гладких поверхностей из набора данных в двух измерениях (обычно обозначаемых как x и y). Каждой точке в этом двумерном пространстве соответствует значение, часто обозначаемое как z. Сплайны используются для создания непрерывной и гладкой функции, проходящей через эти точки или аппроксимирующей их расположение.
Функция bisplrep (B-splinerepresentation) используется для построения B-сплайна, который представляет собой математическую модель для описания гладких кривых и поверхностей. B-сплайны обеспечивают гибкость и точность при работе с данными, позволяя контролировать форму аппроксимации с помощью ряда параметров.
Рассмотрим пример:
# Импортируем необходимые библиотекиimportnumpyasnp# Для работы с массивами и математическими функциями
importmatplotlib.pyplotasplt# Для визуализации данных
fromscipyimportinterpolate# Для интерполяции и работы со сплайнами
# Создаем двумерную сетку координат
# np.mgrid создает двумерную сетку в диапазоне от -2 до 2 с 20 точками по каждой оси
grid_x, grid_y=np.mgrid[-2:2:20j, -2:2:20j]
# Вычисляем значения по функции Гаусса
# Применяем функцию Гаусса к каждой точке сетки
gaussian_z=np.exp(-0.5* (grid_x**2+grid_y**2))
# Визуализируем исходные данные
plt.figure() # Создаем новую фигуру для рисунка
# Рисуем цветовую карту исходных данных Гауссовской функции
plt.pcolor(grid_x, grid_y, gaussian_z, shading='auto')
plt.colorbar() # Добавляем цветовую шкалу для понимания значений
# Устанавливаем заголовок графика
plt.title("Гауссовская функция в дискретных точках")
plt.show() # Показываем график
# Создаем новую, более плотную сетку для интерполяции
# Создаем более плотную сетку для детализированной интерполяции
fine_grid_x, fine_grid_y=np.mgrid[-2:2:100j, -2:2:100j]
# Создаем B-сплайн с использованием функции bisplrep
# bisplrep создает параметры B-сплайна из исходных данных
spline_tck=interpolate.bisplrep(grid_x, grid_y, gaussian_z, s=0)
# Вычисляем значения сплайна на новой сетке
# bisplev вычисляет значения сплайна на новой сетке
interpolated_z=interpolate.bisplev(fine_grid_x[:,0], fine_grid_y[0,:], spline_tck)
# Визуализируем интерполированные данные
plt.figure() # Создаем еще одну фигуру для рисунка
# Рисуем цветовую карту интерполированных данных
plt.pcolor(fine_grid_x, fine_grid_y, interpolated_z, shading='auto')
plt.colorbar() # Добавляем цветовую шкалу
# Устанавливаем заголовок графика
plt.title("Интерполированная Гауссовская функция")
plt.show() # Показываем график

Функция Гаусса, используемая в представленном коде, определяется следующим образом:
defgaussian_function(x, y):
returnnp.exp(-0.5* (x**2+y**2))В этой функции x и y являются координатами, для которых вычисляется значение функции Гаусса. Функция Гаусса, как правило, представляет собой куполообразную форму, центрированную в точке (0, 0) и уменьшающуюся с увеличением расстояния от центра.
В этом конкретном случае, использованная формула представляет двумерное нормальное распределение без смещения и с одинаковыми стандартными отклонениями по обеим осям, центрированное в точке (0, 0). Величина 0.5 в экспоненте определяет "широту" купола функции, при этом большие значения приводят к более "острому" и высокому куполу, а меньшие — к более "плоскому" и широкому.
Поэкспериментируйте с кодом — это поможет вам лучше понять материал и чувствовать себя более уверенно при написании кода.
3.2.5 Радиально-базисные функции для сглаживания и интерполяции
Чтобы понять принцип работы Радиально-базисные функции (РБФ), давайте разберем основные аспекты РБФ:Базовые функции для аппроксимации: РБФ используются для аппроксимации функций, то есть для нахождения функций, которые приближенно соответствуют набору данных. Это особенно полезно, когда у нас есть набор точек данных и мы хотим найти гладкую функцию, которая проходит через эти точки или близко к ним.
Функции расстояния: В основе РБФ лежит понятие расстояния. Эти функции зависят от расстояния от центральной точки, что делает их радиально-симметричными. Это свойство позволяет РБФ быть очень гибкими при работе с данными, распределенными неравномерно или в сложных многомерных пространствах.
Противодействие переобучению: РБФ обычно менее склонны к переобучению по сравнению с некоторыми другими методами аппроксимации. Это связано с их гладкостью и способностью к обобщению.
В библиотеке SciPy представлены различные типы РБФ, каждый из которых имеет свои особенности:
”multiquadric”: sqrt((r/self.epsilon)**2 + 1)Это универсальная функция, обеспечивающая гладкую аппроксимацию. Она может обрабатывать различные виды данных и часто используется по умолчанию.
“inverse”: 1.0/sqrt((r/self.epsilon)**2 + 1)Обратная мультиквадратичная функция создает гладкую поверхность, которая хорошо подходит для аппроксимации гладких функций.
“gaussian”: exp(-(r/self.epsilon)**2)Гауссовская функция обеспечивает очень гладкую аппроксимацию и часто используется в статистическом анализе и машинном обучении.
“linear”: rЛинейная функция проста и может быть полезна для некоторых основных задач аппроксимации.
“cubic”: r**3Кубическая функция предоставляет более резкую аппроксимацию по сравнению с линейной или гауссовской.
“quintic”: r**5Квинтическая функция обеспечивает еще более высокий уровень гладкости.
“thin plate”: r**2 * log(r)Функция "тонкой пластины" особенно полезна для интерполяции пространственных данных и часто используется в геостатистике и компьютерной графике.
Во многих РБФ (например, в мультиквадрической или гауссовской) используется параметр “epsilon“, который контролирует "ширину" функции. Меняя его значение, можно контролировать степень гладкости или локализации аппроксимации.А подбирая подходящий тип РБФ и настраивая параметры, можно добиться оптимального баланса между точностью аппроксимации и устойчивостью к переобучению.
Рассмотрим одномерные примеры.
importnumpyasnp
fromscipy.interpolateimportRbf, InterpolatedUnivariateSpline
importmatplotlib.pyplotasplt
# Генерацияданных
np.set_printoptions(formatter={'float': '{: 0.3f}'.format})
# создание массива точек от -10 до 10, всего 11 точек
sample_points=np.linspace(-10, 10, 11)
# вычислениезначенийкаксинусотsample_points
sample_values=np.cos(sample_points)
# Создание более мелкой сетки для отображения интерполированных данных
# создание более плотной сетки точек от -10 до 10
interpolation_points=np.linspace(-10, 10, 100)
# Использование сплайнов для интерполяции
# созданиеинтерполяторасплайна
spline_interpolator=InterpolatedUnivariateSpline(sample_points, sample_values)
# вычисление интерполированных значений на плотной сетке
spline_values=spline_interpolator(interpolation_points)
plt.subplot(2, 1, 1) # подготовка к отображению 2 графиков вместе
# отображение исходных данных синими точками
plt.plot(sample_points, sample_values, 'bo')
# отображение исходной функции синуса красной линией
plt.plot(interpolation_points, np.cos(interpolation_points), 'r')
# отображение интерполированных значений сплайном зеленой линией
plt.plot(interpolation_points, spline_values, 'g')
plt.title('Интерполяция с использованием одномерного сплайна')
# Добавляется легенда на график
# plt.show()
# Использование РБФ для интерполяции
# создание интерполятора РБФ
rbf_interpolator=Rbf(sample_points, sample_values)
# вычислениеинтерполированныхзначенийметодомРБФ
rbf_values=rbf_interpolator(interpolation_points)
plt.subplot(2, 1, 2) # подготовка ко второму графику
# отображение исходных данных синими точками
plt.plot(sample_points, sample_values, 'bo')
# отображение исходной функции синуса красной линией
plt.plot(interpolation_points, np.cos(interpolation_points), 'r')
# отображение интерполированных значений РБФ зеленой линией
plt.plot(interpolation_points, rbf_values, 'g')
plt.title('Интерполяция с использованием РБФ')
# Добавляется легенда на график
plt.legend(['исходные данные', 'исходная функция', 'интерполированные значения'],
loc='upper center', bbox_to_anchor=(0.5, -0.1),
fancybox=True, shadow=True, ncol=4)
plt.show()
Теперь рассмотрим двумерные примеры:importnumpyasnp
fromscipy.interpolateimportRbf
importmatplotlib.pyplotasplt
frommatplotlibimportcm
# Генерация 2D-тестовых данных
num_points=10 # количество точек
x=np.random.rand(num_points) *6-3 # случайные x в диапазоне [-3, 3]
y=np.random.rand(num_points) *6-3 # случайные y в диапазоне [-3, 3]
z=np.sin(np.sqrt(x**2+y**2)) # z как синус от расстояния до центра координат
# Создание сетки для интерполяции
grid_size=100
xi=np.linspace(-3, 3, grid_size)
yi=np.linspace(-3, 3, grid_size)
XI, YI=np.meshgrid(xi, yi) # создание сетки
# Использование РБФ для интерполяции
rbf_interpolator=Rbf(x, y, z, function='multiquadric', epsilon=0.5) # создание интерполятора РБФ
ZI=rbf_interpolator(XI, YI) # интерполяция
# Визуализация результатов
plt.figure(figsize=(8, 6))
plt.pcolor(XI, YI, ZI, cmap=cm.viridis, shading='auto') # отображение интерполированных данных
plt.scatter(x, y, 30, z, cmap=cm.viridis, edgecolor='k') # отображение исходных точек данных
plt.title('2D Интерполяция РБФ - multiquadric)
plt.xlim(-3, 3)
plt.ylim(-3, 3)
plt.colorbar()
plt.show()
importnumpyasnp
fromscipy.interpolateimportRbf
importmatplotlib.pyplotasplt
# Генерацияслучайных 3D данных
x, y, z=np.random.rand(3, 30) # 30 случайных точек в каждом измерении в диапазоне [0, 1]
d=np.sin(x) +np.cos(y) +np.tan(z) # вычисление значений d на основе функции от x, y и z
# Создание интерполятора РБФ
rbf_interpolator=Rbf(x, y, z, d, function='multiquadric')
# Создание сетки для интерполяции
xi, yi, zi=np.meshgrid(np.linspace(0, 1, 10), np.linspace(0, 1, 10), np.linspace(0, 1, 10))
# Интерполяция
di=rbf_interpolator(xi, yi, zi)
# Визуализация исходных и интерполированных данных
fig=plt.figure(figsize=(12, 6))
# Визуализация исходных данных
ax1=fig.add_subplot(121, projection='3d')
ax1.scatter(x, y, z, c=d, cmap='viridis')
ax1.set_title("Исходные данные")
ax1.set_xlabel("X")
ax1.set_ylabel("Y")
ax1.set_zlabel("Z")
# Визуализация интерполированных данных
ax2=fig.add_subplot(122, projection='3d')
ax2.scatter(xi.flatten(), yi.flatten(), zi.flatten(), c=di.flatten(), cmap='viridis')
ax2.set_title("Интерполированные данные")
ax2.set_xlabel("X")
ax2.set_ylabel("Y")
ax2.set_zlabel("Z")
plt.show()
3.3 Разложение на Одночлены
Разложение на одночлены часто используется для приближенного представления сложных зависимостей между переменными. Методы разложения на одночлены могут варьироваться от классических полиномов до более сложных базисных функций. Глава охватывает теоретические аспекты разложения, такие как выбор базисных функций и оптимизация коэффициентов, а также применение данного метода в практических задачах машинного обучения, таких как регрессия и аппроксимация сложных функций.
3.3.1 Формулировка SVD
SVD - это метод факторизации матрицы, который применяется во многих областях, таких как обработка сигналов, статистика и машинное обучение. Суть SVD заключается в разложении матрицы на три другие матрицы, и это применимо как к квадратным, так и к прямоугольным матрицам.
Разложение матрицы A размера m×p в SVD представляется как: A = UΣV*где:
U - унитарная матрица размера m \timesmm×m.Σ (сигма) - прямоугольная диагональная матрица размера m×pс неотрицательными числами на диагонали. Эти числа называются сингулярными значениями матрицы A.
V* - сопряженно-транспонированная унитарная матрица размера p×p.Унитарные матрицы U и V обладают свойством сохранения длины и угла, что делает их идеальными для описания вращений и отражений в пространстве. В контексте SVD, эти матрицы обеспечивают базисы для входного и выходного пространств матрицы A.
Сингулярные значения Σ указывают на направления, в которых матрица A "растягивает" пространство наиболее сильно. Например, если одно из сингулярных значений намного больше других, это указывает на то, что большая часть "энергии" или "информации" матрицы сосредоточена вдоль соответствующего сингулярного вектора.
Левые сингулярные векторы (столбцы U) представляют направления в исходном пространстве, которые после преобразования матрицей Aпревращаются в соответствующие правые сингулярные векторы (столбцы V). Эти векторы формируют ортонормированный базис в их соответствующих пространствах.
SVD - это мощный инструмент в численном анализе, который позволяет нам "разобрать" матрицу на её составляющие части, понимая, как различные части этой матрицы взаимодействуют друг с другом.
3.3.2 Алгоритмы для SVD
Один из часто используемых методов для вычисления SVD базируется на разложении QR. Разложение QR - это процесс, при котором матрица разлагается на ортогональную матрицу Q и верхнюю треугольную матрицу R. Этот подход хорошо подходит для работы с квадратными и не квадратными матрицами.
Другой подход к вычислению SVD основан на разложении на собственные значения. Этот метод теоретически прост, но на практике редко используется для больших матриц из-за проблем с численной стабильностью. Но для нашего теоретического анализа и вывода формул это не является проблемой, и поэтому рассматривать будем именно его. Для работы этого простого подхода нам потребуется наложить некоторые условия на матрицу A.
Предположим у нас есть матрица A размером m×p состоящаяиз действительных чисел сm>p и рангом равным p.Формирование Нормальной Матрицы B :Эта матрица будет симметричной и квадратной размером p×p. B = ATA
Затем матрица B подвергается разложению на собственные значения, что приводит к формуле: B = VeΛVeT где:
Ve — ортонормированная матрица собственных векторов,
Λ — диагональная матрица собственных значений.Известно, что A имеет разложение SVD, т.е.: A = UΣVT
Поскольку матрица A имеет ранг p, сингулярные значения в Σ будут все положительными действительными числами. Используя формулу A = UΣVT, мы имеем:
Так как UTU = I, и Σ диагонально (не изменяется при транспонировании). Сравнивая предидущую формулу с B = VeΛVeT, мы имеем:
V = Ve
Используя теперь A = UΣVT, и ортонормальное свойствоV: VTV=I, мы имеем: AV = UΣ
Так как Σ имеет все положительные собственные значения, мы наконец получаем: U = AVΣ-1
Утверждение о том, что U унитарна, следует из того, что A представлена сингулярным разложением, где U и V - унитарные матрицы. Наконец, если A недостаточно определена (ранг(A) < p), матрица B будет иметь нулевые собственные значения. В таких случаях мы просто отбрасываем все нулевые собственные значения и их соответствующие собственные векторы. Это все равно даст нам SVD в сокращенной форме, и весь вышеуказанный процесс по-прежнему действителен.
Основная проблема этого метода заключается в численной стабильности, особенно для больших матриц. Проблема возникает из-за квадратов чисел обусловленности в процессе формирования матрицы B = ATA.
Хотя этот метод полезен для теоретического понимания SVD и доказательства того, что любая матрица имеет SVD, он редко используется на практике для вычислений. На практике для вычисления SVD чаще используются другие методы, такие как разложение QR, которое избегает проблем, связанных с формированием матрицы B.
3.3.3 Пример
Давайте рассмотрим пример использования SVD (Singular Value Decomposition) с использованием Python и библиотеки NumPy.importnumpyasnp #Импортируембиблиотеку NumPy
# Создаем матрицу 'my_matrix' со случайными целыми значениями размером 4x5
my_matrix=np.random.randint(0, 100, size=(4, 5))
print('-'*20)
print("Исходнаяматрица:\n", my_matrix)
print('-'*20)
# Выполняем разложение на одночлены (SVD) матрицы 'my_matrix'
# U - левые сингулярные векторы,
# sigma - сингулярные значения,
# Vt - правые сингулярные векторы (транспонированные)
U, sigma, Vt=np.linalg.svd(my_matrix, full_matrices=False)
# Выводим размерности матриц U, sigma и Vt для проверки
print("\nФормы: U, sigma, Vt:", U.shape, sigma.shape, Vt.shape)
print('-'*20)
# Выводим матрицы U, sigma и Vt
print("\nлевые сингулярные векторы U:\n", U)
print("\nсингулярные значения Sigma:", sigma)
print("\nправые сингулярные векторы Vt:\n", Vt)
print('-'*20)
# Преобразуем вектор сингулярных значений 'sigma' в диагональную матрицу
# 'sigma_mat'
sigma_mat=np.diag(sigma)
print("\nдиагональнаяматрица sigma:\n", sigma_mat)
print('-'*20)
# Проверяем, можно ли восстановить исходную матрицу
reconstructed_matrix=np.dot(U, np.dot(sigma_mat, Vt))
print("\nВосстановлена ли исходная матрица? ",
np.allclose(my_matrix, reconstructed_matrix))
print('-'*20)
# Выводим исходную и восстановленную матрицы для сравнения
print("\nисходнаяматрица:\n", my_matrix)
print("\nвосстановленнаяматрица:\n", reconstructed_matrix)
--------------------
Исходная матрица:
[[58 83 22 94 36]
[22 54 45 82 91]
[10 34 62 75 24]
[55 26 46 52 68]]
--------------------
Формы: U, sigma, Vt: (4, 4) (4,) (4, 5)
--------------------
левые сингулярные векторы U:
[[0.56147956 0.79253797 0.237659 -0.01193577]
[0.57205124 -0.43106941 0.05107003 -0.69593706]
[0.40034304 -0.01747367 -0.87370638 0.27578483]
[0.44409856 -0.43099582 0.42136351 0.66292675]]
сингулярные значения Sigma: [242.50494188 59.13289951 48.44113155 33.06221611]
правые сингулярные векторы Vt:
[[0.30341564 0.42329775 0.34368231 0.63011555 0.46216321]
[0.21314985 0.51921871 -0.38678068 0.26091348 -0.68359633]
[0.60580112 0.07706081 -0.56275368 -0.35278672 0.43118031]
[0.70218915 -0.36169658 0.4843455 -0.09172851 -0.36483051]]
--------------------
диагональная матрица sigma:
[[242.50494188 0. 0. 0.]
[0. 59.13289951 0. 0.]
[0. 0. 48.44113155 0.]
[0. 0. 0. 33.06221611]]
--------------------Восстановлена ли исходная матрица? True
--------------------
исходная матрица:
[[58 83 22 94 36]
[22 54 45 82 91]
[10 34 62 75 24]
[55 26 46 52 68]]
восстановленная матрица:
[[58. 83. 22. 94. 36.]
[22. 54. 45. 82. 91.]
[10. 34. 62. 75. 24.]
[55. 26. 46. 52. 68.]]Этот пример кода и комментарии должны помочь лучше понять, как работает SVD и как его можно применять в реальных задачах.
3.3.4 SVD для сжатия данных
Сжатие Изображения с Помощью SVD: для этого примера мы будем использовать изображение, загружаемое из библиотеки PIL (Python Imaging Library), и выполним на нем SVD для сжатия данных.
importnumpyasnp
fromPILimportImage
importmatplotlib.pyplotasplt
# Загрузкаизображения
# Замените 'path_to_image.jpg' на путь к вашему изображению
original_image=Image.open('path_to_image.jpg')
# Преобразование изображения в черно-белый формат для упрощения обработки
gray_image=original_image.convert('L')
# Преобразование изображения PIL в массив numpy для дальнейшей обработки
gray_array=np.array(gray_image)
print('получившийся масив данных из изображения\n', gray_array)
print('-'*20)
print("Форма исходного массива изображений:", gray_array.shape)
# Создаем окно для отображения изображений
plt.figure(figsize=(12, 6))
# Выводим исходное изображение
plt.subplot(1, 2, 1) # 1 строка, 2 столбца, позиция 1
plt.imshow(gray_array, cmap='gray')
plt.title("Исходное изображение")
# Выполнение SVD на матрице изображения
U, S, Vt=np.linalg.svd(gray_array, full_matrices=False)
print('-'*20)
# Вывод количества сингулярных значений оригинального изображения
print("Количество сингулярных значений в исходном изображении:", len(S))
# Определение количества сингулярных значений для использования
k=112 # Экспериментируйте с этим значением для различного уровня сжатия
print('-'*20)
print("Количество используемых сингулярных значений:", k)
# Строим приближенное изображение
# Матричное умножение для воссоздания изображения
# с использованием k сингулярных значений
approximated_image=np.dot(U[:,:k], np.dot(np.diag(S[:k]), Vt[:k,:]))
# Выводим воссозданное изображение
plt.subplot(1, 2, 2) # 1 строка, 2 столбца, позиция 2
plt.imshow(approximated_image, cmap='gray')
plt.title(f"Сжатое изображение с {k} сингулярными значениями")
plt.show()получившийся массив данных из изображения
[[40 40 40... 74 74 73]
[40 40 40... 74 74 74]
[41 41 41... 74 74 75]
...
[53 53 53... 61 61 61]
[53 53 53... 61 61 61]
[53 53 53... 61 61 61]]
--------------------
Форма исходного массива изображений: (1125, 2000)
--------------------
Количество сингулярных значений в исходном изображении: 1125
--------------------
Количество используемых сингулярных значений: 112
Переменная k задает количество сингулярных значений, которые будут использоваться для приближенного воссоздания изображения. Это ключевой параметр, влияющий на качество и степень сжатия изображения. Выражение np.dot(U[:, :k], np.dot(np.diag(S[:k]), Vt[:k, :])) умножает матрицы U, S и VT для воссоздания изображения, используя только k сингулярных значений. Таким образом, мы получаем сжатую версию исходного изображения.
Этот пример демонстрирует, как можно использовать SVD для сжатия изображений, сокращая количество необходимых данных для их хранения и передачи. Важно отметить, что значение k (количество используемых сингулярных значений) является компромиссом между качеством изображения и степенью его сжатия. Слишком маленькое значение k может привести к значительной потере деталей, в то время как слишком большое - к незначительному сжатию. Экспериментируя с различными значениями k, можно найти оптимальный баланс для конкретной задачи.
3.4 Анализ главных компонент
Анализ главных компонент (Principal Component Analysis, PCA) представляет собой статистический метод, используемый в машинном обучении для уменьшения размерности данных, сохраняя при этом как можно больше информации. Впервые предложенный Карлом Пирсоном в 1901 году, PCA позволяет преобразовать большой набор переменных, часто коррелирующих между собой, в меньший набор некоррелированных переменных, называемых главными компонентами.
PCA начинается с исходного набора данных, состоящего из p переменных (признаков). Цель состоит в том, чтобы преобразовать эти данные таким образом, чтобы новые переменные (главные компоненты) были линейно некоррелированными. Эти компоненты упорядочиваются так, что первый главный компонент объясняет наибольшую долю общей вариабельности данных, второй - следующую по значимости часть и так далее.
Преобразование, используемое в PCA, является ортогональным. Это означает, что главные компоненты ортогональны (то есть некоррелированы) друг к другу в многомерном пространстве признаков. Ортогональное преобразование обеспечивает, что каждый главный компонент представляет уникальный аспект данных, без избыточности информации.
Главные компоненты ранжируются по степени объясненной вариативности в данных. Первый главный компонент имеет наивысшую вариативность, второй - меньшую и так далее. Это ранжирование позволяет определить, сколько компонентов необходимо сохранить для адекватного представления данных, уменьшая при этом размерность исходного пространства признаков.
Одним из основных применений PCA в машинном обучении является уменьшение размерности данных. Это особенно полезно при работе с наборами данных с большим количеством признаков, так как уменьшение размерности может помочь ускорить обучение моделей и уменьшить риск переобучения.
При использовании PCA важно помнить, что он чувствителен к масштабированию переменных. Различный масштаб признаков может значительно повлиять на результаты анализа, поэтому часто рекомендуется стандартизировать данные перед применением PCA. Кроме того, PCA опирается на линейные взаимосвязи между переменными и может не быть эффективным для выявления нелинейных структур в данных.
3.4.1 Формулировка PCA
Рассмотрим общую матрицу A размером m×p, состоящую из действительных чисел, где m>p. Сначала формируем матрицу B: B = A TA
Это приводит к созданию симметричной квадратной матрицы размером p×p. Эта матрица, как минимум, является положительно полуопределенной и часто - положительно определенной (SPD). Далее выполняется разложение на собственные значения матрицы B: B = VΣVT, где:
V - ортонормированная матрица размером p×p, состоящая из p собственных векторов матрицы B.
Σ - диагональная матрица размером p×p, содержащая собственные значения, которые являются неотрицательными действительными числами.Тогда PCA определяется как: APCA= AV
Таким образом, мы проецируем исходные данные A на ортонормированные собственные векторы. Полученная матрица APCA сохраняет форму исходной матрицы A (m×p).
Используя все собственные векторы, можно восстановить исходную матрицу A:Ar= APCAVT=AVVT =A
Это возможно благодаря ортонормированности собственных векторов. Однако часто используются только первые несколько собственных векторов (ранжируемых по собственным значениям), которые содержат большую часть информации исходной матрицы A. Пример частичного восстановления с использованием k собственныхвекторов:
Ar= APCA [0 : m, 0 : k]VT[0 : k, 0 : p] = A[0 : m, 0 : p]V[0 : p, 0 : k]VT[0 : k, 0 : p] ≠ A
Это в общем не равно исходному A, но часто может быть очень близко к нему. В этом случае объем хранения составляет m×k + k×p, что может быть значительно меньше исходного размера m×p.
Если размерность матрицы A такова, что m<p, можно использовать ее транспонирование и применить аналогичный процесс. В этом случае формируется матрица B как: B = AAT
Это приводит к созданию симметричной квадратной матрицы размером m×m. После выполнения разложения на собственные значения этой матрицы, мы получаем: B = VΣVT где V и Σ определены аналогично предыдущему случаю, но с размерностями m×m.
Тогда PCA определяется как:APCA=VTA
Результат сохраняет форму исходной матрицы A (m×p). Полное восстановление исходных данных возможно также с использованием всех собственных векторов:
Ar= VAPCA=VVTA =A
Мы можем использовать только небольшое количество собственных векторов для восстановления матрицы A. Например, если мы используем k ≤ m собственных векторов, у нас получится:
Ar= V[0: k, 0: m] VT[0 : m, 0 : k]APCA[0 : k, 0 : p]≠A
Для больших систем вместо прямого формирования матрицы B и выполнения разложения на собственные значения используются более стабильные алгоритмы, такие как QR-преобразование мы об этом писали выше.
3.4.2 примеры
Пример 1:Давайте рассмотрим пример PCA, используемый для анализа набора данных измерений роста и веса. Этот пример поможет проиллюстрировать, как PCA может быть использован для выявления основных направлений вариации в данных.
Представим, что у нас есть набор данных, содержащий измерения роста (в сантиметрах) и веса (в килограммах) для группы людей. Наша цель - использовать PCA для выявления основных направлений вариации в этих двух переменных.
importnumpyasnp
importmatplotlib.pyplotasplt
# Исходные данные
height=np.array([170, 168, 177, 181, 172, 171, 169, 175, 174, 178])
weight=np.array([70, 66, 78, 85, 72, 68, 65, 74, 71, 80])
# Создание матрицы данных
A=np.vstack((height, weight)).TPCA начинается с центрирования данных, то есть вычитания среднего значения каждого признака из соответствующих значений.
# Центрированиеданных
mean_height=np.mean(height)
mean_weight=np.mean(weight)
A_centered=A-np.array([mean_height, mean_weight])
Следующий шаг - вычисление ковариационной матрицы, которая показывает, как переменные изменяются вместе.
# Вычисление ковариационной матрицы
cov_matrix=np.cov(A_centered.T)Теперь мы можем выполнить PCA, найдя собственные значения и собственные векторы ковариационной матрицы.
# Выполнение PCA
eigenvalues, eigenvectors=np.linalg.eig(cov_matrix)
# Сортировка собственных значений и векторов
index=eigenvalues.argsort()[::-1]
eigenvalues=eigenvalues[index]Собственные значения показывают, как много вариации объясняется каждым из главных компонентов. Собственные векторы показывают направления главных компонентов в исходном пространстве признаков.
# Первый главный компонент
first_component=eigenvectors[:,0]
# Визуализация
plt.scatter(A_centered[:, 0], A_centered[:, 1])
plt.quiver(mean_height, mean_weight, first_component[0], first_component[1], scale=5, color='r')
plt.xlabel('Рост')
plt.ylabel('Вес')
plt.title('PCA роста и веса')
plt.axis('equal')
plt.show()
В этом примере первый главный компонент (отмеченный красной стрелкой) показывает направление наибольшей вариации в данных. Если рост и вес сильно коррелируют, этот компонент будет указывать в направлении, где оба этих измерения увеличиваются вместе. Это может представлять, например, общую тенденцию к увеличению веса с ростом.
Теперь, когда мы определили главные компоненты, мы можем проецировать исходные данные на эти компоненты для уменьшения размерности или для дальнейшего анализа.
# Проекция данных на первый главный компонент
projected_data=np.dot(A_centered, eigenvectors)
# Визуализацияпроекции
plt.scatter(projected_data[:, 0], projected_data[:, 1])
plt.xlabel('Первый главный компонент')
plt.ylabel('Второй главный компонент')
plt.title('Проекция данных роста и веса на главные компоненты')
plt.axis('equal')
plt.show()
На этом графике точки данных теперь представлены в системе координат, определенной главными компонентами. Первая ось (ось x) соответствует первому главному компоненту и захватывает наибольшую вариацию в данных. Вторая ось (ось y) - это второй главный компонент, который захватывает оставшуюся вариацию.
Этот пример иллюстрирует, как PCA можно использовать для выявления основных направлений вариации в данных. PCA особенно полезен в ситуациях, когда имеется много переменных, и нужно найти наиболее значимые направления вариации, чтобы упростить анализ или подготовить данные для машинного обучения.
Пример 2
Давайте напишем код для усеченного PCA (PrincipalComponent Analysis), который можно использовать для сжатия изображений. В этом примере мы загрузим изображение, преобразуем его в оттенки серого, применим PCA, а затем восстановим изображение с использованием ограниченного количества главных компонентов.
importnumpyasnp
importmatplotlib.pyplotasplt
fromPILimportImage
# Определение функции для выполнения PCA
defprincomp(A, numpc=0):
# Вычитание среднего значения по каждому признаку для центрирования данных
A=A-np.mean(A, axis=0)
# Вычисление ковариационной матрицы данных
cov_matrix=np.dot(A.T, A) / (A.shape[0]-1)
# Нахождение собственных значений и векторов ковариационной матрицы
eigenvalues, eigenvectors=np.linalg.eig(cov_matrix)
# Преобразование собственных значений и векторов к действительным числам
eigenvalues=np.real(eigenvalues)
eigenvectors=np.real(eigenvectors)
# Сортировка собственных значений и
# соответствующих собственных векторов в порядке убывания
idx=np.argsort(eigenvalues)[::-1]
eigenvalues=eigenvalues[idx]
eigenvectors=eigenvectors[:,idx]
# Усечение количества главных компонентов до указанного числа numpc
ifnumpc<A.shape[1] andnumpc>=0:
eigenvectors=eigenvectors[:,:numpc]
# Проекция данных на главные компоненты для получения новых особенностей
score=np.dot(A, eigenvectors)returneigenvectors, score, eigenvalues
# Загрузка изображения по указанному пути
image=Image.open('path_to_image.jpg') # Замените на путь к изображению
# Преобразование изображения в оттенки серого
image=image.convert('L')
# Преобразование изображения в двумерный массив NumPy
image_array=np.array(image)
# Применение PCA к изображению с ограничением числа главных компонентов до 50
eigenvectors, score, eigenvalues=princomp(image_array, numpc=50)
# Восстановлениеизображенияизсжатогопредставления
reconstructed_image=np.dot(score, eigenvectors.T) +np.mean(image_array, axis=0)
# Использование только действительной части данных
# для восстановленного изображения
reconstructed_image=np.real(reconstructed_image)
# Создание новой фигуры для отображения изображений
plt.figure(figsize=(8, 4))
# Отображение оригинального изображения
plt.subplot(1, 2, 1)
plt.imshow(image_array, cmap='gray') # Показ оригинального изображения
plt.title('Оригинальное изображение') # Заголовок для оригинального изображения
plt.axis('off') # Отключение осей на графике для оригинального изображения
plt.subplot(1, 2, 2)
# Показ восстановленного изображения в оттенках серого
plt.imshow(reconstructed_image, cmap='gray')
# Заголовок для восстановленного изображения
plt.title('Восстановленное изображение')
plt.axis('off') # Отключение осей на графике для восстановленного изображения
plt.show()
В этом коде мы сначала определяем функцию princomp, которая выполняет PCA на входной матрице A. Затем мы загружаем изображение, преобразуем его в оттенки серого и применяем к нему PCA. В результате PCA мы получаем собственные векторы (eigenvectors), проекции данных на главные компоненты (score) и собственные значения (eigenvalues). В конце мы восстанавливаем изображение из его сжатого представления и визуализируем оригинальное и восстановленное изображения.
Мы видим, что использование 50 главных компонентов обеспечивает довольно хорошее качество изображения по сравнению с оригинальным. Но если взять всго 5 главных компонентов то мы получим читаемое изображение но с худшим качеством.

В качестве важного этапа обучения анализу главных компонент (PCA), настоятельно рекомендуется проводить самостоятельные эксперименты с кодом. Это позволяет не только углубить теоретическое понимание метода, но и развить практические навыки его применения. Испытывая различные варианты кода и адаптируя его под разнообразные задачи, вы получите ценный опыт, который будет способствовать более глубокому и интуитивному пониманию, а также позволит вам лучше ориентироваться в реальных сценариях анализа данных. Экспериментирование с кодом также дает возможность визуализировать и интерпретировать результаты.
3.5 Нахождение численных корней.
В области машинного обучения, особенно в алгоритмах оптимизации, часто возникает необходимость в нахождении корней нелинейных уравнений. Модуль scipy.optimize в Python предоставляет функцию fsolve(), предназначенную для поиска корней набора нелинейных уравнений, определенных как f(x) = 0. Эта функция особенно полезна, когда известны предполагаемые местоположения корней.
Функция fsolve() является оболочкой для алгоритмов в MINPACK, которые используют метод итераций Ньютона. Этот метод заключается в выборе начального приближения к корню и последующем его уточнении путем локальной линеаризации функции.
Применение метода Ньютона может быть наглядно продемонстрировано на примере. Рассмотрим функцию с одной переменной:
fromscipy.optimizeimportfsolve
defstress_strain_curve(strain):
# Примерфункции
returnstrain**3-0.2*strain**2+0.1*strain-0.05
# Находимкореньфункции
strain_root=fsolve(stress_strain_curve, 0.1)
print('Кореньуравнения:', strain_root)
Корень уравнения: [0.33940705]
Аналогично можно применить fsolve() для системы уравнений с несколькими переменными, что часто встречается в многомерных задачах:fromscipy.optimizeimportfsolve
defmarket_equilibrium(x):
# Две функции, представляющие спрос и предложение на рынке
demand=10-x[0] +0.5*x[1]
supply=x[0] -5-0.25*x[1]
return [demand, supply]
# Начальное предположение для цены и количества
initial_guess= [2, 2]
price_quantity=fsolve(market_equilibrium, initial_guess)
print('Равновесие на рынке:', price_quantity)
Равновесие на рынке: [-4.03896783e-27 -2.00000000e+01]Важно подчеркнуть, что в машинном обучении при работе с полиномами или алгебраическими уравнениями возможно появление комплексных корней, даже если коэффициенты уравнений являются действительными числами. Это объясняется тем, что комплексное пространство геометрически замкнуто, в отличие от действительного пространства. Таким образом, полином n-ой степени должен иметь n корней, которые могут находиться как в действительном, так ив комплексном пространстве.
3.6 Численное интегрирование в контексте машинного обучения
В численном интегрировании рассматривается задача оценки определенного интеграла функции. Основная идея состоит в аппроксимации интеграла суммой значений функции в определенных точках, умноженных на небольшие интервалы.
Метод трапеций: Это один из основных методов, используемых в численном интегрировании. Метод заключается в делении области под графиком функции на маленькие трапеции и подсчете их суммарной площади. Это простой и эффективный метод для функций, которые не слишком быстро меняют свое поведение.
Метод Симпсона: Этот метод основан на аппроксимации функции параболами на малых интервалах и вычислении площади под этими параболами. Метод Симпсона обеспечивает более высокую точность по сравнению с методом трапеций, особенно для функций, которые изменяются более сложным образом.
Библиотека Numpy предоставляет удобные инструменты для реализации этих методов. Например, функция numpy.trapz() может быть использована для применения метода трапеций.
Пример использования:
# Функция, которую необходимо проинтегрировать
deffunction(x):
returnnp.sin(x)
# Генерируем точки и значения функции
x=np.linspace(0, np.pi, 100)
y=function(x)
# Применяем метод трапеций
integral=np.trapz(y, x)
print("Значение интеграла:", integral)
Значение интеграла: 1.99983216389399273.6.1 Правило трапеций
Рассмотрим Правило трапеций подробнее:это метод численного интегрирования, используемый для приближенного вычисления определенного интеграла функции. Формула правила трапеций выглядит следующим образом:
Для демонстрации правила трапеций на практике, мы определяем простую полиномиальную функцию и выбираем ее значения в конечном диапазоне [a, b] в ns точках, равномерно распределенных.
Приведем пример кода на Python, демонстрирующий использование правила трапеций:
# Импорт необходимых библиотекimportnumpyasnp # Импорт библиотеки NumPy для численных вычислений
importmatplotlib.pyplotasplt # Импортбиблиотеки Matplotlib
fromscipy.integrateimportquad # Импортфункции quad из SciPy
# Настройка формата вывода
# Настройка формата вывода чисел с плавающей точкой в NumPy
np.set_printoptions(formatter={'float': '{: 0.3f}'.format})
# Определение полиномиальной функции
deff(x): # Определение функции f(x)
return5*x**3-6*x**2-7*x+6
# Выборка значений функции
# Установка начальной точки (a), конечной точки (b) и количества точек (n)
a, b, n=-1., 2, 400
# Создание массива из n равномерно распределенных точек между a и b
x=np.linspace(a, b, n)
y=f(x) # Вычисление значений функции f(x) в каждой точке массива x
# Интегрирование функции
ns=6 # Установка количества точек для метода трапеций
# Создание массива из ns равномерно распределенных точек для интегрирования
xint=np.linspace(a, b, ns)
yint=f(xint) # Вычисление значений функции f(x) в каждой точке массива xint
# Визуализация результатов
plt.plot(x, y, lw=2) # Рисование графика функции f(x)
# Заполнение области под графиком между xint и yint
plt.fill_between(xint, 0, yint, facecolor='gray', alpha=0.4)
# Добавление текста с интегралом на график
plt.text((a+b)/2, 12, r"$\int_a^b f(x)dx$", horizontalalignment='center', fontsize=15)
plt.show() # Отображение графика
# Вычисление интеграла
# Вычисление точного значения интеграла и погрешности
integral, error=quad(f, a, b)
# Вычисление приближенного значения интеграла методом трапеций
integral_trapezoid=sum((xint[1:]-xint[:-1])*(yint[1:]+yint[:-1]))/2
# Вывод результатов
# Вывод точного значения интеграла и его погрешности
print("Точный результат:", integral, "+/-", error)
# Вывод приближенного значения интеграла, полученного методом трапеций,
# и количества используемых точек
print("Результат приближения трапецией с", len(xint), "точками:", integral_trapezoid)
Точный результат: 8.25 +/- 1.2419279516758014e-13
Результат приближения трапецией с 6 точками: 8.520000000000001
В данном примере видно, что результаты, полученные с помощью правила трапеций, являются приближенными. Ошибка вычисления может быть значительной, особенно при небольшом количестве точек выборки. Точность результатов правила трапеций напрямую зависит от количества используемых интервалов (ns). Чем больше интервалов, тем точнее приближение, но увеличение количества интервалов также повышает вычислительную сложность.
Правило трапеций представляет собой простой и эффективный метод численного интегрирования. Этот метод позволяет получать приближенные значения интегралов, что часто бывает достаточно для практических целей в машинном обучении. Однако всегда важно помнить о потенциальной ошибке приближения и стремиться к балансу между точностью вычислений и вычислительной сложностью.
3.6.2 Интегрирование по Гауссу
Интегрирование по Гауссу, также известное как квадратурный метод Гаусса, представляет собой высокоэффективную технику численного интегрирования. Этот метод основывается на выборе и суммировании значений функции в специально подобранных точках - точках Гаусса - с использованием соответствующих весов. Подход обеспечивает высокую точность вычислений, особенно при работе с полиномиальными подынтегральными выражениями, так как точки Гаусса соответствуют корням полиномов Лежандра на интервале [−1, 1].
Интегрирование по Гауссу находит свое применение при вычислении интегралов в методах, основанных на байесовской статистике или при решении оптимизационных задач.
Рассмотрим пример, в котором мы используем интегрирование по Гауссу для вычисления интеграла функции. Для этого примера мы возьмем функцию, которая может представлять, например, вероятность ошибки классификатора.
importnumpyasnp #Импортбиблиотеки NumPy
fromscipy.integrateimportquad # Импортфункции quad из SciPy
deferror_function(x):""" Представление функции ошибки классификатора """
# Возвращает значение e в степени (-x^2), что является экспоненциальной функцией
returnnp.exp(-x**2)
# Задаем интервал интегрирования
# Значения a и b определяют нижнюю и верхнюю границы интервала интегрирования
a, b=0, 1
# Интегрирование функции на заданном интервале
# Функция quad используется для численного интегрирования
integral, error=quad(error_function, a, b)
# error_functionот a до b.
# Возвращает значение интеграла и оценочную погрешность
# Выводит значение интеграла
print(f"Интеграл функции ошибки классификатора на интервале [{a}, {b}]:", integral)
# Выводит оценку погрешности вычисления интеграла
print("Оценочная ошибка вычислений:", error)
Интеграл функции ошибки классификатора на интервале [0, 1]: 0.7468241328124271
Оценочная ошибка вычислений: 8.291413475940725e-15Для общих сложных подынтегральных функцийинтегрирование по Гауссу может не дать точного решения. Точность, однако, все равно будет гораздо лучше по сравнению с правилом трапеций или правилом прямоугольников (которое мы не обсуждали, но оно очень похоже на правило трапеций). Другими словами, для получения решений схожей точности интегрирование по Гауссу использует меньшее количество точек выборки.
Такой подход к интегрированию может использоваться, для оценки вероятности ошибки в алгоритмах классификации или для интегрирования функций, возникающих в байесовских методах. Эффективность интегрирования по Гауссу позволяет сократить вычислительные затраты, что особенно ценно при работе с большими объемами данных или сложными моделями.
3.7 Первоначальная Обработка Данных
Первичная обработка данных - ключевой этап в подготовке к анализу и обучению моделей машинного обучения. В этом разделе мы рассмотрим основные методы и техники, применяемые к данным перед началом обучения модели.
Данные часто представляются в виде набора данных X, где X∈Xm×p. Здесь m обозначаетколичествоточекданных, илинаблюдений, а p - количествопризнаковилипеременных. Вреальныхзадачахзначенияданныхмогутсильноварьироваться, чтоможетпривестикпроблемамсчисленнойстабильностью при обучении моделей.
Для обеспечения стабильности и улучшения производительности моделей машинного обучения, данные обычно подвергаются предварительной обработке, включающей нормализацию или масштабирование. Существуют два основных метода нормализации данных масштабирование по методу минимума-максимума и стандартное масштабирование.
3.7.1 Масштабирование по Методу Минимума-Максимума
Масштабирование по методу минимума-максимума являетсяэтапом предварительной обработки данных в машинном обучении. Этот метод позволяет стандартизировать диапазон признаков, что особенно важно для алгоритмов, чувствительных к масштабу признаков, таких как градиентный спуск.
Формула масштабирования задается как:где X.min и X.max представляют собой минимальные и максимальные значения признаков. Этот метод преобразует все значения каждого признака в диапазон от 0 до 1.Метод можно обобщить для преобразования значений в произвольный диапазон [a, b] следующим образом:
Это позволяет адаптировать масштабирование под конкретные потребности.
Как только такое масштабирующее преобразование обучающего набора данных выполнено, X.min и X.max могут быть использованы для выполнения точно такого же преобразования тестового набора данных, чтобы обеспечить согласованность для правильных предсказаний.
Вот простой код для выполнения масштабирования по методу минимума-максимума.importnumpyasnp
# Установка параметров вывода для лучшей читаемости
np.set_printoptions(precision=4)
# Измененный набор данных для примера
data_set= [
[4, -1, 2],
[9, 1, 5],
[14, 2, 8],
[19, 4, 11]
]
print('-'*20)
print(f"Исходный обучающий набор данных:\n{data_set}")
print('-'*20)
# Преобразование списка в массив NumPy для удобства вычислений
data_array=np.array(data_set)
# Выполнение масштабирования по методу минимума-максимума
data_min=data_array.min(axis=0)
data_max=data_array.max(axis=0)
data_normalized= (data_array-data_min) / (data_max-data_min)
print(f"Масштабированный обучающий набор данных:\n{data_normalized}")
print(f"Максимальные значения для каждого признака:\n{data_max}")
print(f"Минимальные значения для каждого признака:\n{data_min}")
print('-'*20)
# Измененный тестовый набор данных
test_data=np.array([[0, 4, 6], [7, 5, 6]])
test_data_normalized= (test_data-data_min) / (data_max-data_min)
print("Масштабированный тестовый набор данных:\n", test_data_normalized)
print('-'*20)
# Обратное преобразование для обучающего набора данных
data_original=data_normalized* (data_max-data_min) +data_min
print("Возвращенный обратно обучающий набор данных:\n", data_original)
print('-'*20)
# Обратное преобразование для тестового набора данных
test_data_original=test_data_normalized* (data_max-data_min) +data_min
print("Возвращенный обратно тестовый набор данных:\n", test_data_original)
--------------------
Исходный обучающий набор данных:
[[4, -1, 2], [9, 1, 5], [14, 2, 8], [19, 4, 11]]
--------------------
Масштабированный обучающий набор данных:
[[0. 0. 0.]
[0.3333 0.4 0.3333]
[0.6667 0.6 0.6667]
[1. 1. 1.]]
Максимальные значения для каждого признака:
[19 4 11]
Минимальные значения для каждого признака:
[4 -1 2]
--------------------
Масштабированный тестовый набор данных:
[[-0.2667 1. 0.4444]
[0.2 1.2 0.4444]]
--------------------
Возвращенный обратно обучающий набор данных:
[[4. -1. 2.]
[9. 1. 5.]
[14. 2. 8.]
[19. 4. 11.]]
--------------------
Возвращенный обратно тестовый набор данных:
[[0. 4. 6.]
[7. 5. 6.]
]Ясно видно, что масштабирование по методу минимума-максимума не наносит вреда набору данных. Все данные можно вернутьобратно, если это необходимо.
3.7.2 Кодирование "One-hot"
Во многих наборах данных машинного обучения используются категориальные признаки. Например, переменная "цвет" может принимать значения "красный", "зеленый" и "синий". Для построения модели машинного обучения необходимо преобразовать эти категориальные значения в числовые. Рассмотрим одномерный вектор признаков, изначально заданный как [[зеленый], [красный], [0], [синий]]. Один из способов кодирования - присвоение каждому цвету уникального числа, например, [[1], [2], [0], [3]].
Однако такое прямое кодирование может неявно ввести отношение порядка между категориями, что не всегда желательно. Например, модель может интерпретировать "синий" (3) как "больше" или "важнее", чем "зеленый" (1), что может быть некорректно.
Для решения этой проблемы часто используется метод "one-hot" кодирования. При этом подходе каждая категория преобразуется в отдельный признак с двоичным значением.Масштабирование такого типа данных обычно не изменяет их, так как значения уже представлены в формате 0 и 1. А обратное преобразование позволяет восстановить исходные значения
Кодирование "one-hot" является эффективным решением для работы с категориальными данными. Оно позволяет избежать ошибочной интерпретации категориальных переменных как числовых с отношениями порядка, что может привести к некорректным выводам при анализе данных. Важно правильно выбирать методы предварительной обработки данных в зависимости от их характера и требований поставленной задачи. Кодирование "one-hot" увеличивает размерность пространства признаков, но позволяет более точно и корректно обрабатывать данные, гарантируя, что каждая категория имеет равное влияние на модель. Это особенно важно в алгоритмах, которые зависят от расстояния между точками данных, таких как k-ближайших соседей или кластеризация.
3.8.3 Стандартное Масштабирование
Когда распределение набора данных близко к нормальному распределению, можно использовать стандартное масштабирование. Формула для стандартного масштабирования приведена следующим образом:
Ниже приведен простой код для выполнения стандартного масштабирования.
importnumpyasnp
X= [[3, 5, 8], # предполагаемый обучающий набор данных
[2, 6, 4.5],
[3, 6, -6],
[11, 5, 5]]
print('-'*20)
print(f"Исходный обучающий набор данных X:\n{X}")
print('-'*20)
X=np.array(X)
X_scaled= (X-X.mean(axis=0)) /X.std(axis=0)
print(f"Стандартно масштабированный обучающий набор данных X:\n{X_scaled}")
print(f"Среднее значение для каждого признака:\n{X.mean(axis=0)}")
print(f"Стандартное отклонение для каждого признака:\n{X.std(axis=0)}")
print('-'*20)
Xtest= [[-15, 9, 2], # предполагаемый тестовый набор данных
[4, 4, 4]]
Xt_scaled= (Xtest-X.mean(axis=0)) /X.std(axis=0)
print(f"Стандартно масштабированный тестовый набор данных Xtest:\n{Xt_scaled}")
print('-'*20)
X_back=X_scaled*X.std(axis=0) +X.mean(axis=0)
print(f"Возвращенный обратно обучающий набор данных:\n{X_back}")
print('-'*20)
Xt_back=Xt_scaled*X.std(axis=0) +X.mean(axis=0)
print(f"Возвращенный обратно тестовый набор данных Xtest:\n{Xt_back}")
--------------------
Исходный обучающий набор данных X:
[[3, 5, 8], [2, 6, 4.5], [3, 6, -6], [11, 5, 5]]
--------------------
Стандартно масштабированный обучающий набор данных X:
[[-0.48189987 -1. 0.96772426]
[-0.75727123 1. 0.3068394]
[-0.48189987 1. -1.67581519]
[1.72107098 -1. 0.40125152]]
Среднее значение для каждого признака:
[4.75 5.5 2.875]
Стандартное отклонение для каждого признака:
[3.63145976 0.5 5.29593004]
--------------------
Стандартно масштабированный тестовый набор данных Xtest:
[[-5.43858429 7. -0.16522122]
[-0.20652852 -3. 0.21242728]]
--------------------
Возвращенный обратно обучающий набор данных:
[[3. 5. 8.]
[2. 6. 4.5]
[3. 6. -6.]
[11. 5. 5.]]
--------------------
Возвращенный обратно тестовый набор данных Xtest:
[[-15. 9. 2.]
[4. 4. 4.]]Важно учитывать, что масштабирование, применяемое к признакам в обучающем наборе данных, может быть также применено к меткам или целевым переменным. Это особенно актуально, когда метки представляют собой числовые значения, а не категории или вероятностное распределение. Например, если метки обучающего набора данных представляют собой числовые значения, такие как вес или цена, их можно масштабировать для улучшения производительности и стабильности обучения модели.
Однако важно помнить, что при тестировании модели или использовании её для предсказаний, метки следует возвращать к исходному масштабу. Это обеспечивает корректность интерпретации результатов, так как масштабированные метки могут быть непонятны или вводить в заблуждение.
Проверь себя
Какая идея лучше всего описывает фокус главы "Основные математические вычисления"?
В машинном обучении теоретические определения полезно проверять на численных примерах и визуализациях.
Какие действия помогают закрепить материал главы?
Пройти тест