最近qwen又有大动作,发布Qwen3 Embeding系列模型,而且MTEB排行榜上获取多个第一,最重要的还是模型全系列开源。 不得不说qwen可能已经完成rag(Retrieval-Augmented Generation)技术栈的大一统了。
Retrieval部分:
- 语义召回可以使用Qwen3 Embeding
- 召回排序可以使用Qwen3 Reranking
Generation部分:
- 问答生成可以使用Qwen3 Chat
真香啊,接下来笔者会简单介绍一下Qwen3 Embeding系列模型, 同时实战将Qwen3 Embeding的向量模型采用lora的方式微调成一个领域Embeding模型, 让模型在这个领域的语义搜索性能进一步提升。其中涉及到
- Qwen3 Embeding系列模型的架构和训练方法
- 难负样本的挖掘的方案和infoNCEloss的原理
- ms-swift微调Qwen3 Embeding的实战
Qwen3 Embeding系列模型
模型架构

主要有两个系列,一个embeding模型系列,一个Reranking模型系列。
- Embeding模型:以单个文本片段作为输入,通过利用与最终[结束符]([EOS])标记对应的隐藏状态向量来提取语义表示。
- Reranking模型:文本对(例如用户查询与候选文档)作为输入,抽取最后一层的,yes 和 no 这两个token的logit 取log_softmax后 取yes 所在位置的分数作为 score。
ini
token_false_id = tokenizer.convert_tokens_to_ids("no")
token_true_id = tokenizer.convert_tokens_to_ids("yes")
def compute_logits(inputs, **kwargs):
batch_scores = model(**inputs).logits[:, -1, :]
true_vector = batch_scores[:, token_true_id]
false_vector = batch_scores[:, token_false_id]
batch_scores = torch.stack([false_vector, true_vector], dim=1)
batch_scores = torch.nn.functional.log_softmax(batch_scores, dim=1)
scores = batch_scores[:, 1].exp().tolist()
return scores
训练方法

三阶段分层训练机制
- 对比预训练阶段:使用海量弱监督数据打底,增强模型泛化能力
- 精调阶段:采用高质量标注数据进行监督训练,提升任务适配性
- 模型融合阶段:通过集成策略合并多个候选模型,实现性能突破 下面废话不多说,实战微调Qwen3-Embedding-0.6B,将这个通用的embeding模型变成一个农林牧渔领域垂域的embeding模型 Lora微调Qwen3 Embeding模型实战
负样本和loss
负样本的构建
我们都知道,训练模型数据最重要,其中这种embeding和reranker模型,负样本的构建算是重中之重。 这里笔者用sentence_transformer库中自带的 mine_hard_negatives挖掘负样本,调整参数挑选出那些相似但不相关的样本。 数据来自这:农林牧渔中文问答数据

主要采用下方流程: 相似度计算和候选生成: 通过恶embeding模型或者reranker模型 计算查询与语料库的相似度,获取top-k最相似的候选 负样本的挑选:排除正例 ,应用各种筛选条件对负样本进行挑选: absolute_margin: 绝对相似度差异阈值 relative_margin: 相对相似度比例阈值 max_score/min_score: 相似度分数阈值 可以根据向量模型或者reranker的输出值去动态调整这些参数,挑选出合适的负样本。
ini
from datasets import load_dataset
from sentence_transformers import SentenceTransformer
from sentence_transformers.util import mine_hard_negatives
dataset = load_dataset("parquet", data_files="/mnt/d/wsl/work/jupyter/data_hub/Chinese-QA-Agriculture/Chinese-QA-AFAF-train-v2.parquet")
split_dataset = dataset["train"].train_test_split(test_size=0.95, seed=42)
# 输出划分后的数据集信息
print("划分后的数据集:", split_dataset)
print(f"训练集大小: {len(split_dataset['train'])}")
print(f"测试集大小: {len(split_dataset['test'])}")
# 加载小的embeding模型
embedding_model = SentenceTransformer("/mnt/d/wsl/work/jupyter/model_hub/m3e-small")
train_dataset = split_dataset['train']
#挖掘难负样本
hard_train_dataset = mine_hard_negatives(
train_dataset,
embedding_model,
anchor_column_name="prompt",
positive_column_name="response",
num_negatives=5, # How many negatives per question-answer pair
range_min=20, # Skip the x most similar samples
range_max=50, # Consider only the x most similar samples
max_score=0.8, # Only consider samples with a similarity score of at most x
absolute_margin=0.1, # Similarity between query and negative samples should be x lower than query-positive similarity
sampling_strategy="top", # Randomly sample negatives from the range
batch_size=64, # Use a batch size of 4096 for the embedding model
output_format="labeled-list",
use_faiss=True, # Using FAISS is recommended to keep memory usage low (pip install faiss-gpu or pip install faiss-cpu)
)
def convert_format(example):
# 获取正确答案和被拒绝的答案
correct_response = next(resp for resp, label in zip(example['response'], example['labels']) if label == 1)
rejected_responses = [resp for resp, label in zip(example['response'], example['labels']) if label == 0]
return {
"query": example['prompt'],
"response": correct_response,
"rejected_response": rejected_responses
}
# 数据格式转换
transformed_dataset = hard_train_dataset.map(convert_format, remove_columns=hard_train_dataset.column_names)
transformed_dataset.to_json("./data_hub/qwen3_emb.json",force_ascii=False)
最终统计结果

负样本和正样本在m3e这个模型的平均相似度是0.87 和 0.75。
可以看一条筛选出来的样本:确实很多样本和query很相似,但是不相关。
{'response': '绿色环境可以帮助老年人放松紧张的中枢神经,改善和调节身体功能,降低皮肤温度,减少脉搏和呼吸频率,保持血压稳定,减轻心脏负担,提供精神舒适感。这对于冠心病、高血压患者以及身体机能退化的老年人尤为有益。',
'query': '绿色对老年人有哪些健康益处?',
'rejected_response': ['绿茶的主要功效是预防癌症和心血管疾病,还能抗氧化,提高免疫力,抑制和杀灭细菌等;白茶的主要功效有保护脑神经,增强记忆,减少焦虑等。',
'生态养殖可以改善肉、蛋、奶品质,能生产出天然绿色食品。']}
InfoNCE Loss
loss采用的是带有负样本的InfoNCE Loss

其中: q 是 query 的 embedding; r+ 是对应正样本的 embedding,r-是对应负样本的 embedding; sim 是点积(或 cosine) 这个 loss 惩罚 query 跟负样本更相似的情况,鼓励 query 跟正样本相似度更高。 训练实战 数据格式如下:response为正样本,rejected_response为负样本
{'response': '绿色环境可以帮助老年人放松紧张的中枢神经,改善和调节身体功能,降低皮肤温度,减少脉搏和呼吸频率,保持血压稳定,减轻心脏负担,提供精神舒适感。这对于冠心病、高血压患者以及身体机能退化的老年人尤为有益。',
'query': '绿色对老年人有哪些健康益处?',
'rejected_response': ['绿茶的主要功效是预防癌症和心血管疾病,还能抗氧化,提高免疫力,抑制和杀灭细菌等;白茶的主要功效有保护脑神经,增强记忆,减少焦虑等。',
'生态养殖可以改善肉、蛋、奶品质,能生产出天然绿色食品。']}
模型微调
训练脚本
比较重要的三个参数
- --task_type embedding
- --model_type qwen3_emb
- --loss_type infonce
lua
INFONCE_MASK_FAKE_NEGATIVE=true
swift sft \
--model /mnt/d/wsl/work/jupyter/model_hub/Qwen3-Embedding-0.6B \
--task_type embedding \
--model_type qwen3_emb \
--train_type lora \
--dataset /mnt/d/wsl/work/jupyter/data_hub/qwen3_emb.json \
--split_dataset_ratio 0.05 \
--eval_strategy steps \
--output_dir output \
--eval_steps 100 \
--num_train_epochs 1 \
--save_steps 100 \
--per_device_train_batch_size 4 \
--per_device_eval_batch_size 4 \
--gradient_accumulation_steps 4 \
--learning_rate 6e-6 \
--loss_type infonce \
--label_names labels \
--dataloader_drop_last true
训练结果
采用最新的ms-swift的版本,训练脚本如下,可以看到loss在下降,而且正负样例的margin从0.20上升到0.235. 其中 margin = sim(q,r+) - sim(q,r-) ,可以看到模型对正负样本的区分能力显著变强。
eval_loss

eval_loss

eval_neg

eval_pos
