{ "nbformat": 4, "nbformat_minor": 0, "metadata": { "colab": { "name": "homework_neuron_activations.ipynb", "version": "0.3.2", "provenance": [], "collapsed_sections": [] }, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" } }, "cells": [ { "metadata": { "colab_type": "text", "id": "RnuMlpJp9if9" }, "cell_type": "markdown", "source": [ "---" ] }, { "metadata": { "colab_type": "text", "id": "lkoop-MT9if-" }, "cell_type": "markdown", "source": [ "

Нейрон с различными функциями активации

" ] }, { "metadata": { "colab_type": "text", "id": "qGKppuWS9if-" }, "cell_type": "markdown", "source": [ "---" ] }, { "metadata": { "colab_type": "text", "id": "IUjxg_IS31tn" }, "cell_type": "markdown", "source": [ "**Какую функции активации стоит выбирать в экспериментах (и в \"реальной жизни\")?** В этом ноутбуке Вам предлагается самим дойти до истины и сравнить нейроны с различными функциями активации (их качество на двух выборках). Не забудьте убедиться, что все эксперименты с разными видами нейронов Вы проводите в одинаковых условиях (иначе ведь эксперимент будет нечестным).\n", "\n", "В данном задании Вам нужно будет: \n", "- самостоятельно реализовать класс **`Neuron()`** с различными функциями активации (ReLU, LeakyReLU и ELU)\n", "\n", "- обучить и протестировать этот класс на сгенерированных и реальных данных" ] }, { "metadata": { "colab_type": "code", "id": "p-OYlV519igB", "colab": {} }, "cell_type": "code", "source": [ "from matplotlib import pyplot as plt\n", "from matplotlib.colors import ListedColormap\n", "import numpy as np\n", "import pandas as pd" ], "execution_count": 0, "outputs": [] }, { "metadata": { "colab_type": "code", "id": "t1VCt_rDHum3", "colab": {} }, "cell_type": "code", "source": [ "RANDOM_SEED = 42\n", "np.random.seed(RANDOM_SEED)" ], "execution_count": 0, "outputs": [] }, { "metadata": { "colab_type": "text", "id": "CmEexUY631uM" }, "cell_type": "markdown", "source": [ "---" ] }, { "metadata": { "colab_type": "text", "id": "cgpFOaPm9igG" }, "cell_type": "markdown", "source": [ "В данном случае мы снова решаем задачу бинарной классификации (2 класса: 1 или 0). Мы уже выяснили (в ноутбуке про `logloss`), что плохо брать для классификации квадратичную функцию потерь, однако здесь для простоты возьмём её:\n", "\n", "$$\n", "Loss(\\hat{y}, y) = \\frac{1}{n}\\sum_{i=1}^{n} (\\hat{y_i} - y_i)^2\n", "$$ \n", "\n", "Здесь $w \\cdot X_i$ - скалярное произведение, а $\\hat{y_i} = \\sigma(w \\cdot X_i) =\\frac{1}{1+e^{-w \\cdot X_i}} $ - сигмоида ($i$ -- номер объекта в выборке). " ] }, { "metadata": { "colab_type": "code", "id": "yn3F0layHunD", "colab": {} }, "cell_type": "code", "source": [ "def Loss(y_pred, y):\n", " y_pred = y_pred.reshape(-1, 1)\n", " y = np.array(y).reshape(-1, 1)\n", " return 0.5 * np.mean((y_pred - y) ** 2)" ], "execution_count": 0, "outputs": [] }, { "metadata": { "colab_type": "text", "id": "rYoQAdXoHunK" }, "cell_type": "markdown", "source": [ "Далее будут предложены несколько функций активации, и Вам нужно реализовать класс `Neuron` по аналогии с тем, как это было на семинаре. Сам принцип тот же, но меняются формула обновления весов и формула предсказания." ] }, { "metadata": { "colab_type": "text", "id": "WHvdwJW9HunN" }, "cell_type": "markdown", "source": [ "

Нейрон с ReLU (Recitified Linear Unit)

" ] }, { "metadata": { "colab_type": "text", "id": "AMA7Gi6KHunQ" }, "cell_type": "markdown", "source": [ "ReLU самая часто используемая (по крайней мере, пару лет назад) функция активации в нейронных сетях. Выглядит она очень просто:\n", "\n", "\\begin{equation*}\n", "ReLU(x) =\n", " \\begin{cases}\n", " 0, &\\text{$x \\le 0$}\\\\\n", " x, &\\text{$x \\gt 0$}\n", " \\end{cases}\n", "\\end{equation*}\n", "\n", "Или по-другому:\n", "\n", "$$\n", "ReLU(x) = \\max(0, x)\n", "$$\n", "\n", "В (свободном) переводе Rectified Linear Unit = \"Усечённая линейная функция\". Собственно, мы по сути просто не даём проходить отрицательным числам.\n", "\n", "Производная здесь берётся как производная от кусочно-заданной функции, то есть на участках, где функция гладкая, и в нуле её доопредляют нулём:\n", "\n", "\\begin{equation*}\n", "ReLU'(x) = \n", " \\begin{cases}\n", " 0, &\\text{$x \\le 0$}\\\\\n", " 1, &\\text{$x \\gt 0$}\n", " \\end{cases}\n", "\\end{equation*}\n", "\n", "График этой функции и её производной выглядят так:\n", "\n", "\n", "\n", "Подставим ReLu в Loss:\n", "\n", "$$Loss(\\hat{y}, y) = \\frac{1}{2n}\\sum_{i=1}^{n} (\\hat{y_i} - y_i)^2 = \\frac{1}{2n}\\sum_{i=1}^{n} (ReLU(w \\cdot X_i) - y_i)^2 = \\begin{equation*}\n", "\\frac{1}{2n}\\sum_{i=1}^{n}\n", " \\begin{cases}\n", " y_i^2, &{w \\cdot X_i \\le 0}\\\\\n", " (w \\cdot X_i - y_i)^2, &{w \\cdot X_i \\gt 0}\n", " \\end{cases}\n", "\\end{equation*}$$ \n", "\n", "(помните, что $w \\cdot X_i$ -- это число в данном случае (результат скалярного произведения двух векторов)).\n", "\n", "Тогда формула для обновления весов при градиентном спуске будет такая (в матричном виде, рекмоендуем вывести самим то, как это получается из формулы для одного объекта):\n", "\n", "$$ \\frac{\\partial Loss}{\\partial w} = \\begin{equation*}\n", "\\frac{1}{n}\\sum_{i=1}^{n}\n", " \\begin{cases}\n", " 0, &{w \\cdot X_i \\le 0}\\\\\n", " \\frac{1}{n} X_i^T (w \\cdot X_i - y), &{w \\cdot X_i \\gt 0}\n", " \\end{cases}\n", "\\end{equation*}$$\n", "\n", "(напоминаем, что здесь $w \\cdot X$ -- матричное произведение вектора $w$ (ведь вектор -- тоже матрица, не так ли?) и матрицы $X$ )\n", "\n", "Почему в первом случае будет 0? Потому что в формулу $y_i^2$ не входят веса , а мы берём производную именно по весам $w$.\n", "\n", "* Реализуйте ReLU и её производную:" ] }, { "metadata": { "colab_type": "code", "id": "DCgAeho19igI", "colab": {} }, "cell_type": "code", "source": [ "def relu(x):\n", " \"\"\"ReLU-функция\"\"\"\n", " return <Ваш код здесь>" ], "execution_count": 0, "outputs": [] }, { "metadata": { "colab_type": "code", "id": "nXwsy-7J9igL", "colab": {} }, "cell_type": "code", "source": [ "def relu_derivative(x):\n", " \"\"\"Производная ReLU\"\"\"\n", " return <Ваш код здесь>" ], "execution_count": 0, "outputs": [] }, { "metadata": { "colab_type": "text", "id": "qKurn-7F9igN" }, "cell_type": "markdown", "source": [ "Теперь нужно написать нейрон с ReLU. Здесь всё очень похоже на перцептрон, но будут по-другому обновляться веса и другая функция активации:" ] }, { "metadata": { "colab_type": "code", "id": "AM9vn3OX9igO", "colab": {} }, "cell_type": "code", "source": [ "class NeuronReLU:\n", " def __init__(self, w=None, b=0):\n", " \"\"\"\n", " :param: w -- вектор весов\n", " :param: b -- смещение\n", " \"\"\"\n", " self.w = w\n", " self.b = b\n", " \n", " \n", " def activate(self, x):\n", " <Ваш код здесь>\n", " \n", " \n", " def forward_pass(self, X):\n", " \"\"\"\n", " Рассчитывает ответ нейрона при предъявлении набора объектов\n", " :param: X -- матрица примеров размера (n, m), каждая строка - отдельный объект\n", " :return: вектор размера (n, 1) из нулей и единиц с ответами нейрона \n", " \"\"\"\n", " n = X.shape[0]\n", " y_pred = np.zeros((n, 1)) # y_pred == y_predicted - предсказанные классы\n", " <Ваш код здесь>\n", " \n", " \n", " def backward_pass(self, X, y, y_pred, learning_rate=0.005):\n", " \"\"\"\n", " Обновляет значения весов нейрона в соответствии с этим объектом\n", " :param: X -- матрица входов размера (n, m)\n", " y -- вектор правильных ответов размера (n, 1)\n", " learning_rate - \"скорость обучения\" (символ alpha в формулах выше)\n", " В этом методе ничего возвращать не нужно, только правильно поменять веса\n", " с помощью градиентного спуска.\n", " \"\"\"\n", " n = len(y)\n", " y = np.array(y).reshape(-1, 1)\n", " <Ваш код здесь>\n", " \n", " \n", " def fit(self, X, y, num_epochs=300):\n", " \"\"\"\n", " Спускаемся в минимум\n", " :param: X -- матрица объектов размера (n, m)\n", " y -- вектор правильных ответов размера (n, 1)\n", " num_epochs -- количество итераций обучения\n", " :return: losses -- вектор значений функции потерь\n", " \"\"\"\n", " self.w = np.zeros((X.shape[1], 1)) # столбец (m, 1)\n", " self.b = 0 # смещение (число)\n", " Loss_values = [] # значения функции потерь на различных итерациях обновления весов\n", " \n", " for i in range(num_epochs):\n", " <Ваш код здесь>\n", " \n", " return Loss_values" ], "execution_count": 0, "outputs": [] }, { "metadata": { "colab_type": "text", "id": "thtFp-at9igS" }, "cell_type": "markdown", "source": [ "

Тестирование нейрона с ReLU

" ] }, { "metadata": { "colab_type": "text", "id": "hOuYzf_u9igS" }, "cell_type": "markdown", "source": [ "Здесь Вам нужно самим протестировать новый нейрон **на тех же данных** (\"Яблоки и Груши\") по аналогии с тем, как это было проделано с перцептроном.\n", "В итоге нужно вывести: \n", "* график, на котором будет показано, как изменяется функция потерь $Loss$ в зависимости от числа итераций обучения\n", "* график с раскраской выборки нейроном" ] }, { "metadata": { "colab_type": "text", "id": "chEeb88gHuny" }, "cell_type": "markdown", "source": [ "***ПРИМЕЧАНИЕ***: пожалуйста, почаще проверяйте `.shape` у матриц и векторов: `self.w`, `X` и `y` внутри класса. Очень часто ошибка решается транспонированием или `.reshape()`'ом. Не забывайте проверять, что на что Вы умножаете и какой вектор (какой размер) хотите получить на выходе -- это очень помогает не запутаться." ] }, { "metadata": { "colab_type": "text", "id": "JGs_F5N331u9" }, "cell_type": "markdown", "source": [ "** Проверка forward_pass()**" ] }, { "metadata": { "colab_type": "code", "id": "aKmrhe_831vG", "colab": {} }, "cell_type": "code", "source": [ "w = np.array([1., 2.]).reshape(2, 1)\n", "b = 2.\n", "X = np.array([[1., 3.],\n", " [2., 4.],\n", " [-1., -3.2]])\n", "\n", "neuron = NeuronReLU(w, b)\n", "y_pred = neuron.forward_pass(X)\n", "print (\"y_pred = \" + str(y_pred))" ], "execution_count": 0, "outputs": [] }, { "metadata": { "colab_type": "text", "id": "dvm9wt9kHuoA" }, "cell_type": "markdown", "source": [ "*Hint: \"**-0.**\" -- это просто ноль*" ] }, { "metadata": { "colab_type": "text", "id": "VYG2uFcy31vP" }, "cell_type": "markdown", "source": [ "**Проверка backward_pass()**" ] }, { "metadata": { "colab_type": "code", "id": "UvUkrgQy31vQ", "colab": {} }, "cell_type": "code", "source": [ "y = np.array([1, 0, 1]).reshape(3, 1)" ], "execution_count": 0, "outputs": [] }, { "metadata": { "colab_type": "code", "id": "pWooOLya31vX", "colab": {} }, "cell_type": "code", "source": [ "neuron.backward_pass(X, y, y_pred)\n", "\n", "print (\"w = \" + str(neuron.w))\n", "print (\"b = \" + str(neuron.b))" ], "execution_count": 0, "outputs": [] }, { "metadata": { "colab_type": "text", "id": "X085TCvBHuoQ" }, "cell_type": "markdown", "source": [ "\"Яблоки и Груши\" (необходимо положить данные в папку `./data`):" ] }, { "metadata": { "colab_type": "code", "id": "LEnSapY_HuoR", "colab": {} }, "cell_type": "code", "source": [ "data = pd.read_csv(\"./data/apples_pears.csv\")\n", "plt.figure(figsize=(10, 8))\n", "plt.scatter(data.iloc[:, 0], data.iloc[:, 1], c=data['target'], cmap='rainbow')\n", "plt.title('Яблоки и груши', fontsize=15)\n", "plt.xlabel('симметричность', fontsize=14)\n", "plt.ylabel('желтизна', fontsize=14)\n", "plt.show();" ], "execution_count": 0, "outputs": [] }, { "metadata": { "colab_type": "code", "id": "03cIoaEsHuod", "colab": {} }, "cell_type": "code", "source": [ "X = data.iloc[:,:2].values # матрица объекты-признаки\n", "y = data['target'].values.reshape((-1, 1)) # классы (столбец из нулей и единиц)" ], "execution_count": 0, "outputs": [] }, { "metadata": { "colab_type": "text", "id": "dR6xZg6kHuol" }, "cell_type": "markdown", "source": [ "Выведите лосс при обучении нейрона с ReLU на этом датасете:" ] }, { "metadata": { "colab_type": "code", "id": "FZDtNG6CHuop", "colab": {} }, "cell_type": "code", "source": [ "%%time\n", "\n", "neuron = <Ваш код здесь>\n", "Loss_values = <Ваш код здесь>\n", "\n", "plt.figure(figsize=(10, 8))\n", "plt.plot(Loss_values)\n", "plt.title('Функция потерь', fontsize=15)\n", "plt.xlabel('номер итерации', fontsize=14)\n", "plt.ylabel('$Loss(\\hat{y}, y)$', fontsize=14)\n", "plt.show()" ], "execution_count": 0, "outputs": [] }, { "metadata": { "colab_type": "text", "id": "sPxo2YVeHuou" }, "cell_type": "markdown", "source": [ "Скорее всего сейчас у вас лосс -- это прямая линия, и вы видите, что веса не обновляются. Но почему?" ] }, { "metadata": { "colab_type": "text", "id": "mXUmEeUBHuov" }, "cell_type": "markdown", "source": [ "Всё просто -- если присмотреться, то видно, что self.w и self.b иницилизируются нулями в начале `.fit()`-метода. Если расписать, как будет идти обновление, то видно, что из-за ReLU веса просто-напросто не будут обновляться, если начать с инициализации нулями. \n", "\n", "Это -- одна из причин, по которой в нейронных сетях веса инициализируют случаными числами (обычно из отрезка [0, 1)).\n", "\n", "Обучим нейрон, инициализировав случайно веса (поставьте 10000 итераций). \n", "\n", "**!!! Закомментируйте инициализацию нулями в функции `.fit()` класса `NeuronReLU` !!!**" ] }, { "metadata": { "colab_type": "code", "id": "E821cUM0Huo8", "colab": {} }, "cell_type": "code", "source": [ "%%time\n", "\n", "neuron = NeuronReLU(w=np.random.rand(X.shape[1], 1), b=np.random.rand(1))\n", "Loss_values = neuron.fit(X, y, num_epochs=10000)\n", "\n", "plt.figure(figsize=(10, 8))\n", "plt.plot(Loss_values)\n", "plt.title('Функция потерь', fontsize=15)\n", "plt.xlabel('номер итерации', fontsize=14)\n", "plt.ylabel('$Loss(\\hat{y}, y)$', fontsize=14)\n", "plt.show()" ], "execution_count": 0, "outputs": [] }, { "metadata": { "colab_type": "text", "id": "jkWMrc9uHupS" }, "cell_type": "markdown", "source": [ "Посмотрим, как предсказывает этот нейрон:" ] }, { "metadata": { "colab_type": "code", "id": "hyjfcthcHupT", "scrolled": false, "colab": {} }, "cell_type": "code", "source": [ "plt.figure(figsize=(10, 8))\n", "plt.scatter(data.iloc[:, 0], data.iloc[:, 1], c=np.array(neuron.forward_pass(X) > 0.5).ravel(), cmap='spring')\n", "plt.title('Яблоки и груши', fontsize=15)\n", "plt.xlabel('симметричность', fontsize=14)\n", "plt.ylabel('желтизна', fontsize=14)\n", "plt.show();" ], "execution_count": 0, "outputs": [] }, { "metadata": { "colab_type": "text", "id": "nDvxxQQDHupZ" }, "cell_type": "markdown", "source": [ "" ] }, { "metadata": { "colab_type": "text", "id": "MSNZuWl9Hupd" }, "cell_type": "markdown", "source": [ "Есть одна тенденция: пороговая функция активации и сигмоида (обычно всё же только сигмоида) чаще используются именно на **выходном слое** нейросети в задаче классификации -- ими предсказывают вероятности объектов принадлежать одному из классов, в то время как продвинутые функции активации (ReLU и те, что будут дальше) используются внутри нейросети, то есть в **скрытых слоях**." ] }, { "metadata": { "colab_type": "text", "id": "_X3lDHElHupo" }, "cell_type": "markdown", "source": [ "Нужно понимать, что ReLU не может вернуть отрицательные числа." ] }, { "metadata": { "colab_type": "text", "id": "sET710OVHupp" }, "cell_type": "markdown", "source": [ "**Плюсы ReLU:**" ] }, { "metadata": { "colab_type": "text", "id": "HY3iXGBWHupr" }, "cell_type": "markdown", "source": [ "* дифференцируемая (с доопределе\n", "нием в нуле)\n", "* нет проблемы затухающих градиентов, как в сигмоиде" ] }, { "metadata": { "colab_type": "text", "id": "Nr_3XwTWHups" }, "cell_type": "markdown", "source": [ "**Возможные минусы ReLU:**" ] }, { "metadata": { "colab_type": "text", "id": "0TAwVdRXHupt" }, "cell_type": "markdown", "source": [ "* не центрирована около 0 (может мешать скорости сходимсти)\n", "* зануляет все отрицательные входы, тем самым веса у занулённых нейронов могут часто *не обновляться*, эту проблему иногда называют *мёртвые нейроны*" ] }, { "metadata": { "colab_type": "text", "id": "Vj1JGXTPHupu" }, "cell_type": "markdown", "source": [ "С последней проблемой можно побороться, а именно:" ] }, { "metadata": { "colab_type": "text", "id": "Yn2taDMNHupv" }, "cell_type": "markdown", "source": [ "

Нейрон с LeakyReLU (Leaky Recitified Linear Unit)

" ] }, { "metadata": { "colab_type": "text", "id": "iCRBWooSHupx" }, "cell_type": "markdown", "source": [ "LeakyReLU очень слабо отличается от ReLU, но часто помогает сети обучаться быстрее, поскольку нет проблемы \"мёртвых нейронов\":\n", "\n", "\\begin{equation*}\n", "LeakyReLU(x) =\n", " \\begin{cases}\n", " \\alpha x, &\\text{$x \\le 0$}\\\\\n", " x, &\\text{$x \\gt 0$}\n", " \\end{cases}\n", "\\end{equation*}\n", "\n", "где $\\alpha$ -- маленькое число от 0 до 1.\n", "\n", "Производная здесь берётся так же, но вместо нуля будет $\\alpha$:\n", "\n", "\\begin{equation*}\n", "LeakyReLU'(x) = \n", " \\begin{cases}\n", " \\alpha, &\\text{$x \\le 0$}\\\\\n", " 1, &\\text{$x \\gt 0$}\n", " \\end{cases}\n", "\\end{equation*}\n", "\n", "График этой функции:\n", "\n", "\n", "\n", "Подставим LeakyReLu в Loss:\n", "\n", "$$\n", "Loss(\\hat{y}, y) = \\frac{1}{2n}\\sum_{i=1}^{n} (\\hat{y_i} - y_i)^2 = \\frac{1}{2n}\\sum_{i=1}^{n} (LeakyReLU(w \\cdot X_i) - y_i)^2 =\n", "\\begin{equation*}\n", "\\frac{1}{2n}\\sum_{i=1}^{n} \n", " \\begin{cases}\n", " (\\alpha \\cdot w \\cdot X_i - y_i)^2, &{w \\cdot X_i \\le 0}\\\\\n", " (w \\cdot X_i - y_i)^2, &{w \\cdot X_i \\gt 0}\n", " \\end{cases}\n", "\\end{equation*}\n", "$$ \n", "\n", "Формула для обновления весов при градиентном спуске:\n", "\n", "$$ \\frac{\\partial Loss}{\\partial w} = \\begin{equation*}\n", "\\frac{1}{n}\\sum_{i=1}^{n} \n", " \\begin{cases}\n", " \\alpha X_i^T (w \\cdot X_i - y), &{w \\cdot X_i \\le 0}\\\\\n", " X_i^T (w \\cdot X_i - y), &{w \\cdot X_i \\gt 0}\n", " \\end{cases}\n", "\\end{equation*}$$\n", "\n", "* Реализуйте LeakyReLU и её производную:" ] }, { "metadata": { "id": "EvvL-J5Tg733", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "def leaky_relu(x, alpha=0.01):\n", " \"\"\"LeakyReLU-функция\"\"\"\n", " <Ваш код здесь>" ], "execution_count": 0, "outputs": [] }, { "metadata": { "id": "An5RxMX_g736", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "def leaky_relu_derivative(x, alpha=0.01):\n", " \"\"\"Производная LeakyReLU\"\"\"\n", " <Ваш код здесь>" ], "execution_count": 0, "outputs": [] }, { "metadata": { "id": "4pVZT9RZg738", "colab_type": "text" }, "cell_type": "markdown", "source": [ "Теперь нужно написать нейрон с LeakyReLU функцией активации. Здесь всё очень похоже на перцептрон, но будут по-другому обновляться веса и другая функция активации:" ] }, { "metadata": { "id": "UM7qpfxPg739", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "class NeuronLeakyReLU:\n", " def __init__(self, w=None, b=0):\n", " \"\"\"\n", " :param: w -- вектор весов\n", " :param: b -- смещение\n", " \"\"\"\n", " self.w = w\n", " self.b = b\n", " \n", " \n", " def activate(self, x):\n", " <Ваш код здесь>\n", " \n", " \n", " def forward_pass(self, X):\n", " \"\"\"\n", " Рассчитывает ответ нейрона при предъявлении набора объектов\n", " :param: X -- матрица примеров размера (n, m), каждая строка - отдельный объект\n", " :return: вектор размера (n, 1) из нулей и единиц с ответами нейрона \n", " \"\"\"\n", " n = X.shape[0]\n", " y_pred = np.zeros((n, 1)) # y_pred == y_predicted - предсказанные классы\n", " <Ваш код здесь>\n", " \n", " \n", " def backward_pass(self, X, y, y_pred, learning_rate=0.005):\n", " \"\"\"\n", " Обновляет значения весов нейрона в соответствии с этим объектом\n", " :param: X -- матрица входов размера (n, m)\n", " y -- вектор правильных ответов размера (n, 1)\n", " learning_rate - \"скорость обучения\" (символ alpha в формулах выше)\n", " В этом методе ничего возвращать не нужно, только правильно поменять веса\n", " с помощью градиентного спуска.\n", " \"\"\"\n", " n = len(y)\n", " y = np.array(y).reshape(-1, 1)\n", " <Ваш код здесь>\n", " \n", " \n", " def fit(self, X, y, num_epochs=300):\n", " \"\"\"\n", " Спускаемся в минимум\n", " :param: X -- матрица объектов размера (n, m)\n", " y -- вектор правильных ответов размера (n, 1)\n", " num_epochs -- количество итераций обучения\n", " :return: losses -- вектор значений функции потерь\n", " \"\"\"\n", "# self.w = np.zeros((X.shape[1], 1)) # столбец (m, 1)\n", "# self.b = 0 # смещение (число)\n", " Loss_values = [] # значения функции потерь на различных итерациях обновления весов\n", " \n", " for i in range(num_epochs):\n", " <Ваш код здесь>\n", " \n", " return Loss_values" ], "execution_count": 0, "outputs": [] }, { "metadata": { "id": "9jZMVbS4g74B", "colab_type": "text" }, "cell_type": "markdown", "source": [ "

Тестирование нейрона с LeakyReLU

" ] }, { "metadata": { "colab_type": "text", "id": "PsCNxcsDHuqb" }, "cell_type": "markdown", "source": [ "\"Яблоки и Груши\":" ] }, { "metadata": { "colab_type": "code", "id": "MEXxd-YRHuqg", "colab": {} }, "cell_type": "code", "source": [ "data = pd.read_csv(\"./data/apples_pears.csv\")\n", "plt.figure(figsize=(10, 8))\n", "plt.scatter(data.iloc[:, 0], data.iloc[:, 1], c=data['target'], cmap='rainbow')\n", "plt.title('Яблоки и груши', fontsize=15)\n", "plt.xlabel('симметричность', fontsize=14)\n", "plt.ylabel('желтизна', fontsize=14)\n", "plt.show();" ], "execution_count": 0, "outputs": [] }, { "metadata": { "colab_type": "code", "id": "uzQyBSMOHuqk", "colab": {} }, "cell_type": "code", "source": [ "X = data.iloc[:,:2].values # матрица объекты-признаки\n", "y = data['target'].values.reshape((-1, 1)) # классы (столбец из нулей и единиц)" ], "execution_count": 0, "outputs": [] }, { "metadata": { "colab_type": "text", "id": "d-92UxDqHuqo" }, "cell_type": "markdown", "source": [ "Обучим нейрон, инициализировав случайно веса (поставьте 10000 итераций).\n", "\n", "**!!! Закомментируйте инициализацию нулями в функции `.fit()` класса `NeuronLeakyReLU` !!!**" ] }, { "metadata": { "colab_type": "code", "id": "SGztqTFhHuqo", "colab": {} }, "cell_type": "code", "source": [ "%%time\n", "\n", "neuron = NeuronLeakyReLU(w=np.random.rand(X.shape[1], 1), b=np.random.rand(1))\n", "Loss_values = neuron.fit(X, y, num_epochs=10000)\n", "\n", "plt.figure(figsize=(10, 8))\n", "plt.plot(Loss_values)\n", "plt.title('Функция потерь', fontsize=15)\n", "plt.xlabel('номер итерации', fontsize=14)\n", "plt.ylabel('$Loss(\\hat{y}, y)$', fontsize=14)\n", "plt.show()" ], "execution_count": 0, "outputs": [] }, { "metadata": { "colab_type": "text", "id": "zvj3t74RHuq3" }, "cell_type": "markdown", "source": [ "Посмотрим, как предсказывает этот нейрон:" ] }, { "metadata": { "colab_type": "code", "id": "jfFexRvcHuq5", "scrolled": false, "colab": {} }, "cell_type": "code", "source": [ "plt.figure(figsize=(10, 8))\n", "plt.scatter(data.iloc[:, 0], data.iloc[:, 1], c=np.array(neuron.forward_pass(X) > 0.5).ravel(), cmap='spring')\n", "plt.title('Яблоки и груши', fontsize=15)\n", "plt.xlabel('симметричность', fontsize=14)\n", "plt.ylabel('желтизна', fontsize=14)\n", "plt.show();" ], "execution_count": 0, "outputs": [] }, { "metadata": { "colab_type": "text", "id": "IgTac_vFHuq8" }, "cell_type": "markdown", "source": [ "**Плюсы LeakyReLU:**" ] }, { "metadata": { "colab_type": "text", "id": "iAw-tzuLHuq9" }, "cell_type": "markdown", "source": [ "* дифференцируемая (с доопределнием в нуле)\n", "* нет проблемы затухающих градиентов, как в сигмоиде\n", "* нет проблемы \"мёртвых нейронов\", как в ReLU" ] }, { "metadata": { "colab_type": "text", "id": "CMUGymwyHuq_" }, "cell_type": "markdown", "source": [ "**Возможные минусы LeakyReLU:**" ] }, { "metadata": { "colab_type": "text", "id": "uQyqD8-LHurA" }, "cell_type": "markdown", "source": [ "* не центрирована около 0 (может мешать скорости сходимсти)\n", "* немного не устойчива к \"шуму\" (см. лекции Стэнфорда)" ] }, { "metadata": { "colab_type": "text", "id": "e7k07EGyHurB" }, "cell_type": "markdown", "source": [ "

Нейрон с ELU (Exponential Linear Unit)

" ] }, { "metadata": { "colab_type": "text", "id": "dWH3Zk2zHurB" }, "cell_type": "markdown", "source": [ "ELU -- не так давно предложенная (в 2015 году) функция активации, которая, как говорят авторы статьи, лучше LeakyReLU. Вот формула ELU:\n", "\n", "\\begin{equation*}\n", "ELU(\\alpha, x) =\n", " \\begin{cases}\n", " \\alpha (e^x - 1), &\\text{$x \\le 0$}\\\\\n", " x, &\\text{$x \\gt 0$}\n", " \\end{cases}\n", "\\end{equation*}\n", "\n", "где $\\alpha$ -- маленькое число от 0 до 1.\n", "\n", "Производная здесь берётся так же, но вместо нуля будет $\\alpha$:\n", "\n", "\\begin{equation*}\n", "ELU'(x) = \n", " \\begin{cases}\n", " ELU(\\alpha, x) + \\alpha, &\\text{$x \\le 0$}\\\\\n", " 1, &\\text{$x \\gt 0$}\n", " \\end{cases}\n", "\\end{equation*}\n", "\n", "Здесь в производной использован постой трюк -- сделано $- \\alpha + \\alpha$, чтобы вычислять было проще.\n", "\n", "График этой функции:\n", "\n", "\n", "\n", "Подставим LeakyReLu в Loss:\n", "\n", "$$Loss(\\hat{y}, y) = \\frac{1}{2n}\\sum_{i=1}^{n} (\\hat{y_i} - y_i)^2 = \\frac{1}{2n}\\sum_{i=1}^{n} (ELU(\\alpha, w \\cdot X_i) - y_i)^2 = \\begin{equation*}\n", "\\frac{1}{2n}\\sum_{i=1}^{n} \n", " \\begin{cases}\n", " (\\alpha (e^{w \\cdot X_i} - 1) - y_i)^2, &{w \\cdot X_i \\le 0}\\\\\n", " (w \\cdot X_i - y_i)^2, &{w \\cdot X_i \\gt 0}\n", " \\end{cases}\n", "\\end{equation*}$$ \n", "\n", "Здесь вам нужно выписать самим град спуск для весов. Брать производную \"в лоб\" некрасиво и неудобно. Нужно воспользоваться **правилом цепочки**, оно же **правило взятия производной сложной функции**:\n", "\n", "$$ \\frac{\\partial Loss}{\\partial w} = \\begin{equation*}\n", "\\frac{1}{n}\\sum_{i=1}^{n} \n", " \\begin{cases}\n", " , &{w \\cdot X_i \\le 0}\\\\\n", " , &{w \\cdot X_i \\gt 0}\n", " \\end{cases}\n", "\\end{equation*}$$\n", "\n", "* Реализуйте ELU и её производную:" ] }, { "metadata": { "colab_type": "code", "id": "Mc6fsB5HzR2Q", "colab": {} }, "cell_type": "code", "source": [ "def eelu(x, alpha=0.01):\n", " \"\"\"LeakyReLU-функция\"\"\"\n", " <Ваш код здесь>" ], "execution_count": 0, "outputs": [] }, { "metadata": { "colab_type": "code", "id": "lMSIjprszR2T", "colab": {} }, "cell_type": "code", "source": [ "def elu_derivative(x, alpha=0.01):\n", " \"\"\"Производная LeakyReLU\"\"\"\n", " <Ваш код здесь>" ], "execution_count": 0, "outputs": [] }, { "metadata": { "colab_type": "text", "id": "P5p1eDNGzR2V" }, "cell_type": "markdown", "source": [ "Теперь нужно написать нейрон с LeakyReLU функцией активации. Здесь всё очень похоже на перцептрон, но будут по-другому обновляться веса и другая функция активации:" ] }, { "metadata": { "colab_type": "code", "id": "3cxpveqozR2X", "colab": {} }, "cell_type": "code", "source": [ "class NeuronELU:\n", " def __init__(self, w=None, b=0):\n", " \"\"\"\n", " :param: w -- вектор весов\n", " :param: b -- смещение\n", " \"\"\"\n", " self.w = w\n", " self.b = b\n", " \n", " \n", " def activate(self, x):\n", " <Ваш код здесь>\n", " \n", " \n", " def forward_pass(self, X):\n", " \"\"\"\n", " Рассчитывает ответ нейрона при предъявлении набора объектов\n", " :param: X -- матрица примеров размера (n, m), каждая строка - отдельный объект\n", " :return: вектор размера (n, 1) из нулей и единиц с ответами нейрона \n", " \"\"\"\n", " n = X.shape[0]\n", " y_pred = np.zeros((n, 1)) # y_pred == y_predicted - предсказанные классы\n", " <Ваш код здесь>\n", " \n", " \n", " def backward_pass(self, X, y, y_pred, learning_rate=0.005):\n", " \"\"\"\n", " Обновляет значения весов нейрона в соответствии с этим объектом\n", " :param: X -- матрица входов размера (n, m)\n", " y -- вектор правильных ответов размера (n, 1)\n", " learning_rate - \"скорость обучения\" (символ alpha в формулах выше)\n", " В этом методе ничего возвращать не нужно, только правильно поменять веса\n", " с помощью градиентного спуска.\n", " \"\"\"\n", " n = len(y)\n", " y = np.array(y).reshape(-1, 1)\n", " <Ваш код здесь>\n", " \n", " \n", " def fit(self, X, y, num_epochs=300):\n", " \"\"\"\n", " Спускаемся в минимум\n", " :param: X -- матрица объектов размера (n, m)\n", " y -- вектор правильных ответов размера (n, 1)\n", " num_epochs -- количество итераций обучения\n", " :return: losses -- вектор значений функции потерь\n", " \"\"\"\n", "# self.w = np.zeros((X.shape[1], 1)) # столбец (m, 1)\n", "# self.b = 0 # смещение (число)\n", " Loss_values = [] # значения функции потерь на различных итерациях обновления весов\n", " \n", " for i in range(num_epochs):\n", " <Ваш код здесь>\n", " \n", " return Loss_values" ], "execution_count": 0, "outputs": [] }, { "metadata": { "id": "n8EcOPw4zL9_", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "" ], "execution_count": 0, "outputs": [] }, { "metadata": { "colab_type": "text", "id": "pskXX7KVHurf" }, "cell_type": "markdown", "source": [ "\"Яблоки и Груши\":" ] }, { "metadata": { "colab_type": "code", "id": "oSzTCZu_Hurh", "colab": {} }, "cell_type": "code", "source": [ "data = pd.read_csv(\"./data/apples_pears.csv\")\n", "plt.figure(figsize=(10, 8))\n", "plt.scatter(data.iloc[:, 0], data.iloc[:, 1], c=data['target'], cmap='rainbow')\n", "plt.title('Яблоки и груши', fontsize=15)\n", "plt.xlabel('симметричность', fontsize=14)\n", "plt.ylabel('желтизна', fontsize=14)\n", "plt.show();" ], "execution_count": 0, "outputs": [] }, { "metadata": { "colab_type": "code", "id": "B1upsgBzHurl", "colab": {} }, "cell_type": "code", "source": [ "X = data.iloc[:,:2].values # матрица объекты-признаки\n", "y = data['target'].values.reshape((-1, 1)) # классы (столбец из нулей и единиц)" ], "execution_count": 0, "outputs": [] }, { "metadata": { "colab_type": "text", "id": "EsRToko0Hurp" }, "cell_type": "markdown", "source": [ "Обучим нейрон, инициализировав случайно веса (поставьте 10000 итераций):" ] }, { "metadata": { "colab_type": "code", "id": "P6lOu_FbHurr", "colab": {} }, "cell_type": "code", "source": [ "%%time\n", "\n", "neuron = NeuronELU(w=np.random.rand(X.shape[1], 1), b=np.random.rand(1))\n", "Loss_values = neuron.fit(X, y, num_epochs=10000)\n", "\n", "plt.figure(figsize=(10, 8))\n", "plt.plot(Loss_values)\n", "plt.title('Функция потерь', fontsize=15)\n", "plt.xlabel('номер итерации', fontsize=14)\n", "plt.ylabel('$Loss(\\hat{y}, y)$', fontsize=14)\n", "plt.show()" ], "execution_count": 0, "outputs": [] }, { "metadata": { "colab_type": "text", "id": "vF92uB8_Hurx" }, "cell_type": "markdown", "source": [ "Посмотрим, как предсказывает этот нейрон:" ] }, { "metadata": { "colab_type": "code", "id": "CW7nCIEyHur2", "scrolled": false, "colab": {} }, "cell_type": "code", "source": [ "plt.figure(figsize=(10, 8))\n", "plt.scatter(data.iloc[:, 0], data.iloc[:, 1], c=np.array(neuron.forward_pass(X) > 0.5).ravel(), cmap='spring')\n", "plt.title('Яблоки и груши', fontsize=15)\n", "plt.xlabel('симметричность', fontsize=14)\n", "plt.ylabel('желтизна', fontsize=14)\n", "plt.show();" ], "execution_count": 0, "outputs": [] }, { "metadata": { "colab_type": "text", "id": "MYiGj1wnHur4" }, "cell_type": "markdown", "source": [ "**Плюсы ELU:**" ] }, { "metadata": { "colab_type": "text", "id": "xOGAh06uHur5" }, "cell_type": "markdown", "source": [ "* дифференцируемая (с доопределнием в нуле)\n", "* нет проблемы затухающих градиентов, как в сигмоиде\n", "* нет проблемы \"мёртвых нейронов\", как в ReLU\n", "* более устойчива к \"шуму\" (см. лекции Стэнфорда)" ] }, { "metadata": { "colab_type": "text", "id": "r4doD89fHur6" }, "cell_type": "markdown", "source": [ "**Возможные минусы ELU:**" ] }, { "metadata": { "colab_type": "text", "id": "AO4bvkKGHur6" }, "cell_type": "markdown", "source": [ "* не очень хорошо центрирована около 0 (может мешать скорости сходимсти)\n", "* вычислительно дольше, чем ReLU и LeakyReLU" ] }, { "metadata": { "colab_type": "text", "id": "4_YkH_HDHur7" }, "cell_type": "markdown", "source": [ "---" ] }, { "metadata": { "colab_type": "text", "id": "wQG-VZuUHur8" }, "cell_type": "markdown", "source": [ "И напоследок -- почти все функции активации:" ] }, { "metadata": { "colab_type": "text", "id": "F2JcPTqKHur-" }, "cell_type": "markdown", "source": [ "" ] }, { "metadata": { "colab_type": "text", "id": "A82W6KU7HusA" }, "cell_type": "markdown", "source": [ "Не хватает `SeLU()` и `Swish()`. Про них можно прочитать здесь: [SeLU](https://arxiv.org/pdf/1706.02515.pdf), [Swish](https://arxiv.org/pdf/1710.05941.pdf).\n", "\n", "`Tanh()` (тангенс гиперболический) используется редко, а `Maxout()` мы решили не рассматривать (так как, опять же, нами не было замечено, что он часто используется, однако про него ходят хорошие слухи). " ] }, { "metadata": { "colab_type": "text", "id": "-PfNPtrZ31vq" }, "cell_type": "markdown", "source": [ "---" ] }, { "metadata": { "colab_type": "text", "id": "EiQGCe9lHusC" }, "cell_type": "markdown", "source": [ "За функцию активации можно взять вообще почти любую функцию (которая, как вы полагаете, будет помогать обучению). Ещё больше функций активации вы можете [найти на википедии](https://en.wikipedia.org/wiki/Activation_function)." ] }, { "metadata": { "colab_type": "text", "id": "yhGNsWPQ9igf" }, "cell_type": "markdown", "source": [ "

Полезные ссылки

\n", "\n", "0). Статья от Стэнфорда: http://cs231n.github.io/neural-networks-1/\n", "\n", "1). Хорошая статья про функции активации: https://www.jeremyjordan.me/neural-networks-activation-functions/\n", "\n", "2). [Видео от Siraj Raval](https://www.youtube.com/watch?v=-7scQpLossT7uo)\n", "\n", "3). Современная статья про функции активации. Теперь на хайпе активация $swish(x) = x\\sigma (\\beta x)$: https://arxiv.org/pdf/1710.05941.pdf (кстати, при её поиске в некоторой степени использовался neural architecture search)\n", "\n", "4). SeLU имеет очень интересные, доказанные с помощью теории вероятностей свойства: https://arxiv.org/pdf/1706.02515.pdf (да, в этой статье 102 страницы)\n", "\n", "5). [Список функций активации из википедии](https://en.wikipedia.org/wiki/Activation_function)" ] } ] }