Генератор отзывов о ресторане:

Собрано около шестнадцати тысяч положительных отзывов от ресторанов с оценкой выше 4,7 (из 5), расположенных в Москве. Подробнее на .

Использованная модель — Qwen3-4B (версия Qwen3, поддерживающая русский язык). Для обучения модели в течение двух эпох использовалась библиотека Unsloth с LoRA (Low-Rank Adaptation — метод тонкой настройки больших языковых моделей). В результате был выбран LoRA 32-го ранга, и обучено 66 миллионов параметров. Теперь модель способна генерировать качественные новые обзоры.

‌Из https://docs.unsloth.ai/

Процесс Fine-Tuning:

Для начала импортирую нужные библиотеки: (обратите внимание, что Unsloth поддерживает только GPU) , https://docs.unsloth.ai/

from unsloth import FastLanguageModel
import torch
import pandas as pd
from datasets import Dataset
from trl import SFTTrainer, SFTConfig
from transformers import TextStreamer

Далее мы определяем, какую модель мы хотим использовать: вы можете скачать Qwen3 по ссылке https://huggingface.co/Qwen/Qwen3-4B и загрузить ее локально или просто воспользоваться библиотекой Unsloth для ее загрузки.

max_seq_length = 2048 # Может увеличиваться для более длинных следов рассуждений
lora_rank = 32 # Larger rank большее число: умнее, но медленнее

model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "unsloth/Qwen3-4B",    # если локальная папка, укажите адрес здесь,
    max_seq_length = max_seq_length,
    load_in_4bit = False, # если у вас недостаточно VRAM, используйте True (результаты менее точные на 3–5%).
    max_lora_rank = lora_rank,
    gpu_memory_utilization = 0.9, # уменьшить его, если out of memory
)

model = FastLanguageModel.get_peft_model(
    model,
    r = lora_rank, 
    target_modules = [
        "q_proj", "k_proj", "v_proj", "o_proj",
        "gate_proj", "up_proj", "down_proj",
    ],
    lora_alpha = lora_rank * 2, 
    use_gradient_checkpointing = "unsloth", 
    random_state = 3407,
)

После запуска этого кода вы получите следующий результат:

Unsloth 2025.10.1 пропатчил 36 слоев с 36 слоями QKV
Unsloth 2025.10.1 пропатчил 36 слоев с 36 слоями QKV

Если у вас есть вопросы по номеру 36, то, пожалуйста, прочитайте статью https://arxiv.org/pdf/2505.09388.

Архитектура модели — 36 слоев для Qwen3 4B
Архитектура модели — 36 слоев для Qwen3 4B

Чтобы упростить процесс, мы не будем использовать оригинальный шаблон чата с токеном thinking и мы сохраняем исходные роли (system, user, assistant). пример простой функции на Python для чтения строк из txt-файла и применения шаблона чата:

def convert_Qwen(input_data):
    converted_data = []
    for line in input_data:
        if line != '\n' :
            messages = [
            {"role": "system", "content": "You are Qwen"},
            {"role": "user", "content": 'напиши позитивный отзыв'},
            {"role": "assistant", "content": line}]
            text = tokenizer.apply_chat_template(
            messages,
            tokenize=False,
            add_generation_prompt=True,
            enable_thinking=False)   
            converted_data.append(text)
    return converted_data

вывод кода будет таким: '<|im_start|>system\nYou are Qwen<|im_end|>\n<|im_start|>user\nнапиши позитивный отзыв<|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\nБрали здесь суши, нам понравились, рыба свежая, начинки вкусные. Закажем еще.\n<|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\n'

использовал библиотеку Pandas для чтения CSV-файла, а затем библиотеку Dataset, чтобы сделать из него пакетный набор данных.

df = pd.read_csv("Qwen3.csv")
dataset = Dataset.from_pandas(df)

Далее мы настраиваем Trainer следующим образом:

trainer = SFTTrainer(
    model = model,  # наша модель Qwen3
    tokenizer = tokenizer, # токенизатор из нашей модели Qwen3
    train_dataset = dataset, 
    args = SFTConfig( 
        dataset_text_field = "text", # колонка в наборе данных, содержащий токенизированный текст
        dataset_num_proc=1,
        per_device_train_batch_size = 4, # размер партии на устройство во время обучения.
        gradient_accumulation_steps = 1, # число шагов обновления для накопления градиентов перед выполнением обратного прохода/прохода обновления.
        warmup_steps = 5, # число шагов, необходимых для линейного увеличения скорости обучения от 0 до начального значения.
        num_train_epochs = 2, # общее количество эпох обучения, которые необходимо выполнить.
        learning_rate = 2e-4, # начальная скорость обучения оптимизатора.
        logging_steps = 5, # частота (в шагах), с которой сообщаются logs.
        optim = "adamw_8bit", # оптимизатор
        weight_decay = 0.01, # снижение веса применено к оптимизатору.
        lr_scheduler_type = "linear", 
        seed = 3407, 
        report_to = "none", 
    ),
)

Теперь запустим процесс обучения:

trainer.train()

как показано ниже, на GPU 4080Super потребовалось 1:46 минуты, чтобы завершить 2 эпохи. Также вы можете увидеть окончательные Training Loss: 'train_loss': 1.1680835474376667

Сохраняем LoRA и токенизатор для возможности загрузки в будущем. Вы можете выбрать любую папку, но сохраните LoRA и токенизатор вместе.

trainer.model.save_pretrained("Qwen3_Tunned_Comments")   # создаст папку Qwen3_Tunned_Comments
trainer.tokenizer.save_pretrained("Qwen3_Tunned_Comments")

Теперь время протестироват�� и заполнить сгенерированный вывод:

messages = [
            {"role": "system", "content": "You are Qwen"},
            {"role": "user", "content": 'Напиши положительный отзыв'},
            ]
# применение шаблона чата (system. user, assistant)
text = tokenizer.apply_chat_template(
    messages,
    tokenize = False,
    add_generation_prompt = True,
    enable_thinking=False
)
_ = model.generate(
    **tokenizer(text, return_tensors = "pt").to("cuda"),
    temperature = 0.6,  # больше ценности, делают модель более креативной, но с риском получения не очень хороших ответов
    max_new_tokens = 500,
    streamer = TextStreamer(tokenizer, skip_prompt = False),
)

Пример вывода:

Как загрузить сохраненный LoRA:

Для начала импортирую нужные библиотеки:

from unsloth import FastLanguageModel
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch

LoRA имеет файл adapter_config.json в папке, и внутри этого JSON-файла есть путь к исходной обученной модели (в данном случае это Qwen3-4B). как показано в примере ниже, я сохранил Qwen на локальном диске G:

path = "Qwen3_lora_adapter" # путь к сохраненной папке LoRA
max_seq_length = 2048 
lora_rank = 32 

model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = path, 
    max_seq_length = max_seq_length,
    load_in_4bit = False, 
    max_lora_rank = lora_rank,
    gpu_memory_utilization = 0.9, # Reduce if out of memory
)

Остальное мы делаем так же, как и раньше:

messages = [
            {"role": "system", "content": "You are Qwen"},
            {"role": "user", "content": 'Напиши положительный отзыв'},
            ]

text = tokenizer.apply_chat_template(
    messages,
    tokenize = False,
    add_generation_prompt = True,# Must add for generation
    enable_thinking=False
)
_ = model.generate(
    **tokenizer(text, return_tensors = "pt").to("cuda"),
    temperature = 0.6,
    max_new_tokens = 500,
    streamer = TextStreamer(tokenizer, skip_prompt = False),
)

По итогу

Мы использовали библиотеку Unsloth для обучения Qwen3-4B на пользовательском наборе данных (отзывы ресторанов) в образовательных целях. Вы можете использовать любой тип набора данных с логикой, представленной в этой статье. Целью было использовать как можно больше простых кодов, чтобы сделать их изучение более эффективным для всех.

Комментарии (0)