Лабораторная работа №4. Заполнение текста с помощью n-грамм
Full API
Дано
Текст на английском языке (
assets/Harry_Potter.txt
), который загружен и сохранен в переменнуюtext
вstart.py
.Тест на уровень владения английским языком (
assets/question_and_answers.json
).
Ранее мы рассмотрели несколько способов детерминированной генерации текста с использованием N-грамм. Детерминированная генерация текста предсказуема и воспроизводима: при одинаковых входных данных она всегда дает один и тот же результат.
В настоящей лабораторной работе Вам предстоит познакомиться с рандомизированным алгоритмом генерации, который кроме всего прочего генерирует текст по словам, а не по символам. В отличие от детерминированной, рандомизированная генерация включает случайные элементы, что делает результаты непредсказуемыми и изменчивыми при каждом запуске. Детерминированные методы обеспечивают стабильность, подходящую для строгих контрольных задач. Рандомизированные методы поддерживают творческую вариативность, подходящую для генерации уникальных идей и контента.
При этом очевидно, что степень случайности необходимо контролировать: полностью случайный текст не будет иметь никакого смысла. Для контроля степени рандомизации в данной работе предлагается использовать технику семплирования Top P. Ее идея заключается в том, что при выборе следующего токена мы ограничиваемся набором наиболее вероятных токенов, порог вхождения для которых определяется параметром.
Кроме реализации описанного подхода Вам также предстоит сравнить знакомые Вам техники генерации текста при помощи метрики Perplexity. Простыми словами, данная метрика оценивает необычность сгенерированного текста, исходя из доступного распределения токенов.
Что необходимо сделать
Шаг 0. Начать работу над лабораторной (вместе с преподавателем на практике)
Измените файлы
main.py
иstart.py
.Закоммитьте изменения и создайте новый Pull Request.
Important
Код, выполняющий все требуемые действия, должен быть написан в
функции main
в модуле start.py
.
Для этого реализуйте функции в модуле main.py
и импортируйте их в start.py
.
Вызов функции в файле start.py
:
if __name__ == '__main__':
main()
В рамках данной лабораторной работы нельзя использовать сторонние модули, а также стандартные модули collections и itertools.
Обратите внимание, что в файле target_score.txt
необходимо выставить
желаемую оценку: 6, 8 или 10. Чем выше желаемая оценка, тем больше
тестов запускается при проверке Вашего Pull Request.
Шаг 1. Творческое задание (будет анонсировано преподавателем на практике)
Important
Выполнение Шага 1 соответствует 4 баллам.
Шаг 2. Предобработка текста
Традиционно работа с текстом начинается с предобработки. В рамках предобработки текст очищается, токенизируется и кодируется в числовые идентификаторы. Это необходимо для оптимизации работы программы: операции над числами требуют меньшего количества ресурсов, чем операции над строками.
Для предобработки текста необходимо реализовать класс
lab_4_fill_words_by_ngrams.main.WordProcessor
.
Данный класс должен наследоваться от
lab_3_generate_by_ngrams.main.TextProcessor
, поскольку их логика
во многом похожа. Принципиальным отличием выступает используемая единица текста:
в данной лабораторной работе мы будем оперировать словами, а не буквами.
Important
Нет необходимости переопределять логику инициализации экземпляра, поскольку она наследуется от класса-родителя.
Пример инициализации экземпляра:
word_processor = WordProcessor('<eos>')
Шаг 2.1. Токенизировать текст
Реализуйте метод
lab_4_fill_words_by_ngrams.main.WordProcessor._tokenize()
,
который позволяет разбить текст на токены.
Поскольку в данной лабораторной токеном выступает слово, а не буква, необходимо переопределить логику токенизации текста.
В ходе выполнения данного метода знаки препинания, обозначающие конец предложения
(!
, ?
, .
), должны быть заменены на специальный токен конца предложения.
Обратите внимание, что токен конца предложения содержится в соответствующем атрибуте,
унаследованном от класса
lab_3_generate_by_ngrams.main.TextProcessor
.
Далее текст необходимо очистить от цифр и специальных символов, оставив только
пробелы, буквы и токены конца предложения. Текст необходимо привести
к нижнему регистру и разделить на слова. Границей слова в настоящей лабораторной
выступает пробел либо сочетание пробельных символов. Обратите внимание, что токен
конца предложения является отдельным токеном и не примыкает к
последнему слову предложения.
Например, строка 'She is happy. He is happy.'
должна быть
токенизирована следующим образом:
('she', 'is', 'happy', '<eos>', 'he', 'is', 'happy', '<eos>')
,
где '<eos>'
- специальный токен конца предложения
Note
Если на вход подается аргумент неправильного типа, то есть не
строка, или строка пустая, то необходимо поднять исключение ValueError
.
Important
Данный метод является защищенным - его использование за пределами методов класса не предполагается.
Шаг 2.2. Добавить токен в хранилище
Нам также необходимо заполнить внутреннее хранилище токенами переданной последовательности. Так как в данной лабораторной мы оперируем словами, а не буквами, требования к токенам отличаются. Поэтому метод добавления токена в хранилище необходимо переопределить.
На этом шаге Вам нужно присвоить токену
некоторый уникальный целочисленный идентификатор.
Для этого реализуйте метод
lab_4_fill_words_by_ngrams.main.WordProcessor._put()
.
Note
Идентификатор - значение, которое однозначно указывает
на токен и равно длине _storage
(атрибут объекта данного
класса) на момент добавления буквы. Идентификатор специального
символа конца предложения принимает значение 0
.
Правила корректного заполнения хранилища:
Идентификаторы уникальны и однозначно указывают на токен (например, при добавлении нового токена можно использовать длину хранилища
_storage
);Для одного токена существует ровно один идентификатор;
Одинаковых идентификаторов у двух разных токенов быть не может;
Если токен уже был добавлен в хранилище, идентификатор остается прежним;
Идентификатор специального токена конца слова должен принимать значение 0. Обратите внимание, что выполнение данного условия гарантируется логикой инициализации экземпляра.
Например, если на вход подается токен 'she'
, то хранилище будет
выглядеть следующим образом - {'<eow>': 0, 'she': 1}
, где '<eow>'
- токен
конца предложения.
Note
Если на вход подается некорректное значение (аргумент
неправильного типа, то есть не строка, или пустая строка),
то необходимо поднять исключение ValueError
.
Important
Данный метод является защищенным - его использование за пределами методов класса не предполагается.
Шаг 2.3. Постобработать текст
Методы получения идентификатора по токену и токена по идентификатору, а также методы кодирования и декодирования текста переопределять не нужно: их логика остается актуальной и для обработки текста по словам. Именно в этом заключается удобство использования наследования при разработке программы. Таким образом, нам остается переопределить только постобработку текста.
Для этого необходимо реализовать метод
lab_4_fill_words_by_ngrams.main.WordProcessor._postprocess_decoded_text()
,
который позволяет перейти от токенизированного текста в формате
кортежа к тексту в строковом формате.
При этом на выходе строка должна соответствовать следующим требованиям:
Каждое предложение должно начинаться с заглавной буквы.
В итоговом тексте не должно быть нескольких пробелов подряд.
Текст должен заканчиваться точкой.
Important
Специальные токены конца предложения должны быть конвертированы в точки.
Например, для декодированного корпуса
('she', 'is', 'happy', '<eos>', 'he', 'is', 'happy', '<eos>')
должен получиться следующий текст: She is happy. He is happy.
,
где '<eow>'
- токен конца предложения.
Note
Если на вход подаются некорректные значения (токенизированный
текст не является кортежем или кортеж пустой),
то необходимо поднять исключение ValueError
.
Important
Данный метод является защищенным - его использование за пределами методов класса не предполагается.
Шаг 3. Рандомизированная генерация текста
Для аппроксимации вероятностного распределения токенов в зависимости
от контекста мы вновь будем опираться на понятие N-граммы.
В этом нам пригодится языковая модель, реализованная в предыдущей
лабораторной работе,
lab_3_generate_by_ngrams.main.NGramLanguageModel
.
Обратите внимание, что ее логика остается актуальной и для задачи недетерминированной генерации, поэтому переопределять сущность языковой модели нет необходимости. Для использования данного класса в текущей лабораторной достаточно его импортировать.
Как уже упоминалось, для рандомизации генерации текста мы будем использовать технику Top P. В данном случае P - это порог суммарной вероятности, который определяет количество вариантов генерации.
Пусть \(w_{1}, w_{2}, ..., w_{n}\) - список токенов, отсортированных таким образом, что \(p(w_{1}|context) > p(w_{2}|context) > ... > p(w_{n}|context)\), где \(p(w_{i}|context)\) - вероятность появления токена \(w_{i}\) в контексте \(context\), \(n\) - количество кандидатов для продолжения последовательности.
Тогда в рамках Top P подхода случайный выбор следующего токена будет происходить из таких первых \(N\) токенов, чтобы удовлетворялись оба условия:
\(\sum_{i=1}^{N}p(w_{i}|context) \geq P\)
\(\sum_{i=1}^{N - 1}p(w_{i}|context) < P\)
Иными словами, мы хотим выбрать минимальное количество токенов, которыми можем превысить заданный порог. Таким образом, если для какого-то контекста существуют варианты продолжения с очень высокой вероятностью, выбор будет производиться среди меньшего количества вариантов. В противном случае, если доступные варианты продолжения последовательности имеют низкую вероятность, выбор слова будет производиться из большего количества вариантов. Это позволяет избегать генерации слишком неправдоподобной последовательности, при этом не делая ее полностью детерминированной.
Рассмотрим пример.
Допустим, нам даны следующие варианты продолжения последовательности:
{'she': 0.46, 'that': 0.2, '<eos>': 0.04, 'cat': 0.01, 'is': 0.33}
.
Отсортируем токены по их вероятности в порядке убывания:
['she', 'is', 'that', '<eos>', 'cat']
.
Пусть порогом суммарной вероятности P является значение 0.8
, тогда
случайный выбор следующего токена будет производиться из набора токенов
[she', 'is', 'that]
, так как их суммарная вероятность равна 0.97
, что превышает
порога 0.8
, и исключение последнего рассматриваемого токена 'that'
снизит
сумму вероятности ниже порога.
Шаг 3.1. Объявить сущность генератора
Для генерации текста описанным способом вам необходимо реализовать
класс lab_4_fill_words_by_ngrams.main.TopPGenerator
.
Данный класс заключает в себе полную логику продолжения последовательности от принятия исходного контекста до постобработки результата.
С интерфейсом инициализации экземпляра данного класса, а также с составом
требуемых атрибутов можно ознакомиться в соответствующей строке документации
в файле main.py
.
Пример инициализации экземпляра:
top_p_generator = TopPGenerator(language_model, word_processor, 0.5)
Шаг 3.2. Сгенерировать последовательность
Реализуйте метод
lab_4_fill_words_by_ngrams.main.TopPGenerator.run()
,
который генерирует последовательность указанной длины по заданному контексту.
Прежде чем приступить к генерации, необходимо предобработать заданное
начало последовательности. Для этого используйте экземпляр
класса WordProcessor
, который хранится в соответствующем атрибуте.
В ходе генерации необходимо предпринять следующие шаги:
Закодировать исходную последовательности. Используйте для этого экземпляр класса
WordProcessor
.Получить возможные токены для продолжения последовательности. Используйте для этого экземпляр класса
NGramLanguageModel
.Выявить N минимальное количество наиболее вероятных токенов, чья суммарная последовательность превышает заданный P или равна ему. Для получения наиболее вероятных токенов отсортируйте токены-кандидаты в порядке убывания их вероятностей. В случае, если несколько токенов имеют одинаковую вероятность, то выбор нужно делать на основе значений ключей, которые сортируются в порядке убывания.
Случайным образом выбрать один из N токенов, отобранных на предыдущем шаге, использовать его для продолжения последовательности.
Повторить шаги 1-3 до тех пор, пока не будет достигнута требуемая длина последовательности.
Декодировать полученную последовательность. Используйте для этого экземпляр класса
WordProcessor
.
Для реализации случайного выбора обратитесь к встроенной библиотеке random
, а
конкретно к методу choice
(документация).
Следует учесть, что:
В случае, если кандидаты не были найдены, генерация прекращается.
С добавлением нового токена к исходной последовательности контекст изменяется.
Метод возвращает сгенерированный текст в виде строки, для этого его необходимо
декодировать, используя экземпляр WordProcessor
.
Note
Если на вход подаются некорректные значения (длина последовательности
не является целым положительным числом,
заданная последовательность не является
строкой или последовательность пустая) или вызываемые методы возвращают
значение None
, то необходимо поднять исключение ValueError
.
Обратите внимание, что все реализуемые Вами алгоритмы генерации текста используют одинаковый набор публичных методов. Это пример полиморфизма. Полиморфизм в объектно-ориентированном программировании (ООП) — это способность объектов разных типов использовать одинаковые имена (например, методы или операторы), но выполнять разные действия в зависимости от своего конкретного типа.
Шаг 3.3. Продемонстрировать работу реализации в start.py
Important
Выполнение Шага 3 соответствует 6 баллам.
Продемонстрируйте результат работы рандомизированного алгоритма генерации текста
в функции main()
модуля start.py
.
Попробуйте в качестве исходной последовательности использовать 'Vernon'
.
Пусть размер n-грамм будет равен 2, длина последовательности - 51,
а значение P - 0.5.
Шаг 4. Оценка качества сгенерированной последовательности
Для оценки вероятности мы будем использовать метрику Perplexity. Перплексия - это метрика, используемая для измерения качества работы моделей генерации текста. Она представляет собой обратную вероятность наблюдения конкретной последовательности слов, сгенерированных моделью. Таким образом, более низкая перплексия указывает на то, что модель более уверенно и точно предсказывает следующее слово в последовательности.
Перплексия вычисляется как экспонента от числа, противоположного усредненной сумме логарифмированных вероятностей появления каждого из токенов:
Здесь \(n\) - количество токенов в оцениваемой последовательности, \(w_{i}\) - i-й токен последовательности, \(context_{i}\) - контекст i-го токена последовательности, \(p(w_{i}|context_{i})\) - вероятность появления токена \(w_{i}\) в данном контексте \(context_{i}\).
Рассмотрим пояснение этой формулы. Вообще, кумулятивную вероятность последовательности можно представить в качестве сложной вероятности, то есть вероятности совпадения независимых событий, что подразумевает перемножение вероятностей. Поскольку в данном случае мы работаем с числами в диапазоне от нуля до единицы, при перемножении большого количества таких чисел вырастает риск столкнуться с проблемой потери значимости: слишком близкое к нулю число может стать неотличимо от нуля для программы. Поэтому мы логарифмируем вероятность, тем самым увеличивая ее порядок. Логарифм также позволяет нам перейти от перемножения к сумме. Однако логарифмирование инвертирует порядок, поэтому необходимо добавить минус. Наконец, возводя число е в степень получившегося значения, мы масштабируем значение метрики таким образом, чтобы любые отклонения от ожидаемого распределения были более значимыми.
Нередко перплексия оказывается наименьшей для жадных алгоритмов генерации текста в силу детерминированности и предсказуемости порождаемых последовательностей. Рандомизированные и нежадные алгоритмы в свою очередь допускают выбор не самых вероятных токенов на каждом из шагов продолжения последовательности, иными словами такие алгоритмы имеют меньше уверенности в той последовательности, которую они порождают. Это также отражается в значении метрики перплексии.
Шаг 4.1. Реализовать сущность, обозначающую тип генерации
В настоящей лабораторной работе мы будем сравнивать следующие виды генерации:
жадный алгоритм
алгоритм Beam Search
алгоритм Top P
Необходимо объявить сущность, хранящую данные виды генерации в качестве атрибутов,
lab_4_fill_words_by_ngrams.main.GeneratorTypes
.
Это необходимо для предотвращения уязвимости кода в дальнейшем: при передаче аргументов
в виде строки всегда есть вероятность допустить опечатку. Поэтому удобно
иметь класс, экземпляры которого содержат фиксированную репрезентацию того
или иного типа.
Атрибуты экземпляров данного класса должны иметь следующие целочисленные значения:
0
для жадного алгоритма;1
для Top P алгоритма;2
для Beam Search алгоритма.
С интерфейсом инициализации экземпляра данного класса, а также с составом
требуемых атрибутов можно ознакомиться в соответствующей строке документации
в файле main.py
.
Пример инициализации экземпляра:
generators_types = GeneratorTypes()
Шаг 4.2. Получить название алгоритма генерации
Присвоение целочисленных идентификаторов каждому из типов генерации удобно для унификации и упрощения операции сравнения, однако строковый формат является более понятным человеку.
Реализуйте метод
lab_4_fill_words_by_ngrams.main.GeneratorTypes.get_conversion_generator_type()
,
который возвращает название метода генерации в виде строки.
Если был использован жадный алгоритм, то следует вернуть строку 'Greedy Generator'
,
если Top P алгоритм - 'Top-P Generator'
,
если Beam Search алгоритм - 'Beam Search Generator'
.
Шаг 4.3. Сохранить оценку генерации текста
Для более удобного управления результатами сравнительного анализа реализуем
сущность, хранящую в себе необходимую информацию о результатах оценки сгенерированной
последовательности,
lab_4_fill_words_by_ngrams.main.GenerationResultDTO
.
DTO (Data Transfer Object) в объектно-ориентированном программировании (ООП) является шаблоном проектирования, который используется для передачи данных между слоями программы.
С интерфейсом инициализации экземпляра данного класса, а также с составом
требуемых атрибутов можно ознакомиться в соответствующей строке документации
в файле main.py
.
Пример инициализации экземпляра:
estimation_result = GenerationResultDTO(generated_text, perplexity, generator_type)
Шаг 4.4. Получить значение метрики качества
Атрибут, хранящий в себе значение метрики перплексии является приватным: мы не предполагаем
его изменения в течение жизненного цикла экземпляра класса GenerationResultDTO
.
Тем не менее, потребность узнать данное значение существует. Для этого реализуйте метод
lab_4_fill_words_by_ngrams.main.GenerationResultDTO.get_perplexity()
,
который возвращает значение перплексии.
Шаг 4.5. Получить оцениваемую последовательность
Атрибут, хранящий в себе текст, по которому было рассчитано
значение метрики перплексии, является приватным: мы не предполагаем
его изменения в течение жизненного цикла экземпляра класса GenerationResultDTO
.
Тем не менее, потребность узнать данное значение существует. Для этого реализуйте метод
lab_4_fill_words_by_ngrams.main.GenerationResultDTO.get_text()
,
который возвращает текст.
Шаг 4.6. Получить идентификатор типа алгоритма генерации
Атрибут, хранящий в себе указатель на алгоритм генерации
является приватным: мы не предполагаем
его изменения в течение жизненного цикла экземпляра класса GenerationResultDTO
.
Тем не менее, потребность узнать данное значение существует. Для этого реализуйте метод
lab_4_fill_words_by_ngrams.main.GenerationResultDTO.get_type()
,
который возвращает идентификатор алгоритма генерации.
Шаг 4.7. Получить отчет об оценке качества генерации
Наконец, необходимо реализовать формирование отчета о полученном результате.
Для этого реализуйте магический метод
lab_4_fill_words_by_ngrams.main.GenerationResultDTO.__str__()
,
который возвращает строку определенного формата, отражающую тип генерации, значение перплексии
и текст, на котором была произведена оценка.
В рамках этого метода необходимо инициализировать экземпляр GeneratorTypes
и обратиться
к его соответствующему методу.
Метод __str__
называют магическим, потому что его переопределение меняет поведение
объекта в случае, когда его конвертируют в строку. Это можно проверить, например, вызвав
print
, передав экземпляр в качестве аргумента.
Рассмотрим пример. Допустим, экземпляр был инициализирован следующим образом:
estimation_result = GenerationResultDTO('I love apple juice', 1.2, 1)
.
Тогда вызов print(estimation_result)
должен вывести в консоль следующую строку:
Perplexity score: 1.2 Top-P Generator Text: I love apple juice
Шаг 4.8. Объявить сущность для оценки сгенерированного текста
Наконец, перейдем к реализации класса, ответственного за проведение
сравнительного анализа,
lab_4_fill_words_by_ngrams.main.QualityChecker
.
Данный класс заключает логику генерации последовательности каждым из заданных алгоритмов, подсчет метрики качества для каждой из получившихся последовательностей и обработку результатов.
С интерфейсом инициализации экземпляра данного класса, а также с составом
требуемых атрибутов можно ознакомиться в соответствующей строке документации
в файле main.py
.
Пример инициализации экземпляра:
generators_dict = { generators_types.greedy: GreedyTextGenerator(language_model, word_processor), generators_types.top_p: TopPGenerator(language_model, word_processor, 0.5), generators_types.beam_search: BeamSearchTextGenerator(language_model, word_processor, 5) } checker = QualityChecker(generators_dict, language_model, word_processor)
Шаг 4.9. Посчитать метрику качества последовательности
Реализуйте метод
lab_4_fill_words_by_ngrams.main.QualityChecker._calculate_perplexity()
.
Метод производит оценку сгенерированной последовательности следующим образом:
Для этого в первую очередь необходимо закодировать последовательность. Используйте для этого экземпляр класса
WordProcessor
.Далее для каждого из токенов последовательности необходимо получить вероятность его появления в данном контексте, используя экземпляр языковой модели.
Полученные вероятности необходимо использовать для подсчета Perplexity по формуле перплексии:
Здесь \(n\) - количество токенов в оцениваемой последовательности, \(w_{i}\) - i-й токен последовательности, \(context_{i}\) - контекст i-го токена последовательности, \(p(w_{i}|context_{i})\) - вероятность появления токена \(w_{i}\) в данном контексте \(context_{i}\). Подробное рассмотрение формулы приведено выше.
Рассмотрим пример. Допустим, мы имеем следующие данные о вероятности появления биграмм:
{ (1, 2): 0.5, (2, 3): 1.0, (3, 4): 1.0, (4, 0): 1.0, (0, 1): 1.0, (1, 5): 0.5, (5, 6): 1.0, (6, 4): 1.0 }
Давайте оценим качество следующей сгенерированной последовательности:
(1, 2, 3)
.
Подсчитаем логарифм вероятности для каждого из токенов в данной последовательности, за
исключением последнего токена конца предложения: для токена 2 логарифм вероятности
составляет -0.6931471805599453
, для токена 3 логарифм вероятности составляет 0.0
.
Таким образом, сумма логарифмов вероятности равна -0.6931471805599453
.
Усредним и инвертируем: 0.34657359027997264
. Значением метрики будет являться
экспонента от этого числа, то есть 1.414213562373095
.
Для подсчета логарифма воспользуйтесь встроенным модулем math
.
Note
Если аргументы имеют некорректный тип данных, то есть не
являются строкой, или строка пустая,
а также если вызываемые методы
возвращают None
, необходимо поднять исключение ValueError
.
Шаг 4.10. Реализовать логику сравнительного анализа
Реализуйте метод
lab_4_fill_words_by_ngrams.main.QualityChecker.run()
,
осуществляющий логику сравнительного анализа.
Для каждого из методов генерации, хранимых в соответствующем атрибуте,
необходимо сгенерировать последовательность
заданной длины по заданному контексту. Полученные последовательности необходимо оценить
при помощи метода
lab_4_fill_words_by_ngrams.main.QualityChecker._calculate_perplexity()
.
Результат оценки необходимо сохранить в экземпляр
lab_4_fill_words_by_ngrams.main.GenerationResultDTO
.
Каждому методу генерации должен соответствовать один экземпляр хранилища результатов.
Возвращаемые значения должны быть отсортированы по значению метрики в порядке
возрастания. В случае, если значения метрики совпадают, необходимо дополнительно
отсортировать по целочисленному идентификатору типа алгоритма.
Note
Если входные аргументы имеют неправильный тип, входная последовательность
является пустой строкой или требуемая длина не является положительным
числом, а также если вызываемые методы возвращают None
,
необходимо поднять исключение ValueError
.
Шаг 4.11. Продемонстрировать работу реализации в start.py
Important
Выполнение Шага 4 соответствует 8 баллам.
Продемонстрируйте результат сравнительного анализа
в функции main()
модуля start.py
. В качестве аргумента для
инициализации экземпляра класса
lab_4_fill_words_by_ngrams.main.QualityChecker
необходимо использовать экземпляр класса
lab_4_fill_words_by_ngrams.main.GeneratorTypes
.
Сравните качество генерации алгоритмами жадной генерации, Beam Search генерации и Top P генерации.
Для Top P алгоритма качестве значения P используйте 0.5
,
в качестве параметра ширины луча для алгоритма Beam Search
используйте 5
. Начните генерацию со строки 'The'
,
при сравнительном анализе используйте длину последовательности 100
.
Шаг 5. Симуляция проведения экзамена на знание языка
В данном шаге предлагается в игровой форме оценить способность изученных алгоритмов генерации корректно дополнять текст пропущенными словами. Частым заданием в тестах на выявление уровня владением языком является вставка пропущенного слова. Давайте реализуем проведение такого экзамена.
Например, задание может выглядеть следующим образом:
текст, который необходимо дополнить:
Frosty the Snowman was made of and had a corncob pipe and a button nose.
;позиция, на которую необходимо вставить слово:
30
;ответ:
Frosty the Snowman was made of snow and had a corncob pipe and a button nose.
.
Шаг 5.1. Объявить сущность экзаменатора
Далее необходимо определить класс
lab_4_fill_words_by_ngrams.main.Examiner
,
роль которого заключается в осуществлении логики составления и оценки
экзамена.
С интерфейсом инициализации экземпляра данного класса, а также с составом
требуемых атрибутов можно ознакомиться в соответствующей строке документации
в файле main.py
.
Обратите внимание, что экземпляры данного класса имеют атрибут, заполнение которого нужно произвести в момент инициализации при помощи метода, который вы реализуете на следующем шаге.
Пример инициализации экземпляра:
examiner = Examiner('./assets/question_and_answers.json')
Шаг 5.2. Загрузить вопросы и ответы
Реализуйте метод
lab_4_fill_words_by_ngrams.main.Examiner._load_from_json()
,
в котором происходит чтение файла из соответствующего атрибута и заполнение
атрибутов его содержимым.
Файл с вопросами и ответами имеет следующую структуру:
[
{"question": "Thank very much.", "location": 6, "answer": "Thank you very much."},
{...}
]
Путь к файлу: assets/questions_and_answers.json
.
Note
Если путь к файлу не является строкой, либо строка пустая или не
заканчивается как .json
,
а также если содержимое файла не является списком,
необходимо поднять исключение ValueError
.
Шаг 5.3. Вывести вопросы
Экзаменатору известны как вопросы, так и ответы. Однако при проведении экзамена необходимо делиться только вопросами, именно поэтому соответствующий атрибут класса экзаменатора является защищенным.
Реализуйте метод
lab_4_fill_words_by_ngrams.main.Examiner.provide_questions()
,
возвращающий вопросы экзамена. Метод не должен возвращать ответы!
Шаг 5.4. Выставить оценку
Реализуйте метод
lab_4_fill_words_by_ngrams.main.Examiner.assess_exam()
,
заключающий логику проверки ответов.
Метод должен сопоставить полученные ответы с правильными и посчитать долю правильных ответов. Правильным ответом считается строка, полностью совпадающая с эталоном.
Note
Если входной аргумент не является словарем либо словарь пустой,
необходимо поднять исключение ValueError
.
Шаг 5.5. Объявить сущность студента
Далее необходимо определить класс
lab_4_fill_words_by_ngrams.main.GeneratorRuleStudent
.
Данный класс представляет абстракцию студента и реализует общую логику ответа
на вопрос одной из изученных техник генерации.
С интерфейсом инициализации экземпляра данного класса, а также с составом
требуемых атрибутов можно ознакомиться в соответствующей строке документации
в файле main.py
.
В случае, если атрибут с экземпляром генератора заполняется Beam Search алгоритмом,
используйте значение параметра ширины луча 5
при инициализации.
В случае, если атрибут с экземпляром генератора заполняется Top P алгоритмом,
используйте значение параметра P равное 0.5
.
Пример инициализации экземпляра:
top_p_student = GeneratorRuleStudent(generators_types.top_p, language_model, word_processor)
Шаг 5.6. Составить ответы на задания
Логику обработки заданий необходимо реализовать в методе
lab_4_fill_words_by_ngrams.main.GeneratorRuleStudent.take_exam()
.
Для каждого из заданий метод осуществляет следующие действия:
Из последовательности извлекается левый контекст до указанной позиции.
По выделенному контексту генерируется новая последовательность длины 1.
Сгенерированная последовательность объединяется с правым контекстом.
Обратите внимание, что если сгенерированная последовательность заканчивается точкой, то точку нужно исключить из предсказания.
Note
Если входной аргумент не является списком либо список пустой,
либо если используемые методы возвращают None,
необходимо поднять исключение ValueError
.
Шаг 5.7. Запросить тип алгоритма заполнения
Перед началом сдачи экзамена каждому студенту необходимо представиться.
Для этого реализуйте метод,
возвращающий тип использованного алгоритма генерации,
lab_4_fill_words_by_ngrams.main.GeneratorRuleStudent.get_generator_type()
.
В данном методе необходимо инициализировать и использовать экземпляр класса GeneratorTypes.
Если был использован жадный алгоритм, то следует вернуть строку 'greedy generator'
,
если Top P алгоритм - 'top-p generator'
,
если Beam Search алгоритм - 'beam search generator'
.
Шаг 5.8. Продемонстрировать работу реализации в start.py
Important
Выполнение Шага 5 соответствует 10 баллам.
Продемонстрируйте проведение экзамена, создав экземпляр экзаменатора и экземпляры студентов для каждого из реализованных алгоритмов.
В качестве заданий используйте набор вопросов и ответов из файла
assets/questions_and_answers.json
.