Методы анализа больших данных (Syllabus) 2025/Lesson ML

Материал из Поле цифровой дидактики
Урок по анализу данных с использованием методов машинного обучения

Диаграмма 1

Диаграмма 2


Диаграмма процесса очистки текста

Процесс получения данных

library(httr)
library(jsonlite)
library(xml2)
library(tidyverse)
library(quanteda)


# Установка дополнительных пакетов
library(stopwords)
library(stringr)

###########

get_wikipedia_category_members <- function(category_name, language = "ru") {
  # Базовый URL API
  base_url <- paste0("https://", language, ".wikipedia.org/w/api.php")
  
  all_members <- data.frame()
  continue_token <- NULL
  
  repeat {
    # Формируем параметры запроса
    params <- list(
      action = "query",
      list = "categorymembers",
      cmtitle = paste0("Category:", category_name),
      cmlimit = 500,  # Максимум результатов за раз
      format = "json",
      cmtype = "page",  # Только статьи, не категории
      cmcontinue = continue_token
    )
    
    # Выполняем запрос
    response <- GET(base_url, query = params)
    
    # Проверяем статус ответа
    if (status_code(response) != 200) {
      warning("Ошибка при запросе к API: статус ", status_code(response))
      break
    }
    
    # Парсим JSON
    data <- fromJSON(content(response, as = "text", encoding = "UTF-8"))
    
    # Извлекаем члены категории
    if (!is.null(data$query$categorymembers)) {
      members <- data$query$categorymembers %>%
        as_tibble() %>%
        select(pageid, title)
      
      all_members <- bind_rows(all_members, members)
    }
    
    # Проверяем, есть ли продолжение запроса
    if (is.null(data$`query-continue`$categorymembers$cmcontinue)) {
      break
    }
    
    continue_token <- data$`query-continue`$categorymembers$cmcontinue
  }
  
  return(all_members)
}

iot_articles <- get_wikipedia_category_members("Интернет вещей")
cat("Найдено статей:", nrow(iot_articles), "\n")
head(iot_articles)

######################
get_wikipedia_text <- function(article_title, language = "ru") {
  base_url <- paste0("https://", language, ".wikipedia.org/w/api.php")
  
  params <- list(
    action = "query",
    titles = article_title,
    prop = "extracts",
    explaintext = TRUE,  # Получаем чистый текст без Wiki-разметки
    format = "json"
  )
  
  response <- GET(base_url, query = params)
  
  if (status_code(response) != 200) {
    return(NA)
  }
  
  data <- fromJSON(content(response, as = "text", encoding = "UTF-8"))
  
  # Извлекаем текст из ответа
  pages <- data$query$pages
  page_id <- names(pages)[1]
  
  if (!is.null(pages[[page_id]]$extract)) {
    return(pages[[page_id]]$extract)
  } else {
    return(NA)
  }
}

# Сбираем тексты для всех найденных статей
# Добавляем задержку между запросами, чтобы не перегружать сервер
collected_texts <- tibble(
  title = character(),
  text = character()
)

for (i in seq_len(min(nrow(iot_articles), 50))) {  # Возьмём первые 50 статей для примера
  
  if (i %% 10 == 0) {
    cat("Обработано статей:", i, "\n")
  }
  
  article_title <- iot_articles$title[i]
  article_text <- get_wikipedia_text(article_title)
  
  if (!is.na(article_text)) {
    collected_texts <- add_row(collected_texts, 
                               title = article_title, 
                               text = article_text)
  }
  
  # Задержка между запросами (500 мс)
  Sys.sleep(0.5)
}

cat("Успешно получено текстов:", nrow(collected_texts), "\n")

###########################


# Функция для предварительной очистки текста
preprocess_text <- function(text) {
  # Приведение к нижнему регистру
  text <- tolower(text)
  
  # Удаление URL
  text <- str_remove_all(text, "https?://[^\\s]+")
  
  # Удаление специальных символов, но сохраняем буквы и пробелы
  text <- str_remove_all(text, "[^а-яА-Яa-zA-Z\\s]")
  
  # Удаление множественных пробелов
  text <- str_squish(text)
  
  return(text)
}

# Применяем предварительную очистку
cleaned_texts <- collected_texts %>%
  mutate(
    cleaned_text = map_chr(text, preprocess_text)
  )

head(cleaned_texts$cleaned_text, 1)


### Работа с пакетом quanteda

# Создание корпуса
corpus_texts <- corpus(cleaned_texts, 
                       docid_field = "title", 
                       text_field = "cleaned_text")

cat("Корпус создан. Документов:", ndoc(corpus_texts), "\n")
cat("Токенов:", ntoken(corpus_texts), "\n")

# Токенизация текста
tokens_data <- tokens(corpus_texts,
                      remove_punct = TRUE,
                      remove_numbers = TRUE,
                      remove_separators = TRUE)

# Приведение к нижнему регистру
tokens_data <- tokens_tolower(tokens_data)

# Получение русских стоп-слов
russian_stopwords <- stopwords("russian")

# Удаление стоп-слов
tokens_clean <- tokens_select(tokens_data, 
                              pattern = russian_stopwords, 
                              selection = "remove",
                              min_nchar = 3)  # Удаляем слова короче 3 символов

# Стемминг (приведение к корню слова)
tokens_stemmed <- tokens_wordstem(tokens_clean, language = "russian")

cat("Размер словаря после очистки:", ntype(tokens_stemmed), "\n")

# Создание матрицы документ-термин
dtm <- dfm(tokens_stemmed)

cat("Размеры DTM: ", nrow(dtm), " документов, ", ncol(dtm), " термов\n", sep = "")

# Просмотр структуры
head(dtm)

# Топ-10 самых частых слов
topfeatures(dtm, 10)

# Фильтрация редких слов
# Оставляем слова, которые встречаются минимум в 2 документах 
# и имеют минимум 5 вхождений в корпусе
dtm_trimmed <- dfm_trim(dtm, 
                        min_docfreq = 2,  # мин. документы
                        min_termfreq = 5)  # мин. вхождения

cat("Размеры отфильтрованной DTM: ", nrow(dtm_trimmed), " документов, ", 
    ncol(dtm_trimmed), " термов\n", sep = "")

# Вычисление TF-IDF (Term Frequency-Inverse Document Frequency)
dtm_tfidf <- dfm_tfidf(dtm_trimmed)

cat("TF-IDF матрица готова\n")

### Статистика текстов


# Получение статистики корпуса
corpus_stats <- tokens_data %>%
  ntoken() %>%
  tibble(doc_id = names(.), n_tokens = .)

# Визуализация статистики
library(ggplot2)

ggplot(corpus_stats, aes(x = n_tokens)) +
  geom_histogram(bins = 20, fill = "steelblue", color = "black") +
  labs(title = "Распределение количества токенов в документах",
       x = "Количество токенов",
       y = "Количество документов") +
  theme_minimal()

# Средняя длина документа
cat("Средняя длина документа:", mean(corpus_stats$n_tokens), "токенов\n")