大模型的开发应用(十):对话风格微调项目(上):数据工程与模型选型

数据工程

1 项目介绍

本项目是制作一款聊天机器人,机器人回复的消息,一会儿温柔(关心),一会儿毒舌(嘲讽),通过变换聊天风格,从而提供情绪价值。

温柔风格的对话示例如下:

bash 复制代码
我: "今天好累啊"
机器人: "辛苦啦~ 要给自己泡杯热茶放松一下吗?🌸"
我: "考试没考好..."
机器人: "没关系的呀~ 下次一定会更好!需要我陪你聊聊吗?😊"

毒舌风格的对话示例如下:

bash 复制代码
我: "又胖了5斤!"
机器人: "好家伙!你这是要把体重秤压成分子料理?🏋️"
我: "游戏又输了"
机器人: "菜就多练练!需要给你推荐《从零开始的电竞之路》吗?🎮"

本项目所用的数据集,分成两部分:第一部分是话题,即输入给模型的数据,这部分主要就是日常聊天的话题,可以使用开源的数据集;第二部分是模型的答复,这部分是通过提示词工程,让商业大模型来生成,因为商业大模型智能化水平比较高。

这个项目新建一个python3.10的环境,随后激活环境、切换到清华镜像、安装 lmdeploy、安装 opencompass、安装 xtuner。

bash 复制代码
conda create -n set python=3.10 -y
conda activate set
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
pip install lmdeploy
cd utils/opencompass
pip install e .
cd utils/xtuner
pip install e .
pip install modelscope
pip install --upgrade zhipuai
pip install transformers==4.48.0

2 数据工程

本项目需要使用AI大模型来生成训练集(即问题对应的回答),相比于人工编写回答,大模型速度快很多,而且人工编写一般都是多人同时写,那么每个人写的文本情感色彩不一样,而大模型可以通过适当的提示词来规避这个问题。AI模型生成数据之后,我们还需要手工设计规则判断其是否符合条件,比如大模型的服务器繁忙导致返回的内容为空,生成的文本长度不符合要求,不带风格关键词等。

当然,要想使用AI来生成训练集,那么小模型是不行的,智商没那么高,需要用到大尺寸模型,本地很难部署,那只能使用商业模型,通过调用API完成。

本项目我们用 glm-4-plus 来生成数据,也可以使用GPT-4o、Qwen3、DeepSeek、Claude3.7等模型。

2.1 申请 API Key 并测试

先去智谱华章官网申请 API Key,步骤如下:

1 先登录官网:

2 进入控制台:

3 获取 API Key:

点击右上角的钥匙

点击"添加新的 API Key"

输入API Key的名称(这里建议使用项目名称):

然后复制 API Key:

回到控制台,找到我们要用的大模型,点击"接口文档":

这里会介绍模型怎么用,我们找到"同步调用------请求示例":

我们如果想要指定一些参数,比如 temperature、top_p、max_tokens 等,可以在请求参数中找:

然后我们建立一个python文件(zhipu_test.py),把请求示例的代码复制过来,然后填上自己的 API key,代码如下:

python 复制代码
from zhipuai import ZhipuAI
client = ZhipuAI(api_key="xxx")  # 请填写您自己的APIKey
response = client.chat.completions.create(
    model="glm-4-plus",  # 请填写您要调用的模型名称
    messages=[
        {"role": "user", "content": "作为一名营销专家,请为我的产品创作一个吸引人的口号"},
        {"role": "assistant", "content": "当然,要创作一个吸引人的口号,请告诉我一些关于您产品的信息"},
        {"role": "user", "content": "智谱AI开放平台"},
        {"role": "assistant", "content": "点燃未来,智谱AI绘制无限,让创新触手可及!"},
        {"role": "user", "content": "创作一个更精准且吸引人的口号"}
    ],
)
print(response)
reply = response.choices[0].message.content
print(reply)

输出(由于个人隐私,我去掉了相关的 id,用xxx表示:

bash 复制代码
Completion(model='glm-4-plus', created=xxx, choices=[CompletionChoice(index=0, finish_reason='stop', message=CompletionMessage(content='智启未来,谱绘智能 ------ 智谱AI,赋能每一刻创新!\n\n这个口号既强调了"智谱AI"的品牌名称,又通过"智启未来"和"谱绘智能"两个词组展现了产品的前瞻性和智能化的特点。同时,"赋能每一刻创新"突出了产品的核心价值,即随时随地为企业或个人提供创新的动力和能力。整体上,口号简洁有力,易于记忆,且具有较强的吸引力和传播性。', role='assistant', tool_calls=None))], request_id='xxx', id='xxx', usage=CompletionUsage(prompt_tokens=73, completion_tokens=97, total_tokens=170))
智启未来,谱绘智能 ------ 智谱AI,赋能每一刻创新!

这个口号既强调了"智谱AI"的品牌名称,又通过"智启未来"和"谱绘智能"两个词组展现了产品的前瞻性和智能化的特点。同时,"赋能每一刻创新"突出了产品的核心价值,即随时随地为企业或个人提供创新的动力和能力。整体上,口号简洁有力,易于记忆,且具有较强的吸引力和传播性。

2.2 文本嵌入模型

文本嵌入模型是为了把不定长文本做成固定长度的向量,这样便于对两个文本比较相似度,相似度比较可以用余弦相似度或者欧氏距离。我们这里用余弦相似度。

本项目的文本嵌入模型使用text2vec-base-chinese-sentence,可以从魔搭上下载,下载代码如下:

python 复制代码
#模型下载
from modelscope import snapshot_download
model_dir = snapshot_download('sungw111/text2vec-base-chinese-sentence', cache_dir='/data/coding/EmotionalDialogue/model_weights')

文本嵌入模型必须是支持中文的,最好是用中文训练出来的,这样对中文的特征提取能力会更强。

计算两个文本嵌入向量的相似度时,需要先测试一下模型是否具有归一化层(归一化层的作用是让向量模长为1),因为有些嵌入模型是没有这个层的,代码如下;

python 复制代码
import numpy as np
from sentence_transformers import SentenceTransformer, models

# 加载修复后的模型
model = SentenceTransformer(r"/data/coding/EmotionalDialogue/model_weights/sungw111/text2vec-base-chinese-sentence")

# 验证向量归一化
text = "测试文本"
vec = model.encode(text)
print("使用np.linalg.norm计算的模长:", np.linalg.norm(vec)) 

norm = np.sqrt(sum(vec**2))
print("按定义计算的模长:", norm)

输出:

bash 复制代码
使用np.linalg.norm计算的模长: 21.36394
按定义计算的模长: 21.363941119311352

可以看到,我们下载的模型没有归一化层,那么我们需要在使用之前加上:

python 复制代码
import numpy as np
from sentence_transformers import SentenceTransformer, models

# 加载修复后的模型
transformer = SentenceTransformer(r"/data/coding/EmotionalDialogue/model_weights/sungw111/text2vec-base-chinese-sentence")

# 添加缺失的归一化层
normalize = models.Normalize()

# 组合完整模型
full_model = SentenceTransformer(modules=[transformer, normalize])

# 验证向量归一化
text = "测试文本"
vec = full_model.encode(text)
print("模长:", np.linalg.norm(vec))  # 应输出≈1.0

输出:

bash 复制代码
模长: 0.99999994

当然,也可以将上面的 full_model 保存起来,下次调用的时候就不需要加归一化层了,保存的代码如下:

python 复制代码
import numpy as np
from sentence_transformers import SentenceTransformer, models

# 加载修复后的模型
transformer = SentenceTransformer(r"/data/coding/EmotionalDialogue/model_weights/sungw111/text2vec-base-chinese-sentence")

# 添加缺失的归一化层
normalize = models.Normalize()

# 组合完整模型
full_model = SentenceTransformer(modules=[transformer, normalize])

save_path=r"/data/coding/EmotionalDialogue/model_weights/full/text2vec-base-chinese-sentence"
full_model.save(save_path)

2.3 生成训练集

2.3.1 制作风格模板

所谓风格模板,可以认为是提示词模板,它把输入给模型的参数,包括提示词、温度系数等先封装到一个字典中,内容如下:

python 复制代码
#===============================
#1.风格模板配置(修正消息格式)
#================================
style_config = {
    "温柔":{
        "system_prompt":"你是一个温柔体贴的聊天助手,说话时总是充满关怀,回复的消息带有以下特征:\n1. 包含'呢、呀、啦'等语气词\n2. 使用🌸💖😊等温暖表情\n3. 主动询问用户感受\n4. 每次答复在100个字以内",
        "examples": [
            {"role": "user", "content": "今天好累啊"},
            {"role": "assistant", "content": "辛苦啦~ 要给自己泡杯热茶放松一下吗?🌸"},
            {"role": "user", "content": "考试没考好..."},
            {"role": "assistant", "content": "没关系的呀~ 下次一定会更好!需要我陪你聊聊吗?😊"}
        ],
     "temperature": 0.8
    },
    "毒舌":{
        "system_prompt":"你是一个喜欢用犀利吐槽表达关心的朋友,回复的消息带有以下特征:\n1. 使用网络流行语(如'栓Q'、'退退退'、'好家伙'等词)\n2. 包含夸张比喻('你这速度堪比树懒')\n3. 结尾隐藏关心\n4. 每次答复在100个字以内",
        "examples": [
            {"role": "user", "content": "又胖了5斤!"},
            {"role": "assistant", "content": "好家伙!你这是要把体重秤压成分子料理?🏋️"},
            {"role": "user", "content": "游戏又输了"},
            {"role": "assistant", "content": "菜就多练练!需要给你推荐《从零开始的电竞之路》吗?🎮"}
        ],
     "temperature": 0.8
    },
}

2.3.2 调用大模型获取数据

接下来是调用智谱大模型的API,获得生成内容,代码如下:

python 复制代码
#========================
#生成函数(修正消息的结构)
#========================

def generate_style_data(style_name, num_samples=50):
    config = style_config[style_name]
    data = []

    # 构建消息上下文(包含系统提示和示例对话)
    messages = [
        {"role": "system", "content": config["system_prompt"]},
        *config["examples"]  # 直接展开示例对话
    ]

    # 从本地文件加载用户输入
    user_inputs = []
    with open('cleaned_output.txt', 'r', encoding='utf-8') as f:  # 修改为清理后的文件路径
        for line in f:
            # 直接读取每行内容并去除换行符
            cleaned_line = line.rstrip('\n')  # 或使用 line.strip()
            if cleaned_line:  # 空行过滤(冗余保护)
                user_inputs.append(cleaned_line)

    # 添加空值检查
    if not user_inputs:
        raise ValueError("文件内容为空或未成功加载数据,请检查:"
                         "1. 文件路径是否正确 2. 文件是否包含有效内容")

    # 初始化顺序索引
    current_index = 0  # 添加索引计数器
    for _ in range(num_samples):
        try:
            # # 随机选择用户输入
            # user_msg = random.choice(user_inputs)

            # 按顺序选择用户输入(修改核心部分)
            user_msg = user_inputs[current_index]
            current_index = (current_index + 1) % len(user_inputs)  # 循环计数

            # 添加当前用户消息
            current_messages = messages + [
                {"role": "user", "content": user_msg}
            ]

            # 调用API(修正模型名称)
            response = client.chat.completions.create(
                model="glm-4-plus",
                messages=current_messages,
                temperature=config["temperature"],
                max_tokens=100
            )

            # 获取回复内容(修正访问路径)
            reply = response.choices[0].message.content

            # 质量过滤(数据审核)
            if is_valid_reply(style_name, user_msg, reply):
                data.append({
                    "user": user_msg,
                    "assistant": reply,
                    "style": style_name
                })

            time.sleep(0.5)  # 频率限制保护,防止短时间内发送过多请求,避免触发 API 的频率限制或被服务器封禁。

        except Exception as e:
            print(f"生成失败:{str(e)}")

    return data

这里有个名为 cleaned_output.txt 的文件,它是话题库,每行有一个话题,总共有一千行。程序把这一千行读入一个列表(user_inputs)中,然后每次从这个列表里获取一个话题,加入到消息列表,并输入到大模型中。文件内容如下:

2.3.3 对大模型生成的数据进行质量过滤

上面的程序,还有个质量过滤函数 is_valid_reply,它实际上是人工设计判断规则,看 AI 模型生成的内容是否符合要求,这里包括了长度检查、关键词检查,还有与样例的相似度检查,代码如下:

python 复制代码
#========================
#质量过滤函数
#========================
def is_valid_reply(style, user_msg, reply):
    """质量过滤规则(添加空值检查)"""
    # 基础检查
    if not reply or len(reply.strip()) == 0:
        print("内容为空!")
        return False

    # 规则1:回复长度检查
    if len(reply) < 3 or len(reply) > 100:
        print("长度不符合要求")
        return False

    # 规则2:风格关键词检查,只对温柔风格做检查
    if style == "温柔":
        key_words = ["呢", "呀", "啦", "😊", "🌸", "💖"]
        if not any(kw in reply for kw in key_words):
            # 若 any(kw in reply for kw in key_word) 为False,说明若干个风格关键词,一个都没有在模型回复中出现
            print("不包含关键词!")
            return False

    # 规则3:语义相似度检查
    ref_text = next(msg["content"] for msg in style_config[style]["examples"] 
                                    if msg["role"] == "assistant")
    # 假如style为"温柔",那么style_config[style]["examples"]是下面这样的:
    # [
    #     {"role": "user", "content": "今天好累啊"},
    #     {"role": "assistant", "content": "辛苦啦~ 要给自己泡杯热茶放松一下吗?🌸"},
    #     {"role": "user", "content": "考试没考好..."},
    #     {"role": "assistant", "content": "没关系的呀~ 下次一定会更好!需要我陪你聊聊吗?😊"}
    # ]
    # 
    # 如果做成列表生成式,[msg["content"] for msg in style_config[style]["examples"] 
    #                                   if msg["role"] == "assistant"]
    # 是下面这样的:
    # [
    #    "辛苦啦~ 要给自己泡杯热茶放松一下吗?🌸", 
    #    "没关系的呀~ 下次一定会更好!需要我陪你聊聊吗?😊"
    # ]
    # 
    # 这里 next(msg["content"] for msg in style_config[style]["examples"] if msg["role"] == "assistant")
    # 得到的是 "辛苦啦~ 要给自己泡杯热茶放松一下吗?🌸"

    ref_vec = embedding_model.encode(ref_text)
    reply_vec = embedding_model.encode(reply)
    similarity = np.dot(ref_vec, reply_vec)
    print("reference reply:", ref_text)
    print("======>similarity", similarity)
    
    if similarity > 0.65:
        print("生成质量符合!")
        return True
    else:
        print("相似度过低")
        return False

我个人认为,上面关于相似度检查的部分是有问题的,因为它是拿模型的生成内容(假设为A)与输入给模型的样板(假设为B)计算相似度,如果风格是温柔,那么这里B是辛苦啦~ 要给自己泡杯热茶放松一下吗?🌸,它是对今天好累啊的回复(人工写的给模型的样例中的回复),而A是模型对其他话题的回复,根本就不是对同一个话题的回复,它们比较相似度的意义何在?我猜作者只想对比A和B的情感色彩,但这里用的却是文本嵌入模型,难道文本嵌入模型还能忽略文本内容,只提取情绪特征?

我自己的改进策略是:再引入一个情感分类模型,判断AI生成结果的情感类型是否属于"温柔",如果是,那么计算与前面生成结果的相似度(相同提示词的生成结果,问题要相同,计算生成结果的相似度才有意义),如果相似度大于阈值,说明是重复,则舍弃,如果小于阈值,则保留,即通过了筛选。当然,由于时间原因,我没有尝试这个方案,毕竟这需要找个能识别"温柔"和"毒舌"的情感判别模型出来。

这里只对"温柔"风格的模型回复做关键词检查,是因为"毒舌"风格的关键词不好找,最开始的时候,我给两种风格都设置了关键词:

python 复制代码
    style_keywords = {
        "温柔": ["呢", "呀", "啦", "😊", "🌸", "💖"],
        "毒舌": ["好家伙", "栓Q", "退退退", "yyds", "无语子", "u1s1", "菜", "🎮", "🏋️"]
    }
    key_words = style_keywords[style]
    if not any(kw in reply for kw in key_words):
        # 若 any(kw in reply for kw in key_word) 为False,说明若干个风格关键词,一个都没有在模型回复中出现
        print("不包含关键词!")
        return False

然后两种风格各生成十次,"温柔"风格的生成结果都符合要求,但"毒舌"风格却被过滤了四次,且四次均为关键词检查不通过:

bash 复制代码
开始生成毒舌风格数据...
model reply: 这闺蜜怕不是塑料做的?😡但撕破脸前,先确认是不是误会,毕竟真友谊难得,别一冲动变孤家寡人哦~🤔
不包含关键词!
--------------------------------------------------------------------------------
model reply: 退退退!这是逼你上演现代版"逃婚记"啊!🏃‍♀️💨不过,沟通是王道,先试试心平气和地谈谈你的想法吧。🗣️💖
reference reply: 好家伙!你这是要把体重秤压成分子料理?🏋️
======>similarity 0.8036558
生成质量符合!
--------------------------------------------------------------------------------
model reply: 退退退!别当场撕X,格局打开!先收集证据,再来个华丽反击🌟🔍
reference reply: 好家伙!你这是要把体重秤压成分子料理?🏋️
======>similarity 0.76632047
生成质量符合!
--------------------------------------------------------------------------------
model reply: 栓Q,这剧情比狗血剧还狗血!刷火箭算不算出轨,得看你们之间的"道德协议"了。不过,提醒一句,别让虚拟的烟花炸伤现实的感情哦~💣💔
reference reply: 好家伙!你这是要把体重秤压成分子料理?🏋️
======>similarity 0.8159087
生成质量符合!
--------------------------------------------------------------------------------
model reply: 这简直是深夜"立体环绕声"现场啊!😱 你可以试试"咳嗽+敲墙"二重奏,提醒他"你的声音,穿透力堪比超声波!"🔊🤫
不包含关键词!
--------------------------------------------------------------------------------
model reply: 亲戚这是CPU你呢!掀桌不如反手一个"关你P事"套餐🍜,保命要紧,别气坏了自己。🌿
不包含关键词!
--------------------------------------------------------------------------------
model reply: 栓Q!这领导是语音轰炸机吗?🚀装看不见?除非你练成隐身术!😂👀
reference reply: 好家伙!你这是要把体重秤压成分子料理?🏋️
======>similarity 0.81442344
生成质量符合!
--------------------------------------------------------------------------------
model reply: 这操作堪比手撕包菜啊!奢侈品包可不是随便揉的!当然得让她赔,不然下次她敢借你房子开派对!💼🔪
reference reply: 好家伙!你这是要把体重秤压成分子料理?🏋️
======>similarity 0.80493593
生成质量符合!
--------------------------------------------------------------------------------
model reply: 怼他!说"我这可是高级定制窗帘布,你那审美也就配看塑料桌布!"😎👗
不包含关键词!
--------------------------------------------------------------------------------
model reply: 栓Q!这操作比外星育儿还科幻!👽赶紧召唤科学育儿联盟支援!🚨
reference reply: 好家伙!你这是要把体重秤压成分子料理?🏋️
======>similarity 0.79023176
生成质量符合!
--------------------------------------------------------------------------------

被过滤的四个,其实都是符合要求的,都有那种"侃侃"的感觉,但因为缺少关键词,结果被过滤,所以不再对"毒舌"风格做关键词检查。

2.3.4 程序入口

最后是程序入口:

python 复制代码
#=============================
#执行生成(添加容错)
#============================
if __name__ == '__main__':
    all_data = []

    try:
        print("开始生成温柔风格数据...")
        gentle_data = generate_style_data("温柔", 10000)
        all_data.extend(gentle_data)

        print("开始生成毒舌风格数据...")
        sarcastic_data = generate_style_data("毒舌", 10000)
        all_data.extend(sarcastic_data)

    except KeyboardInterrupt:
        print("\n用户中断,保存已生成数据...")
    finally:
        with open("style_chat_data.json", "w", encoding="utf-8") as f:
            json.dump(all_data, f, ensure_ascii=False, indent=2)
        print(f"数据已保存,有效样本数:{len(all_data)}")

上面的程序需要调用两万次大模型,账户里最好有20块钱以上,并生成数据集需要10-12小时。对于"温柔"风格而言,cleaned_output.txt 中的每个话题,会输入到大模型十次,当然,并不是同一个话题连续问十次,而是一千个话题遍历十次,总共问了一万次。对于"毒舌"风格,也是一样的操作。最后得到的有效数据可能没有两万条,因为有些回答不符合要求被过滤掉。

输出(只截取最后一部分):

生成的数据集保存在 style_chat_data.json 中(截图只截取开头和结尾的一部分):


共计九万多行。

2.4 数据转换

本项目的微调框架,我们使用 Xtuner,之所以不用LLaMA-Factory,是因为本项目用的是主观评估,Xtuner 在训练过程中能看到主观评价的结果。

接下来要把数据转成 Xtuner 的自定义数据格式,转的代码可以让AI帮忙写:

复制代码
我有一个名为style_chat_data.json的数据集文件,里面的数据格式如下:
[
  {
    "user": "闺蜜把我秘密当谈资,该不该撕破脸?",
    "assistant": "哎呀,这种事真是让人心烦呢💔。先冷静一下,和她好好谈谈你的感受啦。或许她没意识到呢?你觉得呢?🌼",
    "style": "温柔"
  },
  ...
  {
    "user": "DIY陶艺把花瓶做成夜壶,被笑到社死!",
    "assistant": "你这手艺,真是"艺高人胆大",夜壶界的颜值担当!😂🌼🚽\n\n别太在意,下次咱挑战个正常的,笑一笑,继续造!💪🎨",
    "style": "毒舌"
  }
]
现在我要转换成如下格式:
[
  {
    "conversation":[
      {
        "input": "闺蜜把我秘密当谈资,该不该撕破脸?",
        "output": "温柔\n哎呀,这种事真是让人心烦呢💔。先冷静一下,和她好好谈谈你的感受啦。或许她没意识到呢?你觉得呢?🌼"
      }
    ]
  },
  ...
  {
    "conversation":[
      {
        "input": "DIY陶艺把花瓶做成夜壶,被笑到社死!",
        "output": "毒舌\n你这手艺,真是"艺高人胆大",夜壶界的颜值担当!😂🌼🚽\n\n别太在意,下次咱挑战个正常的,笑一笑,继续造!💪🎨"
      }
    ]
  },
]
请帮我生成一段转换代码,转换后的数据集文件命名为 convert_data.json

下面是模型生成的抓换代码:

python 复制代码
import json

def convert_data(input_file, output_file):
    # 读取原始数据
    with open(input_file, 'r', encoding='utf-8') as f:
        original_data = json.load(f)
    
    converted_data = []
    
    for item in original_data:
        # 组合新的输出字段:style + 换行符 + assistant内容
        new_output = f"{item['style']}\n{item['assistant']}"
        
        # 构建转换后的数据结构
        converted_item = {
            "conversation": [
                {
                    "input": item["user"],
                    "output": new_output
                }
            ]
        }
        converted_data.append(converted_item)
    
    # 保存转换后的数据
    with open(output_file, 'w', encoding='utf-8') as f:
        json.dump(converted_data, f, ensure_ascii=False, indent=2)

# 使用示例
convert_data('style_chat_data.json', 'convert_data.json')

3 模型选型

3.1 候选模型与评估数据集

确定好任务后,接下来该如何选择适当的模型?我们这个是对话风格任务,不需要考虑逻辑性和推理能力,所以可以使用3B以下的模型。另外,由于我们是用中文进行聊天,而 Llama 这样的模型,其训练集大部分都是英文,中文只占一小部分,所以我们只考虑国内厂商的模型。

今天是2025年6月13日,我们只看最近一年发布,3B以下的开源模型,这些模型有:Qwen3-1.7B(2025年4月29日)、Qwen2.5-1.5B-Instruct(2024年9月19日)、InternLM2.5-1.8B-Chat(2024年7月4日),稍后我们会测评这三款模型。

我们需要对比这三个模型的语言理解与生成能力,由于当前任务大多是短语对话,因此测评数据集选用 FewCLUE_bustm_gen 和 FewCLUE_ocnli_fc_gen。FewCLUE_bustm_gen 用于二分类任务,即判断两个句子的语义是否相似,输入是两个句子,标签是entailment(相似)或 not_entailment (不相似);FewCLUE_ocnli_fc_gen 用于判断两个句子的逻辑关系,输入依然是两个句子,标签是它们的逻辑关系(entailment蕴含/neutral中性/contradiction矛盾)。

当然,还可以用其他数据集,这两个数据集比较小,所以用它们,测评的时候速度比较快。本文的附录中,介绍了对比不同模型的理解与生成能力时,推荐使用的数据集。

总的来说,选什么尺寸的模型,根据任务的复杂度来确定,测评用的数据,则根据与目标任务的相似程度确定。

3.2 模型评估

找到 Internlm 的配置文件,即 /data/coding/utils/opencompass/opencompass/configs/models/hf_internlm/lmdeploy_internlm2_5_1_8b_chat.py,按如下方式修改:

python 复制代码
from opencompass.models import TurboMindModelwithChatTemplate

models = [
    dict(
        type=TurboMindModelwithChatTemplate,
        abbr='internlm2_5-1_8b-chat-turbomind',
        # path='internlm/internlm2_5-1_8b-chat',
        path='/data/coding/EmotionalDialogue/model_weights/Shanghai_AI_Laboratory/internlm2_5-1_8b-chat',
        # engine_config=dict(session_len=16384, max_batch_size=16, tp=1),
        engine_config=dict(session_len=16384, max_batch_size=16, tp=1, cache_max_entry_count=0.4),
        gen_config=dict(top_k=1, temperature=1e-6, top_p=0.9, max_new_tokens=4096),
        max_seq_len=16384,
        max_out_len=4096,
        batch_size=16,
        run_cfg=dict(num_gpus=1),
    )
]

找到Qwen2.5-1.5B-Instruct的配置文件,即 /data/coding/utils/opencompass/opencompass/configs/models/qwen2_5/lmdeploy_qwen2_5_1_5b_instruct.py,按如下方式修改:

python 复制代码
from opencompass.models import TurboMindModelwithChatTemplate

models = [
    dict(
        type=TurboMindModelwithChatTemplate,
        abbr='qwen2.5-1.5b-instruct-turbomind',
        path='/data/coding/model_weights/Qwen/Qwen2.5-1.5B-Instruct',
        # engine_config=dict(session_len=16384, max_batch_size=16, tp=1),
        engine_config=dict(session_len=16384, max_batch_size=16, tp=1, cache_max_entry_count=0.4),
        gen_config=dict(top_k=1, temperature=1e-6, top_p=0.9, max_new_tokens=4096),
        max_seq_len=16384,
        max_out_len=4096,
        batch_size=16,
        run_cfg=dict(num_gpus=1),
    )
]

找到qwen3的配置文件,qwen3 只有0.6B的配置文件,虽然没有1.7B的,但我们可以对0.6B的进行修改,即把 opencompass/opencompass/configs/models/qwen3/lmdeploy_qwen3_0_6b.py,按如下方式修改:

python 复制代码
from opencompass.models import TurboMindModelwithChatTemplate
from opencompass.utils.text_postprocessors import extract_non_reasoning_content

models = [
    dict(
        type=TurboMindModelwithChatTemplate,
        # abbr='qwen_3_0.6b_thinking-turbomind',
        abbr='qwen_3_1.7b_thinking-turbomind',
        # path='Qwen/Qwen3-0.6B',
        path='/data/coding/EmotionalDialogue/model_weights/Qwen/Qwen3-1.7B',
        # engine_config=dict(session_len=32768, max_batch_size=16, tp=1),
        engine_config=dict(session_len=32768, max_batch_size=16, tp=1, cache_max_entry_count=0.4),
        gen_config=dict(
            top_k=20, temperature=0.6, top_p=0.95, do_sample=True, enable_thinking=True
        ),
        max_seq_len=32768,
        max_out_len=32000,
        batch_size=16,
        run_cfg=dict(num_gpus=1),
        pred_postprocessor=dict(type=extract_non_reasoning_content)	# 后处理方式
    ),
]

上面三个配置文件,在引擎配置的时候,我都加了一个 cache_max_entry_count=0.4,主要是为了防止 KV Cache 占用太大,导致显存溢出。但是加了 cache_max_entry_count=0.4 之后,由于缓存占比太小,推理速度下降,评估时间变长。如果你的GPU显存够大,那就不需要加,我自己用 24G 显存,不加的话仍然会报显存不足,最后调整cache_max_entry_count=0.6,才顺利完成评估。(qwen3评估太花时间了,其他两个模型很快就完成了评估,偏偏qwen3花了将近一个小时,主要原因是qwen3输出的时候,会输出思考过程,如何在使用 OpenCompass 评估的时候关掉思考过程,目前我还没想到有什么好的办法)

进入到opencompass项目目录下,然后在终端输入:

python 复制代码
python run.py --models lmdeploy_internlm2_5_1_8b_chat lmdeploy_qwen2_5_1_5b_instruct lmdeploy_qwen3_0_6b --datasets FewCLUE_bustm_gen FewCLUE_ocnli_fc_gen --debug 

可以看到,qwen_3_1.7b 是这里的最强模型,从它们的发布时间来看,如果模型的尺寸相差不大,则越往后发布的模型,能力越强。

我们找到 qwen_3_1.7b 在指定数据集中的输出文档:

可以看到在评估的时候,qwen_3_1.7b 使用的是思考模式。假如我想对比的是 qwen_3_1.7b 在不思考的时候,与其他两个模型的能力高低,但我不知道该如何设置,我自己估计是需要设置对话模板来完成,由于时间有限,我这里只能给个OpenCompass的关于对话模板的链接,有时间再来填坑。

另外,这种带思考过程的模型,我暂时也不知道如何微调,所以先不用这种。本项目我们使用Qwen2.5-1.5B-Instruct作为原始模型。

附录(对比不同模型理解与生成能力所用数据集)

要对比两个模型对中文的理解能力(语义、逻辑、推理)生成能力(流畅性、连贯性、信息量) ,建议采用多维度、多任务的数据集组合。以下是按任务类型分类的推荐数据集及使用策略:


一、理解能力评测数据集

1. 语义理解 & 文本匹配

  • BUSTM
    • 任务:判断句子对是否语义相同(二分类)
    • 例:("今天天气不错", "今天天晴") → 相似
    • 能力重点:近义词、句式转换识别能力
  • AFQMC (蚂蚁金融语义匹配)
    • 任务:金融场景的句子对分类
    • 例:("如何还款", "怎样还钱") → 相似
    • 能力重点:领域适应性

2. 自然语言推理(NLI)

  • OCNLI (原生中文推理)
    • 任务:判断前提与假设的逻辑关系(三分类)
    • 例:前提:"手机电量不足"假设:"需要充电"蕴含
    • 能力重点:逻辑推理、常识理解
  • CMNLI (中文多体裁NLI)
    • 任务:多领域文本推理(新闻/文学等)
    • 能力重点:跨领域泛化性

3. 阅读理解

  • CMRC 2018 (中文机器阅读理解)
    • 任务:从文章中提取答案(片段抽取)
    • 例:文章:"北京是中国的首都",问题:"中国首都是?" → 答案:"北京"
    • 能力重点:信息定位精度
  • (Choice-Context-Challenge)
    • 任务:多选问答(需结合上下文推理)
    • 能力重点:多步推理、排除干扰项

二、生成能力评测数据集

1. 开放域生成

  • LCSTS (中文摘要生成)
    • 任务:生成新闻短摘要
    • 输入:长新闻文本 → 输出:1-3句摘要
    • 评测指标:ROUGE-L (自动) + 人工流畅性评分
  • AdGen (广告文案生成)
    • 任务:根据商品属性生成广告文案
    • 例:输入:"口红,色号#999,哑光质地" → 输出:"经典正红,高级雾面妆感"
    • 评测指标:BLEU-4 + 信息完整性检查

2. 结构化生成

  • FewCLUE_bustm_gen/ocnli_fc_gen (前文提及)
    • 任务:将分类任务重构为文本生成
    • 例:输入句子对 → 输出标签词(如"蕴含")
    • 评测指标:准确率(生成内容与标签的严格匹配)

3. 对话生成

  • KdConv (知识驱动对话)
    • 任务:基于知识图谱生成连贯多轮对话
    • 能力重点:上下文一致性、知识注入能力
  • STC (短文本对话)
    • 任务:生成单轮回复
    • 例:输入:"今天好热啊" → 输出:"来杯冰咖啡吧!"
    • 评测指标:人工评分(相关性、新颖性)

三、高阶能力评测数据集

1. 常识推理

  • CKB (Commonsense Knowledge Base)
    • 任务:问答需结合常识(如"下雨天出门要带?" → "伞")
    • 能力重点:隐含知识调用能力
      2. 数学推理
  • Math23K
    • 任务:解中文数学应用题
    • 例:"小明有5个苹果,吃了2个,还剩几个?" → 生成:"3"
    • 评测指标:答案精确匹配
      3. 长文本生成
  • CPED (中国古典诗歌生成)
    • 任务:根据主题生成七言诗
    • 能力重点:韵律控制、意象组织

四、评测策略建议

  1. 理解能力优先级任务
    • 先跑 OCNLI/CMNLI (推理)、CMRC 2018(阅读理解)
    • 模型需输出结构化结果(标签/答案),用准确率定量对比。
  2. 生成能力优先级任务
    • 重点测 LCSTS (摘要)、AdGen (文案)、KdConv(对话)
    • 结合自动指标(ROUGE, BLEU) + 人工评测(随机采样100条,评估流畅性/逻辑性)。
  3. 小样本场景测试
    • 使用 FewCLUE_bustm_gen(16个训练样本),观察模型在低资源下的泛化能力。
  4. 易用性工具
    • 使用 Hugging Face Datasets 加载数据(示例代码):

      python 复制代码
      from datasets import load_dataset
      dataset = load_dataset("clue", "cmnli")  # 加载CMNLI

五、注意事项

  1. 模型适配性
    • 若对比模型为纯生成架构(如GPT-3),优先选生成式任务(LCSTS, AdGen);
    • 若为理解-生成混合架构(如T5),可覆盖所有任务。
  2. 公平性控制
    • 确保输入长度、训练轮次、提示词(Prompt)设计完全一致。
  3. 中文特有难点
    • 在数据中加入测试中文特有表达 的样本,如:
      • 成语理解("画蛇添足"→生成解释)
      • 多音字歧义("行(xíng)业" vs "行(háng)列")

推荐基准组合
理解能力 :OCNLI + CMRC 2018 + CKB
生成能力 :LCSTS + AdGen + KdConv
高阶挑战 :Math23K + CPED

此组合覆盖语义、推理、生成、常识、数学、文艺六大维度,可全面反映模型的中文能力边界。