Привет, Хабр!

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

Здесь начинаем разбирать анализ текстовых данных. По-разному «от руки» написанных отчетов о причинах возникновения инцидентов.

Незначительные ошибки в системе или нестабильное соединение от одного из провайдеров, могут на первый взгляд казаться мелочами. Однако, если такие «маленькие» проблемы возникают с завидной регулярностью, это уже тревожный сигнал. Постоянные технические неполадки со стороны провайдера или регулярные проблемы с банковскими партнерами — это повод задуматься и пересмотреть условия сотрудничества.

Сегодня о том, как я пытаюсь выявлять паттерны возникновения Инцидентов, другими словами, искать мелкие Проблемы.

Дано:

Есть BI-система, хранящая информацию обо всех отчетах.

Отчет довольно простой — форма с полями. Единственное интересующее нас поле называется «14. Причина молнии» и содержится в столбце 14 (как неожиданно) .xlsx файла.

Найти: 

На выходе я хочу видеть три столбца:

  • 1 столбец содержит порядковый номер проблемы;

  • 2 столбец содержит название проблемы;

  • 3 столбец содержит все номера схожих проблем.

Пускай Питончик будет возвращать мне result.csv файл, выводить на дашборд будем в другой раз.

Решение:

Поскольку нас интересует сущностное сравнение («проблемы сети» = «проблема с сетью»), шаги следующие:

  • Нам нужно убрать лишние слова, оставив лишь существительные, прилагательные, глаголы и наречия;

  • Срезать стоп-слова (иметь возможность убирать лишние слова, пускай хранятся в текстовом файле);

  • Срезать приставки, суффиксы — достаточно лемматизировать слова;

  • Есть еще пункт, который я пока не реализовал — добавить поиск по  ключевым словам. Они будут лежать в том же репозитории, txt-файлом. Нам следует научить скрипт тому, что ключевые слова важнее остальных совпадений, чтобы была возможность влиять на агрегацию важных для нас инцидентов.

Теперь по порядку:

Нам потребуются библиотеки:

import os
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
from natasha import (
    Segmenter,
    MorphVocab,
    NewsEmbedding,
    NewsMorphTagger,
    Doc
)
  • os” для работы с операционной системой.

  • pandas” для работы с данными в формате DataFrame.

  • TfidfVectorizer” и “cosine_similarity” из “sklearn” для обработки текста и вычисления схожести.

  • natasha” для лемматизации текста на русском языке.

Загружаем явным образом стоп-слова:

def load_stopwords(filepath):
    with open(filepath, 'r', encoding='utf-8') as file:
        stopwords = file.read().splitlines()
    return set(stopwords)

Эта функция открывает файл с заданным filepath, читает стоп-слова (одно на строку) и возвращает их в виде множества для быстрого поиска.

Вводим функцию лемматизации текста:

Эта функция принимает текст, сегментирует его на токены, тегирует их морфологическими тегами, лемматизирует каждый токен, исключает стоп-слова и возвращает лемматизированный текст.

def lemmatize_text(text, morph_vocab, segmenter, morph_tagger, stopwords):
    doc = Doc(text)
    doc.segment(segmenter)
    doc.tag_morph(morph_tagger)
    lemmas = []
    for token in doc.tokens:
        token.lemmatize(morph_vocab)
        if token.lemma not in stopwords:
            lemmas.append(token.lemma)
    return ' '.join(lemmas)

Основная функция анализа схожих проблем (указанных причин инцидента):

def analyze_similar_problems(file_path, stopwords_path, output_path='result.csv'):
    df = pd.read_excel(file_path)
    df = df.dropna(subset=[df.columns[1], df.columns[13]])

Здесь данные загружаются из .xlsx файла с помощью pandas. Затем удаляются строки, в которых отсутствуют номера инцидентов или описания причин (столбцы 2 и 14). Во втором столбце у нас порядковый номер молнии, как правило, это четырехзначное число, в 14 – сам текст.

Загружаем стоп-слова из указанного файла с помощью функции load_stopwords.

stopwords = load_stopwords(stopwords_path)

Инициализируем необходимые компоненты Natasha для сегментации и лемматизации текста и Лемматизация текста в 14-м столбце. 

segmenter = Segmenter()    
morph_vocab = MorphVocab()    
emb = NewsEmbedding()    
morph_tagger = NewsMorphTagger(emb)
df['Лемматизированный текст'] = df[df.columns[13]].apply(        
lambda text: lemmatize_text(str(text), morph_vocab, segmenter, morph_tagger, stopwords)    
)

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

Основная магия: преобразование текста в векторы с использованием TF-IDF и вычисление косинусного расстояния между всеми парами записей.

vectorizer = TfidfVectorizer()    
tfidf_matrix = vectorizer.fit_transform(df['Лемматизированный текст'])
cosine_similarities = cosine_similarity(tfidf_matrix, tfidf_matrix)

Создается объект “TfidfVectorizer”, который преобразует лемматизированный текст в матрицу TF-IDF.

С помощью cosine_similarity вычисляется косинусное расстояние между всеми парами текстов в TF-IDF матрице.

Далее создаём пустой DataFrame для хранения результатов:

result_df = pd.DataFrame(columns=['Порядковый номер', 'Название проблемы', 'Схожие проблемы'])

Заполнение таблицы результатами:

Для каждой строки в исходном DataFrame сравниваются косинусные схожести с другими строками. Если схожесть выше заданного порогового значения (0.5), номер инцидента добавляется в список схожих проблем. Затем для каждой строки создается запись в “result_df” с указанием номера проблемы, названия проблемы и номеров схожих проблем.

for i in range(len(df)):
  similarities = []
  for j in range(len(df)):
      if i != j and cosine_similarities[i][j] > 0.5:                 
          similarities.append(df.iloc[j, 1])
  result_df = pd.concat([result_df, pd.DataFrame({'Порядковый номер': [df.iloc[i, 1]],
                                                  'Название проблемы': [df.iloc[i, 13]],
                                                  'Схожие проблемы': [', 
                                                  '.join(map(str, similarities))]})], ignore_index=True)

Полученный DataFrame “result_df” сохраняется в файл “result.csv”:

result_df.to_csv(output_path, index=False)

Этот блок кода запускает функцию “analyze_similar_problems”, если скрипт выполняется напрямую. Путь к файлу данных и путь к файлу стоп-слов передаются в качестве аргументов функции:

if __name__ == '__main__':
  file_path = 'your_data.xlsx'
  stopwords_path = 'russian_stopwords.txt'
  analyze_similar_problems(file_path, stopwords_path)

В результате получаем файл “result.csv” с корректным результатом. Прямо сейчас дальнейшая работа с ним устроена так: я копирую содержимое и добавляю в гугл-табличку, по которой и сужу о том, пора ли бить тревогу.

Ответ:

Данный скрипт позволяет довольно быстро, хоть и вручную, получить ответ на простой вопрос: «а такое у нас уже случалось?» при разборе очередного инцидента технического характера. 

Следующим шагом станет анализ инцидентов и отклонений онлайн (раз в сутки подгружать из базы описания проблем) и выведением на дашборд таблицы, ранж в которой можно применять по «количеству» и «времени».

P.S.: Пока писал статью, понял, что еще хочу добавить: поиск по ключевым словам, которые будут считаться для скрипта важнее, чем простая повторяемость, чтобы инциденты с такими словами сразу попадали в верхнюю часть списка.

Комментарии (1)


  1. Samhuawei
    07.06.2024 12:57

    Дата-сатанист это хорошо. Контора Рога и копыта.