{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# TF-IDF" ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import pandas as pd\n", "import re\n", "import string\n", "from collections import defaultdict\n", "from sklearn import metrics\n", "from time import time\n", "from nltk.corpus import stopwords\n", "from nltk.stem import WordNetLemmatizer\n", "from nltk.tokenize import RegexpTokenizer\n", "from sklearn.feature_extraction.text import TfidfVectorizer\n", "from sklearn.cluster import KMeans\n", "from sklearn.datasets import fetch_20newsgroups\n", "from sklearn.decomposition import TruncatedSVD\n", "from sklearn.pipeline import make_pipeline\n", "from sklearn.preprocessing import Normalizer\n", "import pymorphy2\n", "from sklearn.model_selection import train_test_split\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.metrics import classification_report, accuracy_score, f1_score" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Загрузка данных" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "df = pd.read_json('data/healthcare_facilities_reviews.jsonl', lines=True)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "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", " \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", " \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", " \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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
review_idcategorytitlecontentsentimentsource_url
00Поликлиники стоматологическиеКлассный мастерОгромное спасибо за чудесное удаление двух зуб...positivehttp://www.spr.ru/forum_vyvod.php?id_tema=2727539
11Поликлиники стоматологическиеЗамечательный врачХочу выразить особую благодарность замечательн...positivehttp://www.spr.ru/forum_vyvod.php?id_tema=2302877
22Поликлиники стоматологическиеБлагодарность работникам рентгенаДобрый вечер! Хотелось бы поблагодарить сотруд...positivehttp://www.spr.ru/forum_vyvod.php?id_tema=2815031
33Поликлиники стоматологическиеДоктор РабиновичЖенщины советского образца в регистратуре не и...negativehttp://www.spr.ru/forum_vyvod.php?id_tema=3443161
44Поликлиники стоматологическиеЕсть кому сказать спасибоУ меня с детства очень плохие зубы (тонкая и х...positivehttp://www.spr.ru/forum_vyvod.php?id_tema=2592430
.....................
7059270592Водительские комиссииХуже районной поликлиникиЗаведение ужасное. Врачи делят 1 кабинет на 2х...negativehttp://www.spr.ru/forum_vyvod.php?id_tema=273326
7059370593Водительские комиссииСправкиЛюди, не обращайтесь в эту фирму! Муж проходил...negativehttp://www.spr.ru/forum_vyvod.php?id_tema=3401583
7059470594Водительские комиссииМед-Альфа - это наше будущееДорогие посетители медицинского центра ООО \"Ме...positivehttp://www.spr.ru/forum_vyvod.php?id_tema=326078
7059570595Водительские комиссииХамское поведениеВ регистратуре сидит хамка, такое отношение и ...negativehttp://www.spr.ru/forum_vyvod.php?id_tema=3171911
7059670596Водительские комиссииТолько хорошие впечатленияХочу поблагодарить весь персонал \"МедАльфаПроф...positivehttp://www.spr.ru/forum_vyvod.php?id_tema=3391562
\n", "

70597 rows × 6 columns

\n", "
" ], "text/plain": [ " review_id category \\\n", "0 0 Поликлиники стоматологические \n", "1 1 Поликлиники стоматологические \n", "2 2 Поликлиники стоматологические \n", "3 3 Поликлиники стоматологические \n", "4 4 Поликлиники стоматологические \n", "... ... ... \n", "70592 70592 Водительские комиссии \n", "70593 70593 Водительские комиссии \n", "70594 70594 Водительские комиссии \n", "70595 70595 Водительские комиссии \n", "70596 70596 Водительские комиссии \n", "\n", " title \\\n", "0 Классный мастер \n", "1 Замечательный врач \n", "2 Благодарность работникам рентгена \n", "3 Доктор Рабинович \n", "4 Есть кому сказать спасибо \n", "... ... \n", "70592 Хуже районной поликлиники \n", "70593 Справки \n", "70594 Мед-Альфа - это наше будущее \n", "70595 Хамское поведение \n", "70596 Только хорошие впечатления \n", "\n", " content sentiment \\\n", "0 Огромное спасибо за чудесное удаление двух зуб... positive \n", "1 Хочу выразить особую благодарность замечательн... positive \n", "2 Добрый вечер! Хотелось бы поблагодарить сотруд... positive \n", "3 Женщины советского образца в регистратуре не и... negative \n", "4 У меня с детства очень плохие зубы (тонкая и х... positive \n", "... ... ... \n", "70592 Заведение ужасное. Врачи делят 1 кабинет на 2х... negative \n", "70593 Люди, не обращайтесь в эту фирму! Муж проходил... negative \n", "70594 Дорогие посетители медицинского центра ООО \"Ме... positive \n", "70595 В регистратуре сидит хамка, такое отношение и ... negative \n", "70596 Хочу поблагодарить весь персонал \"МедАльфаПроф... positive \n", "\n", " source_url \n", "0 http://www.spr.ru/forum_vyvod.php?id_tema=2727539 \n", "1 http://www.spr.ru/forum_vyvod.php?id_tema=2302877 \n", "2 http://www.spr.ru/forum_vyvod.php?id_tema=2815031 \n", "3 http://www.spr.ru/forum_vyvod.php?id_tema=3443161 \n", "4 http://www.spr.ru/forum_vyvod.php?id_tema=2592430 \n", "... ... \n", "70592 http://www.spr.ru/forum_vyvod.php?id_tema=273326 \n", "70593 http://www.spr.ru/forum_vyvod.php?id_tema=3401583 \n", "70594 http://www.spr.ru/forum_vyvod.php?id_tema=326078 \n", "70595 http://www.spr.ru/forum_vyvod.php?id_tema=3171911 \n", "70596 http://www.spr.ru/forum_vyvod.php?id_tema=3391562 \n", "\n", "[70597 rows x 6 columns]" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "df = df[['sentiment', 'content']]" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "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", " \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", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
sentimentcontent
0positiveОгромное спасибо за чудесное удаление двух зуб...
1positiveХочу выразить особую благодарность замечательн...
2positiveДобрый вечер! Хотелось бы поблагодарить сотруд...
3negativeЖенщины советского образца в регистратуре не и...
4positiveУ меня с детства очень плохие зубы (тонкая и х...
.........
70592negativeЗаведение ужасное. Врачи делят 1 кабинет на 2х...
70593negativeЛюди, не обращайтесь в эту фирму! Муж проходил...
70594positiveДорогие посетители медицинского центра ООО \"Ме...
70595negativeВ регистратуре сидит хамка, такое отношение и ...
70596positiveХочу поблагодарить весь персонал \"МедАльфаПроф...
\n", "

70597 rows × 2 columns

\n", "
" ], "text/plain": [ " sentiment content\n", "0 positive Огромное спасибо за чудесное удаление двух зуб...\n", "1 positive Хочу выразить особую благодарность замечательн...\n", "2 positive Добрый вечер! Хотелось бы поблагодарить сотруд...\n", "3 negative Женщины советского образца в регистратуре не и...\n", "4 positive У меня с детства очень плохие зубы (тонкая и х...\n", "... ... ...\n", "70592 negative Заведение ужасное. Врачи делят 1 кабинет на 2х...\n", "70593 negative Люди, не обращайтесь в эту фирму! Муж проходил...\n", "70594 positive Дорогие посетители медицинского центра ООО \"Ме...\n", "70595 negative В регистратуре сидит хамка, такое отношение и ...\n", "70596 positive Хочу поблагодарить весь персонал \"МедАльфаПроф...\n", "\n", "[70597 rows x 2 columns]" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Добрый вечер! Хотелось бы поблагодарить сотрудников рентгена! Протезируюсь, отношусь к поликлинике № 189. Там меня отфутболили! Подходила к Кочину, зам. гл. врачу, заведующей просто сделать 3 снимка (пол-ка рядом с домом)- мне грубо отказали! А сотрудник рентгена просто сидела кроссворд разгадывала! Они видите ли, не принимают с протезирования! Сказали, где протезируетесь, там и делайте, а я говорю, мне у Вас удобно. Побоялись они! Первый раз попала к молодой девушке, она меня выслушала и сделала 1 снимок, а потом записала на другие дни, мне это удобно. Конечно, народу полно было! Бедные сотрудники. Все, кто читает отзыв (особенно жители Люблино 189 пол-ки), давайте жаловаться в департамент! Спасибо еще раз, за рентген (слышала в очереди, что народу у Вас было много и вы уже перебрали с нормой). Спасибо.'" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df['content'][2]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Очистка текста" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [], "source": [ "morph = pymorphy2.MorphAnalyzer()\n", "russian_stopwords = stopwords.words(\"russian\")" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/tmp/ipykernel_75887/650983554.py:12: SettingWithCopyWarning: \n", "A value is trying to be set on a copy of a slice from a DataFrame.\n", "Try using .loc[row_indexer,col_indexer] = value instead\n", "\n", "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n", " df['cleaned_text'] = df['content'].apply(clean_text)\n" ] } ], "source": [ "def clean_text(text):\n", " # Удаление всего, что не является буквами или знаками препинания\n", " clean_pattern = re.compile(r'[^a-zA-Zа-яА-ЯёЁ0-9.,!?;:\\s]')\n", " text = clean_pattern.sub('', text)\n", " url_pattern = re.compile(r'http\\S+|www\\S+|https\\S+')\n", " text = url_pattern.sub(r'', text)\n", " text = text.translate(str.maketrans('', '', string.punctuation))\n", " text = text.lower()\n", " lemmatized_text = ' '.join([morph.parse(word)[0].normal_form for word in text.split() if word not in russian_stopwords])\n", " return lemmatized_text\n", "\n", "df['cleaned_text'] = df['content'].apply(clean_text)" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "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", " \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", " \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", "
sentimentcontentcleaned_text
0positiveОгромное спасибо за чудесное удаление двух зуб...огромный спасибо чудесный удаление два зуб муд...
1positiveХочу выразить особую благодарность замечательн...хотеть выразить особый благодарность замечател...
2positiveДобрый вечер! Хотелось бы поблагодарить сотруд...добрый вечер хотеться поблагодарить сотрудник ...
3negativeЖенщины советского образца в регистратуре не и...женщина советский образец регистратура иметь п...
4positiveУ меня с детства очень плохие зубы (тонкая и х...детство очень плохой зуб тонкий хрупкий эмаль ...
............
70592negativeЗаведение ужасное. Врачи делят 1 кабинет на 2х...заведение ужасный врач делить 1 кабинет 2х спе...
70593negativeЛюди, не обращайтесь в эту фирму! Муж проходил...человек обращаться фирма муж проходить анализ ...
70594positiveДорогие посетители медицинского центра ООО \"Ме...дорогой посетитель медицинский центр ооо медал...
70595negativeВ регистратуре сидит хамка, такое отношение и ...регистратура сидеть хамка такой отношение мане...
70596positiveХочу поблагодарить весь персонал \"МедАльфаПроф...хотеть поблагодарить весь персонал медальфапро...
\n", "

70597 rows × 3 columns

\n", "
" ], "text/plain": [ " sentiment content \\\n", "0 positive Огромное спасибо за чудесное удаление двух зуб... \n", "1 positive Хочу выразить особую благодарность замечательн... \n", "2 positive Добрый вечер! Хотелось бы поблагодарить сотруд... \n", "3 negative Женщины советского образца в регистратуре не и... \n", "4 positive У меня с детства очень плохие зубы (тонкая и х... \n", "... ... ... \n", "70592 negative Заведение ужасное. Врачи делят 1 кабинет на 2х... \n", "70593 negative Люди, не обращайтесь в эту фирму! Муж проходил... \n", "70594 positive Дорогие посетители медицинского центра ООО \"Ме... \n", "70595 negative В регистратуре сидит хамка, такое отношение и ... \n", "70596 positive Хочу поблагодарить весь персонал \"МедАльфаПроф... \n", "\n", " cleaned_text \n", "0 огромный спасибо чудесный удаление два зуб муд... \n", "1 хотеть выразить особый благодарность замечател... \n", "2 добрый вечер хотеться поблагодарить сотрудник ... \n", "3 женщина советский образец регистратура иметь п... \n", "4 детство очень плохой зуб тонкий хрупкий эмаль ... \n", "... ... \n", "70592 заведение ужасный врач делить 1 кабинет 2х спе... \n", "70593 человек обращаться фирма муж проходить анализ ... \n", "70594 дорогой посетитель медицинский центр ооо медал... \n", "70595 регистратура сидеть хамка такой отношение мане... \n", "70596 хотеть поблагодарить весь персонал медальфапро... \n", "\n", "[70597 rows x 3 columns]" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'добрый вечер хотеться поблагодарить сотрудник рентген протезироваться относиться поликлиника 189 отфутболить подходить кочин зам гл врач заведовать просто сделать 3 снимок полка рядом дом грубо отказать сотрудник рентген просто сидеть кроссворд разгадывать видеть принимать протезирование сказать протезироваться делать говорить удобно побояться первый попасть молодой девушка выслушать сделать 1 снимка записать другой день это удобно народ полно бедный сотрудник читать отзыв особенно житель люблино 189 полка давать жаловаться департамент спасибо рентген слышать очередь народ перебрать норма спасибо'" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df['cleaned_text'][2]" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/tmp/ipykernel_75887/3526150694.py:1: SettingWithCopyWarning: \n", "A value is trying to be set on a copy of a slice from a DataFrame.\n", "Try using .loc[row_indexer,col_indexer] = value instead\n", "\n", "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n", " df['sentiment'] = df['sentiment'].apply(lambda x: 1 if x == 'negative' else 0)\n" ] } ], "source": [ "df['sentiment'] = df['sentiment'].apply(lambda x: 1 if x == 'negative' else 0)" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "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", " \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", " \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", "
sentimentcontentcleaned_text
00Огромное спасибо за чудесное удаление двух зуб...огромный спасибо чудесный удаление два зуб муд...
10Хочу выразить особую благодарность замечательн...хотеть выразить особый благодарность замечател...
20Добрый вечер! Хотелось бы поблагодарить сотруд...добрый вечер хотеться поблагодарить сотрудник ...
31Женщины советского образца в регистратуре не и...женщина советский образец регистратура иметь п...
40У меня с детства очень плохие зубы (тонкая и х...детство очень плохой зуб тонкий хрупкий эмаль ...
............
705921Заведение ужасное. Врачи делят 1 кабинет на 2х...заведение ужасный врач делить 1 кабинет 2х спе...
705931Люди, не обращайтесь в эту фирму! Муж проходил...человек обращаться фирма муж проходить анализ ...
705940Дорогие посетители медицинского центра ООО \"Ме...дорогой посетитель медицинский центр ооо медал...
705951В регистратуре сидит хамка, такое отношение и ...регистратура сидеть хамка такой отношение мане...
705960Хочу поблагодарить весь персонал \"МедАльфаПроф...хотеть поблагодарить весь персонал медальфапро...
\n", "

70597 rows × 3 columns

\n", "
" ], "text/plain": [ " sentiment content \\\n", "0 0 Огромное спасибо за чудесное удаление двух зуб... \n", "1 0 Хочу выразить особую благодарность замечательн... \n", "2 0 Добрый вечер! Хотелось бы поблагодарить сотруд... \n", "3 1 Женщины советского образца в регистратуре не и... \n", "4 0 У меня с детства очень плохие зубы (тонкая и х... \n", "... ... ... \n", "70592 1 Заведение ужасное. Врачи делят 1 кабинет на 2х... \n", "70593 1 Люди, не обращайтесь в эту фирму! Муж проходил... \n", "70594 0 Дорогие посетители медицинского центра ООО \"Ме... \n", "70595 1 В регистратуре сидит хамка, такое отношение и ... \n", "70596 0 Хочу поблагодарить весь персонал \"МедАльфаПроф... \n", "\n", " cleaned_text \n", "0 огромный спасибо чудесный удаление два зуб муд... \n", "1 хотеть выразить особый благодарность замечател... \n", "2 добрый вечер хотеться поблагодарить сотрудник ... \n", "3 женщина советский образец регистратура иметь п... \n", "4 детство очень плохой зуб тонкий хрупкий эмаль ... \n", "... ... \n", "70592 заведение ужасный врач делить 1 кабинет 2х спе... \n", "70593 человек обращаться фирма муж проходить анализ ... \n", "70594 дорогой посетитель медицинский центр ооо медал... \n", "70595 регистратура сидеть хамка такой отношение мане... \n", "70596 хотеть поблагодарить весь персонал медальфапро... \n", "\n", "[70597 rows x 3 columns]" ] }, "execution_count": 35, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df" ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [], "source": [ "X_train, X_test, y_train, y_test = train_test_split(df['cleaned_text'], df['sentiment'], test_size=0.2, random_state=42)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Векторизация и сжатие" ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "vectorization done in 4.084 s\n", "n_samples train: 56477, n_features: 1010\n", "n_samples test: 14120, n_features: 1010\n" ] } ], "source": [ "vectorizer = TfidfVectorizer(\n", " max_df=0.9,\n", " min_df=500,\n", " # ngram_range=(1, 2), # Использование униграмм и биграмм\n", " # max_features=5000,\n", " stop_words=stopwords.words('russian'),\n", ")\n", "t0 = time()\n", "X_train_tfidf = vectorizer.fit_transform(X_train)\n", "X_test_tfidf = vectorizer.transform(X_test)\n", "\n", "print(f\"vectorization done in {time() - t0:.3f} s\")\n", "print(f\"n_samples train: {X_train_tfidf.shape[0]}, n_features: {X_train_tfidf.shape[1]}\")\n", "print(f\"n_samples test: {X_test_tfidf.shape[0]}, n_features: {X_test_tfidf.shape[1]}\")" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "LSA done in 14.485 s\n", "Explained variance of the SVD step: 74.3%\n" ] } ], "source": [ "lsa = make_pipeline(TruncatedSVD(n_components=500), Normalizer(copy=False))\n", "t0 = time()\n", "X_train_lsa = lsa.fit_transform(X_train_tfidf)\n", "\n", "# Применение обученной модели LSA к тестовым данным\n", "X_test_lsa = lsa.transform(X_test_tfidf)\n", "explained_variance = lsa[0].explained_variance_ratio_.sum()\n", "\n", "print(f\"LSA done in {time() - t0:.3f} s\")\n", "print(f\"Explained variance of the SVD step: {explained_variance * 100:.1f}%\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Логистическая регрессия" ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " precision recall f1-score support\n", "\n", " 0 0.94 0.94 0.94 8342\n", " 1 0.91 0.92 0.91 5778\n", "\n", " accuracy 0.93 14120\n", " macro avg 0.92 0.93 0.93 14120\n", "weighted avg 0.93 0.93 0.93 14120\n", "\n", "Accuracy: 0.9277620396600567\n", "F1 score: 0.9120689655172414\n" ] } ], "source": [ "model = LogisticRegression()\n", "\n", "# Обучение модели\n", "model.fit(X_train_lsa, y_train)\n", "\n", "# Прогнозирование на тестовой выборке\n", "y_pred = model.predict(X_test_lsa)\n", "\n", "# Вывод результатов\n", "print(classification_report(y_test, y_pred))\n", "print(f'Accuracy: {accuracy_score(y_test, y_pred)}')\n", "print(f'F1 score: {f1_score(y_test, y_pred)}')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Создание пайплайна" ] }, { "cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "[nltk_data] Downloading package stopwords to /home/vera/nltk_data...\n", "[nltk_data] Package stopwords is already up-to-date!\n", "[nltk_data] Downloading package punkt to /home/vera/nltk_data...\n", "[nltk_data] Package punkt is already up-to-date!\n" ] }, { "data": { "text/html": [ "
Pipeline(steps=[('preprocessor', TextPreprocessor()),\n",
       "                ('vectorizer',\n",
       "                 TfidfVectorizer(max_df=0.9, min_df=500,\n",
       "                                 stop_words=['и', 'в', 'во', 'не', 'что', 'он',\n",
       "                                             'на', 'я', 'с', 'со', 'как', 'а',\n",
       "                                             'то', 'все', 'она', 'так', 'его',\n",
       "                                             'но', 'да', 'ты', 'к', 'у', 'же',\n",
       "                                             'вы', 'за', 'бы', 'по', 'только',\n",
       "                                             'ее', 'мне', ...])),\n",
       "                ('lsa',\n",
       "                 Pipeline(steps=[('truncatedsvd',\n",
       "                                  TruncatedSVD(n_components=500)),\n",
       "                                 ('normalizer', Normalizer(copy=False))])),\n",
       "                ('classifier', LogisticRegression())])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('preprocessor', TextPreprocessor()),\n", " ('vectorizer',\n", " TfidfVectorizer(max_df=0.9, min_df=500,\n", " stop_words=['и', 'в', 'во', 'не', 'что', 'он',\n", " 'на', 'я', 'с', 'со', 'как', 'а',\n", " 'то', 'все', 'она', 'так', 'его',\n", " 'но', 'да', 'ты', 'к', 'у', 'же',\n", " 'вы', 'за', 'бы', 'по', 'только',\n", " 'ее', 'мне', ...])),\n", " ('lsa',\n", " Pipeline(steps=[('truncatedsvd',\n", " TruncatedSVD(n_components=500)),\n", " ('normalizer', Normalizer(copy=False))])),\n", " ('classifier', LogisticRegression())])" ] }, "execution_count": 54, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import re\n", "import pandas as pd\n", "import numpy as np\n", "from sklearn.base import BaseEstimator, TransformerMixin\n", "from sklearn.feature_extraction.text import TfidfVectorizer\n", "from sklearn.decomposition import TruncatedSVD\n", "from sklearn.pipeline import Pipeline, FeatureUnion\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.preprocessing import Normalizer\n", "import joblib\n", "import nltk\n", "from nltk.corpus import stopwords\n", "from pymorphy2 import MorphAnalyzer\n", "\n", "nltk.download('stopwords')\n", "nltk.download('punkt')\n", "\n", "class TextPreprocessor(BaseEstimator, TransformerMixin):\n", " def __init__(self):\n", " self.stop_words = set(stopwords.words('russian'))\n", " self.morph = MorphAnalyzer()\n", "\n", " def preprocess_text(self, text):\n", " # Удаление всего, что не является буквами или знаками препинания\n", " clean_pattern = re.compile(r'[^a-zA-Zа-яА-ЯёЁ0-9.,!?;:\\s]')\n", " text = clean_pattern.sub('', text)\n", " url_pattern = re.compile(r'http\\S+|www\\S+|https\\S+')\n", " text = url_pattern.sub(r'', text)\n", " text = text.translate(str.maketrans('', '', string.punctuation))\n", " text = text.lower()\n", " tokens = text.split()\n", " lemmatized_text = ' '.join([self.morph.parse(word)[0].normal_form for word in tokens if word not in self.stop_words])\n", " return lemmatized_text\n", "\n", " def fit(self, X, y=None):\n", " return self\n", "\n", " def transform(self, X, y=None):\n", " return X.apply(self.preprocess_text)\n", "\n", "\n", "# Load and preprocess the dataset\n", "df = pd.read_json('data/healthcare_facilities_reviews.jsonl', lines=True)\n", "df = df[['sentiment', 'content']]\n", "df['cleaned_text'] = df['content'].apply(TextPreprocessor().preprocess_text)\n", "df['sentiment'] = df['sentiment'].apply(lambda x: 1 if x == 'negative' else 0)\n", "\n", "# Split the dataset (this is only for training purposes)\n", "from sklearn.model_selection import train_test_split\n", "train_df, test_df = train_test_split(df, test_size=0.2, random_state=42)\n", "\n", "# Create the pipeline\n", "vectorizer = TfidfVectorizer(\n", " max_df=0.9,\n", " min_df=500,\n", " stop_words=stopwords.words('russian')\n", ")\n", "\n", "lsa = TruncatedSVD(n_components=500)\n", "\n", "pipeline = Pipeline([\n", " ('preprocessor', TextPreprocessor()),\n", " ('vectorizer', vectorizer),\n", " ('lsa', make_pipeline(lsa, Normalizer(copy=False))),\n", " ('classifier', LogisticRegression())\n", "])\n", "\n", "# Train the model\n", "X_train = train_df['cleaned_text']\n", "y_train = train_df['sentiment']\n", "pipeline.fit(X_train, y_train)\n", "\n", "# Save the model\n", "# joblib.dump(pipeline, 'logistic_regression_pipeline.pkl')\n" ] }, { "cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['logistic_regression_pipeline.pkl']" ] }, "execution_count": 55, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Save the model for future use\n", "joblib.dump(pipeline, 'logistic_regression_pipeline.pkl')" ] }, { "cell_type": "code", "execution_count": 56, "metadata": {}, "outputs": [], "source": [ "# Load the model (if not already loaded)\n", "pipeline_test= joblib.load('logistic_regression_pipeline.pkl')" ] }, { "cell_type": "code", "execution_count": 61, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Predicted class: 1\n", "Predicted proba: 0.898\n" ] } ], "source": [ "# Sample text for prediction\n", "sample_text = \"Ужасная клиника, обслуживание из рук вон плохое, хотеловь бы выразить свое разочарование данным заведением. Советую обходить его мимо.\"\n", "\n", "# Use the pipeline to predict the class\n", "predicted_class = pipeline_test.predict(pd.Series([sample_text]))\n", "predicted_prob = pipeline_test.predict_proba(pd.Series([sample_text]))\n", "print(f\"Predicted class: {predicted_class[0]}\")\n", "print(f\"Predicted proba: {round(predicted_prob[0][1], 3)}\")" ] } ], "metadata": { "kernelspec": { "display_name": "base", "language": "python", "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.10.14" } }, "nbformat": 4, "nbformat_minor": 2 }