{ "nbformat": 4, "nbformat_minor": 0, "metadata": { "colab": { "name": "faq_bot.ipynb", "provenance": [], "collapsed_sections": [] }, "kernelspec": { "name": "python3", "display_name": "Python 3" }, "language_info": { "name": "python" } }, "cells": [ { "cell_type": "code", "metadata": { "id": "30RU81xQGAzV" }, "source": [ "!pip install transformers razdel pymorphy2" ], "execution_count": null, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "fwREg80dEkFW" }, "source": [ "import requests\n", "from bs4 import BeautifulSoup\n", "import pandas as pd" ], "execution_count": null, "outputs": [] }, { "cell_type": "code", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "jV57QpSREqIs", "outputId": "9e922b6d-ab3e-4c74-d015-63941a468e46" }, "source": [ "soup = BeautifulSoup(requests.get('https://ma.hse.ru/faq').text)\n", "questions = []\n", "answers = []\n", "for div in soup.findAll('div', {'class': 'faq'}):\n", " questions.append(div.find('div', {'class': 'faq__question'}).text.strip())\n", " answers.append(div.find('div', {'class': 'faq__answer'}).text.strip())\n", "print(len(questions))\n", "data = pd.DataFrame({'q': questions, 'a': answers})" ], "execution_count": null, "outputs": [ { "output_type": "stream", "text": [ "48\n" ], "name": "stdout" } ] }, { "cell_type": "code", "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 220 }, "id": "7eeohcvFFamZ", "outputId": "f34bf598-cb15-4783-d192-1f26ebadbe55" }, "source": [ "pd.options.display.max_colwidth = 300\n", "data.sample(3)" ], "execution_count": null, "outputs": [ { "output_type": "execute_result", "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
qa
43Что делать, если я не могу пройти вступительные испытания в назначенный день? Есть ли запасной день?Резервный день есть. Но учтите, что сдавать в резервный день можно только в том случае, если причина пропуска экзамена уважительная. Уважительной причиной считаются, например, документально подтверждённые болезнь, военные сборы или командировка на работе. А вот, например, сдача экзамена в другом...
22Не получается зарегистрироваться в личном кабинете: не приходят логин и пароль. Куда обращаться?В случае возникновения проблем с регистрацией в личном кабинете абитуриента, необходимо сообщить о проблеме в службу технической поддержки pkadmin3@hse.ru.
4В какое время учатся магистранты? Можно ли совмещать учебу в магистратуре с работой?Вы можете ознакомиться с расписанием прошлых лет на странице вашей программы и уточнить у менеджера программы, чего ожидать от расписания и учебного плана в ближайшем году.
\n", "
" ], "text/plain": [ " q a\n", "43 Что делать, если я не могу пройти вступительные испытания в назначенный день? Есть ли запасной день? Резервный день есть. Но учтите, что сдавать в резервный день можно только в том случае, если причина пропуска экзамена уважительная. Уважительной причиной считаются, например, документально подтверждённые болезнь, военные сборы или командировка на работе. А вот, например, сдача экзамена в другом...\n", "22 Не получается зарегистрироваться в личном кабинете: не приходят логин и пароль. Куда обращаться? В случае возникновения проблем с регистрацией в личном кабинете абитуриента, необходимо сообщить о проблеме в службу технической поддержки pkadmin3@hse.ru.\n", "4 В какое время учатся магистранты? Можно ли совмещать учебу в магистратуре с работой? Вы можете ознакомиться с расписанием прошлых лет на странице вашей программы и уточнить у менеджера программы, чего ожидать от расписания и учебного плана в ближайшем году." ] }, "metadata": { "tags": [] }, "execution_count": 4 } ] }, { "cell_type": "markdown", "metadata": { "id": "OqF51xhoGGLX" }, "source": [ "Построим поиск похожих вопросов - в лоб. Обучим собственный векторизатор!" ] }, { "cell_type": "code", "metadata": { "id": "GhB5Amx7GHC5" }, "source": [ "from sklearn.pipeline import make_pipeline\n", "from sklearn.feature_extraction.text import TfidfVectorizer\n", "from sklearn.decomposition import TruncatedSVD\n", "from sklearn.neighbors import KDTree\n", "from sklearn.preprocessing import Normalizer\n", "from sklearn.base import BaseEstimator, TransformerMixin" ], "execution_count": null, "outputs": [] }, { "cell_type": "code", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "Do8slNK2GWyS", "outputId": "f093de1e-f704-4b4c-99ef-21e3ed2e3c0f" }, "source": [ "vectorizer = make_pipeline(TfidfVectorizer(), TruncatedSVD(n_components=100), Normalizer())\n", "vectorizer.fit(pd.concat([data.q, data.a]))\n", "vectors = vectorizer.transform(data.q)\n", "print(vectors.shape)\n", "index = KDTree(vectors)" ], "execution_count": null, "outputs": [ { "output_type": "stream", "text": [ "(48, 96)\n" ], "name": "stdout" } ] }, { "cell_type": "code", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "k-QOiDjyG7ja", "outputId": "833bb4df-e9d5-4109-9039-1191aa5a021c" }, "source": [ "distances, indices = index.query(vectorizer.transform(['Где находится приёмная комиссия?']), k=3)\n", "for i, d in zip(indices[0], distances[0]):\n", " print(i, d, data.q[i])" ], "execution_count": null, "outputs": [ { "output_type": "stream", "text": [ "26 1.4142135623730945 Нужно ли предоставлять в приёмную комиссию с пакетом документов приписное свидетельство или военный билет?\n", "5 1.4142135623730945 Существуют ли в магистратуре Вышки какие-либо льготы для инвалидов?\n", "6 1.4142135623730947 Есть ли заочная форма обучения?\n" ], "name": "stdout" } ] }, { "cell_type": "markdown", "metadata": { "id": "dcriMjrAH7zb" }, "source": [ "Лемматизируем!" ] }, { "cell_type": "code", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "eZQYq2dcILn7", "outputId": "72702eb7-371c-4b00-c419-7e92f0ebeb74" }, "source": [ "from pymorphy2 import MorphAnalyzer\n", "from razdel import tokenize\n", "\n", "morph = MorphAnalyzer()\n", "\n", "def word2lemma(word):\n", " word = word.lower()\n", " hyps = morph.parse(word)\n", " if not hyps:\n", " return word\n", " nf = hyps[0].normal_form\n", " return nf or word\n", "\n", "print(word2lemma('бегал'))\n", "print(word2lemma('>>>'))" ], "execution_count": null, "outputs": [ { "output_type": "stream", "text": [ "бегать\n", ">>>\n" ], "name": "stdout" } ] }, { "cell_type": "code", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "GXcMGD-HJBKF", "outputId": "ad331d35-6cd8-4aba-e3cf-375f6a54d305" }, "source": [ "class Lemmer(BaseEstimator, TransformerMixin):\n", " def fit(self, X, y=None):\n", " return self\n", " def transform(self, X):\n", " return [' '.join(word2lemma(t.text) for t in tokenize(text)) for text in X]\n", "\n", "Lemmer().transform(['Где находится приёмная комиссия?'])" ], "execution_count": null, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "['где находиться приёмная комиссия ?']" ] }, "metadata": { "tags": [] }, "execution_count": 9 } ] }, { "cell_type": "code", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "4lPlEOrwtIRT", "outputId": "b88f0eab-97c0-44ad-ecb3-3590af9be23e" }, "source": [ "Lemmer().transform(['какой адрес приёмной комиссии?'])" ], "execution_count": null, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "['какой адрес приёмная комиссия ?']" ] }, "metadata": { "tags": [] }, "execution_count": 10 } ] }, { "cell_type": "code", "metadata": { "id": "8OSZpN92JMHD" }, "source": [ "vectorizer = make_pipeline(Lemmer(), TfidfVectorizer(), TruncatedSVD(n_components=100), Normalizer())\n", "vectorizer.fit(pd.concat([data.q, data.a]))\n", "vectors = vectorizer.transform(data.q)\n", "index = KDTree(vectors)" ], "execution_count": null, "outputs": [] }, { "cell_type": "code", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "UB11-DfFJNjK", "outputId": "93aeaa48-0fc9-421f-cc88-afb88986cb19" }, "source": [ "distances, indices = index.query(vectorizer.transform(['Где находится приёмная комиссия?']), k=3)\n", "for i, d in zip(indices[0], distances[0]):\n", " print(i, d, data.q[i])" ], "execution_count": null, "outputs": [ { "output_type": "stream", "text": [ "16 0.44849638069797765 Какой адрес у Приёмной комиссии?\n", "26 1.247588864371713 Нужно ли предоставлять в приёмную комиссию с пакетом документов приписное свидетельство или военный билет?\n", "13 1.4142135623730943 Смогу ли я приступить к очным занятиям в Вышке, если у меня нет сертификата вакцинации от COVID-19?\n" ], "name": "stdout" } ] }, { "cell_type": "code", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "2hC2oLVFJWJg", "outputId": "f1017728-d01c-429a-fdd6-903f50297866" }, "source": [ "distances, indices = index.query(vectorizer.transform(['Где находится приёмная комиссия?']), k=3)\n", "for i, d in zip(indices[0], distances[0]):\n", " print(i, d, data.q[i])" ], "execution_count": null, "outputs": [ { "output_type": "stream", "text": [ "16 0.44849638069797765 Какой адрес у Приёмной комиссии?\n", "26 1.247588864371713 Нужно ли предоставлять в приёмную комиссию с пакетом документов приписное свидетельство или военный билет?\n", "13 1.4142135623730943 Смогу ли я приступить к очным занятиям в Вышке, если у меня нет сертификата вакцинации от COVID-19?\n" ], "name": "stdout" } ] }, { "cell_type": "code", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "9Y4M02Y2Jh97", "outputId": "7c0943c2-53e0-4027-9c75-951f6ad461f4" }, "source": [ "distances, indices = index.query(vectorizer.transform(['какие документы нужны при поступлении?']), k=3)\n", "for i, d in zip(indices[0], distances[0]):\n", " print(i, d, data.q[i])" ], "execution_count": null, "outputs": [ { "output_type": "stream", "text": [ "25 0.8542354881250303 Нужна ли при подаче документов справка формы 086/у?\n", "39 1.1644463287698148 Какие льготы при поступлении полагаются дипломантам Олимпиады студентов и выпускников «Высшая лига», победителям и призёрам Всероссийской олимпиады студентов «Я – профессионал»)?\n", "17 1.1829462455695894 В какое количество вузов одновременно можно подавать документы в магистратуру?\n" ], "name": "stdout" } ] }, { "cell_type": "markdown", "metadata": { "id": "0Ha13GJ0JSzL" }, "source": [ "# предобученные трансформеры" ] }, { "cell_type": "markdown", "metadata": { "id": "6clzrVoksZiV" }, "source": [ "`cointegrated/rubert-tiny` - маленькая, быстрая и глупая\n", "\n", "`DeepPavlov/rubert-base-cased-sentence` - средняя\n", "\n", "`sberbank-ai/sbert_large_nlu_ru` - большая, медленная и умная" ] }, { "cell_type": "code", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "cBumrmm1FBfB", "outputId": "bca77216-3ad0-4a50-a589-3387c13c488c" }, "source": [ "import torch\n", "from transformers import AutoTokenizer, AutoModel\n", "m = 'cointegrated/rubert-tiny'\n", "tokenizer = AutoTokenizer.from_pretrained(m)\n", "model = AutoModel.from_pretrained(m)" ], "execution_count": null, "outputs": [ { "output_type": "stream", "text": [ "Some weights of the model checkpoint at cointegrated/rubert-tiny were not used when initializing BertModel: ['cls.predictions.transform.dense.bias', 'cls.predictions.transform.LayerNorm.weight', 'cls.predictions.transform.dense.weight', 'cls.predictions.decoder.weight', 'cls.predictions.decoder.bias', 'cls.seq_relationship.weight', 'cls.predictions.bias', 'cls.predictions.transform.LayerNorm.bias', 'cls.seq_relationship.bias']\n", "- This IS expected if you are initializing BertModel from the checkpoint of a model trained on another task or with another architecture (e.g. initializing a BertForSequenceClassification model from a BertForPreTraining model).\n", "- This IS NOT expected if you are initializing BertModel from the checkpoint of a model that you expect to be exactly identical (initializing a BertForSequenceClassification model from a BertForSequenceClassification model).\n" ], "name": "stderr" } ] }, { "cell_type": "code", "metadata": { "id": "Q_eYlPgMJ_9n" }, "source": [ "import numpy as np\n", "\n", "def normalize(v):\n", " return v / sum(v**2)**0.5" ], "execution_count": null, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "5PAt1FqZFZpS" }, "source": [ "def mean_pooling(model_output, attention_mask):\n", " token_embeddings = model_output[0] #First element of model_output contains all token embeddings\n", " input_mask_expanded = attention_mask.unsqueeze(-1).expand(token_embeddings.size()).float()\n", " sum_embeddings = torch.sum(token_embeddings ** 2 * input_mask_expanded, 1)\n", " sum_mask = torch.clamp(input_mask_expanded.sum([1]), min=1e-9)\n", " sums = sum_embeddings / sum_mask\n", " return sums" ], "execution_count": null, "outputs": [] }, { "cell_type": "code", "metadata": { "id": "qZkCJK4sJ17E" }, "source": [ "def embed_rubert(text, mean=False):\n", " encoded_input = tokenizer(text, padding=True, truncation=True, max_length=128, return_tensors='pt')\n", " with torch.no_grad():\n", " model_output = model(**encoded_input)\n", " if mean:\n", " sentence_embeddings = mean_pooling(model_output, encoded_input['attention_mask'])\n", " else:\n", " sentence_embeddings = model_output[0][:, 0]\n", " sentence_embeddings = torch.nn.functional.normalize(sentence_embeddings)\n", " return sentence_embeddings[0].numpy()" ], "execution_count": null, "outputs": [] }, { "cell_type": "code", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "OVjyALQrq02y", "outputId": "44ca5eb2-03b0-4a75-82ba-c1e1cfeaf252" }, "source": [ "sum(embed_rubert('hello')**2), sum(embed_rubert('hello') * embed_rubert('здравствуйте')), sum(embed_rubert('hello') * embed_rubert('самолёт'))" ], "execution_count": null, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "(1.0000004561918558, 0.9066641638086708, 0.8826527225089436)" ] }, "metadata": { "tags": [] }, "execution_count": 73 } ] }, { "cell_type": "code", "metadata": { "id": "iLagioZ7J3Dr" }, "source": [ "vectors = np.stack([embed_rubert(t) for t in data.q])\n", "index = KDTree(vectors)" ], "execution_count": null, "outputs": [] }, { "cell_type": "code", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "G_lq9ha_KB8N", "outputId": "e58748a8-22d4-4eb4-97a5-c3f043d4f180" }, "source": [ "distances, indices = index.query(embed_rubert(['какие документы нужны при поступлении?'])[np.newaxis, :], k=3)\n", "for i, d in zip(indices[0], distances[0]):\n", " print(i, d, data.q[i])" ], "execution_count": null, "outputs": [ { "output_type": "stream", "text": [ "15 0.5027486633432314 Какие документы требуются для того, чтобы поступить в магистратуру ВШЭ?\n", "19 0.5593343985471899 Нужно ли нотариально заверять копии документов?\n", "41 0.5865392463804587 Из чего должно состоять портфолио? Как оно оценивается?\n" ], "name": "stdout" } ] }, { "cell_type": "code", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "mu1Eg68kKYkX", "outputId": "88153bae-0605-4b0b-dde3-d83a6f9c6ef8" }, "source": [ "distances, indices = index.query(embed_rubert(['где находится приемная комиссия'])[np.newaxis, :], k=3)\n", "for i, d in zip(indices[0], distances[0]):\n", " print(i, d, data.q[i])" ], "execution_count": null, "outputs": [ { "output_type": "stream", "text": [ "16 0.7612745905852856 Какой адрес у Приёмной комиссии?\n", "1 0.8167795118745597 Есть ли в магистратуре ВШЭ бюджетные места?\n", "32 0.8311538579698093 Каков проходной балл?\n" ], "name": "stdout" } ] }, { "cell_type": "code", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "2jp7a9zHKj4f", "outputId": "03da65b7-d934-4aca-81e9-2fc5da6f97eb" }, "source": [ "distances, indices = index.query(embed_rubert(['где находится вступительная коллегия'])[np.newaxis, :], k=3)\n", "for i, d in zip(indices[0], distances[0]):\n", " print(i, d, data.q[i])" ], "execution_count": null, "outputs": [ { "output_type": "stream", "text": [ "16 0.8380855011738012 Какой адрес у Приёмной комиссии?\n", "1 0.8381529445550144 Есть ли в магистратуре ВШЭ бюджетные места?\n", "32 0.8431039755180321 Каков проходной балл?\n" ], "name": "stdout" } ] }, { "cell_type": "code", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "cwQpZUyMrnF5", "outputId": "fe7d8245-63f2-4d45-e2e9-faa49d451a1e" }, "source": [ "distances, indices = index.query(embed_rubert(['как пройти в библиотеку'])[np.newaxis, :], k=3)\n", "for i, d in zip(indices[0], distances[0]):\n", " print(i, d, data.q[i])" ], "execution_count": null, "outputs": [ { "output_type": "stream", "text": [ "0 0.7770798351769754 С чего начать поступление в Вышку?\n", "30 0.8257408236756344 Как можно получить образовательный кредит на обучение?\n", "32 0.8310525824918598 Каков проходной балл?\n" ], "name": "stdout" } ] }, { "cell_type": "code", "metadata": { "id": "5zo7ycSUKmIg" }, "source": [ "def respond(text):\n", " distances, indices = index.query(embed_rubert([text])[np.newaxis, :], k=3)\n", " if distances[0][0] > 0.77:\n", " return f'Кажется, у меня нет ответа на ваш вопрос. Может быть, вы хотите знать, \"{data.q[np.random.choice(indices[0])]}\"?'\n", " else:\n", " return data.a[indices[0][0]]" ], "execution_count": null, "outputs": [] }, { "cell_type": "code", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "tMXNmCqeLCkL", "outputId": "69e723a9-5f15-436b-acac-0bb0e50c4407" }, "source": [ "print(respond('как проехать до приемной комиссии'))" ], "execution_count": null, "outputs": [ { "output_type": "stream", "text": [ "НИУ ВШЭ: 101000, г. Москва, улица Мясницкая, дом 20 \n", "НИУ ВШЭ — Санкт-Петербург: 194100, г. Санкт-Петербург, ул. Кантемировская, д.3, к.1, лит А. \n", "НИУ ВШЭ — Нижний Новгород: 603155, г. Нижний Новгород, ул. Б. Печерская, д.25/12 \n", "НИУ ВШЭ — Пермь: 614070, г. Пермь, ул. Студенческая, д.38\n", "При подаче документов почтой не забудьте уведомить Приёмную комиссию об отправлении, отправив письмо с темой «ДОКУМЕНТЫ ПОЧТОЙ, МАГИСТРАТУРА» на электронный адрес pochta@hse.ru.\n" ], "name": "stdout" } ] }, { "cell_type": "code", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "dXiSzc09LFJs", "outputId": "cef0459f-9c4b-443d-b5f3-d97b173b4aa1" }, "source": [ "print(respond('как пройти в библиотеку'))" ], "execution_count": null, "outputs": [ { "output_type": "stream", "text": [ "Кажется, у меня нет ответа на ваш вопрос. Может быт, вы хотите знать, \"С чего начать поступление в Вышку?\"?\n" ], "name": "stdout" } ] }, { "cell_type": "code", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "vVMuYkKrLOoX", "outputId": "99fa94f4-2d32-4991-d696-ec876624cdc2" }, "source": [ "print(respond('С чего начать поступление в Вышку?'))" ], "execution_count": null, "outputs": [ { "output_type": "stream", "text": [ "С выбора магистерской программы и осознания того, какие именно вступительные испытания нужны для поступления на конкретно эти программы. Перечень программ вы можете найти здесь, список вступительных испытаний — здесь.\n" ], "name": "stdout" } ] }, { "cell_type": "code", "metadata": { "id": "-aY2oHr3sHeK" }, "source": [ "" ], "execution_count": null, "outputs": [] } ] }