## Лабораторная работа №4 ИС. Распознавание последовательностей. #### Выполнили: Ишутина Е. И., Голубев Т. Л. Проведена работа с отзывами с IMDb в виде последовательностей индексов и в человекочитаемом виде. Выполнена предобработка: все тексты приведены к единой длине путём дополнения / усечения, в результате чего получены матрицы одинаковой размерности, пригодные для подачи в нейронную сеть. На основании предобработанных данных была построена рекуррентная модель, включавшая слои Embedding, LSTM, Dropout и полносвязный классификатор, и проведено её обучение с использованием доли обучающей выборки в качестве валидационной и проверка точности работы модели. Перед началом работы импортированы все нужные модули: ```python # Блок импортов и общих настроек import os import random import numpy as np import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers from tensorflow.keras.datasets import imdb from tensorflow.keras.preprocessing.sequence import pad_sequences from sklearn.metrics import classification_report, roc_curve, auc, roc_auc_score print("TensorFlow version:", tf.__version__) ``` * `layers` предоставляет готовые типы слоёв (Embedding, LSTM, Dense и др.). * `imdb` загружает предобработанный набор IMDb в виде последовательностей индексов. * `pad_sequences` приводит последовательности к фиксированной длине путём дополнения или усечения. * `sklearn.metrics` функции для оценки качества классификации (отчёт, ROC, AUC). #### *1. В среде Google Colab создать новый блокнот (notebook). Настроить блокнот для работы с аппаратным ускорителем GPU.* ```python # 1) Проверка наличия GPU (в Colab после выбора Runtime -> Change runtime type -> GPU) 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)) ``` Вывод дает понять, что подключен аппаратный ускоритель на основе графического процессора (GPU): ```python Found GPU at: /device:GPU:0 ``` #### *2. Загрузить набор данных IMDb, содержащий оцифрованные отзывы на фильмы, размеченные на два класса: позитивные и негативные. При загрузке набора данных параметр seed выбрать равным (4k–1), где k – номер бригады. Вывести размеры полученных обучающих и тестовых массивов данных.* ```python k = 5 seed = 4 * k - 1 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=seed, start_char=1, oov_char=2, index_from=index_from ) print("Размеры: X_train={}, y_train={}, X_test={}, y_test={}".format( X_train.shape, y_train.shape, X_test.shape, y_test.shape)) ``` Функция загрузила по 25 000 примеров в обучающую и тестовую выборки, что и было выведено как формы массивов, потому что набор IMDb в keras был заранее организован в таких размерах. В выводе отображались одномерные массивы, поэтому после запятой не было указано никакой второй размерности. ```python Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/imdb.npz 17464789/17464789 ━━━━━━━━━━━━━━━━━━━━ 0s 0us/step Размеры: X_train=(25000,), y_train=(25000,), X_test=(25000,), y_test=(25000,) ``` #### *3. Вывести один отзыв из обучающего множества в виде списка индексов слов. Преобразовать список индексов в текст и вывести отзыв в виде текста. Вывести длину отзыва. Вывести метку класса данного отзыва и название класса (1 – Positive, 0 – Negative).* ```python # словарь id->word 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()} some_index = 0 review_indices = X_train[some_index] print("Отзыв (список индексов):", review_indices) review_text = ' '.join(id_to_word.get(i, "") for i in review_indices) print("\nОтзыв (текст):\n", review_text) print("\nДлина отзыва (число индексов):", len(review_indices)) print("Меткa класса (y):", y_train[some_index], "- название класса:", ("Positive" if y_train[some_index] == 1 else "Negative")) ``` Преобразование индексов в текст выполнялось через обратный словарь `id_to_word`, сформированный из `word_to_id` с учётом сдвига `index_from` и добавления служебных токенов. Для каждого индекса находилось соответствующее слово, а при отсутствии — подставлялся . Полученные токены объединялись в строку, что давало текстовое отображение отзыва. Токен — это минимальная единица текста, с которой работает модель: слово, символ, подслово или специальный маркер. В датасете IMDb токеном является слово, которому присвоен числовой индекс. Числа в тензоре — это индексы токенов, то есть номера слов в словаре IMDb. Каждое число соответствует одному слову или служебному токену: * 1 — START * 2 — UNK * 0 — PAD остальные числа — реальные слова корпуса, закодированные целыми числами. ```python Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/imdb_word_index.json 1641221/1641221 ━━━━━━━━━━━━━━━━━━━━ 0s 0us/step Отзыв (список индексов): [1, 48, 25, 181, 8, 106, 6, 147, 2, 20, 79, 1070, 7, 4, 2, 2063, 14, 2, ... 989, 143, 8, 4, 2, 15, 1838, 2, 10, 10, 13, 447, 175, 786, 7, 12] ``` ``` Отзыв (текст): if you want to watch a real movie get hold of the formula this film must have cost all of 50 to make it features a thin script bad sets lighting and camera work and a stop motion paper monster that is utterly laughable it looks like they sometimes used a guy in a rubber suit and or a for the monster but all were equally dreadful br br the actors all speak their lines as though theyve never seen them before and are reading off a the special effects are way beyond lousy and the only sad thing is that they dropped the really original title which up exactly what you get for the full 90 minutes br br this is what happens when you the bottom of the so hard you break through to the that lies br br i loved every minute of it Длина отзыва (число индексов): 165 Меткa класса (y): 0 - название класса: Negative ``` #### *4. Вывести максимальную и минимальную длину отзыва в обучающем множестве* ```python max_len = len(max(X_train, key=len)) min_len = len(min(X_train, key=len)) print("Максимальная длина отзыва (в индексах):", max_len) print("Минимальная длина отзыва (в индексах):", min_len) ``` Видно,что отзывы сильно различаются по объёму, и работать с ними в исходном виде невозможно. Нейронная сеть принимает тензоры фиксированной формы. Без выравнивания обучение невозможно, т.к. батч не может содержать последовательности разной длины. ```python Максимальная длина отзыва (в индексах): 2494 Минимальная длина отзыва (в индексах): 11 ``` #### *5. Провести предобработку данных. Выбрать единую длину, к которой будут приведены все отзывы. Короткие отзывы дополнить спецсимволами, а длинные обрезать до выбранной длины.* ```python max_words = 500 X_train_prep = pad_sequences(X_train, maxlen=max_words, value=0, padding='pre', truncating='post') X_test_prep = pad_sequences(X_test, maxlen=max_words, value=0, padding='pre', truncating='post') print("Форма X_train_prep:", X_train_prep.shape) print("Форма X_test_prep:", X_test_prep.shape) ``` Все отзывы приведены к фиксированной длине 500. Это значение было выбрано как компромисс: оно достаточно большое, чтобы сохранить основное содержание средних и длинных отзывов, и достаточно маленькое, чтобы не перегружать память и ускорить обучение. При приведении длины короткие отзывы были дополнены токеном , а слишком длинные — обрезаны справа. ```python Форма X_train_prep: (25000, 500) Форма X_test_prep: (25000, 500) ``` #### *6. Повторить п. 4.* ```python print("После предобработки: длина", X_train_prep.shape[1]) ``` ```python После предобработки: длина 500 ``` #### *7. Повторить п. 3. Сделать вывод о том, как отзыв преобразовался после предобработки.* ```python prep_review_indices = X_train_prep[some_index] print("Предобработанный отзыв (индексы):", prep_review_indices) prep_review_text = ' '.join(id_to_word.get(i, "") for i in prep_review_indices if i != 0) print("\nПредобработанный отзыв (текст, без ):\n", prep_review_text) print("\nДлина предобработанного отзыва:", len(prep_review_indices)) ``` В исходном отзыве оказалось 165 токенов, что меньше выбранной фиксированной длины 500, поэтому при предобработке произошло только дополнение слева токеном PAD, а обрезания не было. ``` Предобработанный отзыв (индексы): [ 0 0 ... 175 786 7 12] Предобработанный отзыв (текст, без ): if you want to watch a real movie get hold of the formula this film must have cost all of 50 to make it features a thin script bad sets lighting and camera work and a stop motion paper monster that is utterly laughable it looks like they sometimes used a guy in a rubber suit and or a for the monster but all were equally dreadful br br the actors all speak their lines as though they've never seen them before and are reading off a the special effects are way beyond lousy and the only sad thing is that they dropped the really original title which up exactly what you get for the full 90 minutes br br this is what happens when you the bottom of the so hard you break through to the that lies br br i loved every minute of it Длина предобработанного отзыва: 500 ``` Чтобы убедиться в корректности преобразования, найден и выведен отзыв с длиной больше 500, к которому применено обрезание (код для поиска опущен, длинные тензоры укорочены). ``` Исходный отзыв (первые 50 индексов): [1, 2, 34, 1308, ... 532, 2, 18] Исходный отзыв (последние 50 индексов): [4, 323, 198, 89, ... 495] После предобработки (первые 50 индексов): [ 1 2 34 1308 ... 532 2 18] После предобработки (последние 50 индексов): [ 2 501 ... 10 32] ``` Как видно, последние индексы теперь выглядят по-другому, то есть произошло обрезание. При выводе появляются пробелы, связанные с тем, что Python пытается выровнять столбцы у `tf.Tensor`. #### *8. Вывести предобработанные массивы обучающих и тестовых данных и их размерности.* ```python print("X_train_prep shape:", X_train_prep.shape) print("X_test_prep shape: ", X_test_prep.shape) print("y_train shape:", y_train.shape) print("y_test shape: ", y_test.shape) for i in range(3): print(f"\nПример {i} (индексы):", X_train_prep[i][:300], "...") print(f"Метка:", y_train[i]) ``` Слишком длинные последовательности индексов были укорочены. ```python X_train_prep shape: (25000, 500) X_test_prep shape: (25000, 500) y_train shape: (25000,) y_test shape: (25000,) Пример 0 (индексы): [0 0 0 ... 0 0 0 0] Метка: 0 Пример 1 (индексы): [ 0 0 0 ... 11 585 2 11 940 11 4 2 720 11 4 4691] Метка: 1 Пример 2 (индексы): [ 0 0 ... 406 1522 29 186 8 412 27 113] Метка: 1 ``` #### *9. Реализовать модель рекуррентной нейронной сети, состоящей из слоев Embedding, LSTM, Dropout, Dense, и обучить ее на обучающих данных с выделением части обучающих данных в качестве валидационных. Вывести информацию об архитектуре нейронной сети. Добиться качества обучения по метрике accuracy не менее 0.8.* В этом пункте была реализована рекуррентная нейронная сеть для задачи бинарной классификации. * Embedding преобразовал числовые индексы слов в плотные векторные представления размерности embedding_dim=32. Благодаря этому слова, имеющие близкий контекст, получали похожие векторы, что облегчало обучение модели. * LSTM (Long Short-Term Memory) — рекуррентный слой с 64 единицами, он учитывал последовательный порядок слов, позволял модели «помнить» информацию из начала текста при обработке его конца. * Dropout — слой регуляризации с вероятностью 0.3, случайным образом отключавший часть нейронов во время обучения. * Dense — полносвязный слой с одним нейроном и сигмоидной активацией, который выдавал вероятность положительного класса (1 — Positive, 0 — Negative). Обучение проводилось с использованием 80% данных для тренировки и 20% для валидации (validation_split=0.2). ```python vocab_size = vocabulary_size embedding_dim = 32 input_length = max_words model = keras.Sequential([ layers.Embedding(input_dim=vocab_size, output_dim=embedding_dim, input_length=input_length), layers.LSTM(64), layers.Dropout(0.3), layers.Dense(1, activation='sigmoid') ]) model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) model.summary() # Обучение с выделением валидации history = model.fit( X_train_prep, y_train, epochs=4, batch_size=64, validation_split=0.2, verbose=1 ) ``` #### *10. Оценить качество обучения на тестовых данных: вывести значение метрики качества классификации на тестовых данных, вывести отчет о качестве классификации тестовой выборки, построить ROC-кривую по результату обработки тестовой выборки и вычислить площадь под ROC-кривой (AUC ROC).* ```python eval_results = model.evaluate(X_test_prep, y_test, verbose=1) print("Результаты оценки на тесте (loss, accuracy):", eval_results) # Получение "сырых" предсказаний и бинарных меток y_score = model.predict(X_test_prep) y_pred = [1 if y_score[i,0] >= 0.5 else 0 for i in range(len(y_score))] print("\nClassification report:\n") print(classification_report(y_test, y_pred, labels=[0,1], target_names=['Negative','Positive'])) # ROC-кривая и AUC fpr, tpr, thresholds = roc_curve(y_test, y_score) roc_auc = auc(fpr, tpr) print("\nAUC ROC (ручной вычисление):", roc_auc) print("AUC ROC (sklearn):", roc_auc_score(y_test, y_score)) import matplotlib.pyplot as plt plt.figure() plt.plot(fpr, tpr) plt.grid() plt.xlabel('False Positive Rate') plt.ylabel('True Positive Rate') plt.title('ROC') plt.show() ``` ```python 782/782 ━━━━━━━━━━━━━━━━━━━━ 8s 10ms/step - accuracy: 0.8603 - loss: 0.3458 Результаты оценки на тесте (loss, accuracy): [0.34332218766212463, 0.8626000285148621] 782/782 ━━━━━━━━━━━━━━━━━━━━ 6s 8ms/step Classification report: precision recall f1-score support Negative 0.83 0.92 0.87 12500 Positive 0.91 0.81 0.85 12500 accuracy 0.86 25000 macro avg 0.87 0.86 0.86 25000 weighted avg 0.87 0.86 0.86 25000 AUC ROC (ручной вычисление): 0.9381251584 AUC ROC (sklearn): 0.9381251584 ``` ![image](pics/i1.png) Идеальное значение площади под графиком — единица (100% угадывание положительных классов), а худший случай — 0,5 («вслепую»). Значение 0,93 показывает хорошую точность предсказаний. #### *11. Сделать выводы по результатам применения рекуррентной нейронной сети для решения задачи определения тональности текста.* В ходе лабораторной работы была построена рекуррентная нейронная сеть с LSTM для задачи бинарной классификации отзывов IMDb. Модель показала высокие показатели качества: точность на тестовой выборке достигла ≈86%, а площадь под ROC-кривой (AUC ROC ≈0.93) подтверждает способность модели надёжно различать положительные и отрицательные отзывы.