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