Лабораторная работа №1. Определение языка текста на основе частотного словаря

Дано

  1. Три текста на английском (assets/texts/en.txt), немецком (assets/texts/de.txt) и неизвестном (assets/texts/unknown.txt) языках.

  2. Языковые профили немецкого, английского, испанского, французского, итальянского, русского и турецкого языков (assets/profiles). Необходимо определить, на каком языке написан неизвестный текст, анализируя встречаемость букв в каждом из языков.

Что надо сделать

Шаг 0. Начать работу над лабораторной (вместе с преподавателем на практике)

  1. Создайте форк репозитория.

  2. Установите необходимые инструменты для работы.

  3. Измените файлы main.py и start.py.

  4. Закоммитьте изменения и создайте Pull request.

Important

В файле start.py вы должны написать код, определяющий язык неизвестного текста.

Для этого реализуйте функции в модуле main.py и импортируйте их в start.py. Весь код, выполняющий детектирование языка, должен быть выполнен в функции main в файле start.py:

def main() -> None:
    pass

Вызов функции в файле start.py:

if __name__ == '__main__':
    main()

В рамках данной лабораторной работы нельзя использовать модули collections, itertools, а также сторонние модули.

Обратите внимание, что желаемую оценку необходимо указать в файле target_score.txt. Возможные значения: 0, 4, 6, 8, 10. Чем большее значение выставлено, тем больше тестов будет запущено.

Шаг 1. Токенизировать текст

Important

Выполнение Шага 1 соответствует 4 баллам.

Реализуйте функцию lab_1_classify_by_unigrams.main.tokenize().

Например, строка 'Hey! How are you?' должна быть токенизирована следующим образом: ['h', 'e', 'y', 'h', 'o', 'w', 'a', 'r', 'e', 'y', 'o', 'u'].

Продемонстрируйте выделяемые токены из текста на английском языке в файле start.py. Текст на английском языке сохранен в переменную en_text.

Шаг 2. Получить частотный словарь по заданному тексту

Реализуйте функцию lab_1_classify_by_unigrams.main.calculate_frequencies().

Под относительной частотой подразумевается отношение количества вхождений токена к общему числу токенов.

Так, из последовательности токенов ['h', 'e', 'y', 'h', 'o', 'w', 'a', 'r', 'e', 'y', 'o', 'u'] должен получиться следующий словарь частот:

{'h': 0.16666666666666666,
 'e': 0.16666666666666666,
 'y': 0.16666666666666666,
 'o': 0.16666666666666666,
 'w': 0.08333333333333333,
 'a': 0.08333333333333333,
 'r': 0.08333333333333333,
 'u': 0.08333333333333333}

Шаг 3. Создать профиль конкретного языка

Important

Выполнение Шагов 1-3 соответствует 6 баллам.

Профиль языка – это структура с информацией о конкретном языке. В настоящей лабораторной работе профиль языка состоит из названия языка и частотного словаря.

В дальнейших лабораторных работах вы будете работать с другими языковыми профилями. Пример языковых профилей вы можете найти в следующем проекте. Несмотря на то что данные профили содержат информацию о n-граммах, с которыми мы познакомимся позднее, структура этих профилей аналогична.

Пример языкового профиля, который требуется в настоящей лабораторной работе:

{
    "name": "en",
    "freq": {
        "g": 0.8,
        "t": 0.2
     }
}

Здесь ключу "freq" соответствует частотный словарь, ключу "name" – название языка.

В данной лабораторной работе языковой профиль обязательно представляет собой словарь, который содержит два ключа – "freq" и "name".

Для создания профиля языка реализуйте функцию lab_1_classify_by_unigrams.main.create_language_profile().

Используйте функцию lab_1_classify_by_unigrams.main.tokenize() для токенизации и функцию lab_1_classify_by_unigrams.main.calculate_frequencies() для получения частотного словаря.

Продемонстрируйте создание языкового профиля для английского языка в файле start.py.

Шаг 4. Рассчитать метрику MSE

В дальнейшем для определения близости двух языковых профилей нам понадобится метрика среднеквадратичной ошибки (MSE, Mean Squared Error). Для начала рассмотрим эту метрику безотносительно применения к задаче детекции языка.

Значение MSE рассчитывается по формуле \(MSE = \frac{\sum (y_{i} - p_{i})^{2}}{n}\), где:

  • y - истинное значение;

  • p - предсказанное значение;

  • n - количество значений.

Обратите внимание, что количество истинных значений y и количество предсказанных значений p совпадает и равно n.

Таким образом, метрика MSE - не что иное, как среднее квадратов разности между истинными значениями и предсказанными значениями. Чем это значение меньше, тем ближе предсказанные значения к истинным.

Для того чтобы рассчитать метрику MSE, реализуйте функцию lab_1_classify_by_unigrams.main.calculate_mse().

Шаг 5. Сравнить два языковых профиля

Чтобы сравнить языковые профили необходимо рассчитать значение метрики MSE, которая определяет различие между двумя языками.

Для этого нужно выделить все токены, встречающиеся в двух языковых профилях, а также сопоставить им частотность в каждом из языков. Иными словами, мы находим объединение множества токенов в первом языке со множеством токенов во втором языке. Далее, для каждого из токенов находим его встречаемость в каждом из множеств.

Для примера рассмотрим два языковых профиля:

profile_1 = {
    'name': 'lang1',
    'freq': {'a': 0.5, 'b': 0.5}
}
profile_2 = {
    'name': 'lang2',
    'freq': {'b': 0.5, 'c': 0.5}
}

В данных профилях встречаются следующие символы: a, b, c. При этом в профиле первого языка их встречаемость равна [0.5, 0.5, 0], а в профиле второго языка - [0, 0.5, 0.5].

Приняв встречаемость символов в первом языке за истинные значения и встречаемость символов во втором языке за предсказанные, мы можем рассчитать разницу профилей по метрике MSE. Ее значение будет равно 0.167 (с округлением до третьего знака).

Note

Что изменится, если сделать наоборот и принять за истинные значения встречаемость токенов во втором языке и за предсказанные - в первом? Почему?

Реализуйте функцию сравнения двух языковых профилей lab_1_classify_by_unigrams.main.compare_profiles(). Для расчета метрики MSE нужно обратиться к функции lab_1_classify_by_unigrams.main.calculate_mse().

Шаг 6. Определить язык неизвестного текста

Important

Выполнение Шагов 1-6 соответствует 8 баллам.

Чтобы определить язык неизвестного текста, реализуйте функцию lab_1_classify_by_unigrams.main.detect_language().

Она устанавливает язык текста на основе метрики MSE и возвращает название языка с ее наименьшим значением. Название языка находится в языковом профиле. Если у двух языков одинаковое значение метрики, отсортируйте названия языков в алфавитном порядке и возьмите первое.

Для нахождения метрики MSE нужно использовать функцию lab_1_classify_by_unigrams.main.compare_profiles().

В файле start.py определите, к какому языку ближе текст на неизвестном языке: к английскому или к немецкому. Текст на немецком языке сохранен в переменной de_text. Текст на неизвестном языке сохранен в переменной unknown_text.

Шаг 7. Загрузить языковой профиль

Для определения языка может быть недостаточно двух языковых профилей. На самом деле в данной задаче может использоваться произвольное количество языковых профилей (например, 6).

Для дальнейшей работы нам потребуется возможность загружать языковой профиль из файла с расширением json. Узнать больше об этом типе файлов можно здесь.

В папке assets для вас сохранены несколько языковых профилей. Для корректной работы необходимо сформировать путь к каждому из них в формате assets/profiles/<filename>.json. Например, путь к испанскому языковому профилю должен выглядеть так: assets/profiles/es.json. Сохраните список таких путей в переменную в файле start.py.

Для чтения и сохранения такого типа файлов часто используется стандартный модуль json. Изучить его документацию можно по ссылке.

Реализуйте функцию чтения языкового профиля из файла lab_1_classify_by_unigrams.main.load_profile(). При этом функция должна только читать файл, никакой дополнительной обработки не подразумевается.

Пример вызова функции:

language_profile = load_profile(filename)

Шаг 8. Обработать языковой профиль

Языковые профили могут выглядеть по-разному. Вы можете заметить, что в папке assets они имеют особый формат: в них содержатся токены не только из одного символа, но и из нескольких, а также присутствует дополнительный ключ n_words.

На данном шаге Вам нужно привести языковой профиль к нашему единому формату. Для этого реализуйте функцию lab_1_classify_by_unigrams.main.preprocess_profile(),

Напоминаем, что языковой профиль должен содержать только два ключа: name и freq. По ключу freq содержится частотный словарь, ключами которого выступают униграммы в нижнем регистре, а значениями - относительная встречаемость токена.

Например, у нас есть следующий необработанный языковой профиль:

profile_raw = {
    'name': 'lang1',
    'freq': {
        'ab': 3,
        '4c': 2,
        'a': 1
        'F': 2,
        'c&': 1,
        'abc': 7
    },
    'n_words': [3, 6, 7]
}

Для приведения языкового профиля к нужному формату нужно:

  1. Выбрать униграммы, состоящие из букв, из представленного набора токенов (ключ freq).

  2. Привести их к нижнему регистру.

  3. Посчитать их относительную частоту.

Поле n_words содержит в себе список из трех чисел, которые обозначают количество униграмм, биграмм и триграмм, соответственно. Для подсчета относительной частоты токенов используйте первое из трех чисел поля n_words, при этом ключа n_words в обработанном профиле быть не должно. Ключ name дополнительно обрабатывать не нужно.

Таким образом, для примера выше должен получится следующий обработанный языковой профиль:

profile_raw = {
    'name': 'lang1',
    'freq': {
        'a': 0.33333333333,
        'f': 0.66666666666,
    },
}

Пример вызова функции:

processed_profile = preprocess_profile(profile_raw)

Шаг 9. Собрать языковые профили

Поскольку нам предстоит сравнить целый ряд языковых профилей, нужно загрузить и предобработать сразу несколько профилей.

Для этого реализуйте функцию lab_1_classify_by_unigrams.main.collect_profiles(), которая должна вызывать lab_1_classify_by_unigrams.main.load_profile() и lab_1_classify_by_unigrams.main.preprocess_profile().

Пример вызова функции:

collected_profiles = collect_profiles(paths_to_profiles)

Шаг 10. Определить язык неизвестного текста

Теперь мы готовы определить язык неизвестного текста, рассматривая сразу несколько возможных вариантов.

Для этого реализуйте функцию lab_1_classify_by_unigrams.main.detect_language_advanced(). Она возвращает отсортированный список кортежей вида [('lang1', score), ('lang2', score)], где первым элементом кортежа выступает название языка, а вторым - значение MSE. Длина списка соответствует количеству переданных профилей известных языков. Сортировка предполагается от меньшего значения метрики к большему. Языки с совпадающим значением MSE нужно упорядочить лексикографически.

Note

Почему в данной задаче лучше сортировать от меньшего к большему, а не наоборот?

Шаг 11. Сформировать отчет

Important

Выполнение Шагов 1-11 соответствует 10 баллам.

Теперь, когда мы можем сравнить целый ряд языков, нужно сформировать понятный отчет.

Для этого реализуйте функцию lab_1_classify_by_unigrams.main.print_report(), которая выводит отчет в следующей форме:

<lang1>: MSE <score>
<lang2>: MSE <score>

Обратите внимание, что score необходимо округлить до пяти знаков после запятой. Например, если был получен результат [('en', 0.00013213), ('de', 0.00016231), ('fr', 0.00010123)], то при вызове функции в консоль будет выведен следующий отчет:

en: MSE 0.00013
de: MSE 0.00016
fr: MSE 0.0001

Для округления можно использовать форматирование строки: f'{score:.5f}'.

Продемонстрируйте детекцию неизвестного языка путём сравнения с языковыми профилями английского, французского, итальянского, русского, испанского и турецкого языков в файле start.py. Для вывода отчета в консоль вызовите функцию print_report.

Полезные ссылки