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

Процесс 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,
)После запуска этого кода вы получите следующий результат:

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

Чтобы упростить процесс, мы не будем использовать оригинальный шаблон чата с токеном 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 torchLoRA имеет файл 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 на пользовательском наборе данных (отзывы ресторанов) в образовательных целях. Вы можете использовать любой тип набора данных с логикой, представленной в этой статье. Целью было использовать как можно больше простых кодов, чтобы сделать их изучение более эффективным для всех.
