Лабораторная работа №1. Выделение ключевых слов =============================================== .. toctree:: :maxdepth: 1 :titlesonly: :caption: Full API lab_1_keywords_tfidf.api.rst Дано ---- 1. Текст сказки Ганса Христиана Андерсена "Дюймовочка" на русском языке (``assets/Дюймовочка.txt``). 2. Список стоп-слов (``assets/stop_words.txt``). 3. Словарь с Inverse Document Frequency значениями (``assets/IDF.json``). 4. Словарь с корпусными частотами (``assets/corpus_frequencies.json``). Что надо сделать ---------------- Шаг 0. Начать работу над лабораторной (вместе с преподавателем на практике) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1. Создайте форк репозитория. 2. Установите необходимые инструменты для работы. 3. Измените файлы ``main.py`` и ``start.py``. 4. Закоммитьте изменения и создайте Pull request. Для этого реализуйте функции в модуле ``main.py`` и импортируйте их в ``start.py``. Весь процесс (от предобработки текста до выделения ключевых слов) должен быть выполнен в функции ``main`` в файле ``start.py``: .. code:: py def main() -> None: pass Вызов функции в файле ``start.py``: .. code:: py if __name__ == '__main__': main() В рамках данной лабораторной работы **нельзя использовать модули collections, itertools, а также сторонние модули.** Обратите внимание, что желаемую оценку необходимо указать в файле ``settings.json`` в поле ``target_score``. Возможные значения: 0, 4, 6, 8, 10. Чем большее значение выставлено, тем больше тестов будет запущено. Шаг 1. Очистить и токенизировать текст ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Реализуйте функцию :py:func:`lab_1_keywords_tfidf.main.clean_and_tokenize`. Функция принимает на вход текст в виде строки. Возвращаемым значением должен быть список строк без: - знаков препинания, - букв верхнего регистра, - лишних пробелов. Например, строка ``'Hey! How are you?'`` должна быть токенизирована следующим образом: ``['hey', 'how', 'are', 'you']``. Если на вход подаются некорректные значения, возвращается ``None``. Некорректным значением считается значение не того типа, который ожидается. Интерфейс: .. code:: py def clean_and_tokenize(text: str) -> list[str]: pass Шаг 2. Удалить стоп-слова ~~~~~~~~~~~~~~~~~~~~~~~~~ .. important:: Выполнение Шагов 1-2 соответствует 4 баллам. Реализуйте функцию :py:func:`lab_1_keywords_tfidf.main.remove_stop_words`. Функция принимает на вход список токенов и список стоп-слов. Возвращаемым значением должен быть список токенов без стоп-слов. Если на вход подаются некорректные значения, возвращается ``None``. Пустой список стоп-слов не считается некорректным значением. Продемонстрируйте выделяемые токены из текста про Дюймовочку без стоп-слов в файле ``start.py``. Текст сохранен в переменную ``target_text``. Интерфейс: .. code:: py def remove_stop_words(tokens: list[str], stop_words: list[str]) -> list[str]: pass Шаг 3. Получить частотный словарь по заданному тексту ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Реализуйте функцию :py:func:`lab_1_keywords_tfidf.main.calculate_frequencies`. Функция принимает список токенов. Возвращает словарь вида ``{токен: количество вхождений}``. Так, из последовательности токенов ``['hey', 'how', 'are', 'you']`` должен получиться следующий словарь частот: .. code:: py {'hey': 1, 'how': 1, 'are': 1, 'you': 1} Интерфейс: .. code:: py def calculate_frequencies(tokens: list[str]) -> dict[str, int]: pass Шаг 4. Получить список первых N по популярности слов ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. important:: Выполнение Шагов 1-4 соответствует 6 баллам. Реализуйте функцию :py:func:`lab_1_keywords_tfidf.main.get_top_n`. Функция принимает частотный словарь и число ``N``. Возвращает список первых ``N`` слов по убыванию частоты. Если ``N`` больше числа слов в словаре, возвращаются все слова. Обратите внимание, что значения ожидаемого на вход словаря могут быть как в целочисленном формате `int`, так и в формате с плавающей точкой `float`. Интерфейс: .. code:: py def get_top_n(frequencies: dict[str, int | float], top: int) -> list[str]: pass Шаг 5. Рассчитать значения Term Frequency для каждого из слов в тексте ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Term Frequency - это отношение числа вхождений некоторого слова к общему числу слов документа. Таким образом оценивается важность слова в пределах отдельного документа. Формула: .. math:: tf(t, d) = \frac{n_t}{N_d} Где: - :math:`n_t` — количество вхождений слова :math:`t` в документ :math:`d`; - :math:`N_d` — общее количество слов в документе :math:`d`. Реализуйте функцию :py:func:`lab_1_keywords_tfidf.main.calculate_tf`. Она должны будет рассчитывать Term Frequency для токенов. Функция принимает на вход частотный словарь. Возвращаемым значением функции должен быть словарь, в котором ключами являются токены, а значениями - соответствующие им значения TF. Интерфейс: .. code:: py def calculate_tf(frequencies: dict[str, int]) -> dict[str, float]: pass Шаг 6. Рассчитать значения TF-IDF для каждого из слов в тексте ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TF-IDF — статистическая мера, используемая для оценки важности слова в контексте документа, являющегося частью коллекции документов или корпуса. Она состоит из двух множителей — Term Frequency (TF) и Inverse Document Frequency (IDF). Формула: .. math:: TFIDF(t, d, D) = TF(t, d) \times IDF(t, D) Множитель Inverse Document Frequency — это инверсия частоты, с которой некоторое слово встречается в документах коллекции: .. math:: idf(t, D) = \log \left(\frac{|D|}{|\{d_i \in D : t \in d_i\}| + 1}\right) Здесь: - числитель (:math:`|D|`) — общее количество документов в коллекции; - знаменатель (:math:`|\{d_i \in D : t \in d_i\}|`) — количество таких документов, в которых встречается слово :math:`t`. Таким образом, вес токена в документе прямо пропорционален количеству его вхождений в этот документ и обратно пропорционален количеству вхождений в остальные документы. Простыми словами: чем чаще слово встречается в нашем документе и чем реже оно встречается где-то ещё, тем оно важнее. .. note:: Если слово не найдено в словаре IDF, используйте формулу выше, подставив 0 вместо :math:`|\{d_i \in D : t \in d_i\}|` и 47 вместо :math:`|D|` (именно столько текстов содержится в коллекции сказок Г. Х. Андерсена). Реализуйте функцию :py:func:`lab_1_keywords_tfidf.main.calculate_tfidf`. Функция принимает на вход два словаря: словарь со значениями TF и словарь со значениями IDF. Возвращает словарь формата ``{слово: значение TF-IDF}``. Если на вход подаются некорректные значения, возвращается ``None``. Интерфейс: .. code:: py def calculate_tfidf(term_freq: dict[str, float], idf: dict[str, float]) -> dict[str, float]: pass Шаг 7. Продемонстрировать топ-10 ключевых слов согласно TF-IDF ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. important:: Выполнение Шагов 1-7 соответствует 8 баллам. Выведите 10 наиболее важных слов согласно значению TF-IDF с помощью функции ``print``. Напомним, чем выше значение TF-IDF, тем более важным считается слово. В этом задании обязательно использовать функцию :py:func:`lab_1_keywords_tfidf.main.get_top_n`. Шаг 8. Рассчитать ожидаемую частотность слов ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Предположим, что важность слова можно оценивать, сравнивая его частотность в документе с его частотностью в коллекции документов. Если токен ключевой для документа, его встречаемость в этом документе будет сильно отличаться от обычной частотности. Если слово не ключевое, частотность примерно совпадает с частотой в коллекции. Формулировка гипотез: - :math:`H_0:` частотность слова :math:`t` в документе :math:`d` статистически значимо превышает его частотность в коллекции :math:`D` - :math:`H_1:` частотность слова :math:`t` в документе :math:`d` не превышает его частотность в коллекции :math:`D` Для вычисления ожидаемой частотности необходимо найти следующие значения: +-----------+-----------+-------------------+ | | d | D | +===========+===========+===================+ | t | j | k | +-----------+-----------+-------------------+ | ~t | l | m | +-----------+-----------+-------------------+ где: - :math:`j` — количество вхождений слова :math:`t` в документ :math:`d` - :math:`k` — количество вхождений слова :math:`t` во все документы коллекции :math:`D` - :math:`l` — количество вхождений всех слов, кроме :math:`t`, в документ :math:`d` - :math:`m` — количество вхождений всех слов, кроме :math:`t`, в коллекцию документов :math:`D` Ожидаемая частотность слова :math:`t` в документе :math:`d` рассчитывается по формуле: .. math:: Expected = \frac{(j + k) \times (j + l)}{j + k + l + m} Реализуйте функцию :py:func:`lab_1_keywords_tfidf.main.calculate_expected_frequency`. Интерфейс: .. code:: py def calculate_expected_frequency(doc_freqs: dict[str, int], corpus_freqs: dict[str, int]) -> dict[str, float]: pass Частотный словарь для коллекции сказок Г. Х. Андерсена загружен в переменной ``corpus_freqs`` в файле ``start.py``. Шаг 9. Рассчитать значения хи-квадрат ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Для оценки того, насколько наблюдаемая частотность превышает ожидаемую, используется статистический критерий :math:`\chi^2`. Формула для каждого слова: .. math:: \chi^2 = \frac{(observed - expected)^2}{expected} где: - ``observed`` — наблюдаемое количество вхождений слова в документе - ``expected`` — ожидаемая частотность слова (рассчитанная в Шаге 8) Реализуйте функцию :py:func:`lab_1_keywords_tfidf.main.calculate_chi_values`. Интерфейс: .. code:: py def calculate_chi_values(expected: dict[str, float], observed: dict[str, int]) -> dict[str, float]: pass Если на вход подаются некорректные значения, функция должна возвращать ``None``. Шаг 10. Выделить статистически значимые слова ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Статистически значимыми словами считаются те, для которых значение :math:`\chi^2` превышает критическое значение. Выбор критического значения зависит от желаемого уровня значимости (вероятность ошибочно назвать неключевое слово ключевым): .. code:: py CRITERION = {0.05: 3.842, 0.01: 6.635, 0.001: 10.828} Реализуйте функцию :py:func:`lab_1_keywords_tfidf.main.extract_significant_words`. Функция принимает словарь значений :math:`\chi^2` и уровень значимости ``alpha``. Возвращает словарь, содержащий только статистически значимые слова. Если на вход подаются некорректные значения, возвращается ``None``. Интерфейс: .. code:: py def extract_significant_words(chi_values: dict[str, float], alpha: float) -> dict[str, float]: pass Шаг 11. Вывести топ-10 ключевых слов согласно :math:`\chi^2` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. important:: Выполнение Шагов 1-11 соответствует 10 баллам. Чем выше значение :math:`\chi^2` для токена, тем более значимым считается слово. Выведите 10 самых важных слов с точки зрения статистики. Ранжирование токенов выполняется с помощью функции :py:func:`lab_1_keywords_tfidf.main.get_top_n`. Полезные ссылки --------------- - `Источник текстов Г. Х. Андерсена `__ - `Пример создания частотного словаря и словаря IDF из корпуса `__ - `TF-IDF clearly explained `__ - `Три странички, объясняющие принцип работы χ² для взвешивания терминов `__