{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "provenance": []
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "source": [
        "**Теория**\n",
        "\n",
        "GPT — Generative Pre-Trained Transformer - семейство порождающих языковых нейросетей, которые заранее обучают на больших массивах текстов, а затем они продолжают предложенный текст, предсказывая следующий фрагмент по уже прочитанному контексту.\n",
        "\n",
        "В 2017 году вышла статья *Attention Is All You Need* c предложением архитектуры трансформера, в которой центральную роль играет механизм внимания.\n",
        "\n",
        "До трансформеров нейросети хуже работали с далёкими связями в длинных предложениях, потому что им было трудно удерживать контекст на больших расстояниях.\n",
        "\n",
        "Трансформер  изменил ситуацию: вместо идеи “идти по тексту как по узкому коридору” появилась идея “сразу смотреть на все и понимать, кто сейчас важнее”.\n",
        "\n",
        "После этого началась быстрая эволюция моделей:\n",
        "- BERT сделал ставку на глубокое понимание текста в обе стороны;\n",
        "- GPT-1 и GPT-2 — на порождение текста слева направо;\n",
        "- InstructGPT и ChatGPT 2022 г. стали версиями семейства GPT, дополнительно настроенными на выполнение инструкций и удобный диалог с человеком.\n",
        "\n",
        "Нейросеть не получает на вход “слова как слова” — она получает числа.\n",
        "\n",
        "То, что человек видит как фразу, для модели сначала превращается в цепочку небольших фрагментов текста, затем в номера элементов словаря, а потом в векторы — наборы чисел, с которыми уже можно выполнять вычисления.\n",
        "\n",
        "Пусть на вход пришла фраза:\n",
        "*\"Мама мыла раму.\"*\n",
        "\n",
        "Тогда путь такой:\n",
        "- Текст разбивается на токены.\n",
        "- Каждый из них заменяется номером из словаря.\n",
        "- Каждый номер заменяется вектором признаков.\n",
        "- К этому вектору добавляется информация о позиции фрагмента в предложении, чтобы модель понимала порядок слов.\n",
        "\n",
        "Это можно представить нижеследующим:\n",
        "\n",
        "*\"Мама мыла раму.\"*\n",
        "\n",
        "      \n",
        "Фрагменты\n",
        "*[\"Ма\", \"ма\", \" мы\", \"ла\", \" ра\", \"му\", \".\"]*\n",
        "      \n",
        "Номера\n",
        "[15, 38, 91, 44, 73, 12, 5]\n",
        "      \n",
        "Векторы\n",
        "\n",
        "15 → [0.12, -0.44, 0.03, ...]\n",
        "\n",
        "38 → [0.91,  0.07, -0.52, ...]\n",
        "...\n",
        "      \n",
        "\n",
        "Снаружи это всё ещё фраза, а внутри — уже большая числовая таблица вида “длина последовательности × размер скрытого представления”."
      ],
      "metadata": {
        "id": "GI-DT5ln_N2g"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "Дальше идет ядро архитектуры GPT.\n",
        "\n",
        "Последовательность векторов проходит через много одинаковых блоков, и в каждом блоке сначала работает механизм внимания, а затем полносвязный многослойный блок.\n",
        "\n",
        "Именно это повторение делает модель глубокой: каждый новый слой чуть лучше понимает, кто в тексте с кем связан, что к чему относится и какой смысл надо усилить.\n",
        "\n",
        "Если идти строго по шагам, то маршрут данных такой:\n",
        "- Разбиение текста на токены.\n",
        "- Замена их на номера.\n",
        "- Замена номеров на векторы.\n",
        "- Добавление позиционной информации.\n",
        "- Первый блок: внимание, затем полносвязная обработка.\n",
        "- Второй блок: внимание, затем полносвязная обработка.\n",
        "- Так повторяется много раз.\n",
        "- Заключительный слой переводит внутренние представления в вероятности следующего фрагмента текста.\n",
        "\n",
        "\n",
        "В GPT действует важное ограничение: текущая позиция не может “подсматривать” будущие токены, поэтому при порождении ответа модель смотрит только на себя и на прошлые позиции.[8]\n",
        "Из-за этого генерация текста идёт слева направо: модель делает шаг, дописывает фрагмент, потом снова считает вероятности и делает следующий шаг."
      ],
      "metadata": {
        "id": "cNUMP8MvAdiM"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "**Механизм внимания на примере**\n",
        "\n",
        "Механизм внимания нужен для того, чтобы текущий фрагмент текста мог решить, какие прошлые фрагменты важны сильнее, а какие слабее.\n",
        "\n",
        "Внутри для каждой позиции строятся три служебных представления: запрос, ключ и значение; по сходству запросов и ключей вычисляются веса важности, а затем из значений собирается новое представление токена.\n",
        "\n",
        "Возьмём предложение:\n",
        "*\"Студент открыл учебник, потому что он готовился к экзамену.\"*\n",
        "\n",
        "Когда модель доходит до слова “он”, ей надо понять, к кому относится местоимение.\n",
        "\n",
        "Механизм внимания позволяет дать больший вес слову “студент”, чем слову “учебник”, если по контексту именно студент выглядит тем, кто мог готовиться.\n",
        "\n",
        "Условно это можно изобразить так:\n",
        "Для слова \"он\":\n",
        "\n",
        "\"Студент\"   → 0.62\n",
        "\"открыл\"    → 0.14\n",
        "\"учебник\"   → 0.08\n",
        "\"потому\"    → 0.05\n",
        "\"что\"       → 0.03\n",
        "прочее      → 0.08\n",
        "\n",
        "То есть слово “он” как будто собирает смысл из прошлого текста, словно студент перед экзаменом собирает конспект из разных лекций, но сильнее опирается на самые нужные страницы.\n",
        "\n",
        "В этом и сила внимания: значение токена не фиксировано раз и навсегда, а пересобирается в зависимости от окружения.\n",
        "\n",
        "Это похоже на прожектор на сцене: свет не падает на всех одинаково, а выделяет именно тех актёров, которые сейчас важны для сцены.\n",
        "\n",
        "**Практика**"
      ],
      "metadata": {
        "id": "Kc_zRor3A-wY"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Разбиение текста и числовые индексы\n",
        "\n",
        "text = \"мама мыла раму\"\n",
        "\n",
        "chars = sorted(set(text))\n",
        "char_to_id = {ch: i for i, ch in enumerate(chars)}\n",
        "id_to_char = {i: ch for ch, i in char_to_id.items()}\n",
        "\n",
        "def encode(s):\n",
        "    return [char_to_id[ch] for ch in s]\n",
        "\n",
        "def decode(ids):\n",
        "    return \"\".join(id_to_char[i] for i in ids)\n",
        "\n",
        "encoded = encode(text)\n",
        "decoded = decode(encoded)\n",
        "\n",
        "print(\"Словарь:\", char_to_id)\n",
        "print(\"Номера:\", encoded)\n",
        "print(\"Обратно в текст:\", decoded)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "2vzBwx6aBT36",
        "outputId": "e660392d-a633-44cb-c2dd-0e1e471062b5"
      },
      "execution_count": 1,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Словарь: {' ': 0, 'а': 1, 'л': 2, 'м': 3, 'р': 4, 'у': 5, 'ы': 6}\n",
            "Номера: [3, 1, 3, 1, 0, 3, 6, 2, 1, 0, 4, 1, 3, 5]\n",
            "Обратно в текст: мама мыла раму\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Векторное представление и позиция\n",
        "import torch\n",
        "import torch.nn as nn\n",
        "\n",
        "text = \"мама мыла раму\"\n",
        "chars = sorted(set(text))\n",
        "char_to_id = {ch: i for i, ch in enumerate(chars)}\n",
        "\n",
        "ids = torch.tensor([char_to_id[ch] for ch in text], dtype=torch.long)\n",
        "T = len(ids)\n",
        "\n",
        "vocab_size = len(chars)\n",
        "hidden_size = 8\n",
        "\n",
        "token_embed = nn.Embedding(vocab_size, hidden_size)\n",
        "pos_embed = nn.Embedding(T, hidden_size)\n",
        "\n",
        "positions = torch.arange(T)\n",
        "\n",
        "x_token = token_embed(ids)\n",
        "x_pos = pos_embed(positions)\n",
        "\n",
        "x = x_token + x_pos\n",
        "\n",
        "print(\"Векторы фрагментов:\", x_token.shape)\n",
        "print(\"Векторы позиций:\", x_pos.shape)\n",
        "print(\"Общий вход:\", x.shape)\n",
        "\n",
        "#Токен превращается не в одно число, а в целый вектор признаков.\n",
        "#Это как заменить фамилию студента на толстую личную карточку\n",
        "#где сразу записаны десятки характеристик."
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "mt5U-48sBcy3",
        "outputId": "f2fdb7a4-b0d5-43b8-c044-0e5b733b3f9f"
      },
      "execution_count": 2,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Векторы фрагментов: torch.Size([14, 8])\n",
            "Векторы позиций: torch.Size([14, 8])\n",
            "Общий вход: torch.Size([14, 8])\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "# Простейший механизм внимания\n",
        "import torch\n",
        "import torch.nn as nn\n",
        "import torch.nn.functional as F\n",
        "\n",
        "torch.manual_seed(42)\n",
        "\n",
        "B, T, C = 1, 5, 8\n",
        "x = torch.randn(B, T, C)\n",
        "\n",
        "head_size = 4\n",
        "to_key = nn.Linear(C, head_size, bias=False)\n",
        "to_query = nn.Linear(C, head_size, bias=False)\n",
        "to_value = nn.Linear(C, head_size, bias=False)\n",
        "\n",
        "K = to_key(x)\n",
        "Q = to_query(x)\n",
        "V = to_value(x)\n",
        "\n",
        "scores = Q @ K.transpose(-2, -1)\n",
        "\n",
        "mask = torch.tril(torch.ones(T, T))\n",
        "scores = scores.masked_fill(mask == 0, float(\"-inf\"))\n",
        "\n",
        "weights = F.softmax(scores, dim=-1)\n",
        "out = weights @ V\n",
        "\n",
        "print(\"Матрица весов внимания:\")\n",
        "print(weights[0])\n",
        "print(\"Выход:\")\n",
        "print(out.shape)\n"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "-7qz08RhBj1H",
        "outputId": "89673fed-e64d-4423-a6cc-440e86980e94"
      },
      "execution_count": 4,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Матрица весов внимания:\n",
            "tensor([[1.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00, 0.0000e+00],\n",
            "        [4.8207e-01, 5.1793e-01, 0.0000e+00, 0.0000e+00, 0.0000e+00],\n",
            "        [3.1139e-02, 1.7645e-01, 7.9241e-01, 0.0000e+00, 0.0000e+00],\n",
            "        [5.1855e-01, 1.0627e-01, 7.9233e-03, 3.6726e-01, 0.0000e+00],\n",
            "        [2.0455e-02, 7.9909e-03, 3.3030e-04, 5.5118e-02, 9.1611e-01]],\n",
            "       grad_fn=<SelectBackward0>)\n",
            "Выход:\n",
            "torch.Size([1, 5, 4])\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "Этот фрагмент показывает главное: каждая позиция вычисляет, на что ей смотреть сильнее.\n",
        "Если представить текст как аудиторию, то внимание — это не общий свет в комнате, а подвижный луч фонаря, который в каждый момент высвечивает нужные места."
      ],
      "metadata": {
        "id": "pJIawUkfBoJc"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "# Один блок GPT\n",
        "import torch\n",
        "import torch.nn as nn\n",
        "\n",
        "class SimpleBlock(nn.Module):\n",
        "    def __init__(self, hidden_size=16, num_heads=4):\n",
        "        super().__init__()\n",
        "        self.attn = nn.MultiheadAttention(\n",
        "            embed_dim=hidden_size,\n",
        "            num_heads=num_heads,\n",
        "            batch_first=True\n",
        "        )\n",
        "        self.norm1 = nn.LayerNorm(hidden_size)\n",
        "\n",
        "        self.ff = nn.Sequential(\n",
        "            nn.Linear(hidden_size, hidden_size * 4),\n",
        "            nn.ReLU(),\n",
        "            nn.Linear(hidden_size * 4, hidden_size)\n",
        "        )\n",
        "        self.norm2 = nn.LayerNorm(hidden_size)\n",
        "\n",
        "    def forward(self, x, attn_mask=None):\n",
        "        attn_out, _ = self.attn(x, x, x, attn_mask=attn_mask)\n",
        "        x = self.norm1(x + attn_out)\n",
        "\n",
        "        ff_out = self.ff(x)\n",
        "        x = self.norm2(x + ff_out)\n",
        "        return x\n",
        "\n",
        "B, T, C = 2, 6, 16\n",
        "x = torch.randn(B, T, C)\n",
        "\n",
        "mask = torch.triu(torch.ones(T, T) * float(\"-inf\"), diagonal=1)\n",
        "\n",
        "block = SimpleBlock(hidden_size=C, num_heads=4)\n",
        "y = block(x, attn_mask=mask)\n",
        "\n",
        "print(\"Размер выхода:\", y.shape)"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "kxJ7QGiVBqmy",
        "outputId": "7f1b0142-bd42-483c-f5df-c2409f4c996e"
      },
      "execution_count": 5,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Размер выхода: torch.Size([2, 6, 16])\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "Это уже  “кирпич” настоящей модели: внимание помогает учесть контекст, а полносвязный блок дорабатывает каждый вектор.\n",
        "Много таких блоков подряд — и перед нами уже не игрушка, а  языковая модель.\n",
        "\n",
        "ЗАПОМНИТЕ\n",
        "Последовательность преобразования такая:\n",
        "- текст;\n",
        "- разбиение на фрагменты;\n",
        "- числовые индексы;\n",
        "- векторы;\n",
        "- позиционные признаки;\n",
        "- повторяющиеся блоки “внимание + полносвязная обработка”;\n",
        "- вероятности следующего фрагмента;\n",
        "- порождение ответа.\n",
        "\n",
        "** GPT —  не “электронный мозг, который сразу понимает смысл”, а многоэтажная фабрика, где на каждом этаже текст по-новому сортируют, подсвечивают, сравнивают и уточняют, пока из сырья не получится осмысленное продолжение **"
      ],
      "metadata": {
        "id": "KN2uyfHsBvz_"
      }
    }
  ]
}