форкнуто от main/is_dnn
Вы не можете выбрать более 25 тем
Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
328 строки
21 KiB
Markdown
328 строки
21 KiB
Markdown
## Лабораторная работа №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["<PAD>"] = 0
|
|
word_to_id["<START>"] = 1
|
|
word_to_id["<UNK>"] = 2
|
|
word_to_id["<UNUSED>"] = 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, "<UNK>") 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` и добавления служебных токенов. Для каждого индекса находилось соответствующее слово, а при отсутствии — подставлялся <UNK>. Полученные токены объединялись в строку, что давало текстовое отображение отзыва.
|
|
|
|
Токен — это минимальная единица текста, с которой работает модель: слово, символ, подслово или специальный маркер. В датасете 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]
|
|
```
|
|
```
|
|
Отзыв (текст):
|
|
<START> if you want to watch a real <UNK> movie get hold of the <UNK> formula this <UNK> film must have cost all of 50 to make it features a <UNK> thin script <UNK> bad sets lighting and camera work and a stop motion paper <UNK> monster that is utterly laughable it looks like they sometimes used a guy in a rubber suit and or a <UNK> <UNK> 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 <UNK> the special effects are way beyond lousy and the only sad thing is that they dropped the really <UNK> original title <UNK> <UNK> which <UNK> up exactly what you get for the full 90 minutes br br this is what happens when you <UNK> the bottom of the <UNK> so hard you break through to the <UNK> that lies <UNK> 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. Это значение было выбрано как компромисс: оно достаточно большое, чтобы сохранить основное содержание средних и длинных отзывов, и достаточно маленькое, чтобы не перегружать память и ускорить обучение. При приведении длины короткие отзывы были дополнены токеном <PAD>, а слишком длинные — обрезаны справа.
|
|
|
|
```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, "<PAD>") for i in prep_review_indices if i != 0)
|
|
print("\nПредобработанный отзыв (текст, без <PAD>):\n", prep_review_text)
|
|
print("\nДлина предобработанного отзыва:", len(prep_review_indices))
|
|
```
|
|
|
|
В исходном отзыве оказалось 165 токенов, что меньше выбранной фиксированной длины 500, поэтому при предобработке произошло только дополнение слева токеном PAD, а обрезания не было.
|
|
|
|
```
|
|
Предобработанный отзыв (индексы): [ 0 0 ... 175 786 7 12]
|
|
|
|
Предобработанный отзыв (текст, без <PAD>):
|
|
<START> if you want to watch a real <UNK> movie get hold of the <UNK> formula this <UNK> film must have cost all of 50 to make it features a <UNK> thin script <UNK> bad sets lighting and camera work and a stop motion paper <UNK> monster that is utterly laughable it looks like they sometimes used a guy in a rubber suit and or a <UNK> <UNK> 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 <UNK> the special effects are way beyond lousy and the only sad thing is that they dropped the really <UNK> original title <UNK> <UNK> which <UNK> up exactly what you get for the full 90 minutes br br this is what happens when you <UNK> the bottom of the <UNK> so hard you break through to the <UNK> that lies <UNK> 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
|
|
```
|
|

|
|
|
|
Идеальное значение площади под графиком — единица (100% угадывание положительных классов), а худший случай — 0,5 («вслепую»). Значение 0,93 показывает хорошую точность предсказаний.
|
|
|
|
#### *11. Сделать выводы по результатам применения рекуррентной нейронной сети для решения задачи определения тональности текста.*
|
|
|
|
В ходе лабораторной работы была построена рекуррентная нейронная сеть с LSTM для задачи бинарной классификации отзывов IMDb. Модель показала высокие показатели качества: точность на тестовой выборке достигла ≈86%, а площадь под ROC-кривой (AUC ROC ≈0.93) подтверждает способность модели надёжно различать положительные и отрицательные отзывы. |