1294 lines
49 KiB
Python
1294 lines
49 KiB
Python
|
|
# -*- coding: utf-8 -*-
|
|||
|
|
"""Пайплайн обучения модели "mT5-small"
|
|||
|
|
|
|||
|
|
Automatically generated by Colab.
|
|||
|
|
|
|||
|
|
Original file is located at
|
|||
|
|
https://colab.research.google.com/drive/1oUJ0Dw-EcH91-4S1kUV_n33RKJwXj2oL
|
|||
|
|
|
|||
|
|
# **Тестирование модели с помощью zero-shot и few-shots**
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
# Установка необходимых библиотек
|
|||
|
|
!pip install transformers torch pandas sentencepiece
|
|||
|
|
|
|||
|
|
import pandas as pd
|
|||
|
|
import torch
|
|||
|
|
from transformers import MT5ForConditionalGeneration, MT5Tokenizer
|
|||
|
|
import random
|
|||
|
|
|
|||
|
|
# Загрузка модели и токенизатора MT5-small
|
|||
|
|
model_name = "google/mt5-small"
|
|||
|
|
tokenizer = MT5Tokenizer.from_pretrained(model_name)
|
|||
|
|
model = MT5ForConditionalGeneration.from_pretrained(model_name)
|
|||
|
|
|
|||
|
|
# Если доступен GPU
|
|||
|
|
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
|||
|
|
model.to(device)
|
|||
|
|
|
|||
|
|
# Функция для загрузки данных из файлов
|
|||
|
|
def load_announcements_data():
|
|||
|
|
# Данные для английского языка
|
|||
|
|
eng_data = [
|
|||
|
|
{
|
|||
|
|
"query": "Announcement of the exhibition",
|
|||
|
|
"title": "The Rossettis",
|
|||
|
|
"date": "6 april – 24 September 2023",
|
|||
|
|
"text": "A major exhibition devoted to the radical Rossetti generation."
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"query": "Announcement photography",
|
|||
|
|
"title": "A World in Common: Contemporary African Photography",
|
|||
|
|
"date": "6 July – 14 January 2024",
|
|||
|
|
"text": "A celebration of the varied landscape of contemporary African photography today."
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"query": "Announcement installation",
|
|||
|
|
"title": "JMW Turner with Lamin Fofana: Dark Waters",
|
|||
|
|
"date": "27 September 2022 – 24 September 2023",
|
|||
|
|
"text": "Experience the power of the sea through paintings, sketches and an immersive sound environment."
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"query": "write an announcement for the exhibition",
|
|||
|
|
"title": "Hilma af Klint & Piet Mondrian: Forms of Life",
|
|||
|
|
"date": "18 April 2023",
|
|||
|
|
"text": "Explore the powerful work of two groundbreaking modern artists."
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
# Данные для финского языка
|
|||
|
|
fin_data = [
|
|||
|
|
{
|
|||
|
|
"query": "Ilmoitus näyttelystä",
|
|||
|
|
"title": "KOKOELMA: AJAN KYSYMYS.",
|
|||
|
|
"date": "Pysyvä näyttely",
|
|||
|
|
"text": "Kokoelmanäyttelymme Ajan kysymys valottaa nykyhetken suuria kysymyksiä kuvataiteen tarjoaman peilin kautta."
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"query": "Ilmoitus näyttelystä",
|
|||
|
|
"title": "RAJOJEN RIKKOJAT. 1800-luvun matkustavat naistaiteilijat.",
|
|||
|
|
"date": "07.3.–24.8.2025",
|
|||
|
|
"text": "Näyttely tuo ensimmäistä kertaa yhteen 1800-luvulla Saksassa opiskelleiden ja työskennelleiden naistaiteilijoiden töitä."
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
"query": "Ilmoitus taidepajasta",
|
|||
|
|
"title": "Lauantaipajat.",
|
|||
|
|
"date": "26.4.2025",
|
|||
|
|
"text": "Tutustu taiteeseen itse tekemällä. Tule Ateneumin työpajaan kokeilemaan ja tekemään itse!"
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
return eng_data, fin_data
|
|||
|
|
|
|||
|
|
# Функция для промптов
|
|||
|
|
def create_prompts(eng_data, fin_data, few_shot_k=3):
|
|||
|
|
# Zero-shot промпты
|
|||
|
|
zero_shot_prompts = {
|
|||
|
|
"en_zero_shot": "[EN] Generate museum announcement: Query: \"Announcement of the exhibition\"",
|
|||
|
|
"fi_zero_shot": "[FI] Generate museum announcement: Query: \"Ilmoitus näyttelystä\""
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Few-shot промпты для английского
|
|||
|
|
eng_examples = eng_data[:few_shot_k]
|
|||
|
|
en_few_shot_prompt = ""
|
|||
|
|
for example in eng_examples:
|
|||
|
|
en_few_shot_prompt += f"[EN] Query: \"{example['query']}\"\n"
|
|||
|
|
en_few_shot_prompt += f"Title: \"{example['title']}\"\n"
|
|||
|
|
en_few_shot_prompt += f"Date: {example['date']}\n"
|
|||
|
|
en_few_shot_prompt += f"Text: {example['text']}\n\n"
|
|||
|
|
en_few_shot_prompt += "[EN] Generate museum announcement: Query: \"Announcement of the exhibition\""
|
|||
|
|
|
|||
|
|
# Few-shot промпты для финского
|
|||
|
|
fin_examples = fin_data[:few_shot_k]
|
|||
|
|
fi_few_shot_prompt = ""
|
|||
|
|
for example in fin_examples:
|
|||
|
|
fi_few_shot_prompt += f"[FI] Query: \"{example['query']}\"\n"
|
|||
|
|
fi_few_shot_prompt += f"Title: \"{example['title']}\"\n"
|
|||
|
|
fi_few_shot_prompt += f"Date: {example['date']}\n"
|
|||
|
|
fi_few_shot_prompt += f"Text: {example['text']}\n\n"
|
|||
|
|
fi_few_shot_prompt += "[FI] Generate museum announcement: Query: \"Ilmoitus näyttelystä\""
|
|||
|
|
|
|||
|
|
few_shot_prompts = {
|
|||
|
|
"en_few_shot": en_few_shot_prompt,
|
|||
|
|
"fi_few_shot": fi_few_shot_prompt
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return zero_shot_prompts, few_shot_prompts
|
|||
|
|
|
|||
|
|
# Функция для генерации текста
|
|||
|
|
def generate_announcement(prompt, max_length=150, num_beams=5, temperature=0.9):
|
|||
|
|
# Токенизация промпта
|
|||
|
|
inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=512).to(device)
|
|||
|
|
|
|||
|
|
# Генерация текста
|
|||
|
|
with torch.no_grad():
|
|||
|
|
outputs = model.generate(
|
|||
|
|
inputs.input_ids,
|
|||
|
|
max_length=max_length,
|
|||
|
|
num_beams=num_beams,
|
|||
|
|
temperature=temperature,
|
|||
|
|
early_stopping=True,
|
|||
|
|
no_repeat_ngram_size=3,
|
|||
|
|
do_sample=True
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Декодирование сгенерированного текста
|
|||
|
|
generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
|||
|
|
|
|||
|
|
return generated_text
|
|||
|
|
|
|||
|
|
# Функция для запуска тестов
|
|||
|
|
def run_tests():
|
|||
|
|
print("=" * 80)
|
|||
|
|
print("Загрузка данных и подготовка промптов...")
|
|||
|
|
print("=" * 80)
|
|||
|
|
|
|||
|
|
# Загрузка данных
|
|||
|
|
eng_data, fin_data = load_announcements_data()
|
|||
|
|
|
|||
|
|
# Создание промптов
|
|||
|
|
zero_shot_prompts, few_shot_prompts = create_prompts(eng_data, fin_data, few_shot_k=2)
|
|||
|
|
|
|||
|
|
results = {}
|
|||
|
|
|
|||
|
|
# Zero-shot тесты
|
|||
|
|
print("\n" + "=" * 80)
|
|||
|
|
print("ZERO-SHOT ТЕСТЫ (без примеров)")
|
|||
|
|
print("=" * 80)
|
|||
|
|
|
|||
|
|
for lang, prompt in zero_shot_prompts.items():
|
|||
|
|
print(f"\n{lang.upper()} ZERO-SHOT:")
|
|||
|
|
print(f"Промпт: {prompt[:150]}...")
|
|||
|
|
print("-" * 40)
|
|||
|
|
|
|||
|
|
generated = generate_announcement(prompt)
|
|||
|
|
results[lang] = generated
|
|||
|
|
|
|||
|
|
print(f"Сгенерированный анонс:\n{generated}")
|
|||
|
|
print("=" * 80)
|
|||
|
|
|
|||
|
|
# Few-shot тесты
|
|||
|
|
print("\n" + "=" * 80)
|
|||
|
|
print("FEW-SHOT ТЕСТЫ (с 2 примерами)")
|
|||
|
|
print("=" * 80)
|
|||
|
|
|
|||
|
|
for lang, prompt in few_shot_prompts.items():
|
|||
|
|
print(f"\n{lang.upper()} FEW-SHOT:")
|
|||
|
|
print(f"Промпт (первые 200 символов): {prompt[:200]}...")
|
|||
|
|
print("-" * 40)
|
|||
|
|
|
|||
|
|
generated = generate_announcement(prompt, max_length=200)
|
|||
|
|
results[f"{lang}_few_shot"] = generated
|
|||
|
|
|
|||
|
|
print(f"Сгенерированный анонс:\n{generated}")
|
|||
|
|
print("=" * 80)
|
|||
|
|
|
|||
|
|
# Тесты с разными параметрами
|
|||
|
|
print("\n" + "=" * 80)
|
|||
|
|
print("ТЕСТЫ С РАЗНЫМИ ПАРАМЕТРАМИ ГЕНЕРАЦИИ")
|
|||
|
|
print("=" * 80)
|
|||
|
|
|
|||
|
|
# Английский с более свободными параметрами (температура)
|
|||
|
|
print("\nАнглийский с более креативными параметрами (temperature=1.2):")
|
|||
|
|
creative_prompt = "[EN] Generate museum announcement: Query: \"Announcement of a contemporary art exhibition\""
|
|||
|
|
creative_output = generate_announcement(creative_prompt, temperature=1.2, num_beams=3)
|
|||
|
|
print(f"Промпт: {creative_prompt}")
|
|||
|
|
print(f"Сгенерированный анонс:\n{creative_output}")
|
|||
|
|
|
|||
|
|
print("\n" + "=" * 80)
|
|||
|
|
|
|||
|
|
# Финский с более структурированными параметрами
|
|||
|
|
print("\nФинский с более структурированными параметрами (num_beams=7, temperature=0.7):")
|
|||
|
|
structured_prompt = "[FI] Generate museum announcement: Query: \"Ilmoitus taidepajasta\""
|
|||
|
|
structured_output = generate_announcement(structured_prompt, num_beams=7, temperature=0.7)
|
|||
|
|
print(f"Промпт: {structured_prompt}")
|
|||
|
|
print(f"Сгенерированный анонс:\n{structured_output}")
|
|||
|
|
|
|||
|
|
print("\n" + "=" * 80)
|
|||
|
|
print("ВСЕ ТЕСТЫ ЗАВЕРШЕНЫ")
|
|||
|
|
print("=" * 80)
|
|||
|
|
|
|||
|
|
return results
|
|||
|
|
|
|||
|
|
# Функция для оценки качества сгенерированных текстов
|
|||
|
|
def evaluate_results(results):
|
|||
|
|
print("\n" + "=" * 80)
|
|||
|
|
print("КАЧЕСТВЕННАЯ ОЦЕНКА СГЕНЕРИРОВАННЫХ ТЕКСТОВ")
|
|||
|
|
print("=" * 80)
|
|||
|
|
|
|||
|
|
for test_name, text in results.items():
|
|||
|
|
print(f"\n{test_name.upper()}:")
|
|||
|
|
print(f"Длина текста: {len(text)} символов")
|
|||
|
|
|
|||
|
|
# Простая проверка на наличие ключевых элементов
|
|||
|
|
checks = {
|
|||
|
|
"Есть заголовок (Title)": "Title" in text or "title" in text or "nimi" in text.lower(),
|
|||
|
|
"Есть дата/время": any(word in text.lower() for word in ["date", "202", "time", "klo", "aika"]),
|
|||
|
|
"Есть описание": len(text.split()) > 10,
|
|||
|
|
"Языковая корректность": True # Здесь можно добавить более сложные проверки
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for check_name, check_result in checks.items():
|
|||
|
|
status = "✓" if check_result else "✗"
|
|||
|
|
print(f" {status} {check_name}")
|
|||
|
|
|
|||
|
|
print(f"Текст: {text[:150]}...")
|
|||
|
|
|
|||
|
|
# Запуск тестов
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
print("Инициализация модели MT5-small...")
|
|||
|
|
print(f"Используется устройство: {device}")
|
|||
|
|
|
|||
|
|
# Запуск основных тестов
|
|||
|
|
test_results = run_tests()
|
|||
|
|
|
|||
|
|
# Оценка результатов
|
|||
|
|
evaluate_results(test_results)
|
|||
|
|
|
|||
|
|
# Пример дополнительного теста с пользовательским промптом
|
|||
|
|
print("\n" + "=" * 80)
|
|||
|
|
print("ДОПОЛНИТЕЛЬНЫЙ ТЕСТ: ГЕНЕРАЦИЯ ПОЛЬЗОВАТЕЛЬСКОГО ПРОМПТА")
|
|||
|
|
print("=" * 80)
|
|||
|
|
|
|||
|
|
# Пользователь может ввести свой промпт
|
|||
|
|
custom_prompt = input("Введите промпт для генерации (или нажмите Enter для примера): ")
|
|||
|
|
if not custom_prompt:
|
|||
|
|
custom_prompt = "[EN] Generate museum announcement: Query: \"Announcement of a photography exhibition about urban landscapes\""
|
|||
|
|
|
|||
|
|
print(f"\nПромпт: {custom_prompt}")
|
|||
|
|
custom_output = generate_announcement(custom_prompt, max_length=200)
|
|||
|
|
print(f"Сгенерированный анонс:\n{custom_output}")
|
|||
|
|
|
|||
|
|
"""# **1 ЭТАП ОБУЧЕНИЯ МОДЕЛИ "mT5-SMALL". АДАПТАЦИЯ.**"""
|
|||
|
|
|
|||
|
|
pip install transformers datasets torch pandas
|
|||
|
|
|
|||
|
|
import pandas as pd
|
|||
|
|
import os
|
|||
|
|
from transformers import (
|
|||
|
|
MT5ForConditionalGeneration,
|
|||
|
|
MT5Tokenizer,
|
|||
|
|
Seq2SeqTrainer,
|
|||
|
|
Seq2SeqTrainingArguments,
|
|||
|
|
DataCollatorForSeq2Seq
|
|||
|
|
)
|
|||
|
|
from datasets import Dataset, concatenate_datasets
|
|||
|
|
import torch
|
|||
|
|
import re
|
|||
|
|
|
|||
|
|
# Параметры
|
|||
|
|
MODEL_NAME = "google/mt5-small"
|
|||
|
|
BATCH_SIZE = 4
|
|||
|
|
MAX_LENGTH = 256
|
|||
|
|
SOURCE_MAX_LENGTH = 128
|
|||
|
|
EPOCHS = 5
|
|||
|
|
|
|||
|
|
# Загрузка и предобработка данных
|
|||
|
|
def load_data(file_path, lang):
|
|||
|
|
df = pd.read_csv(file_path, sep='\t', header=None, names=['input', 'target'])
|
|||
|
|
df = df.dropna()
|
|||
|
|
|
|||
|
|
# Добавляем языковые префиксы
|
|||
|
|
df['input'] = f"{lang}: " + df['input']
|
|||
|
|
|
|||
|
|
# Очистка и форматирование целевых текстов
|
|||
|
|
def format_target(text):
|
|||
|
|
# Удаляем лишние пробелы и переносы строк
|
|||
|
|
text = re.sub(r'\s+', ' ', str(text)).strip()
|
|||
|
|
return text
|
|||
|
|
|
|||
|
|
df['target'] = df['target'].apply(format_target)
|
|||
|
|
return df
|
|||
|
|
|
|||
|
|
# Загрузка данных
|
|||
|
|
df_en = load_data("ENG.tsv", "en")
|
|||
|
|
df_fi = load_data("FI.tsv", "fi")
|
|||
|
|
|
|||
|
|
print(f"Загружено {len(df_en)} английских примеров")
|
|||
|
|
print(f"Загружено {len(df_fi)} финских примеров")
|
|||
|
|
|
|||
|
|
# Создание datasets
|
|||
|
|
dataset_en = Dataset.from_pandas(df_en)
|
|||
|
|
dataset_fi = Dataset.from_pandas(df_fi)
|
|||
|
|
|
|||
|
|
# Объединение датасетов
|
|||
|
|
combined_dataset = concatenate_datasets([dataset_en, dataset_fi])
|
|||
|
|
dataset = combined_dataset.train_test_split(test_size=0.2, seed=42)
|
|||
|
|
|
|||
|
|
print(f"Размер тренировочного набора: {len(dataset['train'])}")
|
|||
|
|
print(f"Размер тестового набора: {len(dataset['test'])}")
|
|||
|
|
|
|||
|
|
# Загрузка модели и токенизатора
|
|||
|
|
print("Загрузка модели и токенизатора...")
|
|||
|
|
tokenizer = MT5Tokenizer.from_pretrained(MODEL_NAME)
|
|||
|
|
model = MT5ForConditionalGeneration.from_pretrained(MODEL_NAME)
|
|||
|
|
|
|||
|
|
# Функция токенизации
|
|||
|
|
def tokenize_function(examples):
|
|||
|
|
model_inputs = tokenizer(
|
|||
|
|
examples['input'],
|
|||
|
|
max_length=SOURCE_MAX_LENGTH,
|
|||
|
|
padding='max_length',
|
|||
|
|
truncation=True
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
with tokenizer.as_target_tokenizer():
|
|||
|
|
labels = tokenizer(
|
|||
|
|
examples['target'],
|
|||
|
|
max_length=MAX_LENGTH,
|
|||
|
|
padding='max_length',
|
|||
|
|
truncation=True
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Для mT5 нужно заменить padding токены в labels на -100
|
|||
|
|
labels['input_ids'] = [
|
|||
|
|
[(label if label != tokenizer.pad_token_id else -100) for label in labels_example]
|
|||
|
|
for labels_example in labels['input_ids']
|
|||
|
|
]
|
|||
|
|
model_inputs['labels'] = labels['input_ids']
|
|||
|
|
return model_inputs
|
|||
|
|
|
|||
|
|
# Токенизация данных
|
|||
|
|
print("Токенизация данных...")
|
|||
|
|
tokenized_datasets = dataset.map(
|
|||
|
|
tokenize_function,
|
|||
|
|
batched=True,
|
|||
|
|
remove_columns=dataset['train'].column_names
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Data collator
|
|||
|
|
data_collator = DataCollatorForSeq2Seq(
|
|||
|
|
tokenizer=tokenizer,
|
|||
|
|
model=model,
|
|||
|
|
padding=True
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Аргументы обучения
|
|||
|
|
training_args = Seq2SeqTrainingArguments(
|
|||
|
|
output_dir="./mt5-museum-announcements",
|
|||
|
|
eval_strategy="epoch",
|
|||
|
|
learning_rate=3e-4,
|
|||
|
|
per_device_train_batch_size=BATCH_SIZE,
|
|||
|
|
per_device_eval_batch_size=BATCH_SIZE,
|
|||
|
|
weight_decay=0.01,
|
|||
|
|
save_total_limit=3,
|
|||
|
|
num_train_epochs=EPOCHS,
|
|||
|
|
predict_with_generate=True,
|
|||
|
|
logging_dir='./logs',
|
|||
|
|
logging_steps=10,
|
|||
|
|
save_steps=100,
|
|||
|
|
warmup_steps=100,
|
|||
|
|
report_to="none"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Trainer
|
|||
|
|
trainer = Seq2SeqTrainer(
|
|||
|
|
model=model,
|
|||
|
|
args=training_args,
|
|||
|
|
train_dataset=tokenized_datasets["train"],
|
|||
|
|
eval_dataset=tokenized_datasets["test"],
|
|||
|
|
data_collator=data_collator,
|
|||
|
|
tokenizer=tokenizer,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Запуск обучения
|
|||
|
|
print("Начало обучения...")
|
|||
|
|
trainer.train()
|
|||
|
|
|
|||
|
|
# Сохранение модели
|
|||
|
|
trainer.save_model("./mt5-museum-final")
|
|||
|
|
tokenizer.save_pretrained("./mt5-museum-final")
|
|||
|
|
print("Модель сохранена в ./mt5-museum-final")
|
|||
|
|
|
|||
|
|
# Загружаем сохраненную модель для инференса
|
|||
|
|
model = MT5ForConditionalGeneration.from_pretrained("./mt5-museum-final")
|
|||
|
|
tokenizer = MT5Tokenizer.from_pretrained("./mt5-museum-final")
|
|||
|
|
|
|||
|
|
# Перемещаем модель на CPU для инференса
|
|||
|
|
model = model.to('cpu')
|
|||
|
|
|
|||
|
|
# Пример генерации - исправленная версия
|
|||
|
|
def generate_announcement(text, language="en"):
|
|||
|
|
input_text = f"{language}: {text}"
|
|||
|
|
|
|||
|
|
# Токенизируем входной текст
|
|||
|
|
inputs = tokenizer(
|
|||
|
|
input_text,
|
|||
|
|
return_tensors="pt",
|
|||
|
|
max_length=SOURCE_MAX_LENGTH,
|
|||
|
|
truncation=True,
|
|||
|
|
padding=True
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Получаем device модели
|
|||
|
|
device = next(model.parameters()).device
|
|||
|
|
|
|||
|
|
# Перемещаем входные данные на тот же device, что и модель
|
|||
|
|
input_ids = inputs['input_ids'].to(device)
|
|||
|
|
attention_mask = inputs['attention_mask'].to(device)
|
|||
|
|
|
|||
|
|
# Генерируем выходной текст
|
|||
|
|
outputs = model.generate(
|
|||
|
|
input_ids=input_ids,
|
|||
|
|
attention_mask=attention_mask,
|
|||
|
|
max_length=MAX_LENGTH,
|
|||
|
|
num_beams=5,
|
|||
|
|
early_stopping=True,
|
|||
|
|
no_repeat_ngram_size=2
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
return tokenizer.decode(outputs[0], skip_special_tokens=True)
|
|||
|
|
|
|||
|
|
# Тестирование
|
|||
|
|
print("\nТестирование модели:")
|
|||
|
|
test_examples = [
|
|||
|
|
("Announcement of the exhibition", "en"),
|
|||
|
|
("kirjoita ilmoitus taidepajasta", "fi"),
|
|||
|
|
("Music event announcement", "en"),
|
|||
|
|
("Näyttelyilmoitus", "fi")
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
for text, lang in test_examples:
|
|||
|
|
result = generate_announcement(text, lang)
|
|||
|
|
print(f"Input ({lang}): {text}")
|
|||
|
|
print(f"Output: {result}\n")
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
import torch
|
|||
|
|
from transformers import MT5ForConditionalGeneration, MT5Tokenizer
|
|||
|
|
|
|||
|
|
# Загрузка обученной модели и токенизатора
|
|||
|
|
model_path = "./mt5-museum-final"
|
|||
|
|
tokenizer = MT5Tokenizer.from_pretrained(model_path)
|
|||
|
|
model = MT5ForConditionalGeneration.from_pretrained(model_path)
|
|||
|
|
|
|||
|
|
# Перемещаем модель на CPU (или GPU, если доступно)
|
|||
|
|
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
|||
|
|
model = model.to(device)
|
|||
|
|
print(f"Модель загружена на устройство: {device}")
|
|||
|
|
|
|||
|
|
# Функция для генерации анонсов
|
|||
|
|
def generate_museum_announcement(prompt, language="en", max_length=256):
|
|||
|
|
"""
|
|||
|
|
Генерирует музейный анонс на основе промпта и языка
|
|||
|
|
|
|||
|
|
Args:
|
|||
|
|
prompt (str): Запрос для генерации
|
|||
|
|
language (str): Язык ('en' для английского, 'fi' для финского)
|
|||
|
|
max_length (int): Максимальная длина генерируемого текста
|
|||
|
|
|
|||
|
|
Returns:
|
|||
|
|
str: Сгенерированный анонс
|
|||
|
|
"""
|
|||
|
|
# Добавляем языковой префикс
|
|||
|
|
input_text = f"{language}: {prompt}"
|
|||
|
|
|
|||
|
|
# Токенизируем входной текст
|
|||
|
|
inputs = tokenizer(
|
|||
|
|
input_text,
|
|||
|
|
return_tensors="pt",
|
|||
|
|
max_length=128,
|
|||
|
|
truncation=True,
|
|||
|
|
padding=True
|
|||
|
|
).to(device)
|
|||
|
|
|
|||
|
|
# Генерируем текст
|
|||
|
|
with torch.no_grad():
|
|||
|
|
outputs = model.generate(
|
|||
|
|
inputs.input_ids,
|
|||
|
|
attention_mask=inputs.attention_mask,
|
|||
|
|
max_length=max_length,
|
|||
|
|
num_beams=5,
|
|||
|
|
early_stopping=True,
|
|||
|
|
no_repeat_ngram_size=2,
|
|||
|
|
temperature=0.9,
|
|||
|
|
do_sample=True
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Декодируем сгенерированный текст
|
|||
|
|
generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
|||
|
|
return generated_text
|
|||
|
|
|
|||
|
|
# Теперь можно использовать функцию
|
|||
|
|
|
|||
|
|
# Генерация нескольких вариантов
|
|||
|
|
def generate_multiple_announcements(prompt, language="en", num_variants=3):
|
|||
|
|
"""Генерирует несколько вариантов анонса"""
|
|||
|
|
print(f"Генерация {num_variants} вариантов для: '{prompt}' ({language})")
|
|||
|
|
print("-" * 50)
|
|||
|
|
|
|||
|
|
for i in range(num_variants):
|
|||
|
|
result = generate_museum_announcement(
|
|||
|
|
prompt,
|
|||
|
|
language=language,
|
|||
|
|
max_length=200
|
|||
|
|
)
|
|||
|
|
print(f"Вариант {i+1}: {result}\n")
|
|||
|
|
|
|||
|
|
# Пример использования
|
|||
|
|
print("=== ГЕНЕРАЦИЯ МУЗЕЙНЫХ АНОНСОВ ===\n")
|
|||
|
|
|
|||
|
|
# Английские примеры
|
|||
|
|
english_prompts = [
|
|||
|
|
"Announcement of the exhibition",
|
|||
|
|
"Workshop announcement for children",
|
|||
|
|
"Music concert in the museum",
|
|||
|
|
"Dance performance announcement",
|
|||
|
|
"Photography exhibition opening"
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
print("--- АНГЛИЙСКИЕ АНОНСЫ ---")
|
|||
|
|
for i, prompt in enumerate(english_prompts, 1):
|
|||
|
|
result = generate_museum_announcement(prompt, language="en")
|
|||
|
|
print(f"{i}. Запрос: {prompt}")
|
|||
|
|
print(f" Анонс: {result}\n")
|
|||
|
|
|
|||
|
|
# Финские примеры
|
|||
|
|
finnish_prompts = [
|
|||
|
|
"Ilmoitus taidepajasta",
|
|||
|
|
"Näyttelyilmoitus",
|
|||
|
|
"Musiikkitapahtuman ilmoitus",
|
|||
|
|
"Tanssiesitys",
|
|||
|
|
"Valokuvausnäyttely"
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
print("--- ФИНСКИЕ АНОНСЫ ---")
|
|||
|
|
for i, prompt in enumerate(finnish_prompts, 1):
|
|||
|
|
result = generate_museum_announcement(prompt, language="fi")
|
|||
|
|
print(f"{i}. Запрос: {prompt}")
|
|||
|
|
print(f" Анонс: {result}\n")
|
|||
|
|
|
|||
|
|
# Генерация нескольких вариантов
|
|||
|
|
print("=== МНОЖЕСТВЕННАЯ ГЕНЕРАЦИЯ ===")
|
|||
|
|
generate_multiple_announcements("Art exhibition announcement", "en", 3)
|
|||
|
|
generate_multiple_announcements("Taidenäyttely", "fi", 2)
|
|||
|
|
|
|||
|
|
# Интерактивный режим
|
|||
|
|
def interactive_mode():
|
|||
|
|
"""Интерактивный режим для генерации анонсов"""
|
|||
|
|
print("=== ИНТЕРАКТИВНЫЙ РЕЖИМ ===")
|
|||
|
|
print("Доступные языки: 'en' (английский), 'fi' (финский)")
|
|||
|
|
print("Для выхода введите 'quit'\n")
|
|||
|
|
|
|||
|
|
while True:
|
|||
|
|
language = input("Выберите язык (en/fi): ").strip().lower()
|
|||
|
|
if language == 'quit':
|
|||
|
|
break
|
|||
|
|
if language not in ['en', 'fi']:
|
|||
|
|
print("Неверный язык. Используйте 'en' или 'fi'")
|
|||
|
|
continue
|
|||
|
|
|
|||
|
|
prompt = input("Введите запрос: ").strip()
|
|||
|
|
if prompt == 'quit':
|
|||
|
|
break
|
|||
|
|
|
|||
|
|
result = generate_museum_announcement(prompt, language=language)
|
|||
|
|
print(f"\nСгенерированный анонс:")
|
|||
|
|
print(f"📝 {result}\n")
|
|||
|
|
|
|||
|
|
# Запуск интерактивного режима (раскомментируйте если нужно)
|
|||
|
|
# interactive_mode()
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
import matplotlib.pyplot as plt
|
|||
|
|
import numpy as np
|
|||
|
|
|
|||
|
|
# Данные экспериментов
|
|||
|
|
experiments = [
|
|||
|
|
{
|
|||
|
|
'name': 'Эксперимент 1',
|
|||
|
|
'epochs': 10,
|
|||
|
|
'learning_rate': 3e-5,
|
|||
|
|
'batch_size': 2,
|
|||
|
|
'temperature': 1.0,
|
|||
|
|
'train_loss': 30.782700,
|
|||
|
|
'val_loss': 33.190113
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
'name': 'Эксперимент 2',
|
|||
|
|
'epochs': 20,
|
|||
|
|
'learning_rate': 2e-5,
|
|||
|
|
'batch_size': 2,
|
|||
|
|
'temperature': 0.5,
|
|||
|
|
'train_loss': 19.559500,
|
|||
|
|
'val_loss': 17.091799
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
'name': 'Эксперимент 3',
|
|||
|
|
'epochs': 50,
|
|||
|
|
'learning_rate': 1.5e-5,
|
|||
|
|
'batch_size': 2,
|
|||
|
|
'temperature': 0.4,
|
|||
|
|
'train_loss': 10.598900,
|
|||
|
|
'val_loss': 8.103792
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
'name': 'Эксперимент 4',
|
|||
|
|
'epochs': 100,
|
|||
|
|
'learning_rate': 1e-5,
|
|||
|
|
'batch_size': 2,
|
|||
|
|
'temperature': 0.3,
|
|||
|
|
'train_loss': 1.276500,
|
|||
|
|
'val_loss': 0.911331
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
# Создаем фигуру и оси
|
|||
|
|
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 7))
|
|||
|
|
fig.suptitle('Результаты экспериментов по обучению модели на мультиязычном датасете',
|
|||
|
|
fontsize=16, fontweight='bold', color='#1a3c8c')
|
|||
|
|
|
|||
|
|
# Оттенки синего для экспериментов
|
|||
|
|
blue_palette = ['#1a3c8c', '#2a5caa', '#3a7dc8', '#4a9de6']
|
|||
|
|
|
|||
|
|
# График 1: Loss по экспериментам
|
|||
|
|
experiment_numbers = np.arange(len(experiments))
|
|||
|
|
width = 0.35
|
|||
|
|
|
|||
|
|
bars1 = ax1.bar(experiment_numbers - width/2,
|
|||
|
|
[exp['train_loss'] for exp in experiments],
|
|||
|
|
width, label='Training Loss', color='#1a3c8c', alpha=0.8)
|
|||
|
|
bars2 = ax1.bar(experiment_numbers + width/2,
|
|||
|
|
[exp['val_loss'] for exp in experiments],
|
|||
|
|
width, label='Validation Loss', color='#4a9de6', alpha=0.8)
|
|||
|
|
|
|||
|
|
ax1.set_xlabel('Эксперимент', fontsize=12, color='#1a3c8c')
|
|||
|
|
ax1.set_ylabel('Loss', fontsize=12, color='#1a3c8c')
|
|||
|
|
ax1.set_title('Сравнение Training и Validation Loss', fontsize=14, color='#1a3c8c')
|
|||
|
|
ax1.set_xticks(experiment_numbers)
|
|||
|
|
ax1.set_xticklabels([exp['name'] for exp in experiments], rotation=45, ha='right')
|
|||
|
|
ax1.legend()
|
|||
|
|
ax1.grid(True, alpha=0.3, linestyle='--')
|
|||
|
|
|
|||
|
|
# Добавляем значения на столбцы
|
|||
|
|
for bars in [bars1, bars2]:
|
|||
|
|
for bar in bars:
|
|||
|
|
height = bar.get_height()
|
|||
|
|
ax1.annotate(f'{height:.2f}',
|
|||
|
|
xy=(bar.get_x() + bar.get_width() / 2, height),
|
|||
|
|
xytext=(0, 3), # 3 points vertical offset
|
|||
|
|
textcoords="offset points",
|
|||
|
|
ha='center', va='bottom',
|
|||
|
|
fontsize=9, color='#1a3c8c')
|
|||
|
|
|
|||
|
|
# График 2: Параметры и метрики
|
|||
|
|
x = np.arange(len(experiments))
|
|||
|
|
|
|||
|
|
# Создаем вторую ось Y
|
|||
|
|
ax2_twin = ax2.twinx()
|
|||
|
|
|
|||
|
|
# Линия для Loss
|
|||
|
|
line1 = ax2.plot(x, [exp['train_loss'] for exp in experiments],
|
|||
|
|
'o-', linewidth=2, markersize=8, color='#1a3c8c',
|
|||
|
|
label='Training Loss')
|
|||
|
|
|
|||
|
|
# Линия для Learning Rate (используем логарифмическую шкалу)
|
|||
|
|
line2 = ax2_twin.plot(x, [exp['learning_rate'] for exp in experiments],
|
|||
|
|
's--', linewidth=2, markersize=8, color='#3a7dc8',
|
|||
|
|
label='Learning Rate')
|
|||
|
|
|
|||
|
|
# Столбцы для Epochs
|
|||
|
|
bars_epochs = ax2.bar(x, [exp['epochs'] for exp in experiments],
|
|||
|
|
alpha=0.3, color='#2a5caa', label='Epochs')
|
|||
|
|
|
|||
|
|
# Настройки основной оси Y
|
|||
|
|
ax2.set_xlabel('Эксперимент', fontsize=12, color='#1a3c8c')
|
|||
|
|
ax2.set_ylabel('Loss / Epochs', fontsize=12, color='#1a3c8c')
|
|||
|
|
ax2.set_title('Параметры обучения и результаты', fontsize=14, color='#1a3c8c')
|
|||
|
|
ax2.set_xticks(x)
|
|||
|
|
ax2.set_xticklabels([f"Exp {i+1}" for i in range(len(experiments))])
|
|||
|
|
|
|||
|
|
# Настройки второй оси Y
|
|||
|
|
ax2_twin.set_ylabel('Learning Rate', fontsize=12, color='#3a7dc8')
|
|||
|
|
ax2_twin.tick_params(axis='y', labelcolor='#3a7dc8')
|
|||
|
|
ax2_twin.set_yscale('log') # Логарифмическая шкала для learning rate
|
|||
|
|
|
|||
|
|
# Добавляем аннотации для температуры
|
|||
|
|
for i, exp in enumerate(experiments):
|
|||
|
|
ax2.annotate(f"T={exp['temperature']}",
|
|||
|
|
xy=(i, exp['train_loss']),
|
|||
|
|
xytext=(0, 10),
|
|||
|
|
textcoords='offset points',
|
|||
|
|
ha='center', fontsize=9,
|
|||
|
|
bbox=dict(boxstyle="round,pad=0.3", facecolor='white', alpha=0.8, edgecolor='#4a9de6'))
|
|||
|
|
|
|||
|
|
# Объединяем легенды
|
|||
|
|
lines1, labels1 = ax2.get_legend_handles_labels()
|
|||
|
|
lines2, labels2 = ax2_twin.get_legend_handles_labels()
|
|||
|
|
ax2.legend(lines1 + lines2, labels1 + labels2, loc='upper right')
|
|||
|
|
|
|||
|
|
ax2.grid(True, alpha=0.3, linestyle='--')
|
|||
|
|
|
|||
|
|
# Добавляем таблицу с параметрами
|
|||
|
|
table_data = []
|
|||
|
|
for exp in experiments:
|
|||
|
|
table_data.append([
|
|||
|
|
exp['epochs'],
|
|||
|
|
f"{exp['learning_rate']:.1e}",
|
|||
|
|
exp['temperature'],
|
|||
|
|
f"{exp['train_loss']:.2f}",
|
|||
|
|
f"{exp['val_loss']:.2f}"
|
|||
|
|
])
|
|||
|
|
|
|||
|
|
# Создаем таблицу под графиками
|
|||
|
|
col_labels = ['Эпохи', 'LR', 'Temp', 'Train Loss', 'Val Loss']
|
|||
|
|
row_labels = [exp['name'] for exp in experiments]
|
|||
|
|
|
|||
|
|
# Добавляем таблицу как отдельный subplot
|
|||
|
|
fig, ax_table = plt.subplots(figsize=(12, 3))
|
|||
|
|
ax_table.axis('tight')
|
|||
|
|
ax_table.axis('off')
|
|||
|
|
|
|||
|
|
table = ax_table.table(cellText=table_data,
|
|||
|
|
colLabels=col_labels,
|
|||
|
|
rowLabels=row_labels,
|
|||
|
|
cellLoc='center',
|
|||
|
|
loc='center',
|
|||
|
|
colColours=['#e6f2ff']*5,
|
|||
|
|
rowColours=['#f0f8ff']*4)
|
|||
|
|
|
|||
|
|
table.auto_set_font_size(False)
|
|||
|
|
table.set_fontsize(10)
|
|||
|
|
table.scale(1.2, 1.5)
|
|||
|
|
|
|||
|
|
# Настраиваем цвета заголовков
|
|||
|
|
for i in range(len(col_labels)):
|
|||
|
|
table[(0, i)].set_facecolor('#1a3c8c')
|
|||
|
|
table[(0, i)].set_text_props(color='white', weight='bold')
|
|||
|
|
|
|||
|
|
# Объединяем все графики
|
|||
|
|
plt.figure(fig.number)
|
|||
|
|
plt.tight_layout()
|
|||
|
|
plt.subplots_adjust(top=0.9)
|
|||
|
|
|
|||
|
|
# Сохраняем график
|
|||
|
|
plt.savefig('experiments_results_blue_theme.png', dpi=300, bbox_inches='tight', facecolor='white')
|
|||
|
|
plt.show()
|
|||
|
|
|
|||
|
|
# Дополнительно: создаем компактный линейный график
|
|||
|
|
fig2, ax3 = plt.subplots(figsize=(12, 6))
|
|||
|
|
|
|||
|
|
# Преобразуем эпохи в массив для smooth линии
|
|||
|
|
epochs_array = np.array([exp['epochs'] for exp in experiments])
|
|||
|
|
train_loss_array = np.array([exp['train_loss'] for exp in experiments])
|
|||
|
|
val_loss_array = np.array([exp['val_loss'] for exp in experiments])
|
|||
|
|
|
|||
|
|
# Создаем интерполированные данные для плавных линий
|
|||
|
|
epochs_smooth = np.linspace(epochs_array.min(), epochs_array.max(), 100)
|
|||
|
|
train_loss_smooth = np.interp(epochs_smooth, epochs_array, train_loss_array)
|
|||
|
|
val_loss_smooth = np.interp(epochs_smooth, epochs_array, val_loss_array)
|
|||
|
|
|
|||
|
|
# График с плавными линиями
|
|||
|
|
ax3.plot(epochs_smooth, train_loss_smooth, '-', linewidth=3,
|
|||
|
|
color='#1a3c8c', alpha=0.7, label='Training Loss (сглаженный)')
|
|||
|
|
ax3.plot(epochs_smooth, val_loss_smooth, '-', linewidth=3,
|
|||
|
|
color='#4a9de6', alpha=0.7, label='Validation Loss (сглаженный)')
|
|||
|
|
|
|||
|
|
# Точки фактических измерений
|
|||
|
|
ax3.scatter(epochs_array, train_loss_array, s=100, color='#1a3c8c',
|
|||
|
|
edgecolors='white', linewidth=2, zorder=5, label='Training Loss')
|
|||
|
|
ax3.scatter(epochs_array, val_loss_array, s=100, color='#4a9de6',
|
|||
|
|
edgecolors='white', linewidth=2, zorder=5, label='Validation Loss')
|
|||
|
|
|
|||
|
|
# Добавляем аннотации с параметрами
|
|||
|
|
for i, exp in enumerate(experiments):
|
|||
|
|
text = f"Exp {i+1}\nLR={exp['learning_rate']:.1e}\nT={exp['temperature']}\nBS={exp['batch_size']}"
|
|||
|
|
ax3.annotate(text,
|
|||
|
|
xy=(exp['epochs'], exp['train_loss']),
|
|||
|
|
xytext=(10, 20 if i%2==0 else -30),
|
|||
|
|
textcoords='offset points',
|
|||
|
|
ha='left',
|
|||
|
|
fontsize=9,
|
|||
|
|
bbox=dict(boxstyle="round,pad=0.5", facecolor='white',
|
|||
|
|
alpha=0.9, edgecolor=blue_palette[i]),
|
|||
|
|
arrowprops=dict(arrowstyle='->', color=blue_palette[i], alpha=0.7))
|
|||
|
|
|
|||
|
|
ax3.set_xlabel('Количество эпох', fontsize=12, color='#1a3c8c')
|
|||
|
|
ax3.set_ylabel('Loss', fontsize=12, color='#1a3c8c')
|
|||
|
|
ax3.set_title('Динамика обучения модели: зависимость Loss от количества эпох и гиперпараметров',
|
|||
|
|
fontsize=14, fontweight='bold', color='#1a3c8c')
|
|||
|
|
ax3.set_yscale('log') # Логарифмическая шкала для лучшей визуализации
|
|||
|
|
ax3.grid(True, alpha=0.3, linestyle='--')
|
|||
|
|
ax3.legend(loc='upper right')
|
|||
|
|
|
|||
|
|
plt.tight_layout()
|
|||
|
|
plt.savefig('training_dynamics_blue_theme.png', dpi=300, bbox_inches='tight', facecolor='white')
|
|||
|
|
plt.show()
|
|||
|
|
|
|||
|
|
"""# **2 ЭТАП ОБУЧЕНИЯ МОДЕЛИ "mT5-SMALL". ОПТИМИЗАЦИЯ.**"""
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
# # Часть 2: Генерация синтетических данных и дообучение
|
|||
|
|
|
|||
|
|
import pandas as pd
|
|||
|
|
import os
|
|||
|
|
from transformers import MT5ForConditionalGeneration, MT5Tokenizer
|
|||
|
|
from datasets import Dataset
|
|||
|
|
import torch
|
|||
|
|
import re
|
|||
|
|
import numpy as np
|
|||
|
|
|
|||
|
|
# =============================================================================
|
|||
|
|
# КОНФИГУРАЦИЯ СИНТЕТИЧЕСКИХ ДАННЫХ
|
|||
|
|
# =============================================================================
|
|||
|
|
|
|||
|
|
class SyntheticDataConfig:
|
|||
|
|
"""Конфигурация для генерации синтетических данных"""
|
|||
|
|
|
|||
|
|
NUM_SYNTHETIC_SAMPLES = 1000
|
|||
|
|
MIN_REWARD_SCORE = 7 # Минимальный балл для сохранения синтетических данных
|
|||
|
|
LEARNING_EPOCHS = 3 # Эпохи дообучения на синтетических данных
|
|||
|
|
|
|||
|
|
CATEGORIES = {
|
|||
|
|
'en': ['Exhibition', 'Workshop', 'Other events', 'Cinema', 'Photography',
|
|||
|
|
'Dance', 'Video art', 'Music', 'Performance', 'Installation'],
|
|||
|
|
'fi': ['Näyttely', 'Taidepaja', 'Esitys', 'Muut tapahtumat', 'Elokuvataide',
|
|||
|
|
'Valokuvataide', 'Tanssitaide', 'Videotaide', 'Musiikki', 'Performanssi', 'Installaatio']
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# =============================================================================
|
|||
|
|
# ФУНКЦИИ ДЛЯ РАБОТЫ С СИНТЕТИЧЕСКИМИ ДАННЫМИ
|
|||
|
|
# =============================================================================
|
|||
|
|
|
|||
|
|
def load_best_model():
|
|||
|
|
"""Загружает лучшую модель из экспериментов"""
|
|||
|
|
# Находим лучшую модель по результатам экспериментов
|
|||
|
|
results_df = pd.read_csv("./experiments/results_summary.csv")
|
|||
|
|
best_exp = results_df.loc[results_df['rouge1_f1'].idxmax()]['experiment']
|
|||
|
|
|
|||
|
|
model_path = f"./models/{best_exp}"
|
|||
|
|
print(f"Загрузка лучшей модели: {best_exp}")
|
|||
|
|
|
|||
|
|
# Загружаем только веса модели
|
|||
|
|
model = MT5ForConditionalGeneration.from_pretrained(model_path)
|
|||
|
|
tokenizer = MT5Tokenizer.from_pretrained(model_path)
|
|||
|
|
|
|||
|
|
# Используем CUDA если доступно
|
|||
|
|
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
|||
|
|
model = model.to(device)
|
|||
|
|
print(f"Модель загружена на устройство: {device}")
|
|||
|
|
|
|||
|
|
return model, tokenizer, best_exp
|
|||
|
|
|
|||
|
|
def generate_synthetic_announcement(model, tokenizer, query, language="en", max_length=128):
|
|||
|
|
"""Генерирует синтетический анонс"""
|
|||
|
|
input_text = f"[{language.upper()}] {query}"
|
|||
|
|
|
|||
|
|
inputs = tokenizer(
|
|||
|
|
input_text,
|
|||
|
|
return_tensors="pt",
|
|||
|
|
max_length=64,
|
|||
|
|
truncation=True,
|
|||
|
|
padding=True
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Перемещаем входные данные на то же устройство, что и модель
|
|||
|
|
device = next(model.parameters()).device
|
|||
|
|
inputs = {key: value.to(device) for key, value in inputs.items()}
|
|||
|
|
|
|||
|
|
outputs = model.generate(
|
|||
|
|
input_ids=inputs['input_ids'],
|
|||
|
|
attention_mask=inputs['attention_mask'],
|
|||
|
|
max_length=max_length,
|
|||
|
|
num_beams=4,
|
|||
|
|
early_stopping=True,
|
|||
|
|
temperature=0.8,
|
|||
|
|
do_sample=True
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
|||
|
|
generated_text = re.sub(r'\s+', ' ', generated_text).strip()
|
|||
|
|
|
|||
|
|
return generated_text
|
|||
|
|
|
|||
|
|
def validate_synthetic_text(text):
|
|||
|
|
"""Проверяет валидность синтетического текста"""
|
|||
|
|
required_fields = ['Title:', 'Date:', 'Text:']
|
|||
|
|
return all(field in text for field in required_fields)
|
|||
|
|
|
|||
|
|
def calculate_reward_score(text):
|
|||
|
|
"""Упрощенная reward-система для синтетических данных"""
|
|||
|
|
score = 0
|
|||
|
|
|
|||
|
|
# Проверка структуры
|
|||
|
|
if validate_synthetic_text(text):
|
|||
|
|
score += 3
|
|||
|
|
|
|||
|
|
# Проверка длины
|
|||
|
|
sentences = re.split(r'[.!?]+', text)
|
|||
|
|
sentence_count = len([s for s in sentences if len(s.strip()) > 5])
|
|||
|
|
if 1 <= sentence_count <= 3: # 1-3 предложения
|
|||
|
|
score += 2
|
|||
|
|
|
|||
|
|
# Проверка уникальности слов
|
|||
|
|
words = re.findall(r'\b\w+\b', text.lower())
|
|||
|
|
unique_ratio = len(set(words)) / len(words) if words else 0
|
|||
|
|
if unique_ratio > 0.7:
|
|||
|
|
score += 2
|
|||
|
|
|
|||
|
|
return min(7, score) # Максимум 7 баллов
|
|||
|
|
|
|||
|
|
def generate_synthetic_dataset(model, tokenizer, num_samples=1000):
|
|||
|
|
"""Генерирует датасет синтетических данных"""
|
|||
|
|
print("Генерация синтетических данных...")
|
|||
|
|
|
|||
|
|
synthetic_data = []
|
|||
|
|
queries = {
|
|||
|
|
'en': [
|
|||
|
|
"Announcement of the exhibition",
|
|||
|
|
"Write an announcement for the exhibition",
|
|||
|
|
"Announcement exhibition",
|
|||
|
|
"Announcement of the workshop",
|
|||
|
|
"Write an announcement for the workshop",
|
|||
|
|
"Announcement workshop",
|
|||
|
|
"Announcement of other events",
|
|||
|
|
"Write an announcement for other events",
|
|||
|
|
"Announcement other events",
|
|||
|
|
"Announcement of the cinema",
|
|||
|
|
"Write an announcement for the cinema",
|
|||
|
|
"Announcement cinema",
|
|||
|
|
"Announcement of photography",
|
|||
|
|
"Write an announcement for photography",
|
|||
|
|
"Announcement photography",
|
|||
|
|
"Announcement of the dance",
|
|||
|
|
"Write an announcement for the dance",
|
|||
|
|
"Announcement dance",
|
|||
|
|
"Announcement of video art",
|
|||
|
|
"Write an announcement for video art",
|
|||
|
|
"Announcement video art",
|
|||
|
|
"Announcement of the music",
|
|||
|
|
"Write an announcement for the music",
|
|||
|
|
"Announcement music",
|
|||
|
|
"Announcement of the performance",
|
|||
|
|
"Write an announcement for the performance",
|
|||
|
|
"Announcement performance",
|
|||
|
|
"Announcement of the installation",
|
|||
|
|
"Write an announcement for the installation",
|
|||
|
|
"Announcement installation"
|
|||
|
|
],
|
|||
|
|
'fi': [
|
|||
|
|
"Ilmoitus näyttelystä",
|
|||
|
|
"Kirjoita ilmoitus näyttelystä",
|
|||
|
|
"Ilmoitus taidepajasta",
|
|||
|
|
"Kirjoita ilmoitus taidepajasta",
|
|||
|
|
"Ilmoitus esityksestä",
|
|||
|
|
"Kirjoita ilmoitus esityksestä",
|
|||
|
|
"Ilmoitus muista tapahtumista",
|
|||
|
|
"Kirjoita ilmoitus muista tapahtumista",
|
|||
|
|
"Ilmoitus elokuvataiteesta",
|
|||
|
|
"Kirjoita ilmoitus elokuvataiteesta",
|
|||
|
|
"Ilmoitus valokuvataiteesta",
|
|||
|
|
"Kirjoita ilmoitus valokuvataiteesta",
|
|||
|
|
"Ilmoitus tanssitaiteesta",
|
|||
|
|
"Kirjoita ilmoitus tanssitaiteesta",
|
|||
|
|
"Ilmoitus videotaiteesta",
|
|||
|
|
"Kirjoita ilmoitus videotaiteesta",
|
|||
|
|
"Ilmoitus musiikista",
|
|||
|
|
"Kirjoita ilmoitus musiikista",
|
|||
|
|
"Ilmoitus performanssista",
|
|||
|
|
"Kirjoita ilmoitus performanssista",
|
|||
|
|
"Ilmoitus installaatiosta",
|
|||
|
|
"Kirjoita ilmoitus installaatiosta"
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
generated_count = 0
|
|||
|
|
while len(synthetic_data) < num_samples and generated_count < num_samples * 2:
|
|||
|
|
lang = 'en' if generated_count % 2 == 0 else 'fi'
|
|||
|
|
query_list = queries[lang]
|
|||
|
|
query = query_list[generated_count % len(query_list)]
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
synthetic_text = generate_synthetic_announcement(model, tokenizer, query, lang)
|
|||
|
|
reward_score = calculate_reward_score(synthetic_text)
|
|||
|
|
|
|||
|
|
if reward_score >= SyntheticDataConfig.MIN_REWARD_SCORE:
|
|||
|
|
# Определяем категорию на основе запроса
|
|||
|
|
category = determine_category_from_query(query, lang)
|
|||
|
|
|
|||
|
|
synthetic_data.append({
|
|||
|
|
'input': f"[{lang.upper()}] {query}",
|
|||
|
|
'target': synthetic_text,
|
|||
|
|
'language': lang,
|
|||
|
|
'category': category,
|
|||
|
|
'reward_score': reward_score
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if len(synthetic_data) % 100 == 0:
|
|||
|
|
print(f"Сгенерировано {len(synthetic_data)} синтетических примеров")
|
|||
|
|
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"Ошибка генерации: {e}")
|
|||
|
|
|
|||
|
|
generated_count += 1
|
|||
|
|
|
|||
|
|
print(f"Генерация завершена: {len(synthetic_data)} качественных примеров")
|
|||
|
|
return pd.DataFrame(synthetic_data)
|
|||
|
|
|
|||
|
|
def determine_category_from_query(query, language):
|
|||
|
|
"""Определяет категорию на основе запроса"""
|
|||
|
|
query_lower = query.lower()
|
|||
|
|
|
|||
|
|
if language == 'en':
|
|||
|
|
if 'exhibition' in query_lower:
|
|||
|
|
return 'Exhibition'
|
|||
|
|
elif 'workshop' in query_lower:
|
|||
|
|
return 'Workshop'
|
|||
|
|
elif 'cinema' in query_lower:
|
|||
|
|
return 'Cinema'
|
|||
|
|
elif 'photography' in query_lower:
|
|||
|
|
return 'Photography'
|
|||
|
|
elif 'dance' in query_lower:
|
|||
|
|
return 'Dance'
|
|||
|
|
elif 'video art' in query_lower:
|
|||
|
|
return 'Video art'
|
|||
|
|
elif 'music' in query_lower:
|
|||
|
|
return 'Music'
|
|||
|
|
elif 'performance' in query_lower:
|
|||
|
|
return 'Performance'
|
|||
|
|
elif 'installation' in query_lower:
|
|||
|
|
return 'Installation'
|
|||
|
|
else:
|
|||
|
|
return 'Other events'
|
|||
|
|
|
|||
|
|
else: # fi
|
|||
|
|
if 'näyttely' in query_lower:
|
|||
|
|
return 'Näyttely'
|
|||
|
|
elif 'taidepaja' in query_lower:
|
|||
|
|
return 'Taidepaja'
|
|||
|
|
elif 'esitys' in query_lower:
|
|||
|
|
return 'Esitys'
|
|||
|
|
elif 'elokuvataide' in query_lower:
|
|||
|
|
return 'Elokuvataide'
|
|||
|
|
elif 'valokuvataide' in query_lower:
|
|||
|
|
return 'Valokuvataide'
|
|||
|
|
elif 'tanssitaide' in query_lower:
|
|||
|
|
return 'Tanssitaide'
|
|||
|
|
elif 'videotaide' in query_lower:
|
|||
|
|
return 'Videotaide'
|
|||
|
|
elif 'musiikki' in query_lower:
|
|||
|
|
return 'Musiikki'
|
|||
|
|
elif 'performanssi' in query_lower:
|
|||
|
|
return 'Performanssi'
|
|||
|
|
elif 'installaatio' in query_lower:
|
|||
|
|
return 'Installaatio'
|
|||
|
|
else:
|
|||
|
|
return 'Muut tapahtumat'
|
|||
|
|
|
|||
|
|
# =============================================================================
|
|||
|
|
# ДООБУЧЕНИЕ НА СИНТЕТИЧЕСКИХ ДАННЫХ
|
|||
|
|
# =============================================================================
|
|||
|
|
|
|||
|
|
def fine_tune_on_synthetic_data(model, tokenizer, synthetic_df, original_train_data, experiment_name):
|
|||
|
|
"""Дообучает модель на синтетических данных"""
|
|||
|
|
|
|||
|
|
print("Начало дообучения на синтетических данных...")
|
|||
|
|
|
|||
|
|
# Подготовка данных
|
|||
|
|
synthetic_dataset = Dataset.from_pandas(synthetic_df)
|
|||
|
|
combined_dataset = concatenate_datasets([original_train_data, synthetic_dataset])
|
|||
|
|
|
|||
|
|
# Токенизация
|
|||
|
|
def tokenize_function(examples):
|
|||
|
|
inputs = [f"{inp}" for inp in examples['input']]
|
|||
|
|
targets = [f"{tgt}" for tgt in examples['target']]
|
|||
|
|
|
|||
|
|
model_inputs = tokenizer(
|
|||
|
|
inputs,
|
|||
|
|
max_length=64,
|
|||
|
|
padding='max_length',
|
|||
|
|
truncation=True,
|
|||
|
|
return_tensors="pt"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
with tokenizer.as_target_tokenizer():
|
|||
|
|
labels = tokenizer(
|
|||
|
|
targets,
|
|||
|
|
max_length=128,
|
|||
|
|
padding='max_length',
|
|||
|
|
truncation=True,
|
|||
|
|
return_tensors="pt"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
labels = labels["input_ids"]
|
|||
|
|
labels[labels == tokenizer.pad_token_id] = -100
|
|||
|
|
|
|||
|
|
model_inputs["labels"] = labels
|
|||
|
|
return model_inputs
|
|||
|
|
|
|||
|
|
tokenized_combined = combined_dataset.map(tokenize_function, batched=True)
|
|||
|
|
|
|||
|
|
# Data collator
|
|||
|
|
from transformers import DataCollatorForSeq2Seq
|
|||
|
|
data_collator = DataCollatorForSeq2Seq(
|
|||
|
|
tokenizer=tokenizer,
|
|||
|
|
model=model,
|
|||
|
|
padding=True
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Аргументы дообучения
|
|||
|
|
from transformers import Seq2SeqTrainingArguments
|
|||
|
|
training_args = Seq2SeqTrainingArguments(
|
|||
|
|
output_dir=f"./synthetic_finetune/{experiment_name}",
|
|||
|
|
evaluation_strategy="epoch",
|
|||
|
|
save_strategy="epoch",
|
|||
|
|
learning_rate=1e-5, # Меньший LR для тонкой настройки
|
|||
|
|
per_device_train_batch_size=8,
|
|||
|
|
per_device_eval_batch_size=8,
|
|||
|
|
num_train_epochs=SyntheticDataConfig.LEARNING_EPOCHS,
|
|||
|
|
predict_with_generate=True,
|
|||
|
|
logging_dir=f'./logs/synthetic_{experiment_name}',
|
|||
|
|
logging_steps=20,
|
|||
|
|
save_total_limit=2,
|
|||
|
|
load_best_model_at_end=True,
|
|||
|
|
metric_for_best_model="eval_loss",
|
|||
|
|
greater_is_better=False,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Trainer
|
|||
|
|
from Part1 import EnhancedContrastiveTrainer # Импортируем из первой части
|
|||
|
|
trainer = EnhancedContrastiveTrainer(
|
|||
|
|
model=model,
|
|||
|
|
args=training_args,
|
|||
|
|
train_dataset=tokenized_combined,
|
|||
|
|
data_collator=data_collator,
|
|||
|
|
tokenizer=tokenizer,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Дообучение
|
|||
|
|
print("Запуск дообучения...")
|
|||
|
|
trainer.train()
|
|||
|
|
|
|||
|
|
# Сохранение модели
|
|||
|
|
trainer.save_model(f"./models/synthetic_finetuned_{experiment_name}")
|
|||
|
|
tokenizer.save_pretrained(f"./models/synthetic_finetuned_{experiment_name}")
|
|||
|
|
|
|||
|
|
print("Дообучение завершено!")
|
|||
|
|
return trainer
|
|||
|
|
|
|||
|
|
# =============================================================================
|
|||
|
|
# ТЕСТИРОВАНИЕ ДООБУЧЕННОЙ МОДЕЛИ
|
|||
|
|
# =============================================================================
|
|||
|
|
|
|||
|
|
def test_finetuned_model(model, tokenizer, test_dataset):
|
|||
|
|
"""Тестирует дообученную модель"""
|
|||
|
|
|
|||
|
|
from Part1 import MetricsCalculator # Импортируем из первой части
|
|||
|
|
|
|||
|
|
metrics_calculator = MetricsCalculator()
|
|||
|
|
|
|||
|
|
print("Тестирование дообученной модели...")
|
|||
|
|
|
|||
|
|
# Генерация предсказаний для тестового набора
|
|||
|
|
predictions = []
|
|||
|
|
references = []
|
|||
|
|
|
|||
|
|
for example in test_dataset:
|
|||
|
|
input_text = example['input']
|
|||
|
|
|
|||
|
|
# Генерация
|
|||
|
|
generated = generate_synthetic_announcement(model, tokenizer,
|
|||
|
|
input_text.replace(f"[{example['language'].upper()}] ", ""),
|
|||
|
|
example['language'])
|
|||
|
|
|
|||
|
|
predictions.append(generated)
|
|||
|
|
references.append(example['target'])
|
|||
|
|
|
|||
|
|
# Вычисление метрик
|
|||
|
|
metrics = metrics_calculator.calculate_custom_metrics(predictions, references)
|
|||
|
|
|
|||
|
|
print("Результаты тестирования дообученной модели:")
|
|||
|
|
for metric, value in metrics.items():
|
|||
|
|
print(f" {metric}: {value:.4f}")
|
|||
|
|
|
|||
|
|
return metrics
|
|||
|
|
|
|||
|
|
# =============================================================================
|
|||
|
|
# ОСНОВНОЙ ПРОЦЕСС ЧАСТИ 2
|
|||
|
|
# =============================================================================
|
|||
|
|
|
|||
|
|
def run_synthetic_data_pipeline():
|
|||
|
|
"""Запускает полный пайплайн синтетических данных"""
|
|||
|
|
|
|||
|
|
# Загрузка лучшей модели
|
|||
|
|
model, tokenizer, best_exp = load_best_model()
|
|||
|
|
|
|||
|
|
# Загрузка оригинальных данных для тестирования
|
|||
|
|
from Part1 import load_excel_data
|
|||
|
|
df_en = load_excel_data("ENG1.xlsx", lang='en')
|
|||
|
|
df_fi = load_excel_data("FI1.xlsx", lang='fi')
|
|||
|
|
|
|||
|
|
from datasets import Dataset, concatenate_datasets
|
|||
|
|
dataset_en = Dataset.from_pandas(df_en)
|
|||
|
|
dataset_fi = Dataset.from_pandas(df_fi)
|
|||
|
|
combined_dataset = concatenate_datasets([dataset_en, dataset_fi])
|
|||
|
|
train_test_split = combined_dataset.train_test_split(test_size=0.15, seed=42)
|
|||
|
|
|
|||
|
|
# Генерация синтетических данных
|
|||
|
|
synthetic_df = generate_synthetic_dataset(
|
|||
|
|
model, tokenizer,
|
|||
|
|
SyntheticDataConfig.NUM_SYNTHETIC_SAMPLES
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Сохранение синтетических данных
|
|||
|
|
synthetic_df.to_csv("./synthetic_data/synthetic_announcements.csv", index=False)
|
|||
|
|
print("Синтетические данные сохранены")
|
|||
|
|
|
|||
|
|
# Анализ распределения категорий
|
|||
|
|
print("\nРаспределение категорий в синтетических данных:")
|
|||
|
|
print("Английские данные:")
|
|||
|
|
en_categories = synthetic_df[synthetic_df['language'] == 'en']['category'].value_counts()
|
|||
|
|
for category, count in en_categories.items():
|
|||
|
|
print(f" {category}: {count} примеров")
|
|||
|
|
|
|||
|
|
print("\nФинские данные:")
|
|||
|
|
fi_categories = synthetic_df[synthetic_df['language'] == 'fi']['category'].value_counts()
|
|||
|
|
for category, count in fi_categories.items():
|
|||
|
|
print(f" {category}: {count} примеров")
|
|||
|
|
|
|||
|
|
# Дообучение на синтетических данных
|
|||
|
|
finetuned_trainer = fine_tune_on_synthetic_data(
|
|||
|
|
model, tokenizer, synthetic_df,
|
|||
|
|
train_test_split['train'], best_exp
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Тестирование
|
|||
|
|
test_metrics = test_finetuned_model(
|
|||
|
|
finetuned_trainer.model, tokenizer, train_test_split['test']
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# Сравнение с оригинальными результатами
|
|||
|
|
original_results = pd.read_csv("./experiments/results_summary.csv")
|
|||
|
|
best_original = original_results.loc[original_results['rouge1_f1'].idxmax()]
|
|||
|
|
|
|||
|
|
print(f"Сравнение результатов:")
|
|||
|
|
print(f" Оригинальная модель - ROUGE-1 F1: {best_original['rouge1_f1']:.4f}")
|
|||
|
|
print(f" Дообученная модель - ROUGE-1 F1: {test_metrics['rouge1_f1']:.4f}")
|
|||
|
|
print(f" Улучшение: {test_metrics['rouge1_f1'] - best_original['rouge1_f1']:.4f}")
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
'synthetic_data': synthetic_df,
|
|||
|
|
'finetuned_trainer': finetuned_trainer,
|
|||
|
|
'test_metrics': test_metrics,
|
|||
|
|
'improvement': test_metrics['rouge1_f1'] - best_original['rouge1_f1']
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# =============================================================================
|
|||
|
|
# ЗАПУСК
|
|||
|
|
# =============================================================================
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
# Создаем директории
|
|||
|
|
os.makedirs("./synthetic_data", exist_ok=True)
|
|||
|
|
os.makedirs("./synthetic_finetune", exist_ok=True)
|
|||
|
|
|
|||
|
|
# Запускаем пайплайн
|
|||
|
|
results = run_synthetic_data_pipeline()
|
|||
|
|
|
|||
|
|
if results['improvement'] > 0:
|
|||
|
|
print(f"Дообучение улучшило модель на {results['improvement']:.4f}!")
|
|||
|
|
else:
|
|||
|
|
print(f"Дообучение не улучшило модель")
|