Многослойная сеть на PyTorch"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "J2msuyHTYJcx"
},
"source": [
"В этом ноутбке мы научимся писать свои нейросети на фреймворке PyTorch, конкретно - рассмотрим, как написать многослойную полносвязную сеть (Fully-Connected, FC)."
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "9xJnMEZrYJcz"
},
"source": [
"
Компоненты нейросети
"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "InwacmvIYJc0"
},
"source": [
"Здесь самое время напомнить о том, какие вещи играют принципиальную роль в построении любой ***нейронной сети*** (все их мы задаём *руками*, самостоятельно): \n",
"\n",
"- непосредственно, сама **архитектура** нейросети (сюда входят типы функций активации у каждого нейрона);\n",
"- начальная **инициализация** весов каждого слоя;\n",
"- метод **оптимизации** нейросети (сюда ещё входит метод изменения `learning_rate`);\n",
"- размер **батчей** (`batch_size`);\n",
"- количество итераций обучения (`num_epochs`);\n",
"- **функция потерь** (`loss`); \n",
"- тип **регуляризации** нейросети (для каждого слоя можно свой); \n",
"\n",
"То, что связано с ***данными и задачей***: \n",
"- само **качество** выборки (непротиворечивость, чистота, корректность постановки задачи); \n",
"- **размер** выборки; "
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "tXujEOB0YJc1"
},
"source": [
"
Многослойная нейронная сеть
"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "mnxH-DajYJc3"
},
"source": [
"Как можно понять из названия, многослойная нейросеть состоит из нескольких **слоёв**. Каждый слой состоит из **нейронов**. Ранее мы уже писали свой нейрон на NumPy, вот из таких нейронов и состоит ***MLP (Multi-Layer Perceptron)***. Ещё такую многослойную нейросеть, у которой каждый нейрон на предыдущем уровне соединён с нейроном на следующем уровне, называют ***Fully-Connected-сетью*** (или ***Dense-сетью***). \n",
"\n",
"Расмотрим их устройство более подробно:"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "onjJUneMYJc5"
},
"source": [
"* Вот так выглядит двухслойная нейросеть (первый слой - input layer - не считается, потому что это, по сути, не слой):"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "owRRulLzYJc6"
},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "tFNxGGBEYJc8"
},
"source": [
"* Так выглядит трёхслойная нейросеть:"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "zRaKX35eYJc9"
},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "6w1FTkO1YJc-"
},
"source": [
".. и так далее для большего случая слоёв."
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "8iKV7m5YYJc_"
},
"source": [
"**Обратите внимание:** связи есть у нейронов со слоя $L_{i-1}$ и нейронов $L_{i}$, но между нейронами в одном слое связей **нет**."
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "URV9qWkfYJdA"
},
"source": [
"**Входной слой** -- это данные (матрица $(n, m)$)."
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "zK8tWuHHYJdB"
},
"source": [
"Слои, которые не являются входными или выходными, называются **скрытыми слоями (hidden layers)**."
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "fz9clUlCYJdC"
},
"source": [
"При решении ***задачи регрессии*** на **выходном слое** обычно один нейрон, который возвращает предсказанные числа (для каждого объекта по числу). \n",
"\n",
"В случае ***задачи классификации*** на **выходном слое** обычно один нейрон, если задача бинарной классификации, и $K$ нейронов, если задача $K$-класовой классификации."
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "nJblXqY5YJdE"
},
"source": [
"#### Forward pass в MLP"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "D87xoAl8YJdF"
},
"source": [
"Каждый слой многослойной нейросети - это матрица весов, столбцы которой -- это нейроны (один столбец - один нейрон). То есть один столбец -- это веса одного нейрона."
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"collapsed": true,
"id": "RyXUqCfVYJdG"
},
"source": [
"Допустим, мы решаем задачу $K$-классовой классификации (на последнем слое $K$ нейронов). Рассмотрим, как в таком случае выглядит `forward_pass` нейросети:"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "YO6gHbOjYJdH"
},
"source": [
"* Вход: $$X =\n",
"\\left(\n",
"\\begin{matrix} \n",
"x_{11} & ... & x_{1M} \\\\\n",
"... & \\ddots & ...\\\\\n",
"x_{N1} & ... & x_{NM} \n",
"\\end{matrix}\n",
"\\right)\n",
"$$\n",
"\n",
"-- матрица $(N, M)$"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"collapsed": true,
"id": "XdImZiQkYJdI"
},
"source": [
"* Структура сети - много слоёв, в слоях много нейронов. Первый слой (после входного) выглядит так:"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "U2c2M4MJYJdJ"
},
"source": [
"$$ W^1 =\n",
"\\left(\n",
"\\begin{matrix} \n",
"w_{11} & ... & w_{1L_1} \\\\\n",
"... & \\ddots & ...\\\\\n",
"w_{M1} & ... & w_{ML_1} \n",
"\\end{matrix}\n",
"\\right)\n",
"$$\n",
"\n",
"-- матрица $(M, L_1)$"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "UUCdeLN0YJdK"
},
"source": [
"То есть это в точности $L_1$ нейронов, каждый имеет свои собственные веса, их $M$ штук."
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "ixHtlMwKYJdL"
},
"source": [
"Мы помним, что нейрон - это линейное преобразование и потом нелинейная функция активации от этого преобразования. Однако в многослойных нейростеях часто отделяют `Linear` часть и `Activation`, то есть слоем считаем набор весов нейронов, а следующий слой всегда функция активации (у всех нейронов из слоя она одна и та же, обычно фреймворки не позволяют задавать конкретному нейрону в слое отличную от других нейронов в этом слое функцию активации, однако это легко сделать, объявив слой из одного нейрона)."
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "AUt1NgTvYJdN"
},
"source": [
"* Другие слои выглядит точно так же, как первый слой. Например, второй слой будет такой:"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "IdtSTvTmYJdN"
},
"source": [
"$$ W^2 =\n",
"\\left(\n",
"\\begin{matrix} \n",
"w_{11} & ... & w_{1L_2} \\\\\n",
"... & \\ddots & ...\\\\\n",
"w_{L_11} & ... & w_{L_1L_2} \n",
"\\end{matrix}\n",
"\\right)\n",
"$$\n",
"\n",
"-- матрица $(L_1, L_2)$"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "R3nGnHoLYJdP"
},
"source": [
"То есть это в точности $L_2$ нейронов, каждый имеет свои собственные веса, их $L_1$ штук."
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "bQhOAQjSYJdR"
},
"source": [
"* Выходной слой: \n",
"\n",
"Пусть в нейросети до выходного слоя идут $t$ слоёв. Тогда выходной слой имеет форму:"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "fWvqm-K0YJdT"
},
"source": [
"$$ W^{out} =\n",
"\\left(\n",
"\\begin{matrix} \n",
"w_{11} & ... & w_{1K} \\\\\n",
"... & \\ddots & ...\\\\\n",
"w_{L_t1} & ... & w_{L_tK} \n",
"\\end{matrix}\n",
"\\right)\n",
"$$\n",
"\n",
"-- матрица $(L_t, K)$, где $L_t$ - количество нейронов в $t$-ом слое, а $K$ -- количество классов."
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "2z5tO89NYJdU"
},
"source": [
"В итоге *для `forward_pass` нам нужно просто последовтельно перемножить матрицы друг за другом, применяя после каждого умножения соответсвующую функцию активации*."
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "IZT4GgsCYJdV"
},
"source": [
"*Примечание*: можно думать об умножении на очередную матрицу весов как на переход в **новое признаковое пространство**. Действительно, когда подаём матрицу $X$ и умножаем на матрицу первого слоя, мы получаем матрицу размера $(N, L_1)$, то есть как будто $L_1$ \"новых\" признаков (построенных как линейная комбинация старых до применения функции активации, и уже как нелинейная комбинация после активации). Здесь уместно вспомнить, что Deep Learning является пообластью Representation Learning, то есть позволяет выучивает новые представляения данных."
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "4RhJ4fsHYJdW"
},
"source": [
"**Backward pass в MLP**"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "MYN043DbYJdX"
},
"source": [
"Обучается с помощью метода \"Error Backpropagation\" - [\"Обратное распространение ошибки\"](https://ru.wikipedia.org/wiki/%D0%9C%D0%B5%D1%82%D0%BE%D0%B4_%D0%BE%D0%B1%D1%80%D0%B0%D1%82%D0%BD%D0%BE%D0%B3%D0%BE_%D1%80%D0%B0%D1%81%D0%BF%D1%80%D0%BE%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B5%D0%BD%D0%B8%D1%8F_%D0%BE%D1%88%D0%B8%D0%B1%D0%BA%D0%B8), принцип распространения очень похож на то, как мы обучали один нейрон - это градиентный спуск, но по \"всей нейросети\" сразу. "
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"collapsed": true,
"id": "oK7Vi4bxYJdZ"
},
"source": [
"Backpropagation работает корректно благодаря ***chain rule*** (=правилу взятия производной сложной функции): \n",
"\n",
"Если $f(x) = f(g(x))$, то: \n",
"\n",
"$$\\frac{\\partial{f}}{\\partial{x}} = \\frac{\\partial{f}}{\\partial{g}} \\frac{\\partial{g}}{\\partial{x}}$$"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"collapsed": true,
"id": "WKvMsaEBYJda"
},
"source": [
"Более подробно про backpropagation можно прочитать здесь (на английском): https://mattmazur.com/2015/03/17/a-step-by-step-backpropagation-example/"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "z5dyjPVNYJdc"
},
"source": [
"