ivanovot commited on
Commit
b83e315
·
1 Parent(s): 89f9f06
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Используем базовый образ с Python
2
+ FROM python:3.12
3
+
4
+ # Устанавливаем рабочую директорию внутри контейнера
5
+ WORKDIR /app
6
+
7
+ # Копируем все файлы из текущей директории на локальной машине в контейнер
8
+ COPY . .
9
+
10
+ # Устанавливаем зависимости
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ # Открываем порт (если нужно)
14
+ EXPOSE 7860
15
+
16
+ # Команда для запуска проекта (измените в зависимости от структуры вашего проекта)
17
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import yaml
3
+ from src.load_model import model, device
4
+
5
+ config = yaml.safe_load(open('config.yaml', 'r'))
6
+ threshold = config['predct']['threshold']
7
+
8
+ def predict(text: str):
9
+ prediction = model(text).item()
10
+ label = "Negative" if prediction >= threshold else "Positive"
11
+ return label, float(prediction)
12
+
13
+ examples = [
14
+ ["Спасибо за подробный разбор, это действительно полезно!"],
15
+ ["Интересный подход, я бы добавил ещё пару примеров для наглядности."],
16
+ ["Никогда не задумывался об этом с такой точки зрения. Подумаю над вашей идеей."],
17
+ ["папа вроде нормальным был а сынок говнюком вырос."],
18
+ ["говно на палке блять чё красивого в этой картинке"],
19
+ ["идиоты! что попало придумывают лишь бы лайки ставили"]
20
+ ]
21
+
22
+ interface = gr.Interface(
23
+ fn=predict,
24
+ title="Text Classification",
25
+ description=f"using device: {device}",
26
+ inputs=gr.Textbox(label="Текст для классификации"),
27
+ outputs=[
28
+ gr.Textbox(label="Класс", interactive=False),
29
+ gr.Slider(minimum=0, maximum=1, label="Оценка модели", interactive=False)
30
+ ],
31
+ live=True,
32
+ examples=examples
33
+ )
34
+
35
+ if __name__ == "__main__":
36
+ interface.launch()
config.yaml ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Конфигурация данных
2
+ data:
3
+ train_path: "data/dataset_train.pt"
4
+ test_path: "data/dataset_test.pt"
5
+ batch_size: 32
6
+
7
+ # Параметры модели
8
+ model:
9
+ bert_name: "cointegrated/rubert-tiny"
10
+
11
+ predct:
12
+ use_model: "models/model_5.pth"
13
+ threshold: 0.75
14
+
15
+ # Гиперпараметры обучения
16
+ training:
17
+ epochs: 5
18
+ learning_rate: 1e-5
19
+ save_dir: "models"
20
+
21
+ # Настройки логирования
22
+ logging:
23
+ log_dir: "logs"
24
+ level: "INFO"
25
+ format: "%(asctime)s - %(levelname)s - %(message)s"
data/dataset_test.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fdd19206c1cd4935e66bb1c3615a4e4d2cfcb28b71e88fd50a3e1ce0bb554fe6
3
+ size 3793204
data/dataset_train.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1ffc4ce42036f92b7cdf2681eb16d33a011fff4ef8cc435febe4e799064af790
3
+ size 34084984
logs/log.log ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 2025-01-28 13:11:34,916 - INFO - Starting training process
2
+ 2025-01-28 13:11:34,916 - INFO - Configuration: {'data': {'train_path': 'data/dataset_train.pt', 'test_path': 'data/dataset_test.pt', 'batch_size': 32}, 'model': {'bert_name': 'cointegrated/rubert-tiny', 'freeze_bert': False}, 'training': {'epochs': 5, 'learning_rate': '1e-5', 'save_dir': 'models'}, 'logging': {'log_dir': 'logs', 'level': 'INFO', 'format': '%(asctime)s - %(levelname)s - %(message)s'}}
3
+ 2025-01-28 13:11:35,626 - INFO - Model initialized
4
+ 2025-01-28 13:11:35,800 - INFO - Train samples: 223461, Test samples: 24829
5
+ 2025-01-28 13:16:05,323 - INFO - Epoch 1 [269.5s]
6
+ 2025-01-28 13:16:05,323 - INFO - Train Loss: 0.1773 | Acc: 0.9310
7
+ 2025-01-28 13:16:05,323 - INFO - Test Loss: 0.1172 | Acc: 0.9571
8
+ 2025-01-28 13:20:34,377 - INFO - Epoch 2 [269.0s]
9
+ 2025-01-28 13:20:34,377 - INFO - Train Loss: 0.1006 | Acc: 0.9639
10
+ 2025-01-28 13:20:34,377 - INFO - Test Loss: 0.0895 | Acc: 0.9689
11
+ 2025-01-28 13:24:56,084 - INFO - Epoch 3 [261.7s]
12
+ 2025-01-28 13:24:56,084 - INFO - Train Loss: 0.0817 | Acc: 0.9709
13
+ 2025-01-28 13:24:56,084 - INFO - Test Loss: 0.0804 | Acc: 0.9720
14
+ 2025-01-28 13:29:29,193 - INFO - Epoch 4 [273.1s]
15
+ 2025-01-28 13:29:29,193 - INFO - Train Loss: 0.0702 | Acc: 0.9746
16
+ 2025-01-28 13:29:29,193 - INFO - Test Loss: 0.0786 | Acc: 0.9733
17
+ 2025-01-28 13:33:50,194 - INFO - Epoch 5 [261.0s]
18
+ 2025-01-28 13:33:50,194 - INFO - Train Loss: 0.0623 | Acc: 0.9781
19
+ 2025-01-28 13:33:50,194 - INFO - Test Loss: 0.0738 | Acc: 0.9752
20
+ 2025-01-28 13:33:50,237 - INFO - Training completed
logs/training_results.csv ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ epoch,train_loss,test_loss,train_acc,test_acc
2
+ 1,0.1773055931961556,0.11715290685436326,0.9310170454799719,0.9571468846912884
3
+ 2,0.10063255767090486,0.08949904675724532,0.9639176411096343,0.9689073261105965
4
+ 3,0.08170484759700015,0.08040267044113512,0.9709121502186063,0.9719682629183616
5
+ 4,0.07021966443530255,0.07857279533562277,0.9746174947753746,0.9732973539006806
6
+ 5,0.062299927920050395,0.0738135943584388,0.9780767113724542,0.9751500261790648
models/.gitkeep ADDED
File without changes
models/model_5.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7a10af129553374bf676c4521828557f1ee7f7028e9c857f5ee17cc30609d560
3
+ size 47160427
notebooks/api.ipynb ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "# Демонстрация работы API"
8
+ ]
9
+ },
10
+ {
11
+ "cell_type": "code",
12
+ "execution_count": 1,
13
+ "metadata": {},
14
+ "outputs": [],
15
+ "source": [
16
+ "text = \"Привет мир\""
17
+ ]
18
+ },
19
+ {
20
+ "cell_type": "markdown",
21
+ "metadata": {},
22
+ "source": [
23
+ "## Способ 1"
24
+ ]
25
+ },
26
+ {
27
+ "cell_type": "code",
28
+ "execution_count": 2,
29
+ "metadata": {},
30
+ "outputs": [
31
+ {
32
+ "name": "stderr",
33
+ "output_type": "stream",
34
+ "text": [
35
+ "/home/timo/rep/TextClassifier/venv/lib/python3.12/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
36
+ " from .autonotebook import tqdm as notebook_tqdm\n"
37
+ ]
38
+ },
39
+ {
40
+ "name": "stdout",
41
+ "output_type": "stream",
42
+ "text": [
43
+ "Loaded as API: http://0.0.0.0:7860/ ✔\n",
44
+ "Текст: Привет мир\n",
45
+ "Статус: Positive\n"
46
+ ]
47
+ }
48
+ ],
49
+ "source": [
50
+ "from gradio_client import Client\n",
51
+ "\n",
52
+ "def classify_text(text: str) -> str:\n",
53
+ " # Создаем клиент для общения с сервером\n",
54
+ " client = Client(\"http://0.0.0.0:7860/\")\n",
55
+ "\n",
56
+ " # Отправляем текст для классификации\n",
57
+ " result = client.predict(\n",
58
+ " text=text,\n",
59
+ " api_name=\"/predict\"\n",
60
+ " )\n",
61
+ "\n",
62
+ " # Обрабатываем результат\n",
63
+ " if result:\n",
64
+ " status = result[0]\n",
65
+ " return status\n",
66
+ "\n",
67
+ " return \"Ошибка классификации\"\n",
68
+ "\n",
69
+ "# Пример использования функции\n",
70
+ "status = classify_text(text)\n",
71
+ "\n",
72
+ "print(f\"Текст: {text}\")\n",
73
+ "print(f\"Статус: {status}\")"
74
+ ]
75
+ },
76
+ {
77
+ "cell_type": "markdown",
78
+ "metadata": {},
79
+ "source": [
80
+ "## Способ 2"
81
+ ]
82
+ },
83
+ {
84
+ "cell_type": "code",
85
+ "execution_count": 3,
86
+ "metadata": {},
87
+ "outputs": [
88
+ {
89
+ "name": "stdout",
90
+ "output_type": "stream",
91
+ "text": [
92
+ "Текст: Привет мир\n",
93
+ "Статус: Positive\n"
94
+ ]
95
+ }
96
+ ],
97
+ "source": [
98
+ "import requests\n",
99
+ "\n",
100
+ "def classify_text(text: str) -> str:\n",
101
+ " # URL и заголовки для POST-запроса\n",
102
+ " url = 'http://0.0.0.0:7860/gradio_api/call/predict'\n",
103
+ " headers = {'Content-Type': 'application/json'}\n",
104
+ " data = {\"data\": [text]}\n",
105
+ "\n",
106
+ " # Отправляем POST-запрос для классификации\n",
107
+ " response = requests.post(url, json=data, headers=headers)\n",
108
+ "\n",
109
+ " # Проверяем успешность ответа\n",
110
+ " if response.status_code == 200:\n",
111
+ " # Извлекаем EVENT_ID из ответа\n",
112
+ " event_id = response.json().get('event_id')\n",
113
+ "\n",
114
+ " # Проверяем, что event_id присутствует\n",
115
+ " if event_id:\n",
116
+ " # Второй запрос с EVENT_ID для получения классификации\n",
117
+ " event_url = f'http://0.0.0.0:7860/gradio_api/call/predict/{event_id}'\n",
118
+ " event_response = requests.get(event_url)\n",
119
+ "\n",
120
+ " # Если второй запрос успешен\n",
121
+ " if event_response.status_code == 200:\n",
122
+ " for line in event_response.iter_lines():\n",
123
+ " if line:\n",
124
+ " decoded_line = line.decode('utf-8')\n",
125
+ "\n",
126
+ " if 'data: ' in decoded_line:\n",
127
+ " parsed_data = decoded_line.split('data: ')[1]\n",
128
+ " parsed_data = parsed_data.strip('[]').split(', ')\n",
129
+ "\n",
130
+ " # Извлекаем статус\n",
131
+ " status = parsed_data[0].strip('\"')\n",
132
+ " return status\n",
133
+ "\n",
134
+ " return \"Ошибка классификации\"\n",
135
+ "\n",
136
+ "# Пример использования функции\n",
137
+ "status = classify_text(text)\n",
138
+ "\n",
139
+ "print(f\"Текст: {text}\")\n",
140
+ "print(f\"Статус: {status}\")"
141
+ ]
142
+ }
143
+ ],
144
+ "metadata": {
145
+ "kernelspec": {
146
+ "display_name": "venv",
147
+ "language": "python",
148
+ "name": "python3"
149
+ },
150
+ "language_info": {
151
+ "codemirror_mode": {
152
+ "name": "ipython",
153
+ "version": 3
154
+ },
155
+ "file_extension": ".py",
156
+ "mimetype": "text/x-python",
157
+ "name": "python",
158
+ "nbconvert_exporter": "python",
159
+ "pygments_lexer": "ipython3",
160
+ "version": "3.12.3"
161
+ }
162
+ },
163
+ "nbformat": 4,
164
+ "nbformat_minor": 2
165
+ }
notebooks/bert.ipynb ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {},
7
+ "outputs": [
8
+ {
9
+ "name": "stderr",
10
+ "output_type": "stream",
11
+ "text": [
12
+ "/home/timo/rep/TextClassifier/venv/lib/python3.12/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
13
+ " from .autonotebook import tqdm as notebook_tqdm\n"
14
+ ]
15
+ }
16
+ ],
17
+ "source": [
18
+ "import os\n",
19
+ "os.chdir('..')\n",
20
+ "\n",
21
+ "import torch\n",
22
+ "from transformers import AutoTokenizer, AutoModel\n",
23
+ "from src import device"
24
+ ]
25
+ },
26
+ {
27
+ "cell_type": "code",
28
+ "execution_count": 2,
29
+ "metadata": {},
30
+ "outputs": [],
31
+ "source": [
32
+ "tokenizer = AutoTokenizer.from_pretrained(\"cointegrated/rubert-tiny\")\n",
33
+ "model = AutoModel.from_pretrained(\"cointegrated/rubert-tiny\")\n",
34
+ "\n",
35
+ "model = model.to(device)"
36
+ ]
37
+ },
38
+ {
39
+ "cell_type": "code",
40
+ "execution_count": 3,
41
+ "metadata": {},
42
+ "outputs": [],
43
+ "source": [
44
+ "def embed_bert_cls(text, model, tokenizer):\n",
45
+ " t = tokenizer(text, padding=True, truncation=True, return_tensors='pt')\n",
46
+ " with torch.no_grad():\n",
47
+ " model_output = model(**{k: v.to(model.device) for k, v in t.items()})\n",
48
+ " embeddings = model_output.last_hidden_state[:, 0, :]\n",
49
+ " embeddings = torch.nn.functional.normalize(embeddings)\n",
50
+ " return embeddings[0].cpu().numpy()"
51
+ ]
52
+ },
53
+ {
54
+ "cell_type": "code",
55
+ "execution_count": 4,
56
+ "metadata": {},
57
+ "outputs": [
58
+ {
59
+ "name": "stdout",
60
+ "output_type": "stream",
61
+ "text": [
62
+ "(312,)\n"
63
+ ]
64
+ }
65
+ ],
66
+ "source": [
67
+ "print(embed_bert_cls('привет мир', model, tokenizer).shape)"
68
+ ]
69
+ }
70
+ ],
71
+ "metadata": {
72
+ "kernelspec": {
73
+ "display_name": "venv",
74
+ "language": "python",
75
+ "name": "python3"
76
+ },
77
+ "language_info": {
78
+ "codemirror_mode": {
79
+ "name": "ipython",
80
+ "version": 3
81
+ },
82
+ "file_extension": ".py",
83
+ "mimetype": "text/x-python",
84
+ "name": "python",
85
+ "nbconvert_exporter": "python",
86
+ "pygments_lexer": "ipython3",
87
+ "version": "3.12.3"
88
+ }
89
+ },
90
+ "nbformat": 4,
91
+ "nbformat_minor": 2
92
+ }
notebooks/classifier.ipynb ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {},
7
+ "outputs": [
8
+ {
9
+ "name": "stderr",
10
+ "output_type": "stream",
11
+ "text": [
12
+ "/home/timo/rep/TextClassifier/venv/lib/python3.12/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
13
+ " from .autonotebook import tqdm as notebook_tqdm\n"
14
+ ]
15
+ }
16
+ ],
17
+ "source": [
18
+ "import os\n",
19
+ "os.chdir('..')\n",
20
+ "\n",
21
+ "from src.classifier import Classifier\n",
22
+ "from src.bert import Bert\n",
23
+ "import torch\n",
24
+ "import yaml"
25
+ ]
26
+ },
27
+ {
28
+ "cell_type": "code",
29
+ "execution_count": 2,
30
+ "metadata": {},
31
+ "outputs": [],
32
+ "source": [
33
+ "device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n",
34
+ "\n",
35
+ "config = yaml.safe_load(open('config.yaml'))\n",
36
+ "\n",
37
+ "bert = Bert(config['model']['bert_name'])\n",
38
+ "model = Classifier(bert).to(device)"
39
+ ]
40
+ },
41
+ {
42
+ "cell_type": "code",
43
+ "execution_count": 3,
44
+ "metadata": {},
45
+ "outputs": [
46
+ {
47
+ "data": {
48
+ "text/plain": [
49
+ "<All keys matched successfully>"
50
+ ]
51
+ },
52
+ "execution_count": 3,
53
+ "metadata": {},
54
+ "output_type": "execute_result"
55
+ }
56
+ ],
57
+ "source": [
58
+ "model.load_state_dict(torch.load('models/model_5.pth', map_location=device, weights_only=True))"
59
+ ]
60
+ },
61
+ {
62
+ "cell_type": "code",
63
+ "execution_count": 4,
64
+ "metadata": {},
65
+ "outputs": [
66
+ {
67
+ "data": {
68
+ "text/plain": [
69
+ "tensor([0.0007], device='cuda:0')"
70
+ ]
71
+ },
72
+ "execution_count": 4,
73
+ "metadata": {},
74
+ "output_type": "execute_result"
75
+ }
76
+ ],
77
+ "source": [
78
+ "text = 'привет мир'\n",
79
+ "\n",
80
+ "with torch.no_grad():\n",
81
+ " predict = model([text])\n",
82
+ " \n",
83
+ "predict"
84
+ ]
85
+ }
86
+ ],
87
+ "metadata": {
88
+ "kernelspec": {
89
+ "display_name": "venv",
90
+ "language": "python",
91
+ "name": "python3"
92
+ },
93
+ "language_info": {
94
+ "codemirror_mode": {
95
+ "name": "ipython",
96
+ "version": 3
97
+ },
98
+ "file_extension": ".py",
99
+ "mimetype": "text/x-python",
100
+ "name": "python",
101
+ "nbconvert_exporter": "python",
102
+ "pygments_lexer": "ipython3",
103
+ "version": "3.12.3"
104
+ }
105
+ },
106
+ "nbformat": 4,
107
+ "nbformat_minor": 2
108
+ }
notebooks/dataset.ipynb ADDED
@@ -0,0 +1,414 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "import os\n",
10
+ "os.chdir(\"..\")\n",
11
+ "\n",
12
+ "import pandas as pd\n",
13
+ "import matplotlib.pyplot as plt"
14
+ ]
15
+ },
16
+ {
17
+ "cell_type": "code",
18
+ "execution_count": 2,
19
+ "metadata": {},
20
+ "outputs": [
21
+ {
22
+ "name": "stderr",
23
+ "output_type": "stream",
24
+ "text": [
25
+ "/home/timo/rep/TextClassifier/venv/lib/python3.12/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
26
+ " from .autonotebook import tqdm as notebook_tqdm\n"
27
+ ]
28
+ }
29
+ ],
30
+ "source": [
31
+ "splits = {'train': 'train.jsonl', 'test': 'test.jsonl'}\n",
32
+ "train = pd.read_json(\"hf://datasets/AlexSham/Toxic_Russian_Comments/\" + splits[\"train\"], lines=True)\n",
33
+ "test = pd.read_json(\"hf://datasets/AlexSham/Toxic_Russian_Comments/\" + splits[\"test\"], lines=True)"
34
+ ]
35
+ },
36
+ {
37
+ "cell_type": "code",
38
+ "execution_count": 3,
39
+ "metadata": {},
40
+ "outputs": [
41
+ {
42
+ "data": {
43
+ "text/html": [
44
+ "<div>\n",
45
+ "<style scoped>\n",
46
+ " .dataframe tbody tr th:only-of-type {\n",
47
+ " vertical-align: middle;\n",
48
+ " }\n",
49
+ "\n",
50
+ " .dataframe tbody tr th {\n",
51
+ " vertical-align: top;\n",
52
+ " }\n",
53
+ "\n",
54
+ " .dataframe thead th {\n",
55
+ " text-align: right;\n",
56
+ " }\n",
57
+ "</style>\n",
58
+ "<table border=\"1\" class=\"dataframe\">\n",
59
+ " <thead>\n",
60
+ " <tr style=\"text-align: right;\">\n",
61
+ " <th></th>\n",
62
+ " <th>text</th>\n",
63
+ " <th>label</th>\n",
64
+ " </tr>\n",
65
+ " </thead>\n",
66
+ " <tbody>\n",
67
+ " <tr>\n",
68
+ " <th>0</th>\n",
69
+ " <td>видимо в разных регионах называют по разному ,...</td>\n",
70
+ " <td>0</td>\n",
71
+ " </tr>\n",
72
+ " <tr>\n",
73
+ " <th>1</th>\n",
74
+ " <td>понятно что это нарушение правил, писать капсл...</td>\n",
75
+ " <td>1</td>\n",
76
+ " </tr>\n",
77
+ " <tr>\n",
78
+ " <th>2</th>\n",
79
+ " <td>какие классные, жизненные стихи....</td>\n",
80
+ " <td>0</td>\n",
81
+ " </tr>\n",
82
+ " <tr>\n",
83
+ " <th>3</th>\n",
84
+ " <td>а и правда-когда его запретили?...</td>\n",
85
+ " <td>0</td>\n",
86
+ " </tr>\n",
87
+ " <tr>\n",
88
+ " <th>4</th>\n",
89
+ " <td>в соленой воде вирусы живут .ученые изучали со...</td>\n",
90
+ " <td>0</td>\n",
91
+ " </tr>\n",
92
+ " <tr>\n",
93
+ " <th>...</th>\n",
94
+ " <td>...</td>\n",
95
+ " <td>...</td>\n",
96
+ " </tr>\n",
97
+ " <tr>\n",
98
+ " <th>223456</th>\n",
99
+ " <td>вова - дима когда же вы подавитесь деньгами???...</td>\n",
100
+ " <td>0</td>\n",
101
+ " </tr>\n",
102
+ " <tr>\n",
103
+ " <th>223457</th>\n",
104
+ " <td>какая красота, просто нет слов выразить чувств...</td>\n",
105
+ " <td>0</td>\n",
106
+ " </tr>\n",
107
+ " <tr>\n",
108
+ " <th>223458</th>\n",
109
+ " <td>вы пост гаи выставити на перекрестке возле 21 ...</td>\n",
110
+ " <td>0</td>\n",
111
+ " </tr>\n",
112
+ " <tr>\n",
113
+ " <th>223459</th>\n",
114
+ " <td>как -то на лебедей непохожи</td>\n",
115
+ " <td>0</td>\n",
116
+ " </tr>\n",
117
+ " <tr>\n",
118
+ " <th>223460</th>\n",
119
+ " <td>интересно чей это самолет!</td>\n",
120
+ " <td>0</td>\n",
121
+ " </tr>\n",
122
+ " </tbody>\n",
123
+ "</table>\n",
124
+ "<p>223461 rows × 2 columns</p>\n",
125
+ "</div>"
126
+ ],
127
+ "text/plain": [
128
+ " text label\n",
129
+ "0 видимо в разных регионах называют по разному ,... 0\n",
130
+ "1 понятно что это нарушение правил, писать капсл... 1\n",
131
+ "2 какие классные, жизненные стихи.... 0\n",
132
+ "3 а и правда-когда его запретили?... 0\n",
133
+ "4 в соленой воде вирусы живут .ученые изучали со... 0\n",
134
+ "... ... ...\n",
135
+ "223456 вова - дима когда же вы подавитесь ден��гами???... 0\n",
136
+ "223457 какая красота, просто нет слов выразить чувств... 0\n",
137
+ "223458 вы пост гаи выставити на перекрестке возле 21 ... 0\n",
138
+ "223459 как -то на лебедей непохожи 0\n",
139
+ "223460 интересно чей это самолет! 0\n",
140
+ "\n",
141
+ "[223461 rows x 2 columns]"
142
+ ]
143
+ },
144
+ "execution_count": 3,
145
+ "metadata": {},
146
+ "output_type": "execute_result"
147
+ }
148
+ ],
149
+ "source": [
150
+ "train"
151
+ ]
152
+ },
153
+ {
154
+ "cell_type": "code",
155
+ "execution_count": 4,
156
+ "metadata": {},
157
+ "outputs": [
158
+ {
159
+ "data": {
160
+ "text/html": [
161
+ "<div>\n",
162
+ "<style scoped>\n",
163
+ " .dataframe tbody tr th:only-of-type {\n",
164
+ " vertical-align: middle;\n",
165
+ " }\n",
166
+ "\n",
167
+ " .dataframe tbody tr th {\n",
168
+ " vertical-align: top;\n",
169
+ " }\n",
170
+ "\n",
171
+ " .dataframe thead th {\n",
172
+ " text-align: right;\n",
173
+ " }\n",
174
+ "</style>\n",
175
+ "<table border=\"1\" class=\"dataframe\">\n",
176
+ " <thead>\n",
177
+ " <tr style=\"text-align: right;\">\n",
178
+ " <th></th>\n",
179
+ " <th>text</th>\n",
180
+ " <th>label</th>\n",
181
+ " </tr>\n",
182
+ " </thead>\n",
183
+ " <tbody>\n",
184
+ " <tr>\n",
185
+ " <th>0</th>\n",
186
+ " <td>хорошо пошло!</td>\n",
187
+ " <td>0</td>\n",
188
+ " </tr>\n",
189
+ " <tr>\n",
190
+ " <th>1</th>\n",
191
+ " <td>посмотрела, как будто дома побывала. как река ...</td>\n",
192
+ " <td>0</td>\n",
193
+ " </tr>\n",
194
+ " <tr>\n",
195
+ " <th>2</th>\n",
196
+ " <td>отдам котят 1,5 месяца в добрые руки.</td>\n",
197
+ " <td>0</td>\n",
198
+ " </tr>\n",
199
+ " <tr>\n",
200
+ " <th>3</th>\n",
201
+ " <td>0,5литровая баночка 200р стоит в таганроге. та...</td>\n",
202
+ " <td>0</td>\n",
203
+ " </tr>\n",
204
+ " <tr>\n",
205
+ " <th>4</th>\n",
206
+ " <td>речь шла о радужных зонтиках над верандой.</td>\n",
207
+ " <td>0</td>\n",
208
+ " </tr>\n",
209
+ " <tr>\n",
210
+ " <th>...</th>\n",
211
+ " <td>...</td>\n",
212
+ " <td>...</td>\n",
213
+ " </tr>\n",
214
+ " <tr>\n",
215
+ " <th>24824</th>\n",
216
+ " <td>и ты будь здоров</td>\n",
217
+ " <td>0</td>\n",
218
+ " </tr>\n",
219
+ " <tr>\n",
220
+ " <th>24825</th>\n",
221
+ " <td>не дорога а прям стекло но правда битое (h)</td>\n",
222
+ " <td>0</td>\n",
223
+ " </tr>\n",
224
+ " <tr>\n",
225
+ " <th>24826</th>\n",
226
+ " <td>спасибо большое. буду ждать хороших новостей. ...</td>\n",
227
+ " <td>0</td>\n",
228
+ " </tr>\n",
229
+ " <tr>\n",
230
+ " <th>24827</th>\n",
231
+ " <td>активирую установку 🌈🌈🌈👍😎🔥🔥🔥</td>\n",
232
+ " <td>0</td>\n",
233
+ " </tr>\n",
234
+ " <tr>\n",
235
+ " <th>24828</th>\n",
236
+ " <td>а вы курс российского рубля видели, кошмар!!!</td>\n",
237
+ " <td>0</td>\n",
238
+ " </tr>\n",
239
+ " </tbody>\n",
240
+ "</table>\n",
241
+ "<p>24829 rows × 2 columns</p>\n",
242
+ "</div>"
243
+ ],
244
+ "text/plain": [
245
+ " text label\n",
246
+ "0 хорошо пошло! 0\n",
247
+ "1 посмотрела, как будто дома побывала. как река ... 0\n",
248
+ "2 отдам котят 1,5 месяца в добрые руки. 0\n",
249
+ "3 0,5литровая баночка 200р стоит в таганроге. та... 0\n",
250
+ "4 речь шла о радужных зонтиках над верандой. 0\n",
251
+ "... ... ...\n",
252
+ "24824 и ты будь здоров 0\n",
253
+ "24825 не дорога а прям стекло но правда битое (h) 0\n",
254
+ "24826 спасибо большое. буду ждать хороших новостей. ... 0\n",
255
+ "24827 активирую установку 🌈🌈🌈👍😎🔥🔥🔥 0\n",
256
+ "24828 а вы курс российского рубля видели, кошмар!!! 0\n",
257
+ "\n",
258
+ "[24829 rows x 2 columns]"
259
+ ]
260
+ },
261
+ "execution_count": 4,
262
+ "metadata": {},
263
+ "output_type": "execute_result"
264
+ }
265
+ ],
266
+ "source": [
267
+ "test"
268
+ ]
269
+ },
270
+ {
271
+ "cell_type": "code",
272
+ "execution_count": 5,
273
+ "metadata": {},
274
+ "outputs": [],
275
+ "source": [
276
+ "train['class'] = train['label'].map({0: 'non-toxic', 1: 'toxic'})\n",
277
+ "test['class'] = test['label'].map({0: 'non-toxic', 1: 'toxic'})"
278
+ ]
279
+ },
280
+ {
281
+ "cell_type": "code",
282
+ "execution_count": 6,
283
+ "metadata": {},
284
+ "outputs": [
285
+ {
286
+ "data": {
287
+ "image/png": "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NzExEb169YKXlxfGjRsHV1dXREdHY926dQYZK/K+nDNnDt544w04OjpiwoQJAAAfH59qPTcVIpmQRYsWSQCknTt3Snfv3pViYmKkFStWSB4eHpKdnZ0UGxsrSZIk5eXlSTqdzuC+N27ckNRqtTR16tTi237++WcJgDRr1qxSy9Lr9cX3AyDNnDmz1DRNmzaVIiMji3/es2ePBEDy9/eXMjIyim9ftWqVBECaO3du8bwbNmwo9e7du3g5kiRJOTk5UnBwsNSzZ89Sy+rQoYPUrFmz4p/v3r0rAZAmTZpUfFt0dLSkUqmkGTNmGNz3zJkzkpWVVanbr1y5IgGQlixZUnzbpEmTpJJ/FgcOHJAASMuXLze477Zt20rdXq9ePalfv36lsr/++uvS/X9q92d///33JW9vbykiIsLgOf3ll18kpVIpHThwwOD+3377rQRAOnjwYKnllRQZGVk8vz/++EOysrKS3nnnnTKnrcjzIUmFr1NJBQUFUrNmzaRu3boZzEupVEoDBw4s9bdY9JqnpaVJTk5OUtu2baXc3NwypykoKJC8vb2lZs2aGUyzefNmCYD08ccfF9/2wgsvSPXq1TOYz/fffy8BkI4ePVrmY5bjviX169ev1Hzq1asnAZC2bdtWavr7n0tJkqTevXtL9evXN7it5OsoSf++1xo3bizl5+cX3z537lwJgHTmzJkH5iz6LDl27Fi507i4uEiPPPLIA7P+9ttvEgBp//79xbfNnDlTAiDduHGj1PQVeby///77Q7NV5n15/+dUbTDJTWE9evSAl5cXAgMDMXToUDg6OuL333+Hv78/AECtVkOpLHxoOp0OycnJcHR0RKNGjXDixIni+axduxaenp544403Si3j/s0flfH888/Dycmp+OfBgwejTp062LJlCwAgKioKV65cwfDhw5GcnIykpCQkJSUhOzsb3bt3x/79+6HX6w3mmZeXB1tb2wcud926ddDr9RgyZEjxPJOSkuDr64uGDRtiz549BtMXFBQAKHy+yrN69Wq4uLigZ8+eBvOMiIiAo6NjqXlqNBqD6ZKSkpCXl/fA3HFxcfjmm28wceJEODo6llp+48aNERYWZjDPos2f9y+/PEePHsWQIUPw9NNPY+bMmWVOU5HnA4DBNvfU1FSkp6ejU6dOBn9b69evh16vx8cff1z8t1ik6G9rx44dyMzM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",
288
+ "text/plain": [
289
+ "<Figure size 640x480 with 1 Axes>"
290
+ ]
291
+ },
292
+ "metadata": {},
293
+ "output_type": "display_data"
294
+ },
295
+ {
296
+ "data": {
297
+ "image/png": "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pIekJ5cB4QEu6FLfC13CPNHE35kjuqhasVjI5BRotDh2PQ17LyfjcEwKYpKzwQFmhrwclWhbxx1dwjwRGeoFN47mohrEYiGTl5Wvxpm4TJy+lY7TcRmIistAWo7lHC/jbm+DJgHOCPd3RpMAF4QHOMPbiftKSDosFjJLcWm5uHD7Hi7ezsSF2/dw4fY93LmXL3WsKnOxs0YTf2c08XdGeEBRkfi78LK+ZFxYLGQxUrMLEJuag/j0PCRk5BX99/7/J6TnIU9tHPtt7GwUqOVmh0A3O9R2s0Mt96L/D/F0QKCbndTxiB6LxUJ0X2p2gb5kEjKK/mXla5BXqEWeWovcQg3y1DrkFWqQp9YW3X7/vgf38chkgK2VAiobBVTWCthayx/4/6L/qmwUsFdawdvRFr7OtvB2toWPky18nG05SotMHouFSAT5ai0K1DrYWMlhay3nqCuyaCwWIiISFc/ZQEREomKxEBGRqFgsREQkKhYLERGJisVCRESiYrEQEZGoWCxERCQqFgsREYmKxUJERKJisRARkahYLEREJCoWCxERiYrFQkREomKxEBGRqFgsREQkKhYLERGJisVCRESiYrEQEZGoWCxERCQqFgsREYmKxUJERKJisRARkahYLEREJCoWCxERiYrFQkREomKxEBGRqFgsREQkKhYLERGJisVCRESiYrEQEZGoWCxERCQqFgsREYmKxUJERKJisRARkahYLEREJCoWCxERiYrFQkREomKxEBGRqFgsREQkKhYLERGJisVCRESiYrEQEZGoWCxERCQqFgsREYmKxUJERKL6f7gLd0g4ndFHAAAAAElFTkSuQmCC",
298
+ "text/plain": [
299
+ "<Figure size 640x480 with 1 Axes>"
300
+ ]
301
+ },
302
+ "metadata": {},
303
+ "output_type": "display_data"
304
+ }
305
+ ],
306
+ "source": [
307
+ "datasets = {\"Train Dataset\": train, \"Test Dataset\": test}\n",
308
+ "\n",
309
+ "for name, df in datasets.items():\n",
310
+ " df['class'].value_counts().plot.pie(\n",
311
+ " autopct='%1.1f%%',\n",
312
+ " ylabel='',\n",
313
+ " title=f\"Распределение классов в {name}\"\n",
314
+ " )\n",
315
+ " plt.show()"
316
+ ]
317
+ },
318
+ {
319
+ "cell_type": "code",
320
+ "execution_count": 7,
321
+ "metadata": {},
322
+ "outputs": [
323
+ {
324
+ "name": "stdout",
325
+ "output_type": "stream",
326
+ "text": [
327
+ "Train Dataset\n",
328
+ "class: non-toxic\n",
329
+ "0 видимо в разных регионах назы��ают по разному ,...\n",
330
+ "2 какие классные, жизненные стихи....\n",
331
+ "3 а и правда-когда его запретили?...\n",
332
+ "4 в соленой воде вирусы живут .ученые изучали со...\n",
333
+ "6 ни в одном из близлежащих строительных не нашл...\n",
334
+ "7 какая дрянь сломала? виноваты родители, выраст...\n",
335
+ "8 да, висели такие на стене, над кроватью. в люб...\n",
336
+ "9 потому что мы так воспитаны\n",
337
+ "10 лапти и не только\n",
338
+ "11 привет обмен на дизель без вложений\n",
339
+ "Name: text, dtype: object \n",
340
+ "\n",
341
+ "class: toxic\n",
342
+ "1 понятно что это нарушение правил, писать капсл...\n",
343
+ "5 правильно! это же тихановская 26 лет растила и...\n",
344
+ "13 на хуй, безликая\n",
345
+ "16 дебилов хватает.надо было с головой\n",
346
+ "30 умник хуев. у каждого своё мнение\n",
347
+ "38 на мыло его дегтярное пустить пидора путинского\n",
348
+ "47 неправильно вы называете таких чиновников идио...\n",
349
+ "52 ретранслятор тебе в жопу\n",
350
+ "67 пидор усатый\n",
351
+ "71 а вы пидоры учились платно???,гандоны!!!\n",
352
+ "Name: text, dtype: object \n",
353
+ "\n",
354
+ "Test Dataset\n",
355
+ "class: non-toxic\n",
356
+ "0 видимо в разных регионах называют по разному ,...\n",
357
+ "2 какие классные, жизненные стихи....\n",
358
+ "3 а и правда-когда его запретили?...\n",
359
+ "4 в соленой воде вирусы живут .ученые изучали со...\n",
360
+ "6 ни в одном из близлежащих строительных не нашл...\n",
361
+ "7 какая дрянь сломала? виноваты родители, выраст...\n",
362
+ "8 да, висели такие на стене, над кроватью. в люб...\n",
363
+ "9 потому что мы так воспитаны\n",
364
+ "10 лапти и не только\n",
365
+ "11 привет обмен на дизель без вложений\n",
366
+ "Name: text, dtype: object \n",
367
+ "\n",
368
+ "class: toxic\n",
369
+ "1 понятно что это нарушение правил, писать капсл...\n",
370
+ "5 правильно! это же тихановская 26 лет растила и...\n",
371
+ "13 на хуй, безликая\n",
372
+ "16 дебилов хватает.надо было с головой\n",
373
+ "30 умник хуев. у каждого своё мнение\n",
374
+ "38 на мыло его дегтярное пустить пидора путинского\n",
375
+ "47 неправильно вы называете таких чиновников идио...\n",
376
+ "52 ретранслятор тебе в жопу\n",
377
+ "67 пидор усатый\n",
378
+ "71 а вы пидоры учились платно???,гандоны!!!\n",
379
+ "Name: text, dtype: object \n",
380
+ "\n"
381
+ ]
382
+ }
383
+ ],
384
+ "source": [
385
+ "for name, df in datasets.items():\n",
386
+ " print(name)\n",
387
+ " for label in train['class'].unique():\n",
388
+ " print(f\"class: {label}\")\n",
389
+ " print(train[train['class'] == label]['text'].iloc[:10], \"\\n\")"
390
+ ]
391
+ }
392
+ ],
393
+ "metadata": {
394
+ "kernelspec": {
395
+ "display_name": "venv",
396
+ "language": "python",
397
+ "name": "python3"
398
+ },
399
+ "language_info": {
400
+ "codemirror_mode": {
401
+ "name": "ipython",
402
+ "version": 3
403
+ },
404
+ "file_extension": ".py",
405
+ "mimetype": "text/x-python",
406
+ "name": "python",
407
+ "nbconvert_exporter": "python",
408
+ "pygments_lexer": "ipython3",
409
+ "version": "3.12.3"
410
+ }
411
+ },
412
+ "nbformat": 4,
413
+ "nbformat_minor": 2
414
+ }
notebooks/evaluate.ipynb ADDED
@@ -0,0 +1,329 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {},
7
+ "outputs": [
8
+ {
9
+ "name": "stderr",
10
+ "output_type": "stream",
11
+ "text": [
12
+ "/home/timo/rep/TextClassifier/venv/lib/python3.12/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
13
+ " from .autonotebook import tqdm as notebook_tqdm\n"
14
+ ]
15
+ }
16
+ ],
17
+ "source": [
18
+ "import os\n",
19
+ "os.chdir('..')\n",
20
+ "\n",
21
+ "from src.load_model import model\n",
22
+ "import pandas as pd\n",
23
+ "import numpy as np"
24
+ ]
25
+ },
26
+ {
27
+ "cell_type": "code",
28
+ "execution_count": 2,
29
+ "metadata": {},
30
+ "outputs": [
31
+ {
32
+ "data": {
33
+ "text/html": [
34
+ "<div>\n",
35
+ "<style scoped>\n",
36
+ " .dataframe tbody tr th:only-of-type {\n",
37
+ " vertical-align: middle;\n",
38
+ " }\n",
39
+ "\n",
40
+ " .dataframe tbody tr th {\n",
41
+ " vertical-align: top;\n",
42
+ " }\n",
43
+ "\n",
44
+ " .dataframe thead th {\n",
45
+ " text-align: right;\n",
46
+ " }\n",
47
+ "</style>\n",
48
+ "<table border=\"1\" class=\"dataframe\">\n",
49
+ " <thead>\n",
50
+ " <tr style=\"text-align: right;\">\n",
51
+ " <th></th>\n",
52
+ " <th>text</th>\n",
53
+ " <th>label</th>\n",
54
+ " </tr>\n",
55
+ " </thead>\n",
56
+ " <tbody>\n",
57
+ " <tr>\n",
58
+ " <th>0</th>\n",
59
+ " <td>хорошо пошло!</td>\n",
60
+ " <td>0</td>\n",
61
+ " </tr>\n",
62
+ " <tr>\n",
63
+ " <th>1</th>\n",
64
+ " <td>посмотрела, как будто дома побывала. как река ...</td>\n",
65
+ " <td>0</td>\n",
66
+ " </tr>\n",
67
+ " <tr>\n",
68
+ " <th>2</th>\n",
69
+ " <td>отдам котят 1,5 месяца в добрые руки.</td>\n",
70
+ " <td>0</td>\n",
71
+ " </tr>\n",
72
+ " <tr>\n",
73
+ " <th>3</th>\n",
74
+ " <td>0,5литровая баночка 200р стоит в таганроге. та...</td>\n",
75
+ " <td>0</td>\n",
76
+ " </tr>\n",
77
+ " <tr>\n",
78
+ " <th>4</th>\n",
79
+ " <td>речь шла о радужных зонтиках над верандой.</td>\n",
80
+ " <td>0</td>\n",
81
+ " </tr>\n",
82
+ " <tr>\n",
83
+ " <th>...</th>\n",
84
+ " <td>...</td>\n",
85
+ " <td>...</td>\n",
86
+ " </tr>\n",
87
+ " <tr>\n",
88
+ " <th>24824</th>\n",
89
+ " <td>и ты будь здоров</td>\n",
90
+ " <td>0</td>\n",
91
+ " </tr>\n",
92
+ " <tr>\n",
93
+ " <th>24825</th>\n",
94
+ " <td>не дорога а прям стекло но правда битое (h)</td>\n",
95
+ " <td>0</td>\n",
96
+ " </tr>\n",
97
+ " <tr>\n",
98
+ " <th>24826</th>\n",
99
+ " <td>спасибо большое. буду ждать хороших новостей. ...</td>\n",
100
+ " <td>0</td>\n",
101
+ " </tr>\n",
102
+ " <tr>\n",
103
+ " <th>24827</th>\n",
104
+ " <td>активирую установку 🌈🌈🌈👍😎🔥🔥🔥</td>\n",
105
+ " <td>0</td>\n",
106
+ " </tr>\n",
107
+ " <tr>\n",
108
+ " <th>24828</th>\n",
109
+ " <td>а вы курс российского рубля видели, кошмар!!!</td>\n",
110
+ " <td>0</td>\n",
111
+ " </tr>\n",
112
+ " </tbody>\n",
113
+ "</table>\n",
114
+ "<p>24829 rows × 2 columns</p>\n",
115
+ "</div>"
116
+ ],
117
+ "text/plain": [
118
+ " text label\n",
119
+ "0 хорошо пошло! 0\n",
120
+ "1 посмотрела, как будто дома побывала. как река ... 0\n",
121
+ "2 отдам котят 1,5 месяца в добрые руки. 0\n",
122
+ "3 0,5литровая баночка 200р стоит в таганроге. та... 0\n",
123
+ "4 речь шла о радужных зонтиках над верандой. 0\n",
124
+ "... ... ...\n",
125
+ "24824 и ты будь здоров 0\n",
126
+ "24825 не дорога а прям стекло но правда битое (h) 0\n",
127
+ "24826 спасибо большое. буду ждать хороших новостей. ... 0\n",
128
+ "24827 активирую установку 🌈🌈🌈👍😎🔥🔥🔥 0\n",
129
+ "24828 а вы курс российского рубля видели, кошмар!!! 0\n",
130
+ "\n",
131
+ "[24829 rows x 2 columns]"
132
+ ]
133
+ },
134
+ "execution_count": 2,
135
+ "metadata": {},
136
+ "output_type": "execute_result"
137
+ }
138
+ ],
139
+ "source": [
140
+ "splits = {'train': 'train.jsonl', 'test': 'test.jsonl'}\n",
141
+ "test = pd.read_json(\"hf://datasets/AlexSham/Toxic_Russian_Comments/\" + splits[\"test\"], lines=True)\n",
142
+ "test"
143
+ ]
144
+ },
145
+ {
146
+ "cell_type": "code",
147
+ "execution_count": 3,
148
+ "metadata": {},
149
+ "outputs": [
150
+ {
151
+ "name": "stderr",
152
+ "output_type": "stream",
153
+ "text": [
154
+ "100%|██████████| 97/97 [00:39<00:00, 2.44it/s]\n"
155
+ ]
156
+ }
157
+ ],
158
+ "source": [
159
+ "from tqdm import tqdm\n",
160
+ "\n",
161
+ "# Размер батча\n",
162
+ "batch_size = 256\n",
163
+ "\n",
164
+ "test['pred'] = [\n",
165
+ " model(text).item()\n",
166
+ " for batch in tqdm(range(0, len(test), batch_size))\n",
167
+ " for text in test['text'][batch:batch + batch_size]\n",
168
+ "]"
169
+ ]
170
+ },
171
+ {
172
+ "cell_type": "code",
173
+ "execution_count": 4,
174
+ "metadata": {},
175
+ "outputs": [
176
+ {
177
+ "name": "stdout",
178
+ "output_type": "stream",
179
+ "text": [
180
+ "\n",
181
+ "Classification Report:\n",
182
+ " precision recall f1-score support\n",
183
+ "\n",
184
+ " non-toxic 0.99 0.98 0.98 20369\n",
185
+ " toxic 0.92 0.94 0.93 4460\n",
186
+ "\n",
187
+ " accuracy 0.98 24829\n",
188
+ " macro avg 0.96 0.96 0.96 24829\n",
189
+ "weighted avg 0.98 0.98 0.98 24829\n",
190
+ "\n"
191
+ ]
192
+ }
193
+ ],
194
+ "source": [
195
+ "from sklearn.metrics import classification_report\n",
196
+ "\n",
197
+ "threshold = 0.5\n",
198
+ "\n",
199
+ "pred = test['pred'].apply(lambda x: 1 if x >= threshold else 0)\n",
200
+ "\n",
201
+ "# Вычисление основных метрик\n",
202
+ "class_report = classification_report(test['label'], pred, target_names=['non-toxic', 'toxic'])\n",
203
+ "\n",
204
+ "print(\"\\nClassification Report:\")\n",
205
+ "print(class_report)"
206
+ ]
207
+ },
208
+ {
209
+ "cell_type": "code",
210
+ "execution_count": 5,
211
+ "metadata": {},
212
+ "outputs": [
213
+ {
214
+ "data": {
215
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjcAAAHHCAYAAABDUnkqAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAARLRJREFUeJzt3XlclOX+//H3sA0oIJqCiiQuqbkXpl81M4tcs5+dU3nUFC3LUr/HJE9pG1m5tbhUpuVJrfO1tGzzpGkuaWp2KreTZe5bKqgpoiiDwP37424GRkABh7lhfD0fDx73Pfdc9z2fuTR5d133YjMMwxAAAICP8LO6AAAAAE8i3AAAAJ9CuAEAAD6FcAMAAHwK4QYAAPgUwg0AAPAphBsAAOBTCDcAAMCnEG4AAIBPIdwAV6GBAwcqNja2WPusXr1aNptNq1evLpWayrtbb71Vt956q+v1/v37ZbPZNHfuXMtqAq5WhBvAC+bOnSubzeb6CQ4OVoMGDTR8+HClpKRYXV6Z5wwKzh8/Pz9VqVJF3bp104YNG6wuzyNSUlI0atQoNWrUSBUqVFDFihUVFxenl156SampqVaXB5QrAVYXAFxNXnjhBdWpU0cZGRlat26dZsyYoSVLlmjbtm2qUKGC1+qYNWuWcnJyirXPLbfcovPnzysoKKiUqrq8Pn36qHv37srOztbOnTv11ltvqVOnTvrxxx/VrFkzy+q6Uj/++KO6d++us2fP6v7771dcXJwk6aefftLEiRP17bff6uuvv7a4SqD8INwAXtStWze1atVKkjR48GBdc801mjx5sr744gv16dOnwH3S09NVsWJFj9YRGBhY7H38/PwUHBzs0TqK68Ybb9T999/vet2hQwd169ZNM2bM0FtvvWVhZSWXmpqqu+++W/7+/tq8ebMaNWrk9v64ceM0a9Ysj3xWafxdAsoipqUAC912222SpH379kkyz4UJDQ3Vnj171L17d4WFhalfv36SpJycHE2dOlVNmjRRcHCwoqKiNGTIEJ06dSrfcb/66it17NhRYWFhCg8P10033aQPPvjA9X5B59zMnz9fcXFxrn2aNWumadOmud4v7Jybjz/+WHFxcQoJCVHVqlV1//336/Dhw25tnN/r8OHD6tWrl0JDQ1WtWjWNGjVK2dnZJe6/Dh06SJL27Nnjtj01NVWPPfaYYmJiZLfbVb9+fU2aNCnfaFVOTo6mTZumZs2aKTg4WNWqVVPXrl31008/udrMmTNHt912myIjI2W329W4cWPNmDGjxDVf7O2339bhw4c1efLkfMFGkqKiovTMM8+4XttsNj3//PP52sXGxmrgwIGu186p0DVr1mjo0KGKjIxUrVq1tHDhQtf2gmqx2Wzatm2ba9tvv/2me+65R1WqVFFwcLBatWqlRYsWXdmXBkoZIzeAhZy/lK+55hrXtqysLHXp0kU333yzXn31Vdd01ZAhQzR37lwNGjRIf//737Vv3z69+eab2rx5s9avX+8ajZk7d64eeOABNWnSRGPGjFFERIQ2b96spUuXqm/fvgXWsXz5cvXp00e33367Jk2aJEnavn271q9frxEjRhRav7Oem266SRMmTFBKSoqmTZum9evXa/PmzYqIiHC1zc7OVpcuXdSmTRu9+uqrWrFihV577TXVq1dPjz76aIn6b//+/ZKkypUru7adO3dOHTt21OHDhzVkyBBde+21+u677zRmzBgdPXpUU6dOdbV98MEHNXfuXHXr1k2DBw9WVlaW1q5dq++//941wjZjxgw1adJEd911lwICAvTvf/9bQ4cOVU5OjoYNG1aiuvNatGiRQkJCdM8991zxsQoydOhQVatWTc8995zS09PVo0cPhYaG6qOPPlLHjh3d2i5YsEBNmjRR06ZNJUm//PKL2rdvr+joaI0ePVoVK1bURx99pF69eumTTz7R3XffXSo1A1fMAFDq5syZY0gyVqxYYRw/ftw4dOiQMX/+fOOaa64xQkJCjN9//90wDMNISEgwJBmjR49223/t2rWGJGPevHlu25cuXeq2PTU11QgLCzPatGljnD9/3q1tTk6Oaz0hIcGoXbu26/WIESOM8PBwIysrq9Dv8M033xiSjG+++cYwDMPIzMw0IiMjjaZNm7p91pdffmlIMp577jm3z5NkvPDCC27HvOGGG4y4uLhCP9Np3759hiRj7NixxvHjx43k5GRj7dq1xk033WRIMj7++GNX2xdffNGoWLGisXPnTrdjjB492vD39zcOHjxoGIZhrFq1ypBk/P3vf8/3eXn76ty5c/ne79Kli1G3bl23bR07djQ6duyYr+Y5c+Zc8rtVrlzZaNGixSXb5CXJSEpKyre9du3aRkJCguu18+/czTffnO/PtU+fPkZkZKTb9qNHjxp+fn5uf0a333670axZMyMjI8O1LScnx2jXrp1x3XXXFblmwNuYlgK8KD4+XtWqVVNMTIz+9re/KTQ0VJ999pmio6Pd2l08kvHxxx+rUqVKuuOOO3TixAnXT1xcnEJDQ/XNN99IMkdgzpw5o9GjR+c7P8ZmsxVaV0REhNLT07V8+fIif5effvpJx44d09ChQ90+q0ePHmrUqJEWL16cb59HHnnE7XWHDh20d+/eIn9mUlKSqlWrpurVq6tDhw7avn27XnvtNbdRj48//lgdOnRQ5cqV3foqPj5e2dnZ+vbbbyVJn3zyiWw2m5KSkvJ9Tt6+CgkJca2fPn1aJ06cUMeOHbV3716dPn26yLUXJi0tTWFhYVd8nMI89NBD8vf3d9vWu3dvHTt2zG2KceHChcrJyVHv3r0lSSdPntSqVat033336cyZM65+/OOPP9SlSxft2rUr3/QjUFYwLQV40fTp09WgQQMFBAQoKipKDRs2lJ+f+/9jBAQEqFatWm7bdu3apdOnTysyMrLA4x47dkxS7jSXc1qhqIYOHaqPPvpI3bp1U3R0tDp37qz77rtPXbt2LXSfAwcOSJIaNmyY771GjRpp3bp1btuc57TkVblyZbdzho4fP+52Dk5oaKhCQ0Ndrx9++GHde++9ysjI0KpVq/T666/nO2dn165d+u9//5vvs5zy9lXNmjVVpUqVQr+jJK1fv15JSUnasGGDzp075/be6dOnValSpUvufznh4eE6c+bMFR3jUurUqZNvW9euXVWpUiUtWLBAt99+uyRzSqply5Zq0KCBJGn37t0yDEPPPvusnn322QKPfezYsXzBHCgLCDeAF7Vu3dp1Lkdh7HZ7vsCTk5OjyMhIzZs3r8B9CvtFXlSRkZHasmWLli1bpq+++kpfffWV5syZowEDBui99967omM7XTx6UJCbbrrJFZokc6Qm78mz1113neLj4yVJd955p/z9/TV69Gh16tTJ1a85OTm644479MQTTxT4Gc5f3kWxZ88e3X777WrUqJEmT56smJgYBQUFacmSJZoyZUqxL6cvSKNGjbRlyxZlZmZe0WX2hZ2YnXfkyclut6tXr1767LPP9NZbbyklJUXr16/X+PHjXW2c323UqFHq0qVLgceuX79+iesFShPhBigH6tWrpxUrVqh9+/YF/rLK206Stm3bVuxfPEFBQerZ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BXO5YIG1dRSEcAMAAIpt/35zycgNAADwCcOGmcuAAGvrKAjhBgAAFFtoqLksizfyI9wAAIBic47YEG4AAIBPcIabsniXYsINAAAoNuddirdutbaOghBuAABAsaWmmktu4gcAAHxCw4bmskoVa+soCOEGAAAUW0SEueScGwAA4BPyPhm8rCHcAACAYnOGm7Nnra2jICW6r2B2drbmzp2rlStX6tixY8rJyXF7f9WqVR4pDgAAlE3+/uZy2zZr6yhIicLNiBEjNHfuXPXo0UNNmzaVzWbzdF0AAKAMq17dXDrvVFyWlCjczJ8/Xx999JG6d+/u6XoAAEA5ULmyuczMtLaOgpTonJugoCDVr1/f07UAAIByIjjYXGZmlr1HMJQo3Dz++OOaNm2aDMPwdD0AAKAcqFQpd/3rr62royAlmpZat26dvvnmG3311Vdq0qSJAp2nTP/p008/9UhxAACgbLLbc9fPnLGujoKUKNxERETo7rvv9nQtAACgHOnYUVqzRiprEzklCjdz5szxdB0AAKCccU7cZGdbW8fFShRunI4fP64dO3ZIkho2bKhq1ap5pCgAAFD2BfyZIspauCnRCcXp6el64IEHVKNGDd1yyy265ZZbVLNmTT344IM6VxYfDwoAADzOeSM/n7haKjExUWvWrNG///1vpaamKjU1VV988YXWrFmjxx9/3NM1AgCAMsgZbsrayE2JpqU++eQTLVy4ULfeeqtrW/fu3RUSEqL77rtPM2bM8FR9AACgjPKpaalz584pKioq3/bIyEimpQAAuEr4/Zki1q61to6LlSjctG3bVklJScrIyHBtO3/+vMaOHau2bdt6rDgAAFB27d1rLmvVsraOi5VoWmratGnq0qWLatWqpRYtWkiStm7dquDgYC1btsyjBQIAgLLpjjukTZvK3gnFJQo3TZs21a5duzRv3jz99ttvkqQ+ffqoX79+CgkJ8WiBAACgbHKec+MT4UaSKlSooIceesiTtQAAgHKk3IebRYsWqVu3bgoMDNSiRYsu2fauu+664sIAAEDZVu7DTa9evZScnKzIyEj16tWr0HY2m03ZxbgmbPr06XrllVeUnJysFi1a6I033lDr1q0LbZ+amqqnn35an376qU6ePKnatWtr6tSp6t69e5E/EwAAXDlnuNm82do6LlbkcJOTk1Pg+pVYsGCBEhMTNXPm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b8rhcGj8+PFasWKF2rZtK0mqW7eu1q1bp7ffflsdO3bU9OnTValSJc2fP1+BgYGSpAYNGriOfdttt7l91jvvvKOIiAitWbNGd955p/e+JACvINwAKBM6deqkGTNmKD09XVOmTFFAQID++te/6pdfftG5c+d0xx13uLXPzMzUDTfcIEnasmWLOnTo4Ao2F0tJSdEzzzyj1atX69ixY8rOzta5c+d08ODBUv9eALyPcAOgTKhYsaLq168vSZo9e7ZatGihd999V02bNpUkLV68WNHR0W772O12SVJISMglj52QkKA//vhD06ZNU+3atWW329W2bVtlZmaWwjcBYDXCDYAyx8/PT0899ZQSExO1c+dO2e12HTx4UB07diywffPmzfXee+/pwoULBY7erF+/Xm+99Za6d+8uSTp06JBOnDhRqt8BgHU4oRhAmXTvvffK399fb7/9tkaNGqWRI0fqvffe0549e7Rp0ya98cYbeu+99yRJw4cPV1pamv72t7/pp59+0q5du/Svf/1LO3bskCRdd911+te//qXt27frP//5j/r163fZ0R4A5RcjNwDKpICAAA0fPlwvv/yy9u3bp2rVqmnChAnau3evIiIidOONN+qpp56SJF1zzTVatWqV/vGPf6hjx47y9/dXy5Yt1b59e0nSu+++q4cfflg33nijYmJiNH78eI0aNcrKrwegFNkMwzCsLgIAAMBTmJYCAAA+hXADAAB8CuEGAAD4FMINAADwKYQbAADgUwg3AADApxBuAACATyHcAAAAn0K4AQAAPoVwAwAAfArhBgAA+BTCDQAA8Cn/H/xsqri4J5o/AAAAAElFTkSuQmCC",
216
+ "text/plain": [
217
+ "<Figure size 640x480 with 1 Axes>"
218
+ ]
219
+ },
220
+ "metadata": {},
221
+ "output_type": "display_data"
222
+ }
223
+ ],
224
+ "source": [
225
+ "import matplotlib.pyplot as plt\n",
226
+ "from sklearn.metrics import precision_recall_curve\n",
227
+ "\n",
228
+ "# Предсказания модели: вероятности для положительного класса\n",
229
+ "y_probs = test['pred']\n",
230
+ "\n",
231
+ "# Истинные метки\n",
232
+ "y_true = test['label']\n",
233
+ "\n",
234
+ "# Вычисление Precision-Recall кривой для разных порогов\n",
235
+ "precision, recall, thresholds = precision_recall_curve(y_true, y_probs)\n",
236
+ "\n",
237
+ "# Построение Precision-Recall кривой\n",
238
+ "plt.plot(recall, precision, \n",
239
+ " color='blue',)\n",
240
+ "plt.grid(\n",
241
+ " color='gray',\n",
242
+ ")\n",
243
+ "plt.xlabel('Recall')\n",
244
+ "plt.ylabel('Precision')\n",
245
+ "plt.title('Precision-Recall Curve')\n",
246
+ "plt.show()"
247
+ ]
248
+ },
249
+ {
250
+ "cell_type": "code",
251
+ "execution_count": 6,
252
+ "metadata": {},
253
+ "outputs": [
254
+ {
255
+ "data": {
256
+ "text/plain": [
257
+ "0.7482494115829468"
258
+ ]
259
+ },
260
+ "execution_count": 6,
261
+ "metadata": {},
262
+ "output_type": "execute_result"
263
+ }
264
+ ],
265
+ "source": [
266
+ "def get_thresholds(thresholds_precision):\n",
267
+ " return thresholds[np.where(precision>=thresholds_precision)[0][0]].item()\n",
268
+ "\n",
269
+ "get_thresholds(0.95)"
270
+ ]
271
+ },
272
+ {
273
+ "cell_type": "code",
274
+ "execution_count": 7,
275
+ "metadata": {},
276
+ "outputs": [
277
+ {
278
+ "name": "stdout",
279
+ "output_type": "stream",
280
+ "text": [
281
+ "\n",
282
+ "Classification Report:\n",
283
+ " precision recall f1-score support\n",
284
+ "\n",
285
+ " non-toxic 0.98 0.99 0.99 20369\n",
286
+ " toxic 0.95 0.91 0.93 4460\n",
287
+ "\n",
288
+ " accuracy 0.98 24829\n",
289
+ " macro avg 0.97 0.95 0.96 24829\n",
290
+ "weighted avg 0.98 0.98 0.98 24829\n",
291
+ "\n"
292
+ ]
293
+ }
294
+ ],
295
+ "source": [
296
+ "threshold = 0.75\n",
297
+ "\n",
298
+ "pred = test['pred'].apply(lambda x: 1 if x >= threshold else 0)\n",
299
+ "\n",
300
+ "# Вычисление основных метрик\n",
301
+ "class_report = classification_report(test['label'], pred, target_names=['non-toxic', 'toxic'])\n",
302
+ "\n",
303
+ "print(\"\\nClassification Report:\")\n",
304
+ "print(class_report)"
305
+ ]
306
+ }
307
+ ],
308
+ "metadata": {
309
+ "kernelspec": {
310
+ "display_name": "venv",
311
+ "language": "python",
312
+ "name": "python3"
313
+ },
314
+ "language_info": {
315
+ "codemirror_mode": {
316
+ "name": "ipython",
317
+ "version": 3
318
+ },
319
+ "file_extension": ".py",
320
+ "mimetype": "text/x-python",
321
+ "name": "python",
322
+ "nbconvert_exporter": "python",
323
+ "pygments_lexer": "ipython3",
324
+ "version": "3.12.3"
325
+ }
326
+ },
327
+ "nbformat": 4,
328
+ "nbformat_minor": 2
329
+ }
src/__init__.py ADDED
File without changes
src/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (142 Bytes). View file
 
src/__pycache__/bert.cpython-312.pyc ADDED
Binary file (1.38 kB). View file
 
src/__pycache__/classifier.cpython-312.pyc ADDED
Binary file (1.27 kB). View file
 
src/__pycache__/dataset.cpython-312.pyc ADDED
Binary file (1.75 kB). View file
 
src/__pycache__/load_model.cpython-312.pyc ADDED
Binary file (873 Bytes). View file
 
src/__pycache__/train.cpython-312.pyc ADDED
Binary file (5.94 kB). View file
 
src/bert.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from torch import nn
3
+ from transformers import AutoModel, AutoTokenizer
4
+
5
+ class Bert(nn.Module):
6
+ def __init__(self, model_name):
7
+ super().__init__()
8
+ self.model = AutoModel.from_pretrained(model_name)
9
+ self.tokenizer = AutoTokenizer.from_pretrained(model_name)
10
+
11
+ def forward(self, texts):
12
+ inputs = self.tokenizer(
13
+ texts,
14
+ padding=True,
15
+ truncation=True,
16
+ return_tensors='pt'
17
+ ).to(self.model.device)
18
+ outputs = self.model(**inputs)
19
+ return outputs.last_hidden_state[:, 0, :]
src/classifier.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+
4
+ class Classifier(nn.Module):
5
+ def __init__(self, bert_model):
6
+ super().__init__()
7
+ self.bert = bert_model
8
+ self.head = nn.Linear(self.bert.model.config.hidden_size, 1)
9
+
10
+ def forward(self, texts:list[str]):
11
+ embeddings = self.bert(texts)
12
+ return torch.sigmoid(self.head(embeddings)).squeeze(1)
src/dataset.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from torch.utils.data import Dataset
2
+
3
+ class TextDataset(Dataset):
4
+ def __init__(self, df):
5
+ self.texts = df['text'].tolist()
6
+ self.labels = df['label'].tolist()
7
+
8
+ def __len__(self):
9
+ return len(self.texts)
10
+
11
+ def __getitem__(self, idx):
12
+ text = self.texts[idx]
13
+ label = self.labels[idx]
14
+ return text, label
15
+
16
+ if __name__ == "__main__":
17
+ import pandas as pd
18
+ import torch
19
+
20
+ splits = {'train': 'train.jsonl', 'test': 'test.jsonl'}
21
+
22
+ df_train = pd.read_json("hf://datasets/AlexSham/Toxic_Russian_Comments/" + splits["train"], lines=True)
23
+ df_test = pd.read_json("hf://datasets/AlexSham/Toxic_Russian_Comments/" + splits["test"], lines=True)
24
+
25
+ dataset_train = TextDataset(df_train)
26
+ dataset_test = TextDataset(df_test)
27
+
28
+ torch.save(dataset_train, 'data/dataset_train.pt')
29
+ torch.save(dataset_test, 'data/dataset_test.pt')
src/load_model.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import yaml
3
+
4
+ from .classifier import Classifier
5
+ from .bert import Bert
6
+
7
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
8
+
9
+ config = yaml.safe_load(open('config.yaml', 'r'))
10
+
11
+ model = Classifier(Bert(config['model']['bert_name']))
12
+
13
+ model.load_state_dict(torch.load(config['predct']['use_model'], map_location=torch.device(device), weights_only=True))
14
+
15
+ model = model.to(device)
16
+
17
+ model.eval()
src/train.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import yaml
3
+ import torch
4
+ import time
5
+ from torch import nn, optim
6
+ from torch.utils.data import DataLoader
7
+ from tqdm import tqdm
8
+ import os
9
+ import pandas as pd
10
+
11
+ from .classifier import Classifier
12
+ from .bert import Bert
13
+ from .dataset import TextDataset
14
+
15
+ def setup_logging(config):
16
+ logging.basicConfig(
17
+ filename=os.path.join(config['logging']['log_dir'], "log.log"),
18
+ filemode='w',
19
+ level=config['logging']['level'],
20
+ format=config['logging']['format']
21
+ )
22
+ return logging.getLogger(__name__)
23
+
24
+ def evaluate(model, dataloader, criterion):
25
+ model.eval()
26
+ total_loss = 0.0
27
+ correct = 0
28
+ total = 0
29
+
30
+ with torch.no_grad():
31
+ for texts, labels in dataloader:
32
+ labels = labels.float().to(device)
33
+ outputs = model(texts).squeeze()
34
+
35
+ loss = criterion(outputs, labels)
36
+ total_loss += loss.item() * labels.size(0)
37
+ correct += ((outputs >= 0.5).float() == labels).sum().item()
38
+ total += labels.size(0)
39
+
40
+ return total_loss / total, correct / total
41
+
42
+ if __name__ == "__main__":
43
+ config = yaml.safe_load(open("config.yaml"))
44
+ logger = setup_logging(config)
45
+
46
+ logger.info("Starting training process")
47
+ logger.info(f"Configuration: {config}")
48
+
49
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
50
+
51
+ # Инициализация модели
52
+ model = Classifier(Bert(config['model']['bert_name'])).to(device)
53
+ logger.info("Model initialized")
54
+
55
+ # Загрузка данных
56
+ train_dataset = torch.load(config['data']['train_path'])
57
+ test_dataset = torch.load(config['data']['test_path'])
58
+
59
+ train_loader = DataLoader(
60
+ train_dataset,
61
+ batch_size=int(config['data']['batch_size']),
62
+ shuffle=True
63
+ )
64
+ test_loader = DataLoader(
65
+ test_dataset,
66
+ batch_size=int(config['data']['batch_size']),
67
+ shuffle=False
68
+ )
69
+
70
+ logger.info(f"Train samples: {len(train_dataset)}, Test samples: {len(test_dataset)}")
71
+
72
+ # Оптимизатор
73
+ optimizer = optim.Adam(
74
+ model.parameters(),
75
+ lr=float(config['training']['learning_rate'])
76
+ )
77
+ criterion = nn.BCELoss()
78
+
79
+ # Для записи результатов обучения
80
+ results = []
81
+
82
+ for epoch in range(int(config['training']['epochs'])):
83
+ start_time = time.time()
84
+
85
+ # Обучение
86
+ model.train()
87
+ train_loss, train_correct = 0.0, 0
88
+
89
+ for texts, labels in tqdm(train_loader, desc=f"Epoch {epoch+1}", ncols=100):
90
+ labels = labels.float().to(device)
91
+ optimizer.zero_grad()
92
+
93
+ outputs = model(texts).squeeze()
94
+ loss = criterion(outputs, labels)
95
+ loss.backward()
96
+ optimizer.step()
97
+
98
+ train_loss += loss.item() * labels.size(0)
99
+ train_correct += ((outputs >= 0.5).float() == labels).sum().item()
100
+
101
+ # Оценка
102
+ train_loss /= len(train_dataset)
103
+ train_acc = train_correct / len(train_dataset)
104
+ test_loss, test_acc = evaluate(model, test_loader, criterion)
105
+
106
+ # Сохранение результатов
107
+ results.append({
108
+ "epoch": epoch + 1,
109
+ "train_loss": train_loss,
110
+ "test_loss": test_loss,
111
+ "train_acc": train_acc,
112
+ "test_acc": test_acc
113
+ })
114
+
115
+ # Логирование
116
+ epoch_time = time.time() - start_time
117
+ logger.info(f"Epoch {epoch+1} [{epoch_time:.1f}s]")
118
+ logger.info(f"Train Loss: {train_loss:.4f} | Acc: {train_acc:.4f}")
119
+ logger.info(f"Test Loss: {test_loss:.4f} | Acc: {test_acc:.4f}")
120
+
121
+ torch.save(model.state_dict(),
122
+ os.path.join(config['training']['save_dir'], f"model_{epoch+1}.pth"))
123
+
124
+ # Сохранение результатов в CSV
125
+ results_df = pd.DataFrame(results)
126
+ results_df.to_csv(os.path.join(config['logging']['log_dir'], "training_results.csv"), index=False)
127
+
128
+ # Финализация обучения
129
+ logger.info("Training completed")