赞
踩
该笔记分为六个part:
一、赛题介绍
二、评估结果
三、比赛解题思路
四、Baseline介绍及其代码
五、我的打榜过程
六、注意事项及答疑学习
比赛链接:
https://challenge.xfyun.cn/topic/info?type=role-element-extraction
在当今数字化时代,企业积累了丰富的对话数据,这些数据不仅是客户与企业之间交流的记录,更是隐藏着宝贵信息的宝库。在这个背景下,群聊对话分角色要素提取成为了企业营销和服务的一项重要策略。
群聊对话分角色要素提取的理念是基于企业对话数据的深度分析和挖掘。通过对群聊对话数据进行分析,企业可以更好地理解客户的需求、兴趣和行为模式,从而精准地把握客户的需求和心理,提供更加个性化和优质的服务。这不仅有助于企业更好地满足客户的需求,提升客户满意度,还可以为企业带来更多的商业价值和竞争优势。
群聊对话分角色要素提取的研究,将企业对话数据转化为可用的信息和智能的洞察,为企业营销和服务提供了新的思路和方法。通过挖掘对话数据中隐藏的客户行为特征和趋势,企业可以更加精准地进行客户定位、推广营销和产品服务,实现营销效果的最大化和客户价值的最大化。这将为企业带来更广阔的发展空间和更持续的竞争优势。
从给定的<客服>与<客户>的群聊对话中, 提取出指定的字段信息,待提取的全部字段见下数据说明。
赛题方提供了184条真实场景的群聊对话数据以及人工标注后的字段提取结果,其中训练数据129条,测试数据 55条。按照各类字段提取的难易程度,共设置了1、2、3三种难度分数。待提取的字段以及提取正确时的得分规则如下:
序号 | 字段名称 | 是否单值 | 是否可为空 | 难度分数 | 答案是否唯一 |
---|---|---|---|---|---|
1 | 基本信息-姓名 | 是 | 是 | 1 | 是 |
2 | 基本信息-手机号码 | 是 | 是 | 1 | 是 |
3 | 基本信息-邮箱 | 是 | 是 | 1 | 是 |
4 | 基本信息-地区 | 是 | 是 | 1 | 是 |
5 | 基本信息-详细地址 | 是 | 是 | 1 | 是 |
6 | 基本信息-性别 | 是 | 是 | 1 | 是 |
7 | 基本信息-年龄 | 是 | 是 | 1 | 是 |
8 | 基本信息-生日 | 是 | 是 | 1 | 是 |
9 | 咨询类型 | 否 | 是 | 2 | 是 |
10 | 意向产品 | 否 | 是 | 3 | 是 |
11 | 购买异议点 | 否 | 是 | 3 | 是 |
12 | 客户预算-预算是否充足 | 是 | 是 | 2 | 是 |
13 | 客户预算-总体预算金额 | 是 | 是 | 2 | 是 |
14 | 客户预算-预算明细 | 是 | 是 | 3 | 否 |
15 | 竞品信息 | 是 | 是 | 2 | 是 |
16 | 客户是否有意向 | 是 | 是 | 1 | 是 |
17 | 客户是否有卡点 | 是 | 是 | 1 | 是 |
18 | 客户购买阶段 | 是 | 是 | 2 | 是 |
19 | 下一步跟进计划-参与人 | 否 | 是 | 2 | 是 |
20 | 下一步跟进计划-时间点 | 是 | 是 | 2 | 是 |
21 | 下一步跟进计划-具体事项 | 是 | 是 | 3 | 否 |
备注:
1)可为空的字段,当判定无相应信息、无法做出判断等情况,统一取值为空字符串
2)对于非单值字段,请使用list来表示
测试集的每条数据同样包含共21个字段, 按照各字段难易程度划分总计满分36分。每个提取正确性的判定标准如下:
1)对于答案唯一字段,将使用完全匹配的方式计算提取是否正确,提取正确得到相应分数,否则为0分
2)对于答案不唯一字段,将综合考虑提取完整性、语义相似度等维度判定提取的匹配分数,最终该字段得分为 “匹配分数 * 该字段难度分数”
每条测试数据的最终得分为各字段累计得分。最终测试集上的分数为所有测试数据的平均得分。
拿到题目首先的思路便是大语言模型做信息抽取,我们需要对原始数据做读取后,编写合适的抽取prompt。这里设计prompt需要强调抽取的数据格式和数据内容。接着将测试集的每一条通过大语言模型抽取得到结果。
我们 Baseline 选择使用大语言模型抽取的思路。
思考:如何让大语言模型理解我们的任务?
在使用大语言模型时,应该对需要完成的任务做一个规划整理。制定一个思考路线。比如我们信息抽取时,应该先理解群聊信息,然后定义抽取的内容,接着限定输出内容,最后将整理好的要求及待处理内容以promopt的形式交给大语言模型。
思考:如何规范抽取的输出?
Datawhale提供了2个Baseline,其中第一个Baseline为线上Baseline,部署在飞桨的Aistudio里面,可以一键运行,第二个Baseline为大语言模型微调。(Baseline直达链接:AI夏令营 - LLM实践教程 )
该Baseline运行后的A榜分数为:15.45
#Step1:下载相关库 !pip install --upgrade -q spark_ai_python #Step2:配置导入 from sparkai.llm.llm import ChatSparkLLM, ChunkPrintHandler from sparkai.core.messages import ChatMessage import json #星火认知大模型Spark3.5 Max的URL值,其他版本大模型URL值请前往文档(https://www.xfyun.cn/doc/spark/Web.html)查看 SPARKAI_URL = 'wss://spark-api.xf-yun.com/v3.5/chat' #星火认知大模型调用秘钥信息,请前往讯飞开放平台控制台(https://console.xfyun.cn/services/bm35)查看 SPARKAI_APP_ID = '' SPARKAI_API_SECRET = '' SPARKAI_API_KEY = '' #星火认知大模型Spark3.5 Max的domain值,其他版本大模型domain值请前往文档(https://www.xfyun.cn/doc/spark/Web.html)查看 SPARKAI_DOMAIN = 'generalv3.5' #Step3:模型测试 def get_completions(text): messages = [ChatMessage( role="user", content=text )] spark = ChatSparkLLM( spark_api_url=SPARKAI_URL, spark_app_id=SPARKAI_APP_ID, spark_api_key=SPARKAI_API_KEY, spark_api_secret=SPARKAI_API_SECRET, spark_llm_domain=SPARKAI_DOMAIN, streaming=False, ) handler = ChunkPrintHandler() a = spark.generate([messages], callbacks=[handler]) return a.generations[0][0].text # 测试模型配置是否正确 text = "你好" get_completions(text) #Step4:数据读取 def read_json(json_file_path): """读取json文件""" with open(json_file_path, 'r') as f: data = json.load(f) return data def write_json(json_file_path, data): """写入json文件""" with open(json_file_path, 'w') as f: json.dump(data, f, ensure_ascii=False, indent=4) # 读取数据 train_data = read_json("dataset/train.json") test_data = read_json("dataset/test_data.json") #Step5:Prompt设计 # prompt 设计 PROMPT_EXTRACT = """ 你将获得一段群聊对话记录。你的任务是根据给定的表单格式从对话记录中提取结构化信息。在提取信息时,请确保它与类型信息完全匹配,不要添加任何没有出现在下面模式中的属性。 表单格式如下: info: Array<Dict( "基本信息-姓名": string | "", // 客户的姓名。 "基本信息-手机号码": string | "", // 客户的手机号码。 "基本信息-邮箱": string | "", // 客户的电子邮箱地址。 "基本信息-地区": string | "", // 客户所在的地区或城市。 "基本信息-详细地址": string | "", // 客户的详细地址。 "基本信息-性别": string | "", // 客户的性别。 "基本信息-年龄": string | "", // 客户的年龄。 "基本信息-生日": string | "", // 客户的生日。 "咨询类型": string[] | [], // 客户的咨询类型,如询价、答疑等。 "意向产品": string[] | [], // 客户感兴趣的产品。 "购买异议点": string[] | [], // 客户在购买过程中提出的异议或问题。 "客户预算-预算是否充足": string | "", // 客户的预算是否充足。示例:充足, 不充足 "客户预算-总体预算金额": string | "", // 客户的总体预算金额。 "客户预算-预算明细": string | "", // 客户预算的具体明细。 "竞品信息": string | "", // 竞争对手的信息。 "客户是否有意向": string | "", // 客户是否有购买意向。示例:有意向, 无意向 "客户是否有卡点": string | "", // 客户在购买过程中是否遇到阻碍或卡点。示例:有卡点, 无卡点 "客户购买阶段": string | "", // 客户当前的购买阶段,如合同中、方案交流等。 "下一步跟进计划-参与人": string[] | [], // 下一步跟进计划中涉及的人员(客服人员)。 "下一步跟进计划-时间点": string | "", // 下一步跟进的时间点。 "下一步跟进计划-具体事项": string | "" // 下一步需要进行的具体事项。 )> 请分析以下群聊对话记录,并根据上述格式提取信息: **对话记录:** ``` {content} ``` 请将提取的信息以JSON格式输出。 不要添加任何澄清信息。 输出必须遵循上面的模式。 不要添加任何没有出现在模式中的附加字段。 不要随意删除字段。 **输出:** ``` [{{ "基本信息-姓名": "姓名", "基本信息-手机号码": "手机号码", "基本信息-邮箱": "邮箱", "基本信息-地区": "地区", "基本信息-详细地址": "详细地址", "基本信息-性别": "性别", "基本信息-年龄": "年龄", "基本信息-生日": "生日", "咨询类型": ["咨询类型"], "意向产品": ["意向产品"], "购买异议点": ["购买异议点"], "客户预算-预算是否充足": "充足或不充足", "客户预算-总体预算金额": "总体预算金额", "客户预算-预算明细": "预算明细", "竞品信息": "竞品信息", "客户是否有意向": "有意向或无意向", "客户是否有卡点": "有卡点或无卡点", "客户购买阶段": "购买阶段", "下一步跟进计划-参与人": ["跟进计划参与人"], "下一步跟进计划-时间点": "跟进计划时间点", "下一步跟进计划-具体事项": "跟进计划具体事项" }}, ...] ``` """ #Step6:主函数启动 import json class JsonFormatError(Exception): def __init__(self, message): self.message = message super().__init__(self.message) def convert_all_json_in_text_to_dict(text): """提取LLM输出文本中的json字符串""" dicts, stack = [], [] for i in range(len(text)): if text[i] == '{': stack.append(i) elif text[i] == '}': begin = stack.pop() if not stack: dicts.append(json.loads(text[begin:i+1])) return dicts # 查看对话标签 def print_json_format(data): """格式化输出json格式""" print(json.dumps(data, indent=4, ensure_ascii=False)) def check_and_complete_json_format(data): required_keys = { "基本信息-姓名": str, "基本信息-手机号码": str, "基本信息-邮箱": str, "基本信息-地区": str, "基本信息-详细地址": str, "基本信息-性别": str, "基本信息-年龄": str, "基本信息-生日": str, "咨询类型": list, "意向产品": list, "购买异议点": list, "客户预算-预算是否充足": str, "客户预算-总体预算金额": str, "客户预算-预算明细": str, "竞品信息": str, "客户是否有意向": str, "客户是否有卡点": str, "客户购买阶段": str, "下一步跟进计划-参与人": list, "下一步跟进计划-时间点": str, "下一步跟进计划-具体事项": str } if not isinstance(data, list): raise JsonFormatError("Data is not a list") for item in data: if not isinstance(item, dict): raise JsonFormatError("Item is not a dictionary") for key, value_type in required_keys.items(): if key not in item: item[key] = [] if value_type == list else "" if not isinstance(item[key], value_type): raise JsonFormatError(f"Key '{key}' is not of type {value_type.__name__}") if value_type == list and not all(isinstance(i, str) for i in item[key]): raise JsonFormatError(f"Key '{key}' does not contain all strings in the list") return data from tqdm import tqdm retry_count = 5 # 重试次数 result = [] error_data = [] for index, data in tqdm(enumerate(test_data)): index += 1 is_success = False for i in range(retry_count): try: res = get_completions(PROMPT_EXTRACT.format(content=data["chat_text"])) infos = convert_all_json_in_text_to_dict(res) infos = check_and_complete_json_format(infos) result.append({ "infos": infos, "index": index }) is_success = True break except Exception as e: print("index:", index, ", error:", e) continue if not is_success: data["index"] = index error_data.append(data) # 故障数据处理 if error_data: retry_count = 10 # 重试次数 error_data_temp = [] while True: if error_data_temp: error_data = error_data_temp error_data_temp = [] for data in tqdm(error_data): is_success = False for i in range(retry_count): try: res = get_completions(PROMPT_EXTRACT.format(content=data["chat_text"])) infos = convert_all_json_in_text_to_dict(res) infos = check_and_complete_json_format(infos) result.append({ "infos": infos, "index": data["index"] }) is_success = True break except Exception as e: print("index:", index, ", error:", e) continue if not is_success: error_data_temp.append(data) if not error_data_temp: break result = sorted(result, key=lambda x: x["index"]) #Step7:生成提交文件 # 保存输出 write_json("output.json", result)
该Baseline运行后的A榜分数为:****
思路:数据集制作–>平台微调
这里我们需要先对原始群聊数据做初步抽取,我们需要准备一下讯飞3.5的api环境配置。和baseline1的配置一样。
!pip uninstall websocket-client !pip install --upgrade spark_ai_python websocket-client from sparkai.llm.llm import ChatSparkLLM, ChunkPrintHandler from sparkai.core.messages import ChatMessage import numpy as np from tqdm import tqdm def chatbot(prompt): #星火认知大模型Spark3.5 Max的URL值,其他版本大模型URL值请前往文档(https://www.xfyun.cn/doc/spark/Web.html)查看 SPARKAI_URL = 'wss://spark-api.xf-yun.com/v3.5/chat' #星火认知大模型调用秘钥信息,请前往讯飞开放平台控制台(https://console.xfyun.cn/services/bm35)查看 SPARKAI_APP_ID = '' SPARKAI_API_SECRET = '' SPARKAI_API_KEY = '' #星火认知大模型Spark3.5 Max的domain值,其他版本大模型domain值请前往文档(https://www.xfyun.cn/doc/spark/Web.html)查看 SPARKAI_DOMAIN = 'generalv3.5' spark = ChatSparkLLM( spark_api_url=SPARKAI_URL, spark_app_id=SPARKAI_APP_ID, spark_api_key=SPARKAI_API_KEY, spark_api_secret=SPARKAI_API_SECRET, spark_llm_domain=SPARKAI_DOMAIN, streaming=False, ) messages = [ChatMessage( role="user", content=prompt )] handler = ChunkPrintHandler() a = spark.generate([messages], callbacks=[handler]) return a.generations[0][0].message.content
这里我们对原群聊对话设计了一个总结Prompt,目的是将原始对话内容进行精简。方便做微调数据。
一方面直接将群聊对话作为数据集的话,会导致上下文过长,超过限制。还有上下文太长会导致抽取效果变差。
过长的上下文也会导致训练时长和费用倍增。(比如我做了一个数据集要花3000多块钱跑完。就算能跑可能也要1-2天……)
好了我们来说说prompt。这个prompt相较于baseline01区别比较明显,对需要抽取的任务做了一次总结。总结了四个方面:
客户基本信息:需要从中区分出客户角色,并得到客户基本信息,其中包括姓名、手机号码、邮箱、地区、详细地址、性别、年龄和生日 客户意向与预算信息: 客户意向与预算信息包括咨询类型、意向产品、购买异议点、预算是否充足、总体预算金额以及预算明细 客户购买准备情况:户购买准备情况包括竞品信息、客户是否有意向、客户是否有卡点以及客户购买阶段 跟进计划信息: 跟进计划信息包括参与人、时间点和具体事项,这些信息用于指导销售团队在未来的跟进工作中与客户互动
通过总结后的数据一方面节约了微调的运算资源,一方面也让数据被清洗后更容易被模型理解,达到更好的抽取效果。
content = '' prompt = f''' 你是一个数据分析大师,你需要从群聊对话中进行分析,里面对话的角色中大部分是客服角色,你需要从中区分出有需求的客户,并得到以下四类数据。 ****群聊对话**** {content} ****分析数据**** 客户基本信息:需要从中区分出客户角色,并得到客户基本信息,其中包括姓名、手机号码、邮箱、地区、详细地址、性别、年龄和生日 客户意向与预算信息: 客户意向与预算信息包括咨询类型、意向产品、购买异议点、预算是否充足、总体预算金额以及预算明细 客户购买准备情况:户购买准备情况包括竞品信息、客户是否有意向、客户是否有卡点以及客户购买阶段 跟进计划信息: 跟进计划信息包括参与人、时间点和具体事项,这些信息用于指导销售团队在未来的跟进工作中与客户互动 ****注意**** 1.只输出客户基本信息、客户意向与预算信息、客户购买准备情况、跟进计划信息对应的信息,不要输出无关内容 2.不要输出分析内容 3.输出内容格式为md格式 '''
jsonl_data 是用来训练的规范单行数据,需要由训练数据组成一个jsonl文件(每行是一个json数据的文件),格式如下:
jsonl_data = {"instruction":"假设你是一个智能交互助手,基于用户的输入文本,解析其中语义,抽取关键信息,以json格式生成结构化的语义内容。","input":"请调小空气净化器的湿度到1","output":"{\"intent\":\"CONTROL\",\"slots\":[{\"name\":\"device\",\"normValue\":\"airCleaner\",\"value\":\"空气净化器\"},{\"name\":\"insType\",\"normValue\":\"set\",\"value\":\"调小\"},{\"name\":\"attr\",\"normValue\":\"humidity\",\"value\":\"湿度\"},{\"name\":\"attrValue\",\"normValue\":\"1\",\"value\":\"1\"}],\"sample\":\"请调小空气净化器的湿度到1\"}"} print(jsonl_data) print(jsonl_data["instruction"]) print(jsonl_data["input"]) print(jsonl_data["output"]) #需要训练的数据文件在官网下载后是train.json这里我直接导入到根目录,无需大家下载 import json # 打开并读取JSON文件 with open('train.json', 'r', encoding='utf-8') as file: data = json.load(file) #这里我们通过星火3.5api清洗原来的数据,总结后按照刚才看到得单行jsonl存储格式将数据存入traindata.jsonl中。大家可以经过处理后自行查阅traindata.jsonl文件,看看都有啥。 import csv # 打开一个文件用于写入CSV数据 with open('test.csv', 'w', newline='', encoding='utf-8') as csvfile: # 创建一个csv writer对象 csvwriter = csv.writer(csvfile) csvwriter.writerow(["input","target"]) # 遍历数据列表,并将每一行写入CSV文件 for line_data in tqdm(data_test): content = line_data["chat_text"] prompt = f''' 你是一个数据分析大师,你需要从群聊对话中进行分析,里面对话的角色中大部分是客服角色,你需要从中区分出有需求的客户,并得到以下四类数据。 ****群聊对话**** {content} ****分析数据**** 客户基本信息:需要从中区分出客户角色,并得到客户基本信息,其中包括姓名、手机号码、邮箱、地区、详细地址、性别、年龄和生日 客户意向与预算信息: 客户意向与预算信息包括咨询类型、意向产品、购买异议点、预算是否充足、总体预算金额以及预算明细 客户购买准备情况:户购买准备情况包括竞品信息、客户是否有意向、客户是否有卡点以及客户购买阶段 跟进计划信息: 跟进计划信息包括参与人、时间点和具体事项,这些信息用于指导销售团队在未来的跟进工作中与客户互动 ****注意**** 1.只输出客户基本信息、客户意向与预算信息、客户购买准备情况、跟进计划信息对应的信息,不要输出无关内容 2.不要输出分析内容 3.输出内容格式为md格式 ''' res = chatbot(prompt=prompt) # print(line_data["chat_text"]) ## 文件内容校验失败: test.jsonl(不含表头起算)第1行的内容不符合规则,限制每组input和target字符数量总和上限为8000,当前行字符数量:10721 line_list = [res, "-"] csvwriter.writerow(line_list) # break data_test
链接:https://training.xfyun.cn/overview
上传第一步制作的数据集
点击确定,等待上传成功
- 运行成功训练表示数据上传结束
回到:https://training.xfyun.cn/dataset/datasetIndex
这次我们选择测试集即可。
上传我们的test.csv文件即可。
# 定义写入函数 def write_json(json_file_path, data): #"""写入json文件""" with open(json_file_path, 'w') as f: json.dump(data, f, ensure_ascii=False, indent=4) import SparkApi import json #以下密钥信息从控制台获取 appid = "" #填写控制台中获取的 APPID 信息 api_secret = "" #填写控制台中获取的 APISecret 信息 api_key ="" #填写控制台中获取的 APIKey 信息 #调用微调大模型时,设置为“patch” domain = "patchv3" #云端环境的服务地址 # Spark_url = "wss://spark-api-n.xf-yun.com/v1.1/chat" # 微调v1.5环境的地址 Spark_url = "wss://spark-api-n.xf-yun.com/v3.1/chat" # 微调v3.0环境的地址 text =[] # length = 0 def getText(role,content): jsoncon = {} jsoncon["role"] = role jsoncon["content"] = content text.append(jsoncon) return text def getlength(text): length = 0 for content in text: temp = content["content"] leng = len(temp) length += leng return length def checklen(text): while (getlength(text) > 8000): del text[0] return text def core_run(text,prompt): # print('prompt',prompt) text.clear Input = prompt question = checklen(getText("user",Input)) SparkApi.answer ="" # print("星火:",end = "") SparkApi.main(appid,api_key,api_secret,Spark_url,domain,question) getText("assistant",SparkApi.answer) # print(text) return text[-1]['content'] text = [] res = core_run(text,'你好吗?') import pandas as pd import re # 读取Excel文件 df_test = pd.read_csv('test.csv',) data_dict_empty = { "基本信息-姓名": "", "基本信息-手机号码": "", "基本信息-邮箱": "", "基本信息-地区": "", "基本信息-详细地址": "", "基本信息-性别": "", "基本信息-年龄": "", "基本信息-生日": "", "咨询类型": [], "意向产品": [], "购买异议点": [], "客户预算-预算是否充足": "", "客户预算-总体预算金额": "", "客户预算-预算明细": "", "竞品信息": "", "客户是否有意向": "", "客户是否有卡点": "", "客户购买阶段": "", "下一步跟进计划-参与人": [], "下一步跟进计划-时间点": "", "下一步跟进计划-具体事项": "" } submit_data = [] for id,line_data in tqdm(enumerate(df_test['input'])): # print(line_data) content = line_data text = [] prompt = json.dumps(content,ensure_ascii=False) # print(json.dumps(content,ensure_ascii=False)) res = core_run(text,prompt) try: data_dict = json.loads(res) except json.JSONDecodeError as e: data_dict = data_dict_empty submit_data.append({"infos":data_dict,"index":id+1}) # 预计执行8min write_json("submit.json",submit_data)
6.28 按照datawhale提供的一站式baseline教程完成了第一次比赛提交
目前排名:349
score:15.45
至此、
}
submit_data = []
for id,line_data in tqdm(enumerate(df_test[‘input’])):
# print(line_data)
content = line_data
text = []
prompt = json.dumps(content,ensure_ascii=False)
# print(json.dumps(content,ensure_ascii=False))
res = core_run(text,prompt)
try:
data_dict = json.loads(res)
except json.JSONDecodeError as e:
data_dict = data_dict_empty
submit_data.append({"infos":data_dict,"index":id+1})
write_json(“submit.json”,submit_data)
# 五、我的打榜过程 6.28 按照datawhale提供的一站式baseline教程完成了第一次比赛提交 目前排名:349 score:15.45 # 六、注意事项及答疑学习 ## 注意事项 ## 答疑学习 **至此、** **谢谢你能阅读到它的结尾。**
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。