零基础入门大模型技术竞赛
本文Tag:#AI夏令营 #Datawhale #夏令营
Datawhale 2024 年 AI 夏令营第一期 的学习活动( “大模型技术”方向),基于讯飞开放平台 “基于星火大模型的群聊对话分角色要素提取挑战赛” 开展实践学习。
有Datawhale提供的baseline,我们几乎可以一键速通,直接get到17的分数。
具体可以参考:Datawhale Baseline
流程
- 下载相关库(大概 10s)
- 配置导入
- 模型测试
- 数据读取
- Prompt设计
- 主函数启动
- 生成提交文件
- 下载 output.json 文件
下面我们仅展示核心的代码。
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)
通过调用 get_completions 函数,我们验证了模型的配置是否正确。返回的结果应为类似 "你好!有什么我可以帮助你的吗?"。
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格式输出。
不要添加任何澄清信息。
输出必须遵循上面的模式。
不要添加任何没有出现在模式中的附加字段。
不要随意删除字段。
**输出:**
[{{
"基本信息-姓名": "姓名",
"基本信息-手机号码": "手机号码",
"基本信息-邮箱": "邮箱",
"基本信息-地区": "地区",
"基本信息-详细地址": "详细地址",
"基本信息-性别": "性别",
"基本信息-年龄": "年龄",
"基本信息-生日": "生日",
"咨询类型": ["咨询类型"],
"意向产品": ["意向产品"],
"购买异议点": ["购买异议点"],
"客户预算-预算是否充足": "充足或不充足",
"客户预算-总体预算金额": "总体预算金额",
"客户预算-预算明细": "预算明细",
"竞品信息": "竞品信息",
"客户是否有意向": "有意向或无意向",
"客户是否有卡点": "有卡点或无卡点",
"客户购买阶段": "购买阶段",
"下一步跟进计划-参与人": ["跟进计划参与人"],
"下一步跟进计划-时间点": "跟进计划时间点",
"下一步跟进计划-具体事项": "跟进计划具体事项"
}}, ...]
"""
我们设计了一个详细的prompt,确保模型能够提取出符合指定格式的信息。这一步至关重要,直接影响模型的输出质量。
但是我们也注意到,原始的聊天数据其实是一坨的🤣。
除去调整prompt之外,对数据的预处理以及限定不同格式的输出应该可以提升不少的分数。
我们继续看到主函数启动的步骤中就有相关的限定输出的实现。
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 isinstance(data, dict):
for key in required_keys:
if key not in data:
data[key] = [] if required_keys[key] is list else ""
elif not isinstance(data[key], required_keys[key]):
raise JsonFormatError(f"Key {key} has wrong type {type(data[key])}, should be {required_keys[key]}")
elif isinstance(data, list):
for item in data:
check_and_complete_json_format(item)
else:
raise JsonFormatError(f"Unsupported type: {type(data)}")
return data
def extract_info(content):
"""从对话中提取信息"""
text = PROMPT_EXTRACT.format(content=content)
result = get_completions(text)
result = convert_all_json_in_text_to_dict(result)
result = check_and_complete_json_format(result)
return result
# 测试提取功能
test_content = "你的群聊对话记录..."
print_json_format(extract_info(test_content))
我们定义了主要的 extract_info 函数,并包含辅助函数来确保输出格式的正确性。通过这个步骤,我们可以从对话记录中提取结构化信息。
跑通之后不出意外可以得到一个17分。
但是可以实现的操作不仅如此,进一步查阅完baseline2、3的实现,其实也还可以通过微调的方式进一步规范模型的输出。(2小时过去了,现在模型微调还在排队中...)
等待的过程中,我们可以进一步分析数据集的分布和示例。
with open(file_path, 'r', encoding='utf-8') as f:
data = json.load(f)
# 分析 chat_text 长度
lengths = [len(item['chat_text']) for item in data]
max_length = max(lengths)
min_length = min(lengths)
avg_length = sum(lengths) / len(lengths)
print(f"Number of records: {len(data)}")
print(f"Max length: {max_length}")
print(f"Min length: {min_length}")
print(f"Average length: {avg_length:.2f}")
输出如下:
Number of records: 129
Max length: 29578
Min length: 334
Average length: 6891.41
输出中,发现最长的内容竟然达到29578个字符,不禁怀疑,这么长的片段,大模型吃的下吗?
我们接着可以分析数据集中的其他字段,并计算其统计信息。下面是对不同的字段进行统计分析,以便于我们观察数据的输出风格 。
部分输出如下:
借此,我们可以挖掘几个重要信息!(其实是推测的)
- 咨询类型类似于一种词语叠加的复合属性。
- 性别极少会透露出是男是女
- 年龄和生日完全没有展示在对话记录里面(但是不确保测试集有没有,亦或者这只是一个干扰项)
- 客户意向是‘有意向’‘无意向’的单选或者留空
- 竞品信息也没有什么参考价值。
还有两个分布回答具有一定的倾向性:
在百来条数据中,产品功能是占据了购买异议点的一半,而这异议点没有空缺的部分。几乎都是产品功能,客户内部问题。(是否可以根据此进行prompt追加示例,给模型输出带来倾向的改造呢?)
这里留一个念想,等着后续微调结果再给结果吧。