From be286b87f8d95ed68fd760bedd17083ddbd4e359 Mon Sep 17 00:00:00 2001 From: itqop Date: Wed, 23 Oct 2024 01:07:56 +0300 Subject: [PATCH] Add preprocess --- app/api/routes.py | 18 ++++-- app/core/cache.py | 2 - app/core/config.py | 3 +- app/core/text_preprocessing.py | 81 +++++++++++++++++++++++++++ tests/test_text_preprocessing.py | 96 ++++++++++++++++++++++++++++++++ 5 files changed, 190 insertions(+), 10 deletions(-) create mode 100644 app/core/text_preprocessing.py create mode 100644 tests/test_text_preprocessing.py diff --git a/app/api/routes.py b/app/api/routes.py index 55b7e62..8bd1bf0 100644 --- a/app/api/routes.py +++ b/app/api/routes.py @@ -1,8 +1,7 @@ -# app/api/routes.py - from fastapi import APIRouter, HTTPException from app.models.schemas import TextInput, ToxicityOutput from app.core.cache import cache +from app.core.text_preprocessing import preprocess_text import json import hashlib import logging @@ -23,7 +22,14 @@ async def assess_toxicity(input: TextInput): - **text**: Текст для оценки """ - cache_key = get_cache_key(input.text) + try: + preprocessed_text = preprocess_text(input.text) + logger.info(f"Текст после предобработки: {preprocessed_text}") + except Exception as e: + logger.error(f"Ошибка при предобработке текста: {e}") + raise HTTPException(status_code=400, detail="Ошибка при предобработке текста.") + + cache_key = get_cache_key(preprocessed_text) # Попытка получить результат из кеша cached_result = await cache.get(cache_key) @@ -37,8 +43,8 @@ async def assess_toxicity(input: TextInput): try: # Отправляем задачу в очередь Celery - result = assess_toxicity_task.delay(input.text) - logger.info(f"Задача отправлена в очередь Celery для текста: {input.text}") + result = assess_toxicity_task.delay(preprocessed_text) + logger.info(f"Задача отправлена в очередь Celery для текста: {preprocessed_text}") toxicity_score = result.get(timeout=10) # Ждем результат до 10 секунд # Сохраняем результат в кеш @@ -48,4 +54,4 @@ async def assess_toxicity(input: TextInput): return {"toxicity_score": toxicity_score} except Exception as e: logger.error(f"Ошибка при обработке текста: {e}") - raise HTTPException(status_code=500, detail=str(e)) + raise HTTPException(status_code=500, detail="Ошибка при оценке токсичности текста.") diff --git a/app/core/cache.py b/app/core/cache.py index 5ffc233..182fc6e 100644 --- a/app/core/cache.py +++ b/app/core/cache.py @@ -1,5 +1,3 @@ -# app/core/cache.py - import asyncio import json import hashlib diff --git a/app/core/config.py b/app/core/config.py index 2019842..ae04c70 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -1,11 +1,10 @@ -# app/core/config.py - from pydantic import Field, validator from pydantic_settings import BaseSettings from typing import Any from transformers import AutoTokenizer import os + class Settings(BaseSettings): MODEL_CHECKPOINT: str = Field( 'cointegrated/rubert-tiny-toxicity', diff --git a/app/core/text_preprocessing.py b/app/core/text_preprocessing.py new file mode 100644 index 0000000..f2d20ba --- /dev/null +++ b/app/core/text_preprocessing.py @@ -0,0 +1,81 @@ +import re +import logging +from typing import List + +logger = logging.getLogger(__name__) + +ENGLISH_TO_RUSSIAN_LETTERS = { + 'a': 'а', + 'c': 'с', + 'e': 'е', + 'o': 'о', + 'p': 'р', + 'x': 'х', + 'y': 'у', + 'B': 'В', + 'M': 'М', + 'T': 'Т', +} + +LETTER_DIGIT_PATTERN = re.compile(r'([а-яё]+)(\d+)', re.IGNORECASE) + +RUSSIAN_LETTER_PATTERN = re.compile(r'[а-яё]', re.IGNORECASE) + +def is_majority_russian(word: str) -> bool: + """Проверяет, составляет ли большинство букв в слове русские буквы.""" + letters = re.findall(r'[а-яёА-ЯЁ]', word) + if not letters: + return False + russian_count = len(letters) + total_letters = len(word) + return russian_count > (total_letters / 2) + +def replace_similar_english_letters(word: str) -> str: + """Заменяет похожие английские буквы на русские эквиваленты.""" + return ''.join([ENGLISH_TO_RUSSIAN_LETTERS.get(char, char) for char in word]) + +def replace_digits(word: str) -> str: + """Заменяет цифры '3' и '0' на буквы 'з' и 'о' соответственно.""" + return word.replace('3', 'з').replace('0', 'о') + +def split_letters_digits(word: str) -> str: + """Разделяет буквы и цифры в слове.""" + return LETTER_DIGIT_PATTERN.sub(r'\1 \2', word) + +def preprocess_text(text: str) -> str: + """ + Предобрабатывает текст: + 1. Приводит к нижнему регистру. + 2. Заменяет похожие английские буквы на русские. + 3. В словах с большинством русских букв заменяет '3' на 'з' и '0' на 'о'. + 4. Разделяет буквы и цифры. + """ + logger.debug("Начало предобработки текста.") + text = text.lower() + logger.debug(f"Текст после приведения к нижнему регистру: {text}") + + words = text.split() + processed_words: List[str] = [] + + for word in words: + original_word = word + + + if is_majority_russian(word): + word = replace_similar_english_letters(word) + logger.debug(f"Слово после замены английских букв: {word}") + word = replace_digits(word) + logger.debug(f"Слово после замены цифр: {word}") + + word = split_letters_digits(word) + logger.debug(f"Слово после разделения букв и цифр: {word}") + + processed_words.append(word) + logger.debug(f"Слово '{original_word}' преобразовано в '{word}'.") + + preprocessed_text = ' '.join(processed_words) + logger.debug(f"Текст после предобработки: {preprocessed_text}") + + return preprocessed_text + + diff --git a/tests/test_text_preprocessing.py b/tests/test_text_preprocessing.py new file mode 100644 index 0000000..82723c9 --- /dev/null +++ b/tests/test_text_preprocessing.py @@ -0,0 +1,96 @@ + +from app.core.text_preprocessing import preprocess_text + +def test_preprocess_lowercase(): + """Тестирование преобразования текста в нижний регистр.""" + input_text = "ПрИвет Мир" + expected = "привет мир" + assert preprocess_text(input_text) == expected + +def test_preprocess_replace_english_letters(): + """Тестирование замены похожих английских букв на русские.""" + input_text = "Hello, how are you?" + expected = "hello, how are you?" # В данном случае, поскольку 'H', 'e', 'l', 'o' заменяются на русские? + # Но согласно ENGLISH_TO_RUSSIAN_LETTERS, 'a' -> 'а', 'c' -> 'с', 'e' -> 'е', 'o' -> 'о', 'p' -> 'р', 'x' -> 'х', 'y' -> 'у', 'B' -> 'В', 'M' -> 'М', 'T' -> 'Т' + # Так как ENGLISH_TO_RUSSIAN_LETTERS не охватывает все, нужно скорректировать + # Лучше использовать пример с заменяемыми буквами + input_text = "Привет a, c, e, o, p, x, y" + expected = "привет а, с, е, о, р, х, у" + assert preprocess_text(input_text) == expected + +def test_preprocess_replace_digits_in_russian_word(): + """Тестирование замены цифр '3' и '0' на 'з' и 'о' в словах с преобладанием русских букв.""" + input_text = "Привет3 и 0дела" + expected = "привет з и одела" + assert preprocess_text(input_text) == expected + +def test_preprocess_split_letters_digits(): + """Тестирование разделения букв и цифр в словах.""" + input_text = "привет12 как дела" + expected = "привет 12 как дела" + assert preprocess_text(input_text) == expected + +def test_preprocess_mixed_russian_english_letters(): + """Тестирование обработки слов с смешанными русскими и английскими буквами.""" + input_text = "а3b0 привет12" + # 'a3b0' -> 'азбо' (замена 'a'->'а', '3'->'з', '0'->'о') и затем разделение букв и цифр + # Но после замены, 'a3b0' -> 'азбо', в котором нет цифр, поэтому разделения не произойдет + # Возможно, другой пример: "a3b0c" -> "азбoc" -> возможно "азбоc", но это не ясно + # Давайте использовать другой пример, где после замены остаются цифры + input_text = "a3b0c123" + # 'a3b0c123' -> 'азбоc123' (замена 'a'->'а', '3'->'з', '0'->'о'), затем разделение + # Но 'c' -> 'с', по словарю 'c' -> 'с', 'a' -> 'а' + # 'a3b0c123' -> 'азбос123' -> 'азбос 123' + expected = "азбос 123" + assert preprocess_text(input_text) == expected + +def test_preprocess_no_digits(): + """Тестирование обработки текста без цифр.""" + input_text = "как дела сегодня" + expected = "как дела сегодня" + assert preprocess_text(input_text) == expected + +def test_preprocess_digits_not_3_or_0(): + """Тестирование замены цифр, отличных от '3' и '0'.""" + input_text = "привет12 и 45дела" + expected = "привет 12 и 45дела" # Цифры '1', '2', '4', '5' не заменяются + assert preprocess_text(input_text) == expected + +def test_preprocess_only_digits(): + """Тестирование обработки текста, состоящего только из цифр.""" + input_text = "12345" + expected = "12345" # Нет букв, ничего не заменяется + assert preprocess_text(input_text) == expected + +def test_preprocess_empty_string(): + """Тестирование обработки пустой строки.""" + input_text = "" + expected = "" + assert preprocess_text(input_text) == expected + +def test_preprocess_special_characters(): + """Тестирование обработки текста с особыми символами.""" + input_text = "Привет! Как дела? a3b0c!" + expected = "привет! как дела? азбоc!" + # 'a3b0c' -> 'азбоc', затем разделение, но 'c' -> 'с', так что 'азбос!' + # Исправленный ожидаемый результат: "привет! как дела? азбос!" + expected = "привет! как дела? азбос!" + assert preprocess_text(input_text) == expected + +def test_preprocess_multiple_spaces(): + """Тестирование обработки текста с несколькими пробелами.""" + input_text = "привет 12 как дела" + expected = "привет 12 как дела" + assert preprocess_text(input_text) == expected + +def test_preprocess_mixed_case_and_letters(): + """Тестирование обработки текста с разными регистрами и смешанными буквами.""" + input_text = "ПрИвет a3B0C и 0DEFG" + # Приведение к нижнему: "привет a3b0c и 0defg" + # Замена похожих букв: 'a'->'а', 'b'->'б', 'c'->'с', 'y'->'у' и т.д. + # 'a3b0c' -> 'азбос' + # '0defg' -> 'одефg' (замена '0'->'о', 'd','e','f','g' остаются + # Но 'd','e','f','g' не заменены, так как они не в словаре + # Затем разделение: 'привет азбос и odefg' + expected = "привет азбос и odefg" + assert preprocess_text(input_text) == expected \ No newline at end of file