[БЕЗ_ЗВУКА] Давайте познакомимся с библиотеками NumPy, SciPy и Matplotlib. Начнем с NumPy. Для начала импортируем NumPy, ну и посмотрим, как выглядят массивы в этой библиотеке. Для этого создадим обычный массив и создадим NumPy-ский массив. Ну и посмотрим, что у нас получилось. Как видите, типы данных действительно разные. Выводятся на печать они тоже немного по-разному, так что уже по выводу на печать можно понять, какой массив у вас используется в конкретном месте. Ну и давайте поищем какие-то более существенные различия. Попробуем применить привычную нам операцию среза, то есть получить элементы с индексами от 1 до 3 (не включая 3). Видим, действительно, всё отработало как нужно для обычного массива. Проверим для NumPy-ского. Для NumPy-ского тоже работает. Ну и в принципе все основные операции, которые можно делать с массивами, можно делать с массивами в NumPy. Но есть некоторые операции, которые добавились. Ну, например, если мы хотим взять элементы с индексами 0 и 2 и для этого хотим передать в качестве индекса нашему исходному массиву массив из 0 и 2, то это, конечно, не отработает. Но вот если мы сделаем то же самое для NumPy-ского массива, то все будет хорошо. Действительно, получились 2 и 4. Давайте посмотрим. Да, 2 и 4. Кроме того, есть логическая индексация, то есть можно написать y > 3, получится массив из true и false, где true будет на тех позициях, где значение действительно больше 3, а false будет на тех позициях, где это условие нарушено. Передать этот массив из true и false в качестве индекса и взять соответствующий элемент. Действительно, 4 и 6. Смотрим на исходный массив. Действительно, только 4 и 6 у нас больше 3 в этом массиве. Вообще, наверное, очень часто вам хотелось сделать какую-нибудь операцию с каждым элементом массива, ну, например, написав x * 5. Но если это сделать с обычным массивом, то значение не умножится на 5, а просто вы пять раз повторите один и тот же массив. В NumPy-ских массивах произойдет именно то, что хочется. Вот, как вы видите. у нас все значения умножились на 5. То же самое можно делать с некоторыми другими операциями, например, возведение в степень. Как мы видим, для обычных массивов возведение в степень вообще не отработало, а для NumPy-ских все будет хорошо. Ну и в случае многомерных массивов, ну, например, двумерных, индексация в NumPy может немножко отличаться. Для этого давайте создадим двумерный массив в обычном виде и NumPy-ский. И захотим получить элемент с индексами [1, 2]. Вот выведем его. Действительно, это нолик. То есть во внешнем массиве мы выбираем элемент с индексом 1. Это вот этот массив. В нем мы выбираем элемент с индексом 2. Это нолик. Точно так же можно сделать и для NumPy-ского двумерного массива, но можно сделать это и немного иначе. Например, просто написав [1, 2]. Результат будет тот же самый. Поэтому если вы увидите где-то такую индексацию, не надо удивляться — что же сломалось, в чем же дело, разве можно так оперировать с массивами — а нужно просто сделать вывод, что, видимо, это был NumPy-ский массив. Конечно, в NumPy есть не только массивы, есть еще много других полезных вещей, и вообще в целом библиотека ориентирована на то, чтобы как-то упростить вам вычисления. Ну, например, там есть случайные числа. Вот сейчас мы сгенерировали некоторое дробное случайное число из равномерного распределения. О распределениях будет подробнее в части про теорию вероятности. Ну а вот это случайное число из нормального распределения. Давайте перегенерируем их, чтобы убедиться, что это действительно какие-то случайные числа. Вот, как вы видите, число меняется. Нормальное, вот даже отрицательным может быть. Но это, разумеется, нормальное распределение с некоторыми фиксированными параметрами. Но это будет более ясно уже после части про вероятность. Ну и мы можем сгенерировать не одно число, а несколько. Просто передадим параметр сколько чисел нам нужно, и вот мы получили четыре числа. Можно сгенерировать даже матрицу или еще более сложный, многомерный массив. Давайте попробуем это сделать. Есть и другие интересные вспомогательные функции в NumPy. Ну, например, аналог функции range, которому мы можем задавать размер шага. Ну и вот что получается. Давайте попробуем тоже самое сделать с помощью range. Мы увидим сообщение об ошибке. И действительно, range нужно передавать целочисленный размер шага. Ну, давайте на всякий случай сравним еще их по производительности. Может быть и здесь есть некоторое превосходство. Magic timeit просто несколько раз выполняет код и замеряет, сколько занимает выполнение один раз. Ну и как мы видим, вариант из NumPy отработал намного быстрее. [БЕЗ_ЗВУКА] Таким образом, NumPy — это библиотека, позволяющая выполнять некоторые вычисления и делать это более хорошо, более оптимизированно, соответственно, тратить на это меньше времени. И поэтому она пользуется такой популярностью. Теперь перейдем к библиотеке SkiPy. SkiPy реализует некоторые более высокоуровневые вещи. То есть если в NumPy речь идет о том, что у нас есть какие-то матрицы, с ними нужно делать какие-то операции, то в SkiPy речь идет о том, что у нас бывают задачи оптимизации, бывают задачи из линейной алгебры, бывают какие-то более сложные вещи из теории вероятности. И некоторые простые операции из этих разделов нужно тоже уметь делать. Ну давайте сначала посмотрим на оптимизацию. Импортируем модуль optimize, сделаем какую-нибудь свою простую функцию, про которую нам понятно, где у нее минимум. Ну вот здесь рассмотрена очень простая функция, которая представляет собой сумму двух квадратов и некоторого числа. Ну и понятно, что вот этот вот квадрат мы можем приравнять к 0, этот квадрат тоже, а с этим числом мы ничего не сделаем. То есть минимальное значение этой функции будет 3. Ну и смотрите, как мы реализовали функцию от нескольких переменных. Мы просто передаем на вход некоторый массив, ну и считаем, что одна переменная — это нулевая координата, а другая переменная — это то, что лежит по индексу 1. Ну и давайте убедимся, что минимум действительно там, где мы его ожидаем. Действительно, все хорошо. Вот, оказывается, в модуле optimize есть метод minimize, которому достаточно передать функцию, передать начальное приближение, ну, то есть вектор, с которого начнется некоторый оптимизационный процесс. Ну и можно запустить и посмотреть, какой результат получится. Вот мы видим, что значение функции равно 3 в точке с вот такими вот координатами, которые с достаточно высокой степенью точности совпадают с правильными. Если нам нужно вывести именно координаты точки минимума, то можно обратиться к полю x у полученного объекта. Конечно, в модуле SciPy есть много разных хороших инструментов, в частности есть инструменты для работы с линейной алгеброй, ну, давайте, например, решим простенькую систему уравнений. Импортируем linalg, задаем систему, первая строчка задает матрицу системы, вторая строчка задает значения, которые должны получиться. Нам нужно найти такой x, что, если матрицу умножить на x, получится вот этот ответ. Подробнее про матрицы и операции с ними вы узнаете в разделе про линейную алгебру. Ну давайте попробуем воспользоваться модулем linalg и посмотреть на результат. Вот мы получили какой-то результат, но, наверное, заведомо не ясно, правильный он или нет. Давайте просто проверим. Давайте умножим матрицу a на полученный x и увидим, что получившийся вектор ровно тот, который мы требовали. Ну, кроме того, в в модуле lynalg есть разные операции для матричных разложений, ну, например, есть сингулярные разложения матрицы, или SVD разложения, о нем мы тоже еще будем говорить подробнее. Оно очень часто используется в анализе данных. Оказывается, все это можно делать буквально в одну строчку. Вот здесь мы сгенерировали матрицу из случайных чисел размером 4 х 3, сделали разложение на 3 матрицы, и вот эти матрицы имеют размеры 4 х 4, диагональная матрица размера 3 и матрица 3 х 3, все, как и должно быть в сингулярном разложении, о чем мы еще поговорим. Модуль MatPlotLib позволяет очень просто строить графики. Ну, для начала выполним magic MatPloLib inline, для того, чтобы все графики отражались сразу в ноутбуке, ну и буквально в пару строчек построим какой-то первый график. Ну вот здесь вот первый аргумент у функции plot, это значение x, второй аргумент это значение y, когда мы выполняем show, мы получаем, собственно, график, ну и, наверное, обычно мы будем хотеть строить не какие-то произвольные зависимости, заданные вот прямо двумя массивами прямо в аргументах, а уже что-то осмысленное. Ну, например, давайте построим график функции y = x в кубе. Здесь нам уже немножко понадобится NumPy. Мы просто задаем массив значений x от −10 до 10 с шагом 0,1. Получаем значение y, просто возводя в степень, ведь у нас есть операции с массивами в NumPy, и строим график, действительно, это похоже на кубическую параболу. Ну и давайте в качестве небольшого итога попробуем разобрать пример, где используется все вместе. Сначала проимпортируем необходимые модули. Смотрите, нам потребуется NumPy, нам потребуется MatPlotLib, и в Scripy мы воспользуемся модулем interpolate для того, чтобы по известным значениям функции приблизить значение в промежуточных точках. Ну, то есть, выполнить задачу интерполяции. Ну и давайте для примера рассмотрим такую вот простую функцию. Это будет просто экспонента, то есть e в степени −x / 3, ну и посмотрим, какие значения x и y у нас получатся. Я вывел только первые пять, чтобы не смотреть на все точки, ну, вроде, что-то получилось. Давайте это построим, но сначала выполним интерполяцию, а теперь построим все вместе. Смотрите, синие точки это известные значения функции. Зеленые точки – это наше приближение. По умолчанию параметр kind равен linear. Ну, то есть, если мы его не будем указывать, результат будет тот же, но его можно задавать. Например, можно сделать так. И вот уже функция получилась более гладкой. Кстати, обратите внимание, если мы сделаем здесь не делить на 3,0, а, например, на 3, то график уже будет выглядеть немного странновато. Это не очень похоже на экспоненту. В чем же дело? Ну, на самом деле нужно быть очень внимательным, так как в Python деление целочисленное, x у нас получились целыми, потому что мы шли от 0 до 10 с шагом 2, и при делении на 3 у нас получилось деление нацело, поэтому в итоге все немного поменялось. Ну, и конечно, в машинном обучении мы будем иметь дело не просто с такой ситуацией, что некоторые значения функции нам известны точно, а некоторые значения будут известны нам с некоторым шумом. Давайте просто возьмем и добавим шум, мы же это умеем, у нас есть библиотека NumPy. Возьмем и добавим шумы, для этого нам потребуется сгенерировать какие-то случайные числа, их нам нужно сгенерировать по длине массива x, ну и давайте, чтобы шум был не слишком большой, домножим этот шум на 0,1. И посмотрим, что получится. Ну, даже, наверное, можно еще меньше: на 0,5. Так, вот здесь уже как раз видно, что у нас получается не совсем экспонента, а у нас получается небольшой изгиб здесь, чего на самом деле быть не должно. Ну, и как вы видите, просто интерполировать, чтобы восстановить зашумленную зависимость, уже недостаточно. А вот что же делать, когда в зависимости действительно есть какой-то шум, вы узнаете в нашей специализации. Итак, подведем итог. Мы с вами познакомились с библиотеками NumPy, Scripy, MatPlotLib. NumPy нам предоставляет средства для высокопроизводительных вычислений, Scripy предоставляет некоторые более высокоуровневые вещи из математики, а MatPlotLib позволяет все очень легко визуализировать.