{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Векторизация на примере вычисления статистик\n", "\n", "В домашнем задании №2 есть задача, в которой необходимо реализовать вычисление среднего арифметического и среднеквадратичного отклонения для каждого столбца массива чисел.\n", "\n", "Рассмотрим в качестве примере то же задание, но для вычисления этих же характеристик одного числового ряда. Т.е. будем считать, что есть числовой ряд $x = \\{x_1, ..., x_N\\}$, данный нам в виде массива `x`. Реализуем функцию, которая выдаст нам среднее и среднеквадратичное отклонения этого числового ряда.\n", "\n", "## Подход в лоб\n", "\n", "Для начала реализуем этот алгоритм так, как мы бы это делали в `C/C++`, т.е. используя циклы и не пользуясь векторизацией. Напишем отдельную функцию для вычисления среднего значения." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "def mean1D(x):\n", " s = 0 \n", " for number in x:\n", " s += number\n", " return s / x.size" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Приведенная выше функция заводит переменную `S`, в которую на протяжении цикла накапливается сумма. Когда сумма всего массива накоплена, то возвращается результат деления этой суммы на количество элементов в массиве, которое можно узнать у массива `x` по атрибуту `size` (предполагаем, что массив одномерный). \n", "\n", "Реализуем функцию вычисления среднеквадратичного отклонения. В выражении для среднеквадратичного отклонения участвует среднее арифметическое. Чтобы не дублировать вычисления, будем передавать среднее значение вторым аргументов в функцию." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "import math\n", "\n", "\n", "def std1D(x, mean):\n", " s = 0\n", " for number in x:\n", " s += (number - mean) ** 2\n", " return math.sqrt(s / x.size)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Приведенная функция накапливает сумму квадратов отклонений чисел массива от среднего значения и возвращает корень из этой суммы деленной на количество элементов в массиве. В `NumPy` есть методы `mean` и `std`. Проведем хотя бы один тест, чтобы убедиться, что приведенные выше функции работают." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.5153244859252695 0.5153244859252698\n", "0.28374330737876574 0.28374330737876574\n" ] } ], "source": [ "import numpy as np\n", "\n", "N = 100 # размер массива\n", "x = np.random.random(N)\n", "\n", "mean_1 = mean1D(x)\n", "mean_2 = x.mean()\n", "std_1 = std1D(x, mean_1)\n", "std_2 = x.std()\n", "\n", "\n", "print(mean_1, mean_2)\n", "print(std_1, std_2)\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Векторизованные версии.\n", "\n", "Функции работают, теперь перепишем их, используя векторизацию. " ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "def mean1D_vectorized(x):\n", " return x.sum() / x.size" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Векторизованная версия функции `mean1D` примитивна. Цикл суммирования заменили на вызов метода `ndarrray.sum`." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "def std1D_vectorized(x, mean):\n", " tmp_1 = x - mean\n", " tmp_2 = tmp_1 ** 2\n", " tmp_3 = tmp_2.sum()\n", " return math.sqrt(tmp_3 / x.size)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Вычисление среднеквадратичного отклонения рассмотрим подробнее. \n", "```python\n", "def std1D_vectorized(x, mean):\n", " tmp_1 = x - mean\n", "```\n", "\n", "Как и прежде, чтобы не вычислять дважды среднее принимаем `mean` в качестве второго аргумента. На следующей стоке вычисляется разница между массивом `x` и средним значением `mean` (скаляром). Формы не совпадают, а значит производится `broadcasting` и в результате `tmp_1[i] = x[i] - mean`. \n", "\n", "```python\n", "def std1D_vectorized(x, mean):\n", " tmp_1 = x - mean\n", " tmp_2 = tmp_1 ** 2\n", "```\n", "\n", "Далее встречается возведение одномерного массива `tmp_1` в степень `2`. Т.к. `2` --- скаляр, то производится `broadcasting` и значение `tmp_2[i] = tmp_1[i] ** 2 = (x[i] - mean) ** 2`.\n", "\n", "```python\n", "def std1D_vectorized(x, mean):\n", " tmp_1 = x - mean\n", " tmp_2 = tmp_1 ** 2\n", " tmp_3 = tmp_2.sum()\n", "\n", "```\n", "\n", "В следующей строке вычисляется сумма элементов массива `tmp_2`, т.е. $\\sum_{i=0}^{N-1}$ `tmp_2[i]` = $\\sum_{i=0}^{N-1}$`tmp_1[i]`${}^2$= $\\sum_{i=0}^{N-1} ($`x[i] - mean`$) ^ 2$.\n", "\n", "```python\n", "def std1D_vectorized(x, mean):\n", " tmp_1 = x - mean\n", " tmp_2 = tmp_1 ** 2\n", " tmp_3 = tmp_2.sum()\n", " return math.sqrt(tmp_3 / x.size)\n", "```\n", "\n", "И наконец, вычислив такую сумму, функцию возвращает квадратный корень из нее деленной на размер массива.\n", "\n", "Конечно, вводить переменные `tmp_1`, `tmp_2` и `tmp_3` вовсе не обязательно. Это сделано в этом примере, лишь для того, чтобы подробнее по этапам объяснить, что происходит под капотом функции ниже." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0.2686383564940283 0.2686383564940283\n" ] } ], "source": [ "def std1D_vectorized(x, mean):\n", " return np.sqrt(((x - mean) ** 2).sum() / x.size)\n", "\n", "print(x.std(), std1D_vectorized(x, x.mean()))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Тем не менее первая версия этой функции позволяет явно обратить ваше внимание на то, что в процессе работы векторизованной функции (и изначальной и сокращенной) создаются дополнительные массивы. Для этого выделяется дополнительная память и в ряде случаев это может быть критично. Эти дополнительные массивы собираются сборщиком мусора, как только они перестают быть нужными. В первом случае на выходе из функции (когда локальные имена `tmp_1`, `tmp_2` и `tmp_3` уничтожаются) и во втором случае сразу, как только вычисления с ними закончены.\n", "\n" ] } ], "metadata": { "interpreter": { "hash": "8617202e12f254480e1fae3258716b685f1a56bcbf234a446366b4fd3345ed22" }, "kernelspec": { "display_name": "Python 3.8.10 64-bit", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.10" } }, "nbformat": 4, "nbformat_minor": 5 }