ЛР4
Ключевые слова: частое или характерное?

Keywords · 1 балл за два раздела

Ключевые слова: частое или характерное?

Топ по всем статьям и топ для одного домена отвечают на разные вопросы.

Term frequency
Суммарное число упоминаний токена.
Контраст
Сравнение частоты внутри домена с частотой вне него.
Сглаживание
Добавление малого значения, чтобы избежать нулей.
На пальцах. «Человек» может быть частым и в медицине, и в социологии. «Нейрон» встречается реже, но может лучше отличать одну область.

Для общего топ-20 суммирую веса по всем train-статьям. Для домена сравниваю нормированные частоты в нём и во всех остальных. Беру логарифм отношения с добавлением 1. Отсекаю слова с весом внутри домена меньше 5, чтобы случайное редкое слово не стало лидером.

score = ln(p_inside / p_outside)

CountVectorizer даёт целые числа, TF-IDF даёт вещественные веса. При TF-IDF влияют редкость и нормализация длины статьи. Топы могут меняться даже при одном словаре.

Ловушка. Просто отсортировать частоты внутри домена недостаточно: в каждом домене могут победить одни и те же общие слова.

Код и построчный разбор

Код исходной ячейки 26: def keywords(matrix, feature_names, targets, top_n=20):
def keywords(matrix, feature_names, targets, top_n=20):
    overall = np.asarray(matrix.sum(axis=0)).ravel()
    print('Общий топ-20:', ', '.join(feature_names[np.argsort(-overall, kind='stable')[:top_n]]))
    rows = []
    for domain in labels:
        inside = np.asarray(matrix[np.asarray(targets)==domain].sum(axis=0)).ravel()
        outside = overall - inside
        p_inside = (inside+1)/(inside.sum()+len(inside))
        p_outside = (outside+1)/(outside.sum()+len(outside))
        # Контраст домен / остальные, чтобы не выдавать общие слова за специфичные.
        score = np.log(p_inside/p_outside)
        score[inside < 5] = -np.inf
        chosen = np.argsort(-score, kind='stable')[:top_n]
        rows.append({'domain': domain, 'keywords': ', '.join(feature_names[chosen])})
    with pd.option_context('display.max_colwidth', None):
        display(pd.DataFrame(rows))

keyword_count = CountVectorizer(max_features=15000, min_df=3)
keyword_X = keyword_count.fit_transform(train_text)
keywords(keyword_X, keyword_count.get_feature_names_out(), y_train)
  1. 1: def keywords(matrix, feature_names, targets, top_n=20):
    Объявляет функцию: код внутри выполняется при её вызове.
  2. 2: overall = np.asarray(matrix.sum(axis=0)).ravel()
    Превращает результат вычисления в массив NumPy; ravel делает его одномерным.
  3. 3: print('Общий топ-20:', ', '.join(feature_names[np.argsort(-overall, kind='stable')[:top_n]]))
    Возвращает индексы сортировки, чтобы выбрать самые большие оценки.
  4. 4: rows = []
    Вычисляет правую часть и сохраняет результат в rows.
  5. 5: for domain in labels:
    Повторяет вложенный блок для каждого элемента указанной последовательности.
  6. 6: inside = np.asarray(matrix[np.asarray(targets)==domain].sum(axis=0)).ravel()
    Превращает результат вычисления в массив NumPy; ravel делает его одномерным.
  7. 7: outside = overall - inside
    Вычисляет правую часть и сохраняет результат в outside.
  8. 8: p_inside = (inside+1)/(inside.sum()+len(inside))
    Вычисляет правую часть и сохраняет результат в p_inside.
  9. 9: p_outside = (outside+1)/(outside.sum()+len(outside))
    Вычисляет правую часть и сохраняет результат в p_outside.
  10. 10: # Контраст домен / остальные, чтобы не выдавать общие слова за специфичные.
    Комментарий объясняет причину следующего действия.
  11. 11: score = np.log(p_inside/p_outside)
    Вычисляет натуральный логарифм отношения; используется в IDF или контрасте частот.
  12. 12: score[inside < 5] = -np.inf
    Вычисляет правую часть и сохраняет результат в score[inside < 5].
  13. 13: chosen = np.argsort(-score, kind='stable')[:top_n]
    Возвращает индексы сортировки, чтобы выбрать самые большие оценки.
  14. 14: rows.append({'domain': domain, 'keywords': ', '.join(feature_names[chosen])})
    Добавляет один результат в список; порядок сохраняется.
  15. 15: with pd.option_context('display.max_colwidth', None):
    Управляет ресурсом: открытым архивом или больше не нужной матрицей.
  16. 16: display(pd.DataFrame(rows))
    Показывает таблицу в ноутбуке.
  17. 18: keyword_count = CountVectorizer(max_features=15000, min_df=3)
    Вычисляет правую часть и сохраняет результат в keyword_count.
  18. 19: keyword_X = keyword_count.fit_transform(train_text)
    Выучивает словарь и статистику по обучающим текстам и сразу строит их матрицу.
  19. 20: keywords(keyword_X, keyword_count.get_feature_names_out(), y_train)
    Продолжает выражение предыдущей строки: здесь заданы данные или аргументы вызова.
Код исходной ячейки 38: keyword_tf = TfidfVectorizer(max_features=15000, min_df=3)
keyword_tf = TfidfVectorizer(max_features=15000, min_df=3)
keyword_T = keyword_tf.fit_transform(train_text)
keywords(keyword_T, keyword_tf.get_feature_names_out(), y_train)
print('Совпадение общих топ-20:', len(set(keyword_count.get_feature_names_out()[np.asarray(keyword_X.sum(axis=0)).ravel().argsort()[-20:]]) &
      set(keyword_tf.get_feature_names_out()[np.asarray(keyword_T.sum(axis=0)).ravel().argsort()[-20:]])))
  1. 1: keyword_tf = TfidfVectorizer(max_features=15000, min_df=3)
    Вычисляет правую часть и сохраняет результат в keyword_tf.
  2. 2: keyword_T = keyword_tf.fit_transform(train_text)
    Выучивает словарь и статистику по обучающим текстам и сразу строит их матрицу.
  3. 3: keywords(keyword_T, keyword_tf.get_feature_names_out(), y_train)
    Продолжает выражение предыдущей строки: здесь заданы данные или аргументы вызова.
  4. 4: print('Совпадение общих топ-20:', len(set(keyword_count.get_feature_names_out()[np.asarray(keyword_X.sum(axis=0)).ravel().argsort()[-20:]]) &
    Превращает результат вычисления в массив NumPy; ravel делает его одномерным.
  5. 5: set(keyword_tf.get_feature_names_out()[np.asarray(keyword_T.sum(axis=0)).ravel().argsort()[-20:]])))
    Превращает результат вычисления в массив NumPy; ravel делает его одномерным.

Вопросы защиты

Почему слова только из train?

Это часть анализа выбранных признаков, test остаётся закрытым до оценки.

У TF-IDF сумма это число упоминаний?

Нет, сумма нормализованных весов. Называть её количеством слов некорректно.

Как понять, что топ разумный?

Прочитать слова и проверить, связаны ли они с областью; это интерпретация, а не доказательство причин.

Одна фразаХарактерное слово чаще встречается в нужном домене относительно остальных.