From e63b79b0d877c7322e38e465256572ade159a5ba Mon Sep 17 00:00:00 2001 From: Troyanov Daniil Date: Wed, 10 Dec 2025 21:53:01 +0300 Subject: [PATCH] LW4 done --- labworks/LW4/lab4.ipynb | 751 ++++++++++++++++++++++++++++++++ labworks/LW4/lab4_no_outs.ipynb | 451 +++++++++++++++++++ labworks/LW4/report.md | 343 +++++++++++++++ labworks/LW4/roc_curve.png | Bin 0 -> 35636 bytes 4 files changed, 1545 insertions(+) create mode 100644 labworks/LW4/lab4.ipynb create mode 100644 labworks/LW4/lab4_no_outs.ipynb create mode 100644 labworks/LW4/report.md create mode 100644 labworks/LW4/roc_curve.png diff --git a/labworks/LW4/lab4.ipynb b/labworks/LW4/lab4.ipynb new file mode 100644 index 0000000..8008782 --- /dev/null +++ b/labworks/LW4/lab4.ipynb @@ -0,0 +1,751 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "gz18QPRz03Ec" + }, + "source": [ + "### 1) В среде Google Colab создали новый блокнот (notebook). Импортировали необходимые для работы библиотеки и модули." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "id": "mr9IszuQ1ANG" + }, + "outputs": [], + "source": [ + "# импорт модулей\n", + "import os\n", + "\n", + "from tensorflow import keras\n", + "from tensorflow.keras import layers\n", + "from tensorflow.keras.models import Sequential\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "FFRtE0TN1AiA" + }, + "source": [ + "### 2) Загрузили набор данных IMDb, содержащий оцифрованные отзывы на фильмы, размеченные на два класса: позитивные и негативные. При загрузке набора данных параметр seed выбрали равным значению (4k – 1)=3, где k=1 – номер бригады. Вывели размеры полученных обучающих и тестовых массивов данных." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "id": "Ixw5Sp0_1A-w" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Shape of X train: (25000,)\n", + "Shape of y train: (25000,)\n", + "Shape of X test: (25000,)\n", + "Shape of y test: (25000,)\n" + ] + } + ], + "source": [ + "# загрузка датасета\n", + "import ssl\n", + "ssl._create_default_https_context = ssl._create_unverified_context\n", + "\n", + "from keras.datasets import imdb\n", + "\n", + "vocabulary_size = 5000\n", + "index_from = 3\n", + "\n", + "(X_train, y_train), (X_test, y_test) = imdb.load_data(\n", + " path=\"imdb.npz\",\n", + " num_words=vocabulary_size,\n", + " skip_top=0,\n", + " maxlen=None,\n", + " seed=3,\n", + " start_char=1,\n", + " oov_char=2,\n", + " index_from=index_from\n", + " )\n", + "\n", + "# вывод размерностей\n", + "print('Shape of X train:', X_train.shape)\n", + "print('Shape of y train:', y_train.shape)\n", + "print('Shape of X test:', X_test.shape)\n", + "print('Shape of y test:', y_test.shape)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "aCo_lUXl1BPV" + }, + "source": [ + "### 3) Вывели один отзыв из обучающего множества в виде списка индексов слов. Преобразовали список индексов в текст и вывели отзыв в виде текста. Вывели длину отзыва. Вывели метку класса данного отзыва и название класса (1 – Positive, 0 – Negative)." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "id": "9W3RklPcZyH0" + }, + "outputs": [], + "source": [ + "# создание словаря для перевода индексов в слова\n", + "# загрузка словаря \"слово:индекс\"\n", + "word_to_id = imdb.get_word_index()\n", + "# уточнение словаря\n", + "word_to_id = {key:(value + index_from) for key,value in word_to_id.items()}\n", + "word_to_id[\"\"] = 0\n", + "word_to_id[\"\"] = 1\n", + "word_to_id[\"\"] = 2\n", + "word_to_id[\"\"] = 3\n", + "# создание обратного словаря \"индекс:слово\"\n", + "id_to_word = {value:key for key,value in word_to_id.items()}" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "id": "Nu-Bs1jnaYhB" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[1, 37, 1388, 4, 2739, 495, 94, 96, 143, 49, 2, 875, 551, 19, 195, 2210, 5, 1698, 8, 401, 4, 65, 24, 64, 1728, 21, 400, 642, 45, 77, 6, 137, 237, 207, 258, 141, 6, 1562, 1301, 1562, 737, 22, 10, 10, 4, 22, 9, 1490, 3862, 4, 744, 19, 307, 1385, 5, 2, 2, 4, 2, 2656, 2, 1669, 19, 4, 1074, 200, 4, 55, 406, 55, 3048, 5, 246, 55, 1451, 105, 688, 8, 4, 321, 177, 32, 677, 7, 4, 678, 1850, 26, 1669, 221, 5, 3921, 10, 10, 13, 386, 37, 1388, 4, 2739, 45, 6, 66, 163, 20, 15, 304, 6, 3049, 168, 33, 4, 4352, 15, 75, 70, 2, 23, 257, 85, 5, 4, 2789, 878, 21, 1305, 2, 1773, 7, 2]\n", + "len: 130\n" + ] + } + ], + "source": [ + "print(X_train[26])\n", + "print('len:',len(X_train[26]))" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "id": "JhTwTurtZ6Sp" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " who loves the sun works its way through some subject matter with enough wit and grace to keep the story not only engaging but often hilarious it's been a while since i've found such a thoroughly touching thoroughly enjoyable film br br the film is gorgeous drawing the eye with beautiful scenery and the imagery wonderfully with the tension between the very human very flawed and yet very likable characters due to the excellent cast all five of the major players are wonderfully interesting and dynamic br br i recommend who loves the sun it's a really funny movie that takes a poignant look at the hurts that we can on each other and the amazingly difficult but equally process of \n", + "len: 738\n", + "Label: 1\n", + "Class: Positive\n" + ] + } + ], + "source": [ + "review_as_text = ' '.join(id_to_word[id] for id in X_train[26])\n", + "print(review_as_text)\n", + "print('len:',len(review_as_text))\n", + "\n", + "# вывод метки класса и названия класса\n", + "print('Label:', y_train[26])\n", + "print('Class:', 'Positive' if y_train[26] == 1 else 'Negative')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "4hclnNaD1BuB" + }, + "source": [ + "### 4) Вывели максимальную и минимальную длину отзыва в обучающем множестве." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "id": "xJH87ISq1B9h" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MAX Len: 2494\n", + "MIN Len: 11\n" + ] + } + ], + "source": [ + "print('MAX Len: ',len(max(X_train, key=len)))\n", + "print('MIN Len: ',len(min(X_train, key=len)))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "7x99O8ig1CLh" + }, + "source": [ + "### 5) Провели предобработку данных. Выбрали единую длину, к которой будут приведены все отзывы. Короткие отзывы дополнили спецсимволами, а длинные обрезали до выбранной длины." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "id": "lrF-B2aScR4t" + }, + "outputs": [], + "source": [ + "# предобработка данных\n", + "from tensorflow.keras.utils import pad_sequences\n", + "max_words = 500\n", + "X_train = pad_sequences(X_train, maxlen=max_words, value=0, padding='pre', truncating='post')\n", + "X_test = pad_sequences(X_test, maxlen=max_words, value=0, padding='pre', truncating='post')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "HL2_LVga1C3l" + }, + "source": [ + "### 6) Повторили пункт 4." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "id": "81Cgq8dn9uL6" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MAX Len: 500\n", + "MIN Len: 500\n" + ] + } + ], + "source": [ + "print('MAX Len: ',len(max(X_train, key=len)))\n", + "print('MIN Len: ',len(min(X_train, key=len)))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "KzrVY1SR1DZh" + }, + "source": [ + "### 7) Повторили пункт 3. Сделали вывод о том, как отзыв преобразовался после предобработки." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "id": "vudlgqoCbjU1" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n", + " 0 0 0 0 0 0 1 37 1388 4 2739 495 94 96\n", + " 143 49 2 875 551 19 195 2210 5 1698 8 401 4 65\n", + " 24 64 1728 21 400 642 45 77 6 137 237 207 258 141\n", + " 6 1562 1301 1562 737 22 10 10 4 22 9 1490 3862 4\n", + " 744 19 307 1385 5 2 2 4 2 2656 2 1669 19 4\n", + " 1074 200 4 55 406 55 3048 5 246 55 1451 105 688 8\n", + " 4 321 177 32 677 7 4 678 1850 26 1669 221 5 3921\n", + " 10 10 13 386 37 1388 4 2739 45 6 66 163 20 15\n", + " 304 6 3049 168 33 4 4352 15 75 70 2 23 257 85\n", + " 5 4 2789 878 21 1305 2 1773 7 2]\n", + "len: 500\n" + ] + } + ], + "source": [ + "print(X_train[26])\n", + "print('len:',len(X_train[26]))" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "id": "dbfkWjDI1Dp7" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " who loves the sun works its way through some subject matter with enough wit and grace to keep the story not only engaging but often hilarious it's been a while since i've found such a thoroughly touching thoroughly enjoyable film br br the film is gorgeous drawing the eye with beautiful scenery and the imagery wonderfully with the tension between the very human very flawed and yet very likable characters due to the excellent cast all five of the major players are wonderfully interesting and dynamic br br i recommend who loves the sun it's a really funny movie that takes a poignant look at the hurts that we can on each other and the amazingly difficult but equally process of \n", + "len: 2958\n" + ] + } + ], + "source": [ + "review_as_text = ' '.join(id_to_word[id] for id in X_train[26])\n", + "print(review_as_text)\n", + "print('len:',len(review_as_text))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "mJNRXo5TdPAE" + }, + "source": [ + "#### В результате предобработки данных все отзывы были приведены к единой длине 500 токенов. Для отзывов, исходная длина которых была меньше 500, в начало последовательности были добавлены специальные токены заполнения (со значением 0). Это обеспечило единообразие входных данных для нейронной сети и позволило эффективно обрабатывать последовательности различной длины." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "YgiVGr5_1D3u" + }, + "source": [ + "### 8) Вывели предобработанные массивы обучающих и тестовых данных и их размерности." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "id": "7MqcG_wl1EHI" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "X train: \n", + " [[ 0 0 0 ... 12 38 76]\n", + " [ 0 0 0 ... 33 4 130]\n", + " [ 0 0 0 ... 437 7 58]\n", + " ...\n", + " [ 0 0 0 ... 1874 1553 422]\n", + " [ 0 0 0 ... 18 1552 234]\n", + " [ 0 0 0 ... 7 87 1090]]\n", + "X test: \n", + " [[ 0 0 0 ... 6 194 717]\n", + " [ 0 0 0 ... 30 87 292]\n", + " [ 0 0 0 ... 495 55 73]\n", + " ...\n", + " [ 0 0 0 ... 7 12 908]\n", + " [ 0 0 0 ... 61 477 2302]\n", + " [ 0 0 0 ... 5 68 4580]]\n", + "Shape of X train: (25000, 500)\n", + "Shape of X test: (25000, 500)\n" + ] + } + ], + "source": [ + "# вывод данных\n", + "print('X train: \\n',X_train)\n", + "print('X test: \\n',X_test)\n", + "\n", + "# вывод размерностей\n", + "print('Shape of X train:', X_train.shape)\n", + "print('Shape of X test:', X_test.shape)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "amaspXGW1EVy" + }, + "source": [ + "### 9) Реализовали модель рекуррентной нейронной сети, состоящей из слоев Embedding, LSTM, Dropout, Dense, и обучили ее на обучающих данных с выделением части обучающих данных в качестве валидационных. Вывели информацию об архитектуре нейронной сети. Добились качества обучения по метрике accuracy не менее 0.8." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "id": "ktWEeqWd1EyF" + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/keras/src/layers/core/embedding.py:97: UserWarning: Argument `input_length` is deprecated. Just remove it.\n", + " warnings.warn(\n", + "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/keras/src/layers/core/embedding.py:100: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.\n", + " super().__init__(**kwargs)\n" + ] + }, + { + "data": { + "text/html": [ + "
Model: \"sequential_1\"\n",
+              "
\n" + ], + "text/plain": [ + "\u001b[1mModel: \"sequential_1\"\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n",
+              "┃ Layer (type)                     Output Shape                  Param # ┃\n",
+              "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n",
+              "│ embedding_1 (Embedding)         │ (None, 500, 32)        │       160,000 │\n",
+              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+              "│ lstm_1 (LSTM)                   │ (None, 64)             │        24,832 │\n",
+              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+              "│ dropout_1 (Dropout)             │ (None, 64)             │             0 │\n",
+              "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+              "│ dense_1 (Dense)                 │ (None, 1)              │            65 │\n",
+              "└─────────────────────────────────┴────────────────────────┴───────────────┘\n",
+              "
\n" + ], + "text/plain": [ + "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n", + "┃\u001b[1m \u001b[0m\u001b[1mLayer (type) \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1mOutput Shape \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1m Param #\u001b[0m\u001b[1m \u001b[0m┃\n", + "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n", + "│ embedding_1 (\u001b[38;5;33mEmbedding\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m500\u001b[0m, \u001b[38;5;34m32\u001b[0m) │ \u001b[38;5;34m160,000\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ lstm_1 (\u001b[38;5;33mLSTM\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m64\u001b[0m) │ \u001b[38;5;34m24,832\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ dropout_1 (\u001b[38;5;33mDropout\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m64\u001b[0m) │ \u001b[38;5;34m0\u001b[0m │\n", + "├─────────────────────────────────┼────────────────────────┼───────────────┤\n", + "│ dense_1 (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m1\u001b[0m) │ \u001b[38;5;34m65\u001b[0m │\n", + "└─────────────────────────────────┴────────────────────────┴───────────────┘\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Total params: 184,897 (722.25 KB)\n",
+              "
\n" + ], + "text/plain": [ + "\u001b[1m Total params: \u001b[0m\u001b[38;5;34m184,897\u001b[0m (722.25 KB)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Trainable params: 184,897 (722.25 KB)\n",
+              "
\n" + ], + "text/plain": [ + "\u001b[1m Trainable params: \u001b[0m\u001b[38;5;34m184,897\u001b[0m (722.25 KB)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
 Non-trainable params: 0 (0.00 B)\n",
+              "
\n" + ], + "text/plain": [ + "\u001b[1m Non-trainable params: \u001b[0m\u001b[38;5;34m0\u001b[0m (0.00 B)\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "embed_dim = 32\n", + "lstm_units = 64\n", + "\n", + "model = Sequential()\n", + "model.add(layers.Embedding(input_dim=vocabulary_size, output_dim=embed_dim, input_length=max_words, input_shape=(max_words,)))\n", + "model.add(layers.LSTM(lstm_units))\n", + "model.add(layers.Dropout(0.5))\n", + "model.add(layers.Dense(1, activation='sigmoid'))\n", + "\n", + "model.summary()" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "id": "CuPqKpX0kQfP" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Epoch 1/3\n", + "\u001b[1m313/313\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m108s\u001b[0m 338ms/step - accuracy: 0.7596 - loss: 0.4853 - val_accuracy: 0.8086 - val_loss: 0.4447\n", + "Epoch 2/3\n", + "\u001b[1m313/313\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m85s\u001b[0m 273ms/step - accuracy: 0.8680 - loss: 0.3281 - val_accuracy: 0.8228 - val_loss: 0.3993\n", + "Epoch 3/3\n", + "\u001b[1m313/313\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m84s\u001b[0m 267ms/step - accuracy: 0.8818 - loss: 0.3008 - val_accuracy: 0.8714 - val_loss: 0.3097\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# компилируем и обучаем модель\n", + "batch_size = 64\n", + "epochs = 3\n", + "model.compile(loss=\"binary_crossentropy\", optimizer=\"adam\", metrics=[\"accuracy\"])\n", + "model.fit(X_train, y_train, batch_size=batch_size, epochs=epochs, validation_split=0.2)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "id": "hJIWinxymQjb" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1m782/782\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m54s\u001b[0m 69ms/step - accuracy: 0.8659 - loss: 0.3207\n", + "\n", + "Test accuracy: 0.865880012512207\n" + ] + } + ], + "source": [ + "test_loss, test_acc = model.evaluate(X_test, y_test)\n", + "print(f\"\\nTest accuracy: {test_acc}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "mgrihPd61E8w" + }, + "source": [ + "### 10) Оценили качество обучения на тестовых данных:\n", + "### - вывели значение метрики качества классификации на тестовых данных\n", + "### - вывели отчет о качестве классификации тестовой выборки \n", + "### - построили ROC-кривую по результату обработки тестовой выборки и вычислили площадь под ROC-кривой (AUC ROC)" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": { + "id": "Rya5ABT8msha" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Test accuracy: 0.865880012512207\n" + ] + } + ], + "source": [ + "#значение метрики качества классификации на тестовых данных\n", + "print(f\"\\nTest accuracy: {test_acc}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "id": "2kHjcmnCmv0Y" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1m782/782\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m40s\u001b[0m 51ms/step\n", + " precision recall f1-score support\n", + "\n", + " Negative 0.87 0.87 0.87 12500\n", + " Positive 0.87 0.86 0.87 12500\n", + "\n", + " accuracy 0.87 25000\n", + " macro avg 0.87 0.87 0.87 25000\n", + "weighted avg 0.87 0.87 0.87 25000\n", + "\n" + ] + } + ], + "source": [ + "#отчет о качестве классификации тестовой выборки\n", + "y_score = model.predict(X_test)\n", + "y_pred = [1 if y_score[i,0]>=0.5 else 0 for i in range(len(y_score))]\n", + "\n", + "from sklearn.metrics import classification_report\n", + "print(classification_report(y_test, y_pred, labels = [0, 1], target_names=['Negative', 'Positive']))" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "id": "Kp4AQRbcmwAx" + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAArMAAAIjCAYAAAAQgZNYAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjcsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvTLEjVAAAAAlwSFlzAAAPYQAAD2EBqD+naQAAWqhJREFUeJzt3Xl4U1X+x/FPkjZpCy1baUuhWBbZBFmFAVQGZHFjZNwY8SeIiivqyOgILiBuuKKO4jAuiLgMqKMOIwgCigKiKIuiQJF9baEU6N6kyf39kTZQW6ApSW7Tvl/Pw9N7T+7N/aaH6ofTc8+1GIZhCAAAAAhDVrMLAAAAAKqKMAsAAICwRZgFAABA2CLMAgAAIGwRZgEAABC2CLMAAAAIW4RZAAAAhC3CLAAAAMIWYRYAAABhizALAACAsEWYBYBqYObMmbJYLL4/ERERatq0qa6//nrt3bu33PGGYeidd97R+eefr/r16ysmJkadOnXSo48+qry8vBNe55NPPtFFF12k+Ph42e12JScn6+qrr9aXX34ZzI8HAEFjMQzDMLsIAKjtZs6cqdGjR+vRRx9VixYtVFhYqO+++04zZ85UamqqfvnlF0VFRUmS3G63RowYoQ8++EDnnXeeLr/8csXExGjZsmV6//331aFDBy1evFiJiYm+9zcMQzfccINmzpyprl276sorr1RSUpL279+vTz75RKtXr9aKFSvUp08fs74FAFA1BgDAdG+99ZYhyfjhhx/KtN9///2GJGPOnDm+tieffNKQZNx7773l3mfu3LmG1Wo1LrzwwjLtzz77rCHJ+Otf/2p4PJ5y582aNcv4/vvvA/RpACB0mGYAANXYeeedJ0naunWrJKmgoEDPPvus2rRpoylTppQ7fujQoRo1apQWLFig7777znfOlClT1K5dOz333HOyWCzlzrvuuuvUs2fPIH4SAAgOwiwAVGM7duyQJDVo0ECStHz5ch0+fFgjRoxQREREheeMHDlSkvTZZ5/5zsnKytKIESNks9mCXzQAhFDF/yUEAJji6NGjyszMVGFhob7//ntNnjxZDodDl156qSRpw4YNkqTOnTuf8D1KX9u4cWOZr506dQpm6QBgCsIsAFQjAwcOLLOfmpqqd999V82aNZMk5eTkSJJiY2NP+B6lr2VnZ5f5erJzACBcEWYBoBqZNm2a2rRpo6NHj2rGjBn65ptv5HA4fK+XBtLSUFuR3wfeuLi4U54DAOGKObMAUI307NlTAwcO1BVXXKG5c+eqY8eOGjFihHJzcyVJ7du3lyT9/PPPJ3yP0tc6dOggSWrXrp0kaf369cEsHQBMQZgFgGrKZrNpypQp2rdvn1555RVJ0rnnnqv69evr/fffl9vtrvC8WbNmSZJvnu25556rBg0a6N///vcJzwGAcEWYBYBq7I9//KN69uypF198UYWFhYqJidG9996rtLQ0Pfjgg+WOnzdvnmbOnKkhQ4boD3/4gyQpJiZG999/vzZu3Kj7779fRgXPynn33Xe1atWqoH8eAAg05swCQDV333336aqrrtLMmTN16623avz48Vq7dq2efvpprVy5UldccYWio6O1fPlyvfvuu2rfvr3efvvtcu/x66+/6vnnn9dXX33lewJYenq6Pv30U61atUrffvutSZ8QAKqOx9kCQDVQ+jjbH374QT169CjzmsfjUZs2bSRJaWlpstls8ng8mjVrlt544w2tX79eTqdTrVq10tVXX62//e1vqlOnToXX+c9//qPXXntNP/74o7Kzs9W4cWOdf/75uu2229SvX7+gf04ACDTCLAAAAMIWc2YBAAAQtgizAAAACFuEWQAAAIQtwiwAAADCFmEWAAAAYYswCwAAgLBV6x6a4PF4tG/fPsXGxspisZhdDgAAAH7HMAzl5OQoOTlZVuvJx15rXZjdt2+fUlJSzC4DAAAAp7B79241a9bspMfUujAbGxsryfvNiYuLC/r1XC6XvvjiCw0ePFiRkZFBvx4Cjz4Mf/Rh+KMPwxv9F/5C3YfZ2dlKSUnx5baTqXVhtnRqQVxcXMjCbExMjOLi4vgBDlP0YfijD8MffRje6L/wZ1YfVmZKKDeAAQAAIGwRZgEAABC2CLMAAAAIW4RZAAAAhC3CLAAAAMIWYRYAAABhizALAACAsEWYBQAAQNgizAIAACBsEWYBAAAQtgizAAAACFuEWQAAAIQtwiwAAADCFmEWAAAAYcvUMPvNN99o6NChSk5OlsVi0aeffnrKc5YuXapu3brJ4XCodevWmjlzZtDrBAAAQPVkapjNy8tT586dNW3atEodv337dl1yySXq37+/1q1bp7/+9a+66aabtHDhwiBXCgAAgOoowsyLX3TRRbrooosqffz06dPVokULPf/885Kk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AgAAIGwRZgEAABC2CLMAAAAIW4RZAAAAhC3CLAAAAMIWYRYAJM2cOVP169c3u4wqs1gs+vTTT096zPXXX69hw4aFpB4ACBXCLIAa4/rrr5fFYin3Z8uWLWaXppkzZ/rqsVqtatasmUaPHq0DBw4E5P3379+viy66SJK0Y8cOWSwWrVu3rswxL730kmbOnBmQ653II4884vucNptNKSkpuvnmm5WVleXX+xC8AVRWhNkFAEAgXXjhhXrrrbfKtDVu3NikasqKi4tTWlqaPB6PfvrpJ40ePVr79u3TwoULT/u9k5KSTnlMvXr1Tvs6lXHWWWdp8eLFcrvd2rhxo2644QYdPXpUc+bMCcn1AdQujMwCqFEcDoeSkpLK/LHZbJo6dao6deqkOnXqKCUlRbfffrtyc3NP+D4//fST+vfvr9jYWMXFxal79+768ccffa8vX75c5513nqKjo5WSkqK77rpLeXl5J63NYrEoKSlJycnJuuiii3TXXXdp8eLFKigokMfj0aOPPqpmzZrJ4XCoS5cuWrBgge9cp9OpsWPHqkmTJoqKitIZZ5yhKVOmlHnv0mkGLVq0kCR17dpVFotFf/zjHyWVHe187bXXlJycLI/HU6bGyy67TDfccINv/7///a+6deumqKgotWzZUpMnT1ZxcfFJP2dERISSkpLUtGlTDRw4UFdddZUWLVrke93tduvGG29UixYtFB0drbZt2+qll17yvf7II4/o7bff1n//+1/fKO/SpUslSbt379bVV1+t+vXrq2HDhrrsssu0Y8eOk9YDoGYjzAKoFaxWq/7xj3/o119/1dtvv60vv/xSf//73094/LXXXqtmzZrphx9+0OrVqzV+/HhFRkZKkrZu3aoLL7xQV1xxhX7++WfNmTNHy5cv19ixY/2qKTo6Wh6PR8XFxXrppZf0/PPP67nnntPPP/+sIUOG6E9/+pN+++03SdI//vEPzZ07Vx988IHS0tL03nvvKTU1tcL3XbVqlSRp8eLF2r9/vz7++ONyx1x11VU6dOiQvvrqK19bVlaWFixYoGuvvVaStGzZMo0cOVJ33323NmzYoH/961+aOXOmnnjiiUp/xh07dmjhwoWy2+2+No/Ho2bNmunDDz/Uhg0bNHHiRD3wwAP64IMPJEn33nuvrr76al144YXav3+/9u/frz59+sjlcmnIkCGKjY3VsmXLtGLFCtWtW1cXXnihnE5npWsCUMMYAFBDjBo1yrDZbEadOnV8f6688soKj/3www+NRo0a+fbfeusto169er792NhYY+bMmRWee+ONNxo333xzmbZly5YZVqvVKCgoqPCc37//5s2bjTZt2hg9evQwDMMwkpOTjSeeeKLMOeecc45x++23G4ZhGHfeeacxYMAAw+PxVPj+koxPPvnEMAzD2L59uyHJWLt2bZljRo0aZVx22WW+/csuu8y44YYbfPv/+te/jOTkZMPtdhuGYRgXXHCB8eSTT5Z5j3feecdo0qRJhTUYhmFMmjTJsFqtRp06dYyoqChDkiHJmDp16gnPMQzDuOOOO4wrrrjihLWWXrtt27ZlvgdFRUVGdHS0sXDhwpO+P4CaizmzAGqU/v3765///Kdvv06dOpK8o5RTpkzRpk2blJ2dreLiYhUWFio/P18xMTHl3mfcuHG66aab9M477/h+Vd6qVStJ3ikIP//8s9577z3f8YZhyOPxaPv27Wrfvn2FtR09elR169aVx+NRYWGhzj33XL3xxhvKzs7Wvn371Ldv3zLH9+3bVz/99JMk7xSBQYMGqW3btrrwwgt16aWXavDgwaf1vbr22ms1ZswYvfrqq3I4HHrvvff0l7/8RVar1fc5V6xYUWYk1u12n/T7Jklt27bV3LlzVVhYqHfffVfr1q3TnXfeWeaYadOmacaMGdq1a5cKCgrkdDrVpUuXk9b7008/acuWLYqNjS3TXlhYqK1bt1bhOwCgJiDMAqhR6tSpo9atW5dp27Fjhy699FLddttteuKJJ9SwYUMtX75cN954o5xOZ4Wh7JFHHtGIESM0b948ff7555o0aZJmz56tP//5z8rNzdUtt9yiu+66q9x5zZs3P2FtsbGxWrNmjaxWq5o0aaLo6GhJUnZ29ik/V7du3bR9+3Z9/vnnWrx4sa6++moNHDhQH3300SnPPZGhQ4fKMAzNmzdP55xzjpYtW6YXXnjB93pubq4mT56syy+/vNy5UVFRJ3xfu93u64OnnnpKl1xyiSZPnqzHHntMkjR79mzde++9ev7559W7d2/Fxsbq2Wef1ffff3/SenNzc9W9e/cy/4goVV1u8gMQeoRZADXe6tWr5fF49Pzzz/tGHUvnZ55MmzZt1KZNG91zzz265ppr9NZbb+nPf/6zunXrpg0bNpQLzaditVorPCcuLk7JyclasWKF+vXr52tfsWKFevbsWea44cOHa/jw4bryyit14YUXKisrSw0bNizzfqXzU91u90nriYqK0uWXX6733ntPW7ZsUdu2bdWtWzff6926dVNaWprfn/P3HnroIQ0YMEC33Xab73P26dNHt99+u++Y34+s2u32cvV369ZNc+bMUUJCguLi4k6rJgA1BzeAAajxWrduLZfLpZdfflnbtm3TO++8o+nTp5/w+IKCAo0dO1ZLly7Vzp07tWLFCv3www++6QP333+/vv32W40dO1br1q3Tb7/9pv/+979+3wB2vPvuu09PP/205syZo7S0NI0fP17r1q3T3XffLUmaOnWq/v3vf2vTpk3avHmzPvzwQyUlJVX4oIeEhARFR0drwYIFysjI0NGjR0943WuvvVbz5s3TjBkzfDd+lZo4caJmzZqlyZMn69dff9XGjRs1e/ZsPfTQQ359tt69e+vss8/Wk08+KUk688wz9eOPP2rhwoXavHmzHn74Yf3www9lzklNTdXPP/+stLQ0ZWZmyuVy6dprr1V8fLwuu+wyLVu2TNu3b9fSpUt11113ac+ePX7VBKDmIMwCqPE6d+6sqVOn6umnn1bHjh313nvvlVnW6vdsNpsOHTqkkSNHqk2bNrr66qt10UUXafLkyZKks88+W19//bU2b96s8847T127dtXEiROVnJxc5RrvuusujRs3Tn/729/UqVMnLViwQHPnztWZZ54pyTtF4ZlnnlGPHj10zjnnaMeOHZo/f75vpPl4ERER+sc//qF//etfSk5O1mWXXXbC6w4YMEANGzZUWlqaRowYUea1IUOG6LPPPtMXX3yhc845R3/4wx/0wgsv6IwzzvD7891zzz164403tHv3bt1yyy26/PLLNXz4cPXq1UuHDh0qM0orSWPGjFHbtm3Vo0cPNW7cWCtWrFBMTIy++eYbNW/eXJdffrnat2+vG2+8UYWFhYzUArWYxTAMw+wiAAAAgKpgZBYAAABhizALAACAsEWYBQAAQNgizAIAACBsEWYBAAAQtgizAAAACFuEWQAAAIQtwiwAAADCFmEWAAAAYYswCwAAgLBFmAUAAEDY+n/Dg4Bglqrv4QAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "AUC ROC: 0.9386348447999999\n" + ] + } + ], + "source": [ + "#построение ROC-кривой и AUC ROC\n", + "from sklearn.metrics import roc_curve, auc\n", + "\n", + "fpr, tpr, thresholds = roc_curve(y_test, y_score)\n", + "plt.figure(figsize=(8, 6))\n", + "plt.plot(fpr, tpr)\n", + "plt.grid()\n", + "plt.xlabel('False Positive Rate')\n", + "plt.ylabel('True Positive Rate')\n", + "plt.title('ROC')\n", + "plt.savefig('roc_curve.png', dpi=150, bbox_inches='tight')\n", + "plt.show()\n", + "print('AUC ROC:', auc(fpr, tpr))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "MsM3ew3d1FYq" + }, + "source": [ + "### 11) Сделали выводы по результатам применения рекуррентной нейронной сети для решения задачи определения тональности текста. " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "xxFO4CXbIG88" + }, + "source": [ + "Таблица1:" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "xvoivjuNFlEf" + }, + "source": [ + "| Модель | Количество настраиваемых параметров | Количество эпох обучения | Качество классификации тестовой выборки |\n", + "|----------|-------------------------------------|---------------------------|-----------------------------------------|\n", + "| Рекуррентная | 184 897 | 3 | accuracy:0.8659 ; loss:0.3207 ; AUC ROC:0.9386 |\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "YctF8h_sIB-P" + }, + "source": [ + "#### Анализируя полученные результаты применения рекуррентной нейронной сети для классификации тональности текстовых отзывов, можно констатировать успешное выполнение поставленной задачи. Достигнутый уровень точности accuracy = 0.8659 существенно превосходит минимально необходимый порог 0.8, что свидетельствует о надежности разработанной модели. Показатель AUC ROC = 0.9386, превышающий значение 0.9, демонстрирует отличную дискриминационную способность модели в различении позитивных и негативных отзывов. Сбалансированные метрики precision и recall (0.87 для обоих классов) указывают на отсутствие значимого смещения в сторону одного из классов, что подтверждает корректность работы алгоритма классификации." + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "T4", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "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.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/labworks/LW4/lab4_no_outs.ipynb b/labworks/LW4/lab4_no_outs.ipynb new file mode 100644 index 0000000..864dc84 --- /dev/null +++ b/labworks/LW4/lab4_no_outs.ipynb @@ -0,0 +1,451 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "gz18QPRz03Ec" + }, + "source": [ + "### 1) В среде Google Colab создали новый блокнот (notebook). Импортировали необходимые для работы библиотеки и модули." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "mr9IszuQ1ANG" + }, + "outputs": [], + "source": [ + "# импорт модулей\n", + "import os\n", + "\n", + "from tensorflow import keras\n", + "from tensorflow.keras import layers\n", + "from tensorflow.keras.models import Sequential\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "FFRtE0TN1AiA" + }, + "source": [ + "### 2) Загрузили набор данных IMDb, содержащий оцифрованные отзывы на фильмы, размеченные на два класса: позитивные и негативные. При загрузке набора данных параметр seed выбрали равным значению (4k – 1)=3, где k=1 – номер бригады. Вывели размеры полученных обучающих и тестовых массивов данных." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Ixw5Sp0_1A-w" + }, + "outputs": [], + "source": [ + "# загрузка датасета\n", + "import ssl\n", + "ssl._create_default_https_context = ssl._create_unverified_context\n", + "\n", + "from keras.datasets import imdb\n", + "\n", + "vocabulary_size = 5000\n", + "index_from = 3\n", + "\n", + "(X_train, y_train), (X_test, y_test) = imdb.load_data(\n", + " path=\"imdb.npz\",\n", + " num_words=vocabulary_size,\n", + " skip_top=0,\n", + " maxlen=None,\n", + " seed=3,\n", + " start_char=1,\n", + " oov_char=2,\n", + " index_from=index_from\n", + " )\n", + "\n", + "# вывод размерностей\n", + "print('Shape of X train:', X_train.shape)\n", + "print('Shape of y train:', y_train.shape)\n", + "print('Shape of X test:', X_test.shape)\n", + "print('Shape of y test:', y_test.shape)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "aCo_lUXl1BPV" + }, + "source": [ + "### 3) Вывели один отзыв из обучающего множества в виде списка индексов слов. Преобразовали список индексов в текст и вывели отзыв в виде текста. Вывели длину отзыва. Вывели метку класса данного отзыва и название класса (1 – Positive, 0 – Negative)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "9W3RklPcZyH0" + }, + "outputs": [], + "source": [ + "# создание словаря для перевода индексов в слова\n", + "# загрузка словаря \"слово:индекс\"\n", + "word_to_id = imdb.get_word_index()\n", + "# уточнение словаря\n", + "word_to_id = {key:(value + index_from) for key,value in word_to_id.items()}\n", + "word_to_id[\"\"] = 0\n", + "word_to_id[\"\"] = 1\n", + "word_to_id[\"\"] = 2\n", + "word_to_id[\"\"] = 3\n", + "# создание обратного словаря \"индекс:слово\"\n", + "id_to_word = {value:key for key,value in word_to_id.items()}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Nu-Bs1jnaYhB" + }, + "outputs": [], + "source": [ + "print(X_train[26])\n", + "print('len:',len(X_train[26]))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "JhTwTurtZ6Sp" + }, + "outputs": [], + "source": [ + "review_as_text = ' '.join(id_to_word[id] for id in X_train[26])\n", + "print(review_as_text)\n", + "print('len:',len(review_as_text))\n", + "\n", + "# вывод метки класса и названия класса\n", + "print('Label:', y_train[26])\n", + "print('Class:', 'Positive' if y_train[26] == 1 else 'Negative')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "4hclnNaD1BuB" + }, + "source": [ + "### 4) Вывели максимальную и минимальную длину отзыва в обучающем множестве." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "xJH87ISq1B9h" + }, + "outputs": [], + "source": [ + "print('MAX Len: ',len(max(X_train, key=len)))\n", + "print('MIN Len: ',len(min(X_train, key=len)))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "7x99O8ig1CLh" + }, + "source": [ + "### 5) Провели предобработку данных. Выбрали единую длину, к которой будут приведены все отзывы. Короткие отзывы дополнили спецсимволами, а длинные обрезали до выбранной длины." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "lrF-B2aScR4t" + }, + "outputs": [], + "source": [ + "# предобработка данных\n", + "from tensorflow.keras.utils import pad_sequences\n", + "max_words = 500\n", + "X_train = pad_sequences(X_train, maxlen=max_words, value=0, padding='pre', truncating='post')\n", + "X_test = pad_sequences(X_test, maxlen=max_words, value=0, padding='pre', truncating='post')" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "HL2_LVga1C3l" + }, + "source": [ + "### 6) Повторили пункт 4." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "81Cgq8dn9uL6" + }, + "outputs": [], + "source": [ + "print('MAX Len: ',len(max(X_train, key=len)))\n", + "print('MIN Len: ',len(min(X_train, key=len)))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "KzrVY1SR1DZh" + }, + "source": [ + "### 7) Повторили пункт 3. Сделали вывод о том, как отзыв преобразовался после предобработки." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "vudlgqoCbjU1" + }, + "outputs": [], + "source": [ + "print(X_train[26])\n", + "print('len:',len(X_train[26]))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "dbfkWjDI1Dp7" + }, + "outputs": [], + "source": [ + "review_as_text = ' '.join(id_to_word[id] for id in X_train[26])\n", + "print(review_as_text)\n", + "print('len:',len(review_as_text))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "mJNRXo5TdPAE" + }, + "source": [ + "#### В результате предобработки данных все отзывы были приведены к единой длине 500 токенов. Для отзывов, исходная длина которых была меньше 500, в начало последовательности были добавлены специальные токены заполнения (со значением 0). Это обеспечило единообразие входных данных для нейронной сети и позволило эффективно обрабатывать последовательности различной длины." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "YgiVGr5_1D3u" + }, + "source": [ + "### 8) Вывели предобработанные массивы обучающих и тестовых данных и их размерности." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "7MqcG_wl1EHI" + }, + "outputs": [], + "source": [ + "# вывод данных\n", + "print('X train: \\n',X_train)\n", + "print('X test: \\n',X_test)\n", + "\n", + "# вывод размерностей\n", + "print('Shape of X train:', X_train.shape)\n", + "print('Shape of X test:', X_test.shape)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "amaspXGW1EVy" + }, + "source": [ + "### 9) Реализовали модель рекуррентной нейронной сети, состоящей из слоев Embedding, LSTM, Dropout, Dense, и обучили ее на обучающих данных с выделением части обучающих данных в качестве валидационных. Вывели информацию об архитектуре нейронной сети. Добились качества обучения по метрике accuracy не менее 0.8." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "ktWEeqWd1EyF" + }, + "outputs": [], + "source": [ + "embed_dim = 32\n", + "lstm_units = 64\n", + "\n", + "model = Sequential()\n", + "model.add(layers.Embedding(input_dim=vocabulary_size, output_dim=embed_dim, input_length=max_words, input_shape=(max_words,)))\n", + "model.add(layers.LSTM(lstm_units))\n", + "model.add(layers.Dropout(0.5))\n", + "model.add(layers.Dense(1, activation='sigmoid'))\n", + "\n", + "model.summary()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "CuPqKpX0kQfP" + }, + "outputs": [], + "source": [ + "# компилируем и обучаем модель\n", + "batch_size = 64\n", + "epochs = 3\n", + "model.compile(loss=\"binary_crossentropy\", optimizer=\"adam\", metrics=[\"accuracy\"])\n", + "model.fit(X_train, y_train, batch_size=batch_size, epochs=epochs, validation_split=0.2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "hJIWinxymQjb" + }, + "outputs": [], + "source": [ + "test_loss, test_acc = model.evaluate(X_test, y_test)\n", + "print(f\"\\nTest accuracy: {test_acc}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "mgrihPd61E8w" + }, + "source": [ + "### 10) Оценили качество обучения на тестовых данных:\n", + "### - вывели значение метрики качества классификации на тестовых данных\n", + "### - вывели отчет о качестве классификации тестовой выборки \n", + "### - построили ROC-кривую по результату обработки тестовой выборки и вычислили площадь под ROC-кривой (AUC ROC)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Rya5ABT8msha" + }, + "outputs": [], + "source": [ + "#значение метрики качества классификации на тестовых данных\n", + "print(f\"\\nTest accuracy: {test_acc}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "2kHjcmnCmv0Y" + }, + "outputs": [], + "source": [ + "#отчет о качестве классификации тестовой выборки\n", + "y_score = model.predict(X_test)\n", + "y_pred = [1 if y_score[i,0]>=0.5 else 0 for i in range(len(y_score))]\n", + "\n", + "from sklearn.metrics import classification_report\n", + "print(classification_report(y_test, y_pred, labels = [0, 1], target_names=['Negative', 'Positive']))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Kp4AQRbcmwAx" + }, + "outputs": [], + "source": [ + "#построение ROC-кривой и AUC ROC\n", + "from sklearn.metrics import roc_curve, auc\n", + "\n", + "fpr, tpr, thresholds = roc_curve(y_test, y_score)\n", + "plt.figure(figsize=(8, 6))\n", + "plt.plot(fpr, tpr)\n", + "plt.grid()\n", + "plt.xlabel('False Positive Rate')\n", + "plt.ylabel('True Positive Rate')\n", + "plt.title('ROC')\n", + "plt.savefig('roc_curve.png', dpi=150, bbox_inches='tight')\n", + "plt.show()\n", + "print('AUC ROC:', auc(fpr, tpr))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "MsM3ew3d1FYq" + }, + "source": [ + "### 11) Сделали выводы по результатам применения рекуррентной нейронной сети для решения задачи определения тональности текста. " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "xxFO4CXbIG88" + }, + "source": [ + "Таблица1:" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "xvoivjuNFlEf" + }, + "source": [ + "| Модель | Количество настраиваемых параметров | Количество эпох обучения | Качество классификации тестовой выборки |\n", + "|----------|-------------------------------------|---------------------------|-----------------------------------------|\n", + "| Рекуррентная | 184 897 | 3 | accuracy:0.8659 ; loss:0.3207 ; AUC ROC:0.9386 |\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "YctF8h_sIB-P" + }, + "source": [ + "#### Анализируя полученные результаты применения рекуррентной нейронной сети для классификации тональности текстовых отзывов, можно констатировать успешное выполнение поставленной задачи. Достигнутый уровень точности accuracy = 0.8659 существенно превосходит минимально необходимый порог 0.8, что свидетельствует о надежности разработанной модели. Показатель AUC ROC = 0.9386, превышающий значение 0.9, демонстрирует отличную дискриминационную способность модели в различении позитивных и негативных отзывов. Сбалансированные метрики precision и recall (0.87 для обоих классов) указывают на отсутствие значимого смещения в сторону одного из классов, что подтверждает корректность работы алгоритма классификации." + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "T4", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "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.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/labworks/LW4/report.md b/labworks/LW4/report.md new file mode 100644 index 0000000..e945908 --- /dev/null +++ b/labworks/LW4/report.md @@ -0,0 +1,343 @@ +# Отчёт по лабораторной работе №4 + +**Троянов Д.С., Чернов Д.Е. — А-01-22** + +--- +## Задание 1 + +### 1) В среде Google Colab создали новый блокнот (notebook). Импортировали необходимые для работы библиотеки и модули. Настроили блокнот для работы с аппаратным ускорителем GPU. + +```python +# импорт модулей +import os +os.chdir('/content/drive/MyDrive/Colab Notebooks/is_lab4') + +from tensorflow import keras +from tensorflow.keras import layers +from tensorflow.keras.models import Sequential +import matplotlib.pyplot as plt +import numpy as np +``` +```python +import tensorflow as tf +device_name = tf.test.gpu_device_name() +if device_name != '/device:GPU:0': + raise SystemError('GPU device not found') +print('Found GPU at: {}'.format(device_name)) +``` +``` +Found GPU at: /device:GPU:0 +``` + +### 2) Загрузили набор данных IMDb, содержащий оцифрованные отзывы на фильмы, размеченные на два класса: позитивные и негативные. При загрузке набора данных параметр seed выбрали равным значению (4k – 1)=3, где k=1 – номер бригады. Вывели размеры полученных обучающих и тестовых массивов данных. + +```python +# загрузка датасета +import ssl +ssl._create_default_https_context = ssl._create_unverified_context + +from keras.datasets import imdb + +vocabulary_size = 5000 +index_from = 3 + +(X_train, y_train), (X_test, y_test) = imdb.load_data( + path="imdb.npz", + num_words=vocabulary_size, + skip_top=0, + maxlen=None, + seed=3, + start_char=1, + oov_char=2, + index_from=index_from + ) + +# вывод размерностей +print('Shape of X train:', X_train.shape) +print('Shape of y train:', y_train.shape) +print('Shape of X test:', X_test.shape) +print('Shape of y test:', y_test.shape) +``` +``` +Shape of X train: (25000,) +Shape of y train: (25000,) +Shape of X test: (25000,) +Shape of y test: (25000,) +``` + +### 3) Вывели один отзыв из обучающего множества в виде списка индексов слов. Преобразовали список индексов в текст и вывели отзыв в виде текста. Вывели длину отзыва. Вывели метку класса данного отзыва и название класса (1 – Positive, 0 – Negative). + +```python +# создание словаря для перевода индексов в слова +# загрузка словаря "слово:индекс" +word_to_id = imdb.get_word_index() +# уточнение словаря +word_to_id = {key:(value + index_from) for key,value in word_to_id.items()} +word_to_id[""] = 0 +word_to_id[""] = 1 +word_to_id[""] = 2 +word_to_id[""] = 3 +# создание обратного словаря "индекс:слово" +id_to_word = {value:key for key,value in word_to_id.items()} +``` +```python +print(X_train[26]) +print('len:',len(X_train[26])) +``` +``` +[1, 37, 1388, 4, 2739, 495, 94, 96, 143, 49, 2, 875, 551, 19, 195, 2210, 5, 1698, 8, 401, 4, 65, 24, 64, 1728, 21, 400, 642, 45, 77, 6, 137, 237, 207, 258, 141, 6, 1562, 1301, 1562, 737, 22, 10, 10, 4, 22, 9, 1490, 3862, 4, 744, 19, 307, 1385, 5, 2, 2, 4, 2, 2656, 2, 1669, 19, 4, 1074, 200, 4, 55, 406, 55, 3048, 5, 246, 55, 1451, 105, 688, 8, 4, 321, 177, 32, 677, 7, 4, 678, 1850, 26, 1669, 221, 5, 3921, 10, 10, 13, 386, 37, 1388, 4, 2739, 45, 6, 66, 163, 20, 15, 304, 6, 3049, 168, 33, 4, 4352, 15, 75, 70, 2, 23, 257, 85, 5, 4, 2789, 878, 21, 1305, 2, 1773, 7, 2] +len: 130 +``` +```python +review_as_text = ' '.join(id_to_word[id] for id in X_train[26]) +print(review_as_text) +print('len:',len(review_as_text)) + +# вывод метки класса и названия класса +print('Label:', y_train[26]) +print('Class:', 'Positive' if y_train[26] == 1 else 'Negative') +``` +``` + who loves the sun works its way through some subject matter with enough wit and grace to keep the story not only engaging but often hilarious it's been a while since i've found such a thoroughly touching thoroughly enjoyable film br br the film is gorgeous drawing the eye with beautiful scenery and the imagery wonderfully with the tension between the very human very flawed and yet very likable characters due to the excellent cast all five of the major players are wonderfully interesting and dynamic br br i recommend who loves the sun it's a really funny movie that takes a poignant look at the hurts that we can on each other and the amazingly difficult but equally process of +len: 738 +Label: 1 +Class: Positive +``` + + +### 4) Вывели максимальную и минимальную длину отзыва в обучающем множестве. + +```python +print('MAX Len: ',len(max(X_train, key=len))) +print('MIN Len: ',len(min(X_train, key=len))) +``` +``` +MAX Len: 2494 +MIN Len: 11 +``` + +### 5) Провели предобработку данных. Выбрали единую длину, к которой будут приведены все отзывы. Короткие отзывы дополнили спецсимволами, а длинные обрезали до выбранной длины. + +```python +# предобработка данных +from tensorflow.keras.utils import pad_sequences +max_words = 500 +X_train = pad_sequences(X_train, maxlen=max_words, value=0, padding='pre', truncating='post') +X_test = pad_sequences(X_test, maxlen=max_words, value=0, padding='pre', truncating='post') +``` + +### 6) Повторили пункт 4. + +```python +print('MAX Len: ',len(max(X_train, key=len))) +print('MIN Len: ',len(min(X_train, key=len))) +``` +``` +MAX Len: 500 +MIN Len: 500 +``` + +### 7) Повторили пункт 3. Сделали вывод о том, как отзыв преобразовался после предобработки. +```python +print(X_train[26]) +print('len:',len(X_train[26])) +``` +``` +[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 1 4 78 + 46 304 39 2 7 968 2 295 209 101 147 65 10 10 + 2643 2 497 8 30 6 147 284 5 996 174 10 10 11 + 4 130 4 2 4979 11 2 10 10 2] +len: 500 +``` + +```python +review_as_text = ' '.join(id_to_word[id] for id in X_train[26]) +print(review_as_text) +print('len:',len(review_as_text)) +``` +``` + the bad out takes from of fire together without any real story br br dean tries to be a real actor and fails again br br in the end the quit in br br +len: 2947 +``` +#### В результате предобработки данных все отзывы были приведены к единой длине 500 токенов. Для отзывов, исходная длина которых была меньше 500, в начало последовательности были добавлены специальные токены заполнения (со значением 0). Это обеспечило единообразие входных данных для нейронной сети и позволило эффективно обрабатывать последовательности различной длины. + + +### 8) Вывели предобработанные массивы обучающих и тестовых данных и их размерности. + +```python +# вывод данных +print('X train: \n',X_train) +print('X test: \n',X_test) + +# вывод размерностей +print('Shape of X train:', X_train.shape) +print('Shape of X test:', X_test.shape) +``` +``` +X train: + [[ 0 0 0 ... 12 38 76] + [ 0 0 0 ... 33 4 130] + [ 0 0 0 ... 437 7 58] + ... + [ 0 0 0 ... 1874 1553 422] + [ 0 0 0 ... 18 1552 234] + [ 0 0 0 ... 7 87 1090]] +X test: + [[ 0 0 0 ... 6 194 717] + [ 0 0 0 ... 30 87 292] + [ 0 0 0 ... 495 55 73] + ... + [ 0 0 0 ... 7 12 908] + [ 0 0 0 ... 61 477 2302] + [ 0 0 0 ... 5 68 4580]] +Shape of X train: (25000, 500) +Shape of X test: (25000, 500) +``` + +### 9) Реализовали модель рекуррентной нейронной сети, состоящей из слоев Embedding, LSTM, Dropout, Dense, и обучили ее на обучающих данных с выделением части обучающих данных в качестве валидационных. Вывели информацию об архитектуре нейронной сети. Добились качества обучения по метрике accuracy не менее 0.8. + +```python +embed_dim = 32 +lstm_units = 64 + +model = Sequential() +model.add(layers.Embedding(input_dim=vocabulary_size, output_dim=embed_dim, input_length=max_words, input_shape=(max_words,))) +model.add(layers.LSTM(lstm_units)) +model.add(layers.Dropout(0.5)) +model.add(layers.Dense(1, activation='sigmoid')) + +model.summary() +``` + +**Model: "sequential"** +| Layer (type) | Output Shape | Param # | +| ----------------------- | --------------- | ------: | +| embedding_4 (Embedding) | (None, 500, 32) | 160,000 | +| lstm_4 (LSTM) | (None, 64) | 24,832 | +| dropout_4 (Dropout) | (None, 64) | 0 | +| dense_4 (Dense) | (None, 1) | 65 | + +**Total params:** 184,897 (722.25 KB) +**Trainable params:** 184,897 (722.25 KB) +**Non-trainable params:** 0 (0.00 B) + +```python +# компилируем и обучаем модель +batch_size = 64 +epochs = 3 +model.compile(loss="binary_crossentropy", optimizer="adam", metrics=["accuracy"]) +model.fit(X_train, y_train, batch_size=batch_size, epochs=epochs, validation_split=0.2) +``` +``` +Epoch 1/3 +313/313 ━━━━━━━━━━━━━━━━━━━━ 108s 338ms/step - accuracy: 0.7596 - loss: 0.4853 - val_accuracy: 0.8086 - val_loss: 0.4447 +Epoch 2/3 +313/313 ━━━━━━━━━━━━━━━━━━━━ 85s 273ms/step - accuracy: 0.8680 - loss: 0.3281 - val_accuracy: 0.8228 - val_loss: 0.3993 +Epoch 3/3 +313/313 ━━━━━━━━━━━━━━━━━━━━ 84s 267ms/step - accuracy: 0.8818 - loss: 0.3008 - val_accuracy: 0.8714 - val_loss: 0.3097 +``` +```python +test_loss, test_acc = model.evaluate(X_test, y_test) +print(f"\nTest accuracy: {test_acc}") +``` +``` +782/782 ━━━━━━━━━━━━━━━━━━━━ 54s 69ms/step - accuracy: 0.8659 - loss: 0.3207 + +Test accuracy: 0.865880012512207 +``` + +### 10) Оценили качество обучения на тестовых данных: +### - вывели значение метрики качества классификации на тестовых данных +### - вывели отчет о качестве классификации тестовой выборки +### - построили ROC-кривую по результату обработки тестовой выборки и вычислили площадь под ROC-кривой (AUC ROC) + +```python +#значение метрики качества классификации на тестовых данных +print(f"\nTest accuracy: {test_acc}") +``` +``` +Test accuracy: 0.865880012512207 +``` + +```python +#отчет о качестве классификации тестовой выборки +y_score = model.predict(X_test) +y_pred = [1 if y_score[i,0]>=0.5 else 0 for i in range(len(y_score))] + +from sklearn.metrics import classification_report +print(classification_report(y_test, y_pred, labels = [0, 1], target_names=['Negative', 'Positive'])) +``` +``` + precision recall f1-score support + + Negative 0.87 0.87 0.87 12500 + Positive 0.87 0.86 0.87 12500 + + accuracy 0.87 25000 + macro avg 0.87 0.87 0.87 25000 +weighted avg 0.87 0.87 0.87 25000 +``` + +```python +#построение ROC-кривой и AUC ROC +from sklearn.metrics import roc_curve, auc + +fpr, tpr, thresholds = roc_curve(y_test, y_score) +plt.figure(figsize=(8, 6)) +plt.plot(fpr, tpr) +plt.grid() +plt.xlabel('False Positive Rate') +plt.ylabel('True Positive Rate') +plt.title('ROC') +plt.savefig('roc_curve.png', dpi=150, bbox_inches='tight') +plt.show() +print('AUC ROC:', auc(fpr, tpr)) +``` +![ROC-кривая](roc_curve.png) +``` +AUC ROC: 0.9386348447999999 +``` + +### 11) Сделали выводы по результатам применения рекуррентной нейронной сети для решения задачи определения тональности текста. + +Таблица1: + +| Модель | Количество настраиваемых параметров | Количество эпох обучения | Качество классификации тестовой выборки | +|----------|-------------------------------------|---------------------------|-----------------------------------------| +| Рекуррентная | 184 897 | 3 | accuracy:0.8659 ; loss:0.3207 ; AUC ROC:0.9386 | + + +#### Анализируя полученные результаты применения рекуррентной нейронной сети для классификации тональности текстовых отзывов, можно констатировать успешное выполнение поставленной задачи. Достигнутый уровень точности accuracy = 0.8659 существенно превосходит минимально необходимый порог 0.8, что свидетельствует о надежности разработанной модели. +#### Показатель AUC ROC = 0.9386, превышающий значение 0.9, демонстрирует отличную дискриминационную способность модели в различении позитивных и негативных отзывов. Сбалансированные метрики precision и recall (0.87 для обоих классов) указывают на отсутствие значимого смещения в сторону одного из классов, что подтверждает корректность работы алгоритма классификации. \ No newline at end of file diff --git a/labworks/LW4/roc_curve.png b/labworks/LW4/roc_curve.png new file mode 100644 index 0000000000000000000000000000000000000000..d68dc776d702851749e95c8b0b3601bc953eccec GIT binary patch literal 35636 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 TK=gW22V|@=H^|d--uHh1)%{@3 literal 0 HcmV?d00001