【多模态】天池多模态比赛-多模态对话系统意图识别挑战赛

1. 前言

  正好有一个天池的多模态比赛,WWW2025多模态对话系统意图识别挑战赛,借这个比赛学习了一下minicpm/qwen/qwenvl/internvl系列模型使用,虽然最后分数不高(任务一0.85,任务二0.81),也有一些方法还没来得及尝试,记录一下过程。

2. 赛题

  具体可以参考赛事介绍,简而言之,这个比赛有2个任务,每个任务对应不同的数据集。训练集有1000条样本,其中任务一有300条,任务二有700条。线上测试集10000条无标签数据,每个任务各5000条。

2.1 任务一——用户对话意图识别

  这个任务是对用户和客服的对话内容进行分类,判断用户对话意图(或者说对这个对话进行分类)。例如,总共有20多个类别,要把对话分类到其中一个类别,前几个类别是:
在这里插入图片描述  用户和客服的对话内容里面有可能有图片,有可能没有图片,有可能有多张图片,例如一条label="商品材质"的对话如下,可以看到这个应该是用户和一个智能客服的对话(也可能后期切了人工),但是智能客服是不能识别图片的,如果用户传了图,会要求用户文字解释/进了人工客服/直接结束对话了。

[label]商品材质
<用户与客服的对话 START>
用户: <image>
客服: 抱歉哦,小富目前还不能识别图片,您可以简单描述一下遇到的问题吗?/:068~~
用户: <http>
客服: 若您仍有疑问,可回复“客服”以便进一步协助您~
用户: 内胆使用的是316不锈钢吗
客服: 亲爱的,升级版的抗菌不锈钢是在304不锈钢的基础上改进的,它有抗菌功能(其内胆表面能够释放具有抑菌作用的铜离子,有效阻止大肠杆菌和金黄色葡萄球菌等有害细菌的生长);不过它的抗酸碱和抗腐蚀性能比不上316不锈钢;而316不锈钢在抗腐蚀和抗酸碱方面表现更佳,但没有抗菌功能。  ▲对于居住在海边或喜欢储存各种饮品的人来说,推荐使用316不锈钢;如果是为了儿童使用,建议选择抗菌不锈钢;追求性价比的话,304不锈钢是不错的选择;/:810
<用户与客服的对话 END>

2.2 任务二——对用户上传的图片进行分类

  这个任务是图片分类任务,输入是图片,输出是类别,总共有二十多类别,前几个类别是:
在这里插入图片描述

3. baseline

  看了下任务之后,先用minicpm-v-2.6跑了个baseline,任务一+任务二的分数0.77,改成qwen2-vl-7b之后,任务一+任务二的分数达到0.79,换了internvl2.5_8b之后达到0.80。分析任务有几个发现:

  • 任务一和图片没有太大关系,可以试一下拆分任务,一个模型只做一个任务
  • 任务一的数据集有一些问题,尤其是标注手势上
  • 任务二的数据集似乎也不干净

4. 数据标注检查——任务一数据

  一开始是打算都审视一遍任务一和任务二的数据集,但是时间没腾出来,任务一的300条训练集全部过了一遍用于作为训练集,任务二的只看了一小部分最后没用上。
  在任务一的训练集中,二十多个类别,发现有的数据标注错误(占比可能接近10%),以及存在一些某种情况下的妥协标注(不确定是否写入SOP作为标注手势,训练集和测试集标注是否是多人一致的标注,这两份数据手势是否一致),这些问题就导致了"准确率"的天花板本身就不会是100%。数据里面存在2种情况:(1)无法标注;(2)标注错误;(3)特殊手势。

4.1 无法标注

  无法标注的是,从图片还有文本完全看不出来和label有什么关联性emmmm,例如下面这条,用户说’这是一些电源’,应该是在图片里提供了一系列规格的电源,标注的label是"商品规格",没有客服回答,也只有一张图,很抽象,测试集里面应该也有这样的问题。

[label]商品规格
<用户与客服的对话 START>
用户: <http>
客服: 亲爱的,您有什么疑问可以直接告诉我哦~ 比如说关于【物流时间】或者【店铺促销】等方面的问题。用户: <image>
客服: 亲爱的,我在这儿,不过只有一个图片信息,不太明白您的问题,能否用文字具体说明一下,比如“发货日期”、“店铺促销”等。
用户: 这是一些电源
客服: 
<用户与客服的对话 END>

4.2 特殊手势

  有一些非常特殊的手势,文本和图片数据集里面都看到有,这些手势应该是相对一致的,训练集和测试集应该是相同的标注手势,不然就emmm
  举一个例子,例如下面的对话里面用户和客户对话里面命中了多个意图标签,最后一问一答里面用户问’成效如何呢’,这种情况下结合最后两句用户和客服的话,最接近’是否好用’标签,所以做这样的归类。这应该是一个手势,看到了好几个这样的归类方法,命中多个时看最后一问一答:

[label]是否好用
<用户与客服的对话 START>
用户: 收到的货物数量不足
客服: 亲爱的顾客,我们店铺的商品由多个厂家直接供应,如果购买的商品较多,可能会分开包装发货,具体情况请参考订单中的包裹详情哦~~  ✿您可以在【个人中心-我的订单-选择相应订单-查看物流信息】中检查是否为分批发货,如果是分批发货,页面会显示拆分后的包裹数量及对应的物流追踪号。 ❤如果所有包裹都已收到,但仍有商品缺失,您可以进入退款页面申请“部分退款”,并上传全部商品的照片、外包装的照片以及清晰显示物流单号的快递单照片,我们会进一步核实处理哦~
用户: 发送错误了
客服: 亲爱的顾客,非常抱歉给您带来了不佳的购物体验。如果收到的商品与您下单时选择的不符,请您通过订单页面申请退货退款或仅退款。请上传【商品内部照片、商品包装照片以及清晰显示运单号的快递单照片】,并详细说明您的需求,我们的客服人员会尽快处理。经确认确实发错货后,退货运费将由商家承担,您可以在退款页面留言告知运费金额。
用户: <image>
客服: 亲爱的顾客,非常抱歉给您带来了不佳的购物体验。如果收到的商品与您下单时选择的不符,请您通过订单页面申请退货退款或仅退款。请上传【商品内部照片、商品包装照片以及清晰显示运单号的快递单照片】,并详细说明您的需求,我们的客服人员会尽快处理。经确认确实发错货后,退货运费将由商家承担,您可以在退款页面留言告知运费金额。
用户: 87元一盒的是含有十片吗?
客服: 亲爱的,为了便于您挑选商品,您可以查看页面中的商品详情哦~ 路径如下:从【商品页】→【详情】中查看。
用户: 并非此款
客服: 
用户: 并非此款
客服: 
用户: 成效如何呢?
客服: 亲爱的,这些商品质量都很棒,您可以查看商品详情页或阅读其他买家的评价来了解更多哦~~
<用户与客服的对话 END>

  在任务二图片数据集里面也有类似的情况,例如有一个类别叫“支付页面”,还有几个类别叫物流页面- XXX,但是显示菜鸟裹裹寄件的页面,有已付金额显示的,统一归类到"支付页面"类别,而不是其它类别,如果没看训练集真没想到会有这样的手势。。。。
在这里插入图片描述

4.3 标注错误

  图片的没怎么看所以不确定,文本的看完了里面有一些标注错误的问题,例如下面的按照定义和其它类别的样本手势,类似的问如何使用的应该统一归类到"用法用量",问客服要说明书咨询功能特性的才归类到"功效功能"

[label]功效功能
<用户与客服的对话 START>
用户: <image>
客服: 
用户: 这东西怎么用
客服: <用户与客服的对话 END>

5. 任务拆分

  VLM模型的文本能力通常会下降,尝试把任务一用小的纯文本模型LLM来做,任务二模型只做图片任务用VLM。

5.1 任务一——LLM

  这里尝试了qwen2.5系列的1.5B/3B/7B,尝试了lora、一半的参数微调以及全参数微调,测下来lora效果最好,可能因为数据少微调参数越多越容易过拟合。

5.1.1 模型训练

  训练数据使用前面步骤过滤后的277条数据,同时还有标注的推理链条数据(为什么某段对话被分类为xxx),这个推理链条也可以用更大的模型给定对话、指定标签的情况下来生成的,或者让大的模型做COT,当回答对的时候采信这条。
  训练这里使用huggingface的API来,没用swift3,效果最好的是qwen2.5_3B。

####################################
# 1. 模型下载
from modelscope import snapshot_download
model_dir = snapshot_download('Qwen/Qwen2.5-3B-Instruct',cache_dir='/data/coding/llm_model/')

#####################################
## 2. 训练数据准备
import pandas as pd
import json
import numpy as np
from datasets import Dataset

import pandas as pd
import json
import numpy as np

# train data file_template
with open('train.json', 'r',encoding='utf-8') as file:
    train_json = json.load(file)
train_df = pd.DataFrame(train_json)
# 归类一下到底需要识别什么
train_df['task_category'] = train_df['instruction'].apply(lambda x: 'content' if '电商客服专家' in x else 'image')

content_df = train_df[train_df['task_category']=='content'].reset_index(drop=True)

exp_df = pd.read_csv('case分析_part5.csv',sep='\t')  # 这个是case分析后的数据
exp_df['if_use'] = exp_df['if_false'].apply(lambda x: 1 if 'ok' in x else 0)  # 只使用标签打对的数据
exp_df = exp_df[exp_df['if_use']==1].reset_index(drop=True)
exp_df = pd.merge(exp_df,content_df,on='id',how='inner')
content_df = pd.merge(content_df,exp_df[['id']],on='id',how='inner')

merge_df = pd.merge(content_df,exp_df[['id','explanation']],on='id',how='inner')  # 自己标注了一些explanation,训练部分没有使用

query = """你是一个电商客服专家,请分析以下用户与客服的对话,判断用户的实际意图,从下面列出的标签范围中选择出最符合的一个,直接回答该标签名,不要解释或评论。
其中意图分类标签的标签范围为:
['反馈密封性不好','是否好用','是否会生锈','排水方式','包装区别','发货数量','反馈用后症状','商品材质','功效功能','是否易褪色','适用季节','能否调光','版本款型区别','单品推荐','用法用量','控制方式','上市时间','商品规格','信号情况','养护方法','套装推荐','何时上货','气泡']。

用户与客服的对话为:"""

label_list = ['反馈密封性不好','是否好用','是否会生锈','排水方式','包装区别','发货数量','反馈用后症状','商品材质','功效功能','是否易褪色','适用季节','能否调光','版本款型区别','单品推荐','用法用量','控制方式','上市时间','商品规格','信号情况','养护方法','套装推荐','何时上货','气泡']

sft_query_list = []
sft_answer_list = []
for i in range(0,merge_df.shape[0]):
    conversation_list = merge_df.loc[i]['instruction'].replace('image','picture').replace('\t','  ').split('\n')
    conversation_list[0] = ''
    conversation_list[-2] = ''
    instruction = '\n'.join(conversation_list[:-2])
    pos_label = merge_df.loc[i,'output']

    # 正例
    prompt = query + instruction + '\n请问最符合对话的标签名称是什么?请直接回答该标签名,不要解释或评论。'
    answer = pos_label
    sft_query_list.append(prompt)
    sft_answer_list.append(answer)
sft_df1 = pd.DataFrame({'prompt':sft_query_list,'answer':sft_answer_list})
train_dataset = Dataset.from_pandas(sft_df1)

#################################
## 3. 原始模型加载
from modelscope import AutoModelForCausalLM, AutoTokenizer
import torch

model_name = "/data/coding/llm_model/Qwen/Qwen2___5-3B-Instruct"

model = AutoModelForCausalLM.from_pretrained(
    model_name, torch_dtype="auto", device_map="auto",trust_remote_code=True,attn_implementation='flash_attention_2'
)
tokenizer = AutoTokenizer.from_pretrained(model_name,trust_remote_code=True)
tokenizer.padding_side='left'



#########################
# 4.预处理数据集
def preprocess_function_test(examples):
    return tokenizer(examples['prompt'], padding=True, truncation=True, max_length=4500)

def process_func_train(example):
    MAX_LENGTH = 4500
    instruction = tokenizer(example['prompt'])
    response = tokenizer(example["answer"]+tokenizer.eos_token)
    
    input_ids = instruction["input_ids"] + response["input_ids"]
    attention_mask = instruction["attention_mask"] + response["attention_mask"]
    labels = [-100] * len(instruction["input_ids"]) + response["input_ids"]
    
    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 collate_fn(examples):
    input_ids = [example['input_ids'] for example in examples]
    attention_mask = [example['attention_mask'] for example in examples]

    # 批量填充
    return {
        'input_ids': torch.tensor(input_ids, dtype=torch.long).to(model.device),
        'attention_mask': torch.tensor(attention_mask, dtype=torch.long).to(model.device)
    }

encoded_train_dataset = train_dataset.map(process_func_train)

##########################
# 5. 准备lora
from peft import LoraConfig, TaskType, get_peft_model
lora_config = LoraConfig(
            init_lora_weights="gaussian",
            task_type=TaskType.CAUSAL_LM,
            target_modules=["q_proj","k_proj", "v_proj", "o_proj"],
            r=8,
            lora_alpha=32,
            lora_dropout=0.1,
            inference_mode=False,
        )

lora_model = get_peft_model(model, lora_config)

# 打印可训练参数
lora_model.print_trainable_parameters()

############################
# 6. 定义训练参数,开始训
import torch
from datasets import load_dataset
from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments, Trainer
from transformers import DataCollatorForLanguageModeling, PreTrainedTokenizerBase

training_args = TrainingArguments(
    output_dir="/data/coding/qwen_3b/",
    per_device_train_batch_size=1,
    max_steps=277*7,
    max_grad_norm=1.0,
    learning_rate=5e-5,
    logging_steps=50,
    logging_dir='./logs',
    save_strategy="steps",
    save_steps=277*7,
    save_total_limit=1,
    push_to_hub=False
)

# 7. 定义Trainer
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=encoded_train_dataset
)

# 8. 开始训练
trainer.train()

5.1.2 模型推理

  推理时用了一些提示词工程的方法,首先是提示词上给一些规则,尤其是标注数据中发现的手势规则,写到提示词里面:

意图分类时有一些规则需要遵守:
<rule>
规则1:如果用户有多个提问,并且多个提问命中了以上的不同标签,这种情况下请关注用户的最后的提问和客服的回答进行判断。
规则2"单品推荐""套装推荐"容易混淆,请关注用户描述和客服回答的商品的单位,如果是"这个""这款"很有可能是单品,如果是说"套""系列"很有可能是套装。
规则3"包装区别""版本款型区别"容易混淆,重点关注是否提到包装,提到包装的优先分类为"包装区别"。
规则4"控制方式""用法用量"容易混淆,提到了"遥控器""控制"需要优先分类为"控制方式"而不是"用法用量",如果提到了"说明书""用户指南"或者"操作手册"则更可能应该分类为"用法用量"。
规则5"是否好用""功效功能"容易混淆,如果用户问产品效果怎么样、是否好用管用、是否合身,需要分为"是否好用"而不是"功效功能",如果用户想问产品能干什么、有什么功能则侧重于"功效功能"。
规则6:如果有细分的更匹配的标签,不要分到笼统的标签,例如如果是问褪色、生锈的匹配到对应的标签,而不是认为最接近笼统的"是否好用",问灯能不能调光和角度的选择"能否调光"而不是"功效功能",信号不好等匹配到"信号情况"而不是"是否好用"</rule>

  同时发现,可以结合in-context learning类似的方式,给LLM提供一些对应类别的例子是可以提升效果的(3%-5%),但是不能给太多,并且例子需要精挑细选。采用的方式是,在推理一条测试集数据时,筛选出和这条数据的句子向量最接近的训练集数据出来,选出最接近的5个类别的数据的解释,放入提示词里面给模型看,例如对于某条测试集数据,它的prompt是下面的形式:

"""
你是一个电商客服专家,请分析以下用户与客服的对话,判断用户的实际意图,从下面列出的标签范围中选择出最符合的一个,直接回答该标签名,不要解释或评论。
其中意图分类标签的标签范围为:
['反馈密封性不好','是否好用','是否会生锈','排水方式','包装区别','发货数量','反馈用后症状','商品材质','功效功能','是否易褪色','适用季节','能否调光','版本款型区别','单品推荐','用法用量','控制方式','上市时间','商品规格','信号情况','养护方法','套装推荐','何时上货','气泡']。

意图分类时有一些规则需要遵守:
<rule>
规则1:如果用户有多个提问,并且多个提问命中了以上的不同标签,这种情况下请关注用户的最后的提问和客服的回答进行判断。
规则2:如果有细分的更匹配的标签,不要分到笼统的标签,例如如果是问褪色、生锈的匹配到对应的标签,而不是认为最接近笼统的"是否好用",问灯能不能调光和角度的选择"能否调光"而不是"功效功能",信号不好等匹配到"信号情况"而不是"是否好用"。
规则3:"单品推荐"和"套装推荐"容易混淆,请关注用户描述和客服回答的商品的单位,如果是"这个"、"这款"很有可能是单品,如果是说"套"、"系列"很有可能是套装。
规则4:"包装区别"和"版本款型区别"容易混淆,重点关注是否提到包装,提到包装的优先分类为"包装区别"。
规则5:"控制方式"和"用法用量"容易混淆,提到了"遥控器"、"控制"需要优先分类为"控制方式"而不是"用法用量",如果提到了"说明书"、"用户指南"或者"操作手册"则更可能应该分类为"用法用量"。
规则6:"是否好用"和"功效功能"容易混淆,如果用户问产品效果怎么样、是否好用管用、是否合身,需要分为"是否好用"而不是"功效功能",如果用户想问产品能干什么、有什么功能则侧重于"功效功能"。
</rule>

为了便于你分类,我再给你提供一些标签对应的示例供你参考,你需要根据标签的描述、提示以及示例,结合用户和客服的对话内容,按要求进行回答。下面是几个例子:
<example>

示例1:用户问'如何设置洗衣机的排水管',与'排水方式'标签一致
示例2:用户问'哪个按钮是用来加水',与'用法用量'标签描述'咨询商品使用方法/步骤/顺序'一致
示例3:用户问'为何会漏水',与'反馈密封性不好'标签一致
示例4:用户问'这款洗衣机能洗多少衣物',结合客服回答,用户想知道洗衣机的能力,与'功效功能'标签描述更接近
示例5:用户问'什么时候开始销售的',与'上市时间'标签一致
</example>

...............
用户: 出现了漏水情况
客服: 亲爱的顾客,非常抱歉给您带来了不便。如果商品出现问题,您可以通过搜索小程序【家电服务助手】来预约专业人员上门服务。温馨提示:①如果您购买的商品在15天内出现质量问题,经过鉴定符合要求的话,我们提供退换服务;②如果超过15天但在保修期内,性能问题可以免费维修;如涉及收费项目,工作人员会按照统一标准向您展示费用。
<用户与客服的对话 END>
请问最符合对话的标签名称是什么?请直接回答该标签名,不要解释或评论。
"""
(A)句子向量生成

  使用gte模型生成训练集和测试集的句子向量

##模型下载
from modelscope import snapshot_download
model_dir = snapshot_download('iic/nlp_gte_sentence-embedding_chinese-base',cache_dir='/data/coding/llm_model/')

from modelscope.models import Model
from modelscope.pipelines import pipeline
from modelscope.utils.constant import Tasks

model_id = "/data/coding/llm_model/iic/nlp_gte_sentence-embedding_chinese-base"
pipeline_se = pipeline(Tasks.sentence_embedding,
                       model=model_id,
                       sequence_length=2048,
                       device='cuda'
                       ) # sequence_length 代表最大文本长度,默认值为128


#####################################
import pandas as pd
import json
import numpy as np

# train data file_template
with open('train.json', 'r',encoding='utf-8') as file:
    train_json = json.load(file)
train_df = pd.DataFrame(train_json)
# 归类一下到底需要识别什么
train_df['task_category'] = train_df['instruction'].apply(lambda x: 'content' if '电商客服专家' in x else 'image')

content_df = train_df[train_df['task_category']=='content'].reset_index(drop=True)

exp_df = pd.read_csv('case分析_part5.csv',sep='\t')
exp_df['if_use'] = exp_df['if_false'].apply(lambda x: 1 if 'ok' in x else 0)
exp_df = exp_df[exp_df['if_use']==1].reset_index(drop=True)
exp_df = pd.merge(exp_df,content_df,on='id',how='inner')
content_df = pd.merge(content_df,exp_df[['id']],on='id',how='inner')


###################

##### 处理训练集json
description ="""可供选择的意图标签及其代表的含义如下:
- 反馈密封性不好:买家反馈商品密封性差会漏
- 是否好用:买家咨询商品是否好用
- 是否会生锈:咨询商品是否会生锈
- 排水方式:(适用产品:洗衣机、热水器)咨询商品排水方式
- 包装区别:咨询商品不同包装的区别
- 发货数量:咨询商品发货数量
- 反馈用后症状:买家反馈用完后引起的人体反应
- 商品材质:咨询商品具体材质,配件材质,填充物
- 功效功能:咨询商品功效功能
- 是否易褪色:咨询商品是否易褪色
- 适用季节:咨询商品适用季节
- 能否调光:咨询商品光源/灯光/光线/亮度是否可调
- 版本款型区别:咨询两个版本/型号/款式/类型/套装/组合等区别,不包括商品数量/重量/尺寸类区别
- 单品推荐:消费者咨询能否帮助推荐一下某类/某个商品,非套餐和套装级别
- 用法用量:咨询商品使用方法/步骤/顺序,包括但不限于用量,使用时间,使用部位
- 控制方式:咨询商品如何控制,能否可以通过手机/电脑控制
- 上市时间:咨询商品的上市时间
- 商品规格:咨询商品的数量、重量、含量、容量
- 信号情况:咨询手机使用的信号是否良好,以及信号不佳如何处理
- 养护方法:咨询商品的养护方法
- 套装推荐:消费者希望能推荐一些套装,非单品级别
- 何时上货:咨询补货/上货时间
- 气泡:咨询贴膜如何避免产生气泡及除气泡方法
请你在看完用户和客服的对话后,思考用户的意图最匹配哪一个标签,回答这个标签名,不要解释。
"""
for i in range(len(content_df)):
    conversation_list = content_df.loc[i]['instruction'].replace('image','picture').replace('\t','  ').split('\n')
    #conversation_list[0] = '你是一个电商客服专家,你的任务是根据用户与客服的对话内容,判断用户的实际意图,并将其归类到以下列出的特定分类标签中。每个分类标签都有明确的定义,见下方列表。阅读完用户和客服的对话后,请仅回复一个最符合用户意图的标签名称,不要添加额外的解释或评论。'
    conversation_list[-2] = ''
    instruction = '\n'.join(conversation_list[2:-3]).replace('用户:','').replace('客服:','').replace('客服:','').replace('<picture>','').replace('<http>','')
    content_df.loc[i,'prompt'] = instruction
    content_df.loc[i,'image']
    content_df.loc[i,'answer'] = content_df.loc[i,'output']


##########################
merge_df = pd.merge(content_df,exp_df[['id','explanation']],on='id',how='inner')
merge_df['prompt'] = merge_df['prompt'].apply(lambda x : x if len(x)<512 else x[-512:])
####################
########   向量生成
import tqdm
train_vectors = np.zeros((merge_df.shape[0],768))
id_list = []
idx_list = []
label_list = []
explanation_list = []
for i in tqdm.trange(0,merge_df.shape[0]):
    id_list.append(merge_df.loc[i,'id'])
    idx_list.append(i)
    label_list.append(merge_df.loc[i,'answer'])
    explanation_list.append(merge_df.loc[i,'explanation'])
    inputs2 = {
        "source_sentence": [merge_df.loc[i,'prompt']]
        }
    result = pipeline_se(input=inputs2)
    train_vectors[i] = result['text_embedding'][0]
train_vec_idx_df = pd.DataFrame({
    'id': id_list,
    'idx': idx_list,
    'label':label_list,
    'explanation':explanation_list
})
train_vec_idx_df.to_csv('train_vec_idx_df.csv',sep='\t',index=None)
np.save('train_vectors.npy',train_vectors)  # 存起来训练集的向量


########### 测试集向量也存起来后面用
import pandas as pd
import json
import numpy as np

# train data file_template
with open('test1.json', 'r',encoding='utf-8') as file:
    test_json = json.load(file)
test_df = pd.DataFrame(test_json)
# 归类一下到底需要识别什么
test_df['task_category'] = test_df['instruction'].apply(lambda x: 'content' if '电商客服专家' in x else 'image')

content_df = test_df[test_df['task_category']=='content'].reset_index(drop=True)

for i in range(len(content_df)):
    conversation_list = content_df.loc[i]['instruction'].replace('image','picture').replace('\t','  ').split('\n')
    conversation_list[-2] = ''
    instruction = '\n'.join(conversation_list[2:-3]).replace('用户:','').replace('客服:','').replace('客服:','').replace('<picture>','').replace('<http>','')
    content_df.loc[i,'prompt'] = instruction

content_df['prompt'] = content_df['prompt'].apply(lambda x : x if len(x)<512 else x[-512:])

import tqdm
test_vectors = np.zeros((content_df.shape[0],768))
id_list = []
idx_list = []
for i in tqdm.trange(0,content_df.shape[0]):
    id_list.append(content_df.loc[i,'id'])
    idx_list.append(i)
    inputs2 = {
        "source_sentence": [content_df.loc[i,'prompt']]
        }
    result = pipeline_se(input=inputs2)
    test_vectors[i] = result['text_embedding'][0]
test_vec_idx_df = pd.DataFrame({
    'id': id_list,
    'idx': idx_list
})
np.save('test_vectors.npy',test_vectors)
test_vec_idx_df.to_csv('test_vec_idx_df.csv',sep='\t',index=None)

(B)LLM推理

###### 加载模型,没有合并lora直接加载了
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch

# 1、加载基础模型
model_name = "/data/coding/qwen_3b_ft/checkpoint-1385/"
tokenizer_name = '/data/coding/llm_model/Qwen/Qwen2___5-3B-Instruct/'
model = AutoModelForCausalLM.from_pretrained(
    model_name, torch_dtype="auto", device_map="auto",trust_remote_code=True,
)  # 这里可以用flash_attention加速
tokenizer = AutoTokenizer.from_pretrained(tokenizer_name,trust_remote_code=True)
tokenizer.padding_side='left'
model = model.to('cuda')
model.eval()

###############  数据集读取

import pandas as pd
import json
import numpy as np

# train data file_template
with open('train.json', 'r',encoding='utf-8') as file:
    train_json = json.load(file)
train_df = pd.DataFrame(train_json)
# 归类一下到底需要识别什么
train_df['task_category'] = train_df['instruction'].apply(lambda x: 'content' if '电商客服专家' in x else 'image')

content_df = train_df[train_df['task_category']=='content'].reset_index(drop=True)

exp_df = pd.read_csv('case分析_part5.csv',sep='\t')
exp_df['if_use'] = exp_df['if_false'].apply(lambda x: 1 if 'ok' in x else 0)
exp_df = exp_df[exp_df['if_use']==1].reset_index(drop=True)
exp_df = pd.merge(exp_df,content_df,on='id',how='inner')
content_df = pd.merge(content_df,exp_df[['id']],on='id',how='inner')

merge_df = pd.merge(content_df,exp_df[['id','explanation']],on='id',how='inner')
# merge_df里面存的是训练集
##############################


import numpy as np
import pandas as pd
from sklearn.metrics.pairwise import cosine_similarity


train_vec_idx_df = pd.read_csv('train_vec_idx_df.csv',sep='\t')
test_vec_idx_df = pd.read_csv('test_vec_idx_df.csv',sep='\t')

train_vectors = np.load('train_vectors.npy')
test_vectors = np.load('test_vectors.npy')


# train data file_template
with open('test1.json', 'r',encoding='utf-8') as file:
    test_file_json = json.load(file)
test_file_df = pd.DataFrame(test_file_json)
# 归类一下到底需要识别什么
test_file_df['task_category'] = test_file_df['instruction'].apply(lambda x: 'content' if '电商客服专家' in x else 'image')

content_df = test_file_df[test_file_df['task_category']=='content'].reset_index(drop=True)


## 不解释
description ="""
你是一个电商客服专家,请分析以下用户与客服的对话,判断用户的实际意图,并将其归类到列出的其中一个特定分类标签,把答案写入<answer>标记中,不要解释或评论。
其中意图分类标签的标签范围为:
[反馈密封性不好,是否好用,是否会生锈,排水方式,包装区别,发货数量,反馈用后症状,商品材质,功效功能,是否易褪色,适用季节,能否调光,版本款型区别,单品推荐,用法用量,控制方式,上市时间,商品规格,信号情况,养护方法,套装推荐,何时上货,气泡]。

意图分类时有一些规则需要遵守:
<rule>
规则1:如果用户有多个提问,并且多个提问命中了以上的不同标签,这种情况下请关注用户的最后的提问和客服的回答进行判断。
规则2:"单品推荐"和"套装推荐"容易混淆,请关注用户描述和客服回答的商品的单位,如果是"这个"、"这款"很有可能是单品,如果是说"套"、"系列"很有可能是套装。
规则3:"包装区别"和"版本款型区别"容易混淆,重点关注是否提到包装,提到包装的优先分类为"包装区别"。
规则4:"控制方式"和"用法用量"容易混淆,提到了"遥控器"、"控制"需要优先分类为"控制方式"而不是"用法用量",如果提到了"说明书"、"用户指南"或者"操作手册"则更可能应该分类为"用法用量"。
规则5:"是否好用"和"功效功能"容易混淆,如果用户问产品效果怎么样、是否好用管用、是否合身,需要分为"是否好用"而不是"功效功能",如果用户想问产品能干什么、有什么功能则侧重于"功效功能"。
规则6:如果有细分的更匹配的标签,不要分到笼统的标签,例如如果是问褪色、生锈的匹配到对应的标签,而不是认为最接近笼统的"是否好用",问灯能不能调光和角度的选择"能否调光"而不是"功效功能",信号不好等匹配到"信号情况"而不是"是否好用"。
</rule>

为了便于你分类,我再给你提供一些标签对应的示例供你参考,你需要根据标签的描述、提示以及示例,结合用户和客服的对话内容,按要求进行回答。下面是几个例子:
<example>"""

description = """你是一个电商客服专家,请分析以下用户与客服的对话,判断用户的实际意图,从下面列出的标签范围中选择出最符合的一个,直接回答该标签名,不要解释或评论。
其中意图分类标签的标签范围为:
['反馈密封性不好','是否好用','是否会生锈','排水方式','包装区别','发货数量','反馈用后症状','商品材质','功效功能','是否易褪色','适用季节','能否调光','版本款型区别','单品推荐','用法用量','控制方式','上市时间','商品规格','信号情况','养护方法','套装推荐','何时上货','气泡']。

意图分类时有一些规则需要遵守:
<rule>
规则1:如果用户有多个提问,并且多个提问命中了以上的不同标签,这种情况下请关注用户的最后的提问和客服的回答进行判断。
规则2:如果有细分的更匹配的标签,不要分到笼统的标签,例如如果是问褪色、生锈的匹配到对应的标签,而不是认为最接近笼统的"是否好用",问灯能不能调光和角度的选择"能否调光"而不是"功效功能",信号不好等匹配到"信号情况"而不是"是否好用"。
规则3:"单品推荐"和"套装推荐"容易混淆,请关注用户描述和客服回答的商品的单位,如果是"这个"、"这款"很有可能是单品,如果是说"套"、"系列"很有可能是套装。
规则4:"包装区别"和"版本款型区别"容易混淆,重点关注是否提到包装,提到包装的优先分类为"包装区别"。
规则5:"控制方式"和"用法用量"容易混淆,提到了"遥控器"、"控制"需要优先分类为"控制方式"而不是"用法用量",如果提到了"说明书"、"用户指南"或者"操作手册"则更可能应该分类为"用法用量"。
规则6:"是否好用"和"功效功能"容易混淆,如果用户问产品效果怎么样、是否好用管用、是否合身,需要分为"是否好用"而不是"功效功能",如果用户想问产品能干什么、有什么功能则侧重于"功效功能"。
</rule>

为了便于你分类,我再给你提供一些标签对应的示例供你参考,你需要根据标签的描述、提示以及示例,结合用户和客服的对话内容,按要求进行回答。下面是几个例子:
<example>
"""
max_len = 0
# 对于每条记录,找到每个类最接近的
content_df['prompt'] = ''
for i in range(0,content_df.shape[0]):
    last_prompt='\n\n用户与客服的对话为:'
    idx = test_vec_idx_df[test_vec_idx_df['id']==content_df.loc[i,'id']]['idx'].values
    test_vector = test_vectors[idx]
    # 取top10的train_vectors的下标train_idx,关联到train_vec_idx_df
    # 
    # 如果这10个记录里面有label一样的,每个label只保留最相似的那个
    # 选中的train_vectors的下标为candidate_idx,不同candidate_idx对应的label不一样

    # 计算与所有训练向量的余弦相似度
    similarities = cosine_similarity(test_vector, train_vectors).flatten()
    
    # 找到相似度最高的前20个训练向量的索引
    top_20_indices = np.argsort(similarities)[-20:][::-1]  # 取最大的20个,并保持顺序
    
    # 从top_20_indices中筛选出不同标签的最相似记录
    selected_indices_top_20 = []  # 这个是结果
    labels_in_top_20 = set()
    
    exp_idx = 1
    pad_prompt = ''
    for index in top_20_indices:
        label = train_vec_idx_df.loc[index, 'label']
        if label not in labels_in_top_20:
            labels_in_top_20.add(label)
            selected_indices_top_20.append(index)
            pad_prompt = pad_prompt +'\n示例'+str(exp_idx)+':'+ merge_df.loc[index,'explanation']
            exp_idx += 1
    
    conversation_list = content_df.loc[i]['instruction'].replace('image','picture').replace('\t','  ').split('\n')
    conversation_list[0] = description + pad_prompt + '\n</example>\n' + last_prompt
    #conversation_list[0] = description + last_prompt
    conversation_list[-2] = ''
    instruction = '\n'.join(conversation_list[:-2])
    content_df.loc[i,'prompt'] = instruction + '\n请问最符合对话的标签名称是什么?请直接回答该标签名,不要解释或评论。'



############# 标签提取,直接提取应该有一份是ok的不记得是哪一份了
from datasets import Dataset
test_dataset = Dataset.from_pandas(content_df)
# 预处理数据集
def preprocess_function_test(examples):
    return tokenizer(examples['prompt'], padding=True, truncation=True, max_length=4800)

encoded_test_dataset = test_dataset.map(preprocess_function_test,batched=True,remove_columns=test_dataset.column_names)

def collate_fn(examples):
    input_ids = [example['input_ids'] for example in examples]
    attention_mask = [example['attention_mask'] for example in examples]

    # 批量填充
    return {
        'input_ids': torch.tensor(input_ids, dtype=torch.long).to(model.device),
        'attention_mask': torch.tensor(attention_mask, dtype=torch.long).to(model.device)
    }
    
def append_to_file_exp(file_path,idx, data_id, model_output, exp_out):
    """
    将数据的 ID、模型的输出和提取的标签以制表符分隔的形式追加到指定的文件中。

    :param file_path: 文本文件的路径
    :param data_id: 数据的 ID
    :param model_output: 模型的输出
    """
    with open(file_path, 'a') as file:
        file.write(f"{idx}\t{data_id}\t{model_output}\t{exp_out}\n")

def append_to_file(file_path,idx, data_id, model_output):
    """
    将数据的 ID、模型的输出和提取的标签以制表符分隔的形式追加到指定的文件中。

    :param file_path: 文本文件的路径
    :param data_id: 数据的 ID
    :param model_output: 模型的输出
    """
    with open(file_path, 'a') as file:
        file.write(f"{idx}\t{data_id}\t{model_output}\n")
# 指定文件路径
file_path = "/data/coding/qwen_output_1211_2.txt"

import re
def extract_answer(response_text):
    # 使用正则表达式提取<answer>部分
    match = re.search(r'<answer>(.*?)</answer>', response_text, re.DOTALL)
    if match:
        return match.group(1).strip()
    else:
        return 'error'

import re
def extract_single_label(text):
    # 定义标签列表及其优先级(数字越大优先级越高)
    LABELS_PRIORITY = {
        "反馈密封性不好": 1,
        "是否好用": 2,
        "是否会生锈": 3,
        "排水方式": 4,
        "包装区别": 5,
        "发货数量": 6,
        "反馈用后症状": 7,
        "商品材质": 8,
        "功效功能": 9,
        "是否易褪色": 10,
        "适用季节": 11,
        "能否调光": 12,
        "版本款型区别": 13,
        "单品推荐": 14,
        "用法用量": 15,
        "控制方式": 16,
        "上市时间": 17,
        "商品规格": 18,
        "信号情况": 19,
        "养护方法": 20,
        "套装推荐": 21,
        "何时上货": 22,
        "气泡": 23
    }
    found_labels = []

    # 对于每一个标签,检查其是否在文本中出现
    for label in LABELS_PRIORITY.keys():
        # 使用正则表达式进行不区分大小写和部分匹配
        if re.search(r'\b' + re.escape(label) + r'\b', text, re.IGNORECASE):
            found_labels.append((label, LABELS_PRIORITY[label]))

    if not found_labels:
        return 'error'  # 没有找到任何匹配的标签

    # 如果找到了多个标签,则选择优先级最高的一个
    best_match = max(found_labels, key=lambda x: x[1])

    return best_match[0]

from torch.utils.data import DataLoader
from tqdm import tqdm
# 创建DataLoader
batch_size = 5
data_loader = DataLoader(encoded_test_dataset, batch_size=batch_size, collate_fn=collate_fn, shuffle=False)
gen_kwargs = {"max_new_tokens": 300, "do_sample": False}
idx_cnt = 0

for i,batch in enumerate(tqdm(data_loader)):
    torch.cuda.empty_cache()
    try:
        with torch.inference_mode():
            outputs = model.generate(**batch,**gen_kwargs)
        generated_outputs = outputs[:, batch["input_ids"].shape[1]:]
        texts = tokenizer.batch_decode(generated_outputs,skip_special_tokens=True)
    except:
        texts = ['error'*batch_size]
    for k in range(len(texts)):
        tmp = extract_single_label(str(texts[k]).strip())
        append_to_file_exp(file_path, idx_cnt+k,content_df.loc[idx_cnt+k,'id'], tmp.replace('\t',' ').replace('\n',' '), str(texts[k]).replace('\t',' ').replace('\n',' '))
    idx_cnt = idx_cnt+len(texts)

5.2 任务二——VLM

  这里尝试了minicpm-v-2.6的8B和int4模型,qwen2.5系列的2B/7B,internvl2.5的4B/8B,尝试了lora、一半的参数微调,以及小的模型的全参数微调,测下来internvl2.5_8B的lora效果最好,可能因为数据少微调参数越多越容易过拟合。一开始是打算把文本刷上去然后VLM用internvl2.5的4B来做的,图片标注手势那里应该有很多可以明确的一些避免模型过拟合,或者不知道往哪里拟合,以及写一些推理链条。

6. 没有效果的尝试

6.1 任务一——文本

  • 把文本explanation数据混入到训练集里面一起训练(可能下面的方式才会有效果,这样当然没效果)
  • 加入更多规则或者更多示例(上下文太长了)
  • 使用更大的模型或者30%/50%/全部参数微调而不是lora(就277条数据容易过拟合)
  • 加入图片来训(图片看上去和结果没什么关联性)
  • 让模型思考,step-by-step

6.2 任务二——图片

  • 用更大的模型或者30%/50%/全部参数微调而不是lora(就700条数据容易过拟合)
  • 文本和图片一起训(任务一和任务二合并)
  • 加入更多规则或者使用图片示例in-contenxt learning(上下文太长了,图片也尝试了类似的检索,目前VLM可能本身不适合COT除了internvl,internvl没试这种方式)

7.TODO

  如果是业务上遇到人工标注手势的问题,应该会先去对齐手势明确什么样的标注为某个标签,写到SOP里大家都遵循,多个人标注一致确认的才作为训练和测试数据,最后才是让模型和人工一致的吧。
(1)初期可以用上置信学习之类的方法交叉检验筛出疑似标注错误的数据。
(2)混入COT的数据,label在前面(其它任务上测可以提升2%)
在这里插入图片描述
(3)训练时训更好,lora改成lora+(B矩阵比A矩阵学习率高,可以提升1%-2%),使用dora(其它数据上测没有提升,但是可能有效果),rank-stabilized LoRA(其它数据上测没有提升,但是可能有效果)
(4)提示词工程,在保证手势一致的情况下可以加一些提示词,以及更好的使用COT

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值