Сегодня мы рассмотрим, так ли оптимизация NumPy (сокр. от Numeric Python) хороша по сравнению с обычными списками в Python. Главным критерием мы возьмём время вычислений, а тестировать будем три операции: обычное умножение, вычисление гиперболического тангенса и нормализация в промежуток [0, 1].
Вычисление времени в Python
Для вычисления времени мы будем использовать модуль стандартной библиотеки Python — time. Функция time.time() возвращает текущее время в секундах, и именно от этой функции мы будем отталкиваться.
Не забудьте подписаться группу Вконтакте и Telegram-канал. Там можно найти еще больше интересных и полезных материалов по программированию.
Начало работы NumPy
Для начала импортируем все необходимые модули.

Назначение модуля time уже известно. math необходим для доступа к точному значению константы e. random нам необходим для случайного заполнения списка Python, NumPy для тестов массивов np.array и matplotlib для визуализации полученных нами данных.
Умножение
Первая операция, которую мы будем тестировать — обычное умножение. Т.к. массивы NumPy поддерживают векторизированные операции(т.е. операция применяется к каждому элемента массива), то выполнять умножение по отношению к дин.массиву мы будем с помощью listcomp’а.
Простой список, array * n и array.dot
Здесь мы рассмотрим вычисления векторизированного умножения списка, обычного умножения массива NumPy на число и отдельный метод np.array для умножения. Все тестовые блоки будут выглядеть очень похоже:

С помощью random.random() создаём список длиной size, элементы которого принадлежат диапазону [0, 1]. Создаём новый список, элементы которого ровно в 1 миллион раз больше элементов исходного массива. Время вычислений для каждой длины массива заносим в массив.
Далее таким же методом исследуем операцию np.array * n:

Примечание. time.sleep(0.1) здесь нужно из-за того, что некоторые значения будут равны нулю из-за особенности хранения чисел с плавающей точкой в Python.
Тоже самое делаем для метода np.array.dot:

Визуализация
Теперь изобразим график зависимости времени вычислений от длины массива:

Если вы читали другие статьи по Python в Code Blog, то данный фрагмент кода может вас смутить только в 3-ей строке. Да, использовав функцию figure() и передав туда кортеж значений, мы можем изменить размер исходной картинки.
Пройдёмся еще по некоторым «непонятностям»:
- %matplotlib inline — «магическая» команда Jupyter Notebook, которая позволяет выводить графики в самом блокноте.
- plt.legend() — вызывает легенду(по умолчанию выбирает лучшее для неё расположение).
- plt.grid() — устанавливает «сетку».
Получаем довольно интересный результат:

Как мы можем заметить, выражения array * n и array.dot(n) работают по времени почти одинаково (резкие скачки связаны с тем, что выполняя этот пример, у меня были запущены еще некоторые программы, которые использовали ресурсы процессора).
Почему на отрезке [10000, 30000] время выполнения операций с массивами NumPy больше, чем с обычным списком Python?
Как вы можете помнить, мы добавили в тесты массивов NumPy конструкцию time.sleep(0.1), чтобы избавиться от нулевых значений. Так что для получения полной картины необходимо переместить график списков еще на два «блока» вверх, хотя и без этого всё наглядно продемонстрировано.
Действительно, обычное векторизированное умножение массива длиной один миллион в обычном Python’е займёт 0.35 секунды, что является очень большим числом, учитывая результат NumPy.
Гиперболический тангенс
В следующем примере мы будем измерять скорость вычисления гиперболического тангенса. Для меня эта функцию представляет интерес, поскольку она часто является функцией активации в простом персептроне или нейросети.
Подробнее почитать про эту функцию можно почитать в Википедии, там предоставлен хороший материал.
tanh в Python
Чтобы предоставить чистоту эксперимента, мы реализуем функцию tanh вручную, а не будем пользоваться math.tanh.

По той же схеме вычисляем время работы каждой функции для разных длин массивов:

Результаты имеются, осталось только их визуализировать:

Здесь опять появляется новая конструкция, применение которой, по моему мнению, нуждается в объяснении.
plt.legend(loc=, fontsize=) — параметр loc указывает на расположение легенды (в нашем случае слева вверху), а fontsize устанавливает размер текста легенды, чтобы сделать её больше.
Получаем такой результат:

Как мы можем заметить, для вычисления гиперболического тангенса каждого элемента списка длиной 1 миллион, «питону» потребуется 2 секунды (просто представьте, сколько бы времени занимало обучение нейросети, которая использует значения, которые хранятся в списках!). График зависимости NumPy имеет намного меньший угловой коэффициент, что говорит о быстрой работе даже на огромных массивах данных.
Нормализация
Нормализация — приведение данных к определённому промежутку для улучшения модели машинного обучения (например, тот же самый SVC требует нормализации к промежутку [0, 1]).
Подробнее про нормализацию можно почитать здесь, действительно понятно написано.
Для сегодняшнего примера мы используем такую формулу нормализации:

Функции нормализации, используя builtin-функции и средства numpy продемонстрированы ниже:

В этот раз объединим тесты в один цикл:

И создадим обычный график:

«Домашнее задание». Скорее всего, Вы открыли эту статью, поскольку интересуетесь языком программирования Python. Я предлагаю Вам попрактиковаться в визуализации данных.
Суть в чём:
- выполнить несколько тестов одной и той же операции и найти среднее отклонение
- визуализировать зависимость времени работы от длины массива, изобразив на нём также среднее отклонение
- попробовать изменить стиль графика, поэкспериментировать с настройками
- результат своей работы можете скинуть в комментарии
Ладно, вернёмся к нормализации. Мы получаем такой график зависимости:

Результат почти такой же, как и с гиперболическим тангенсом. Вы можете также проанализировать другие функции или операции, чтобы найти слабые места NumPy.
Заключение NumPy vs Python
Цель этой статьи не в том, чтобы доказать очевидное. Я имел цель продемонстрировать как много получает пользователь, который знаком с NumPy. Фрагменты кода, продемонстрированные в статье дают начало бесконечному миру исследования вычислений с помощью различных инструментов, в котором Вы можете принять участие.
Весь код можно найти по ссылке в виде документа Jupyter Notebook.
Также рекомендую прочитать статью Анализ данных с pandas
Также заходите на мой YouTube-канал, где я обучают программированию на C#, а также рассказываю об общих вопросах программирования.