/
ProD
/
TestGenerator
Обзор
Документация
Войти
/
ProD
/
TestGenerator
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
core.py
349 строк
16 KB
prod
Функции генерации перенесены в файл core.py
26 май 2026, 00:00
26 май 2026, 00:00
398bcf4
Код
Авторство
О чём код?
import nltk from nltk import bigrams from nltk.util import ngrams from nltk.tokenize import sent_tokenize, word_tokenize from nltk.corpus import stopwords from nltk.probability import FreqDist import random import string import pymorphy3 from docx import Document import re from functools import lru_cache # Загрузка необходимых ресурсов NLTK nltk.download('punkt', quiet=True) nltk.download('stopwords', quiet=True) nltk.download('punkt_tab') # Инициализация морфологического анализатора morph = pymorphy3.MorphAnalyzer() @lru_cache(maxsize=10000) def parse_word(word): """ Кэшированная функция для морфологического анализа слова. Возвращает результат morph.parse() или пустой список при ошибке. """ try: result = morph.parse(word) return result if result else [] except: return [] def extract_key_concepts(text, top_k=5): """ Извлекает ключевые понятия из текста с использованием частотного анализа и POS-теггинга. Оставляем только существительные и пропускаем стоп-слова. Поддерживает n-граммы (составные термины). """ # Токенизация words = word_tokenize(text.lower()) sentences = sent_tokenize(text) # Удаление пунктуации и стоп-слов stop_words = set(stopwords.words('russian') + stopwords.words('english')) words_filtered = [ word for word in words if word not in stop_words and word not in string.punctuation and word.isalpha() ] # Лемматизация всех слов lemmatized_words = [] for word in words_filtered: parsed = parse_word(word) if parsed: lemma = parsed[0].normal_form lemmatized_words.append(lemma) else: lemmatized_words.append(word) # оставляем как есть при ошибке # Фильтрация по длине (минимум 3 символа) и частоте word_freq = FreqDist(lemmatized_words) min_freq = 2 # минимум 2 вхождения min_len = 3 # минимум 3 символа words_final = [ word for word in lemmatized_words if word_freq[word] >= min_freq and len(word) >= min_len ] # Извлечение n-грамм (от 1 до 3 слов) из лемматизированных слов ngrams_list = [] for n in range(1, 4): # униграммы, биграммы, триграммы ngs = list(ngrams(words_final, n)) # Преобразуем тьюплы в строки ngs = [' '.join(ng) for ng in ngs] ngrams_list.extend(ngs) # Специальная обработка для пар прилагательное + существительное adj_noun_pairs = [] for word1, word2 in bigrams(words_final): parsed1 = parse_word(word1) parsed2 = parse_word(word2) if not parsed1 or not parsed2: continue # Проверяем комбинации: прилагательное + существительное if ('ADJF' in parsed1[0].tag or 'ADJS' in parsed1[0].tag) and 'NOUN' in parsed2[0].tag: # Приводим к начальной форме adj_normal = parsed1[0].normal_form noun_normal = parsed2[0].normal_form adj_noun_pairs.append(f"{adj_normal} {noun_normal}") # И существительное + прилагательное (редкий случай, но возможный) elif 'NOUN' in parsed1[0].tag and ('ADJF' in parsed2[0].tag or 'ADJS' in parsed2[0].tag): noun_normal = parsed1[0].normal_form adj_normal = parsed2[0].normal_form adj_noun_pairs.append(f"{noun_normal} {adj_normal}") # Фильтрация n-грамм: оставляем только те, что содержат хотя бы одно существительное key_nouns = [] for ng in ngrams_list: ng_words = ng.split() # Проверяем, содержит ли n-грамма существительное has_noun = False filtered_words = [] for word in ng_words: parsed = parse_word(word) if not parsed: continue # Проверяем, является ли слово существительным if 'NOUN' in parsed[0].tag: has_noun = True filtered_words.append(parsed[0].normal_form) else: # Оставляем только существительные continue if has_noun: key_nouns.append(' '.join(filtered_words)) # Добавляем пары прилагательное + существительное key_nouns.extend(adj_noun_pairs) # Частотный анализ freq = FreqDist(key_nouns) # Получаем топ-K наиболее частых понятий (включая составные) в начальной форме key_concepts = [word for word, _ in freq.most_common(top_k)] return key_concepts, sentences def generate_distractors(word, all_concepts, num=3): """ Генерирует ложные варианты (дистракторы) на основе других ключевых понятий. Если недостаточно понятий, добавляет общие случайные слова. """ distractors = [concept for concept in all_concepts if concept != word] random.shuffle(distractors) # Если не хватает, добавляем общие слова common_words = ['система', 'процесс', 'метод', 'функция', 'объект', 'модель', 'данные'] while len(distractors) < num: fake = random.choice(common_words) if fake != word and fake not in distractors: distractors.append(fake) return distractors[:num] def generate_test(text, num_questions=5): """ Генерирует тест из заданного текста в формате Cloze (с пропущенными словами) и с контекстными подсказками в вариантах ответов. """ key_concepts, sentences = extract_key_concepts(text, top_k=num_questions) if len(key_concepts) == 0: return "Не удалось извлечь ключевые понятия из текста." questions = [] used_sentences = set() # Генерируем вопросы в формате Cloze (с пропущенными словами) for sent in sentences: if len(questions) >= num_questions: break # Ищем ключевые понятия в предложении с учетом границ слов concept_in_sent = None for c in key_concepts: if c.lower() != 'это' and c.lower() != 'является': pattern = r'\b' + re.escape(c.lower()) + r'\b' if re.search(pattern, sent.lower()): concept_in_sent = c break if not concept_in_sent or sent in used_sentences: continue used_sentences.add(sent) # Создаем пропуск в предложении с точной заменой pattern = r'\b' + re.escape(concept_in_sent) + r'\b' blank_sent = re.sub(pattern, "_____", sent, count=1) # Формулируем вопрос с разными формулировками question_templates = [ f"{blank_sent.strip()}", f"Какое слово пропущено в данном утверждении из текста: '{blank_sent.strip()}'?", f"Выберите подходящий по смыслу термин для заполнения пропуска: '{blank_sent.strip()}'", f"Какое понятие пропущено в предложенном контексте: '{blank_sent.strip()}'?", f"Вставьте правильный термин в следующее утверждение: '{blank_sent.strip()}'" ] question_text = random.choice(question_templates) # Генерируем дистракторы distractors = generate_distractors(concept_in_sent, key_concepts) # Создаем варианты ответов с контекстными подсказками options = [] # Правильный ответ с пояснением correct_explanations = [ f"{concept_in_sent.capitalize()} - ключевое понятие в области машинного обучения", f"{concept_in_sent.capitalize()} - фундаментальный термин, описанный в тексте", f"{concept_in_sent.capitalize()} - основное понятие, упомянутое в предложении", f"{concept_in_sent.capitalize()} - центральный термин данного контекста" ] options.append(random.choice(correct_explanations)) # Неправильные ответы с подсказками for distractor in distractors: distractor_explanations = [ f"{distractor.capitalize()} - не подходит в данный контекст", f"{distractor.capitalize()} - менее релевантный термин для этого предложения", f"{distractor.capitalize()} - не соответствует описанию в предложении", f"{distractor.capitalize()} - второстепенное понятие в данном контексте" ] options.append(random.choice(distractor_explanations)) random.shuffle(options) # Определяем правильный индекс correct_idx = next(i for i, opt in enumerate(options) if concept_in_sent.lower() in opt.lower() and 'не подходит' not in opt.lower() and 'менее релевантный' not in opt.lower() and 'не соответствует' not in opt.lower() and 'второстепенное' not in opt.lower()) correct_letter = chr(65 + correct_idx) # A, B, C, D options_with_labels = [f"{chr(65 + i)}) {opt}" for i, opt in enumerate(options)] questions.append({ "question": question_text, "options": options_with_labels, "correct": correct_letter }) # Если не удалось сгенерировать достаточно вопросов, добавляем резервные while len(questions) < num_questions: # Резервная стратегия: случайные предложения с пропусками unused_sentences = [s for s in sentences if s not in used_sentences] if not unused_sentences: break sent = random.choice(unused_sentences) used_sentences.add(sent) # Просто заменяем случайное существительное на пропуск words = word_tokenize(sent) nouns = [word for word in words if word.isalpha() and word.lower() not in stopwords.words('russian')] if nouns: word_to_blank = random.choice(nouns) blank_sent = sent.replace(word_to_blank, "_____", 1) question_text = f"{blank_sent.strip()}" # Простые варианты ответов options = [word_to_blank.capitalize()] + [random.choice(['Система', 'Процесс', 'Метод', 'Функция']) for _ in range(3)] random.shuffle(options) correct_idx = options.index(word_to_blank.capitalize()) correct_letter = chr(65 + correct_idx) options_with_labels = [f"{chr(65 + i)}) {opt}" for i, opt in enumerate(options)] questions.append({ "question": question_text, "options": options_with_labels, "correct": correct_letter }) return questions[:num_questions] def print_test(questions): """ Выводит сгенерированный тест в удобочитаемом виде. """ for i, q in enumerate(questions, 1): print(f"\n{i}. {q['question']}") for opt in q['options']: print(f" {opt}") print(f" Правильный ответ: {q['correct']}") def save_test_to_docx(questions, filename="generated_test.docx"): """ Сохраняет сгенерированный тест в документ Word (.docx). Args: questions (list): Список вопросов, сгенерированных функцией generate_test. filename (str): Имя выходного файла. """ doc = Document() doc.add_heading('Сгенерированный тест', 0) for i, q in enumerate(questions, 1): # Добавляем вопрос doc.add_paragraph(f"{i}. {q['question']}", style='BodyText') # Добавляем варианты ответов for opt in q['options']: doc.add_paragraph(opt, style='List Bullet') # Добавляем правильный ответ doc.add_paragraph(f"Правильный ответ: {q['correct']}", style='Intense Quote') # Добавляем пустую строку между вопросами doc.add_paragraph() doc.save(filename) # Пример использования def read_text_from_docx(file_path): """ Читает текст из Word документа (.docx) """ doc = Document(file_path) text = [] for paragraph in doc.paragraphs: text.append(paragraph.text) return '\n'.join(text) if __name__ == "__main__": # Пример использования с Word документом try: # Попробуем прочитать из Word документа text = read_text_from_docx('sample.docx') print("Текст успешно загружен из Word документа.") except Exception as e: print("Word документ не найден, используем пример текста.") text = """ Машинное обучение — это метод анализа данных, основанный на том, что системы учатся делать выводы и прогнозы на основе данных, минуя явное программирование. Искусственные нейронные сети имитируют работу человеческого мозга. Глубокое обучение — подмножество машинного обучения, использующее многослойные нейронные сети. Алгоритмы обучения могут быть как с учителем, так и без учителя. """ test_questions = generate_test(text, num_questions=4) print_test(test_questions) # Сохраняем тест в Word документ save_test_to_docx(test_questions, "output_test.docx")