From 9670a1e9607ecd573574de764255db9d54b475a7 Mon Sep 17 00:00:00 2001 From: "podiukov.iv" Date: Thu, 23 Apr 2026 20:39:47 +0500 Subject: [PATCH] add validator --- NER.py | 27 --------------- README.md | 8 +++-- main.py | 35 +++++++++++-------- modules/NER.py | 24 +++++++++++++ modules/paraGenerator.py | 42 +++++++++++++++++++++++ modules/validator.py | 74 ++++++++++++++++++++++++++++++++++++++++ paraGenerator.py | 33 ------------------ 7 files changed, 166 insertions(+), 77 deletions(-) delete mode 100644 NER.py create mode 100644 modules/NER.py create mode 100644 modules/paraGenerator.py create mode 100644 modules/validator.py delete mode 100644 paraGenerator.py diff --git a/NER.py b/NER.py deleted file mode 100644 index 1b0ea37..0000000 --- a/NER.py +++ /dev/null @@ -1,27 +0,0 @@ -# 1 модуль - Распознавание именованных сущностей (NER) - -import spacy - -class NER: - """ - Класс для выделения именованных сущностей из текста с помощью библиотеки spaCy. - """ - def __init__(self): - self.nlp = spacy.load("ru_core_news_lg") - - def extract_entities(self, text): - """ - Выделение именованных сущностей из текста - Использование: text (<текст>) - Возвращает: List[Dict[text, type]] - список словарей, каждый из которых содержит информацию об одной сущности - """ - doc = self.nlp(text) - entities = [] - for ent in doc.ents: - entities.append({ - 'text': ent.text, # Текст сущности - 'type': ent.label_, # Тип сущности (PER, LOC, ORG, DATE и т.д.) - 'start': ent.start_char, # Начальная позиция в тексте - 'end': ent.end_char # Конечная позиция в тексте - }) - return entities diff --git a/README.md b/README.md index 7f40112..4acb06d 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,15 @@ ## Программная реализация ВКР "Алгоритм парафразирования для улучшения извлечения сущностей с использованием больших языковых моделей" ### Установка библиотек -#### для NER +#### Для NER и validator ``` pip install spacy python -m spacy download ru_core_news_lg ``` -#### для paraGenerator +#### Для paraGenerator ``` CMAKE_ARGS="-DGGML_CUDA=on -DLLAMA_OPENSSL=ON" pip install llama-cpp-python ``` +#### Для validator +``` +pip install pymorphy3 +``` diff --git a/main.py b/main.py index 02a092a..fd37b6d 100644 --- a/main.py +++ b/main.py @@ -1,26 +1,31 @@ # Точка входа программы -from NER import NER -from paraGenerator import ParaphraseGenerator +from modules.NER import NER +from modules.paraGenerator import ParaphraseGenerator +#from modules.validator import validator -## Запуск рабочих инструментов ner = NER() pg = ParaphraseGenerator() -### srcText = 'Добрый день, я, Сидоров Иван Иванович. Прошу перевести сто тысяч рублей Якову Петру Игнатьевичу в Москву.' -entities1 = ner.extract_entities(srcText) -print(entities1) +def main(srcText): + # поиск сущностей + srcEntities = ner.extract_entities(srcText) + print(srcEntities) -#paraphrase1 = pg.generate(srcText, entities1) -#print(paraphrase1) + # генерация парафраза + paraphrase = pg.generate(srcText, srcEntities) + print(paraphrase) + + # поиск сущностей в парафразе + paraEntities = ner.extract_entities(paraphrase) + print(paraEntities) + + # Валидация + # return validator(srcText, paraphrase, srcEntities, paraEntities) + +result = main(srcText) +print(result) -#entities2 = ner.extract_entities(paraphrase1) -#print(entities2) -gg = () -gg.append(1) -gg.append(2) -gg.append(1) -print(gg) \ No newline at end of file diff --git a/modules/NER.py b/modules/NER.py new file mode 100644 index 0000000..b726677 --- /dev/null +++ b/modules/NER.py @@ -0,0 +1,24 @@ +# 1 модуль - Распознавание именованных сущностей (NER) + +import spacy + +class NER: + """ + Класс для выделения именованных сущностей из текста с помощью библиотеки spaCy. + """ + def __init__(self): + self.nlp = spacy.load('ru_core_news_lg') + + def extract_entities(self, text): + """ + Выделение именованных сущностей из текста. + + Использование: text (<текст>) + + Возвращает set сущностей без повторения + """ + doc = self.nlp(text) + entities = set() + for ent in doc.ents: + entities.add(ent.text) + return entities diff --git a/modules/paraGenerator.py b/modules/paraGenerator.py new file mode 100644 index 0000000..b9943df --- /dev/null +++ b/modules/paraGenerator.py @@ -0,0 +1,42 @@ +# 2 модуль - Генератор парафраза на основе llama.cpp + +from openai import OpenAI + +class ParaphraseGenerator: + """ + Класс для использования генеративной модели на базе llama.cpp + + Использование: ParaphraseGenerator([температура], [максимальное количество токенов]) + """ + + def __init__(self, temperature=0.8, max_tokens=200): + self.client = OpenAI(base_url='http://127.0.0.1:8080', api_key='') + + def generateByPrompt(self, prompt): + try: + response = self.client.chat.completions.create( + model = '', + messages = [{'role': 'user', 'content': prompt}], + temperature = self.temperature, + max_tokens = self.max_tokens + ) + return response.choices[0].message.content + except Exception as e: + print(f'Ошибка генерации: {e}') + return None + + def generate(self, srcText, entities): + """ + Генерация парафраза. + + Использование: generate(<исходный текст>, <сущности>) + + Возвращает set сущностей (без повторения) + """ + prompt = ( + f'Перефразируй следующий текст, сохранив все именованные сущности "{', '.join(entity for entity in entities)}" в точности такими же, как в оригинале. ' + 'Не изменяй эти фрагменты текста, не заменяй их синонимами, не переставляй слова внутри них. ' + 'Можешь изменять грамматическую структуру предложения, порядок слов, использовать синонимы для' + f'остальных частей текста, но именованные сущности должны остаться неизменными. Исходный текст: "{srcText}"' + ) + return self.generateByPrompt(prompt) diff --git a/modules/validator.py b/modules/validator.py new file mode 100644 index 0000000..7388790 --- /dev/null +++ b/modules/validator.py @@ -0,0 +1,74 @@ +# 3 модуль - Валидация и восстановление сущностей + +from NER import NER +from paraGenerator import ParaphraseGenerator +from pymorphy3 import MorphAnalyzer + +ner = NER() +pg = ParaphraseGenerator() +morph = MorphAnalyzer() + +def compare_entities(original, generated): + """ + Сравнивает два списка сущностей. Новые сущности допускаются. + + Возвращает: + - True - всё на месте + - False - что то потерялось + """ + def normalize(text): + """ + Приводит слово к нормальной форме. Пример: Ивану --> Иван + + Возвращает: + - Совпадают или нет сущности (bool) + - Какие сущности потерялись (set) + """ + return morph.parse(text)[0].normal_form + + if original.issubset(generated): # если original является подмножеством множества generated + return True, set() + + # если нет, проверяем дополнительно, вдруг кейс по типу "Иван" - "Ивану" + orig_norm = {normalize(e) for e in original} # нормализуем списки + gen_norm = {normalize(e) for e in generated} + + if orig_norm.issubset(gen_norm): + return True, set() + + # если по прежнему false, то ищем потерянные сущности + lost = set() + for o in original: + if (normalize(o) not in gen_norm): + lost.add(o) + return False, lost + +def validator(srcText, srcEntities, paraEntities): + """ + Использование: validator(<исходный текст>, <сущности исходного текста>, <сущности перефразированного текста>) + + Возвращает: + - Исходный текст если сущности сохранены + - Изменённый текст, если сущности не сохранены и были восстановлены + - None, если сущности не удалось восстановить с трёх раз + """ + ce = compare_entities(srcEntities, paraEntities) + if ce[0]: + return srcText # если всё нормально, возвращаем текст в неизменном виде + + regen_prompt = ( + f'При перефразировании текста "{srcText}" из списка элементов "{', '.join(entity for entity in srcEntities)}"' + f'были утеряны или изменены следующие важные элементы: "{', '.join(e for e in ce[1])}". ' + 'Перефразируй исходный текст заново, обратив особое внимание на сохранение этих элементов. Выведи только текст.' + ) + + for _ in range(3): + newParaphrase = pg.generateByPrompt(regen_prompt) + paraEntities = ner.extract_entities(newParaphrase) + if (compare_entities(srcEntities, paraEntities)): + return newParaphrase + return None + +# a = set(['a','b', 'c', 'd']) +# b = set(['a','b']) +# validator('123', '123', a, b) diff --git a/paraGenerator.py b/paraGenerator.py deleted file mode 100644 index f5111c6..0000000 --- a/paraGenerator.py +++ /dev/null @@ -1,33 +0,0 @@ -# 2 модуль - Генератор парафраза на основе llama.cpp - -from openai import OpenAI - -class ParaphraseGenerator: - """ - Класс для использования генеративной модели на базе llama.cpp - """ - def __init__(self): - self.client = OpenAI(base_url="http://127.0.0.1:8080", api_key="") - - def generate(self, srcText, entities): - """ - Генерация парафраза по промпту. - Использование: generate(<исходный текст>, <сущности>) - """ - entityList = ', '.join(entity['text'] for entity in entities) # генерация списка выделенных сущностей - prompt = 'Перефразируй следующий текст, сохранив все именованные сущности "' + entityList + '" в точности такими же, как в оригинале. ' \ - 'Не изменяй эти фрагменты текста, не заменяй их синонимами, не переставляй слова внутри них. ' \ - 'Можешь изменять грамматическую структуру предложения, порядок слов, использовать синонимы для' \ - 'остальных частей текста, но именованные сущности должны остаться неизменными. Исходный текст: "' + srcText + '"' - try: - response = self.client.chat.completions.create( - model = "", - messages = [{"role": "user", "content": prompt}], - temperature = 0.8, - top_p = 0.95, - max_tokens = 200 - ) - return response.choices[0].message.content - except Exception as e: - print(f"Ошибка генерации: {e}") - return None