NumPy vs Python

Сегодня мы рассмотрим, так ли оптимизация 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#, а также рассказываю об общих вопросах программирования.