0%

金牌销售微调(Qwen3)

金牌销售模型微调

前言

使用 Qwen3-1.7b 模型基于“销售部金牌优质话术”微调模型。实现销售实时通话时进行话术辅助,提高成单率以及专业度。
同时使用SwanLab监控训练过程、评估模型效果。

环境要求

Python>=3.8
swanlab
modelscope==1.22.0
transformers==4.51.3
datasets==3.2.0
accelerate
pandas
addict

一键安装命令

pip install swanlab modelscope==1.22.0 “transformers==4.51.3” datasets==3.2.0 accelerate pandas addict

备注:如果后续允许训练脚本时遇到“TypeError: argument of type ‘NoneType’ is not iterable”,请检查transformers版本是否为4.51.3

https://github.com/QwenLM/Qwen2.5-VL/issues/1239

准备数据集

数据集格式分析

1
2
3
4
5
6
7
{
"question": "英语你们现在这边英语教学是什么样子的?我先要看,不是直接就试听的,我要先看到底合不合适我儿子。",
"answer": "我们上课内容会同步强化课本内容,并拓展语法、单词背诵等应试技巧。一对一教学可以针对孩子的薄弱环节进行个性化辅导,确保学习效果。",
"scene": "客户异议处理",
"keyPoint": "担心效果",
"studentParentContext": "家长类型:效果导向型家长和观望型家长,转化阶段:首通,考试分数:未知,薄弱科目:英语,地区:未知,年级:五年级"
}
  • question:根据对话内容凝练的问题
  • answer:销售优质话术
  • scene:问题场景
  • keyPoint:回答和问题要点
  • studentParentContext:上下文信息(用户画像)

首先我们下载数据集,按比例分割成train.jsonl(训练集)和val.jsonl(验证集)两个文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from modelscope.msdatasets import MsDataset
from modelscope.hub.api import HubApi
import json
import random

random.seed(42)

api = HubApi()
api.login('token')

ds = MsDataset.load('xpp011/k12-topSeller', subset_name='default', split='train')
data_list = list(ds)
random.shuffle(data_list)

split_idx = int(len(data_list) * 0.9)

train_data = data_list[:split_idx]
val_data = data_list[split_idx:]

with open('train.jsonl', 'w', encoding='utf-8') as f:
for item in train_data:
json.dump(item, f, ensure_ascii=False)
f.write('\n')

with open('val.jsonl', 'w', encoding='utf-8') as f:
for item in val_data:
json.dump(item, f, ensure_ascii=False)
f.write('\n')

print(f"The dataset has been split successfully.")
print(f"Train Set Size:{len(train_data)}")
print(f"Val Set Size:{len(val_data)}")

SwanLab监控训练

image-20250604171907189

SwanLab 是一款开源、轻量的 AI 模型训练跟踪与可视化工具,面向人工智能与深度学习开发者,提供了一个跟踪、记录、比较、和协作实验的平台,常被称为"中国版 Weights & Biases + Tensorboard"。SwanLab同时支持云端和离线使用,并适配了从PyTorch、Transformers、Lightning再到LLaMA Factory、veRL等30+ AI训练框架。

Doc: https://docs.swanlab.cn/guide_cloud/general/what-is-swanlab.html

训练

目录结构

|— train.py
|— train.jsonl
|— val.jsonl

image-20250604172551093

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import json
import pandas as pd
import torch
from datasets import Dataset
from modelscope import snapshot_download, AutoTokenizer
from transformers import AutoModelForCausalLM, TrainingArguments, Trainer, DataCollatorForSeq2Seq
import os
import swanlab

os.environ["SWANLAB_PROJECT"] = "qwen3-sft-k12"
PROMPT = "你是一位K12教育销售专家,专注于为掌门教育提供家长沟通解决方案。"
MAX_LENGTH = 2048
swanlab.config.update({
"model": "Qwen/Qwen3-1.7B",
"prompt": PROMPT,
"data_max_length": MAX_LENGTH,
})


def dataset_jsonl_transfer(origin_path, new_path):
"""
将原始数据集转换为大模型微调所需数据格式的新数据集
"""
messages = []
# 读取旧的JSONL文件
with open(origin_path, "r") as file:
for line in file:
# 解析每一行的json数据
data = json.loads(line)
input = f"{data['question']}"
output = f"话术:{data['answer']}"
message = {
"instruction": PROMPT,
"input": f"{input}",
"output": output,
}
messages.append(message)
# 保存重构后的JSONL文件
with open(new_path, "w", encoding="utf-8") as file:
for message in messages:
file.write(json.dumps(message, ensure_ascii=False) + "\n")


def process_func(example):
"""
将数据集进行预处理
"""
input_ids, attention_mask, labels = [], [], []
instruction = tokenizer(
f"<|im_start|>system\n{PROMPT}<|im_end|>\n<|im_start|>user\n{example['input']}<|im_end|>\n<|im_start|>assistant\n",
add_special_tokens=False,
)
response = tokenizer(f"{example['output']}", add_special_tokens=False)
input_ids = instruction["input_ids"] + response["input_ids"] + [tokenizer.pad_token_id]
attention_mask = (
instruction["attention_mask"] + response["attention_mask"] + [1]
)
labels = [-100] * len(instruction["input_ids"]) + response["input_ids"] + [tokenizer.pad_token_id]
if len(input_ids) > MAX_LENGTH: # 做一个截断
input_ids = input_ids[:MAX_LENGTH]
attention_mask = attention_mask[:MAX_LENGTH]
labels = labels[:MAX_LENGTH]
return {"input_ids": input_ids, "attention_mask": attention_mask, "labels": labels}


def predict(messages, model, tokenizer):
device = "cuda"
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True
)
model_inputs = tokenizer([text], return_tensors="pt").to(device)
generated_ids = model.generate(
model_inputs.input_ids,
max_new_tokens=MAX_LENGTH,
)
generated_ids = [
output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
return response


# 在modelscope上下载Qwen模型到本地目录下
model_dir = snapshot_download("Qwen/Qwen3-1.7B", cache_dir="/root/autodl-tmp/", revision="master")

# Transformers加载模型权重
tokenizer = AutoTokenizer.from_pretrained("/root/autodl-tmp/Qwen/Qwen3-1.7B", use_fast=False, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained("/root/autodl-tmp/Qwen/Qwen3-1.7B",
torch_dtype=torch.bfloat16)
model.enable_input_require_grads() # 开启梯度检查点时,要执行该方法

# 加载、处理数据集和测试集
train_dataset_path = "train.jsonl"
test_dataset_path = "val.jsonl"
train_jsonl_new_path = "train_format.jsonl"
test_jsonl_new_path = "val_format.jsonl"

if not os.path.exists(train_jsonl_new_path):
dataset_jsonl_transfer(train_dataset_path, train_jsonl_new_path)
if not os.path.exists(test_jsonl_new_path):
dataset_jsonl_transfer(test_dataset_path, test_jsonl_new_path)

# 得到训练集
train_df = pd.read_json(train_jsonl_new_path, lines=True)
train_ds = Dataset.from_pandas(train_df)
train_dataset = train_ds.map(process_func, remove_columns=train_ds.column_names)

# 得到验证集
eval_df = pd.read_json(test_jsonl_new_path, lines=True)
eval_ds = Dataset.from_pandas(eval_df)
eval_dataset = eval_ds.map(process_func, remove_columns=eval_ds.column_names)

args = TrainingArguments(
output_dir="/root/autodl-tmp/output/Qwen3-1.7B",
per_device_train_batch_size=1,
per_device_eval_batch_size=1,
gradient_accumulation_steps=4,
eval_strategy="steps",
eval_steps=100,
logging_steps=10,
num_train_epochs=2,
save_steps=400,
learning_rate=1e-4,
save_on_each_node=True,
gradient_checkpointing=True,
report_to="swanlab",
run_name="qwen3-1.7B",
)

trainer = Trainer(
model=model,
args=args,
train_dataset=train_dataset,
eval_dataset=eval_dataset,
data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),
)

trainer.train()

# 用测试集的前3条,主观看模型
test_df = pd.read_json(test_jsonl_new_path, lines=True)[:3]
test_text_list = []
for index, row in test_df.iterrows():
instruction = row['instruction']
input_value = row['input']
messages = [
{"role": "system", "content": f"{instruction}"},
{"role": "user", "content": f"{input_value}"}
]
response = predict(messages, model, tokenizer)
response_text = f"""
Question: {input_value}
LLM:{response}
"""

test_text_list.append(swanlab.Text(response_text))
print(response_text)

swanlab.log({"Prediction": test_text_list})

swanlab.finish()

监控

其中的训练过程可在Swanlab中查看

image-20250604173954719

image-20250604174004094

运行和上传模型

运行模型

训练中的checkpoint在output目录下

CUDA_VISIBLE_DEVICES=0
swift infer
–model /root/autodl-tmp/output/Qwen3-1.7B/checkpoint-1524
–stream true
–temperature 0
–max_new_tokens 2048

image-20250604173853568

上传模型至modelscope

modelscope login --token “token”

modelscope upload xpp011/K12TopSeller-v1 /root/autodl-tmp/output/Qwen3-1.7B/checkpoint-1524

image-20250604175141225

文章作者:xpp011

发布时间:2025年06月05日 - 10:06

原始链接:http://xpp011.cn/2025/06/05/aa436ebd.html

许可协议: 转载请保留原文链接及作者。