Datawhale AI 夏令营第一期 学习笔记

1,201 阅读7分钟

零基础入门大模型技术竞赛

本文Tag:#AI夏令营 #Datawhale #夏令营

Datawhale 2024 年 AI 夏令营第一期 的学习活动( “大模型技术”方向),基于讯飞开放平台 “基于星火大模型的群聊对话分角色要素提取挑战赛” 开展实践学习。

image.png

有Datawhale提供的baseline,我们几乎可以一键速通,直接get到17的分数。

具体可以参考:Datawhale Baseline

流程

  1. 下载相关库(大概 10s)
  2. 配置导入
  3. 模型测试
  4. 数据读取
  5. Prompt设计
  6. 主函数启动
  7. 生成提交文件
  8. 下载 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,确保模型能够提取出符合指定格式的信息。这一步至关重要,直接影响模型的输出质量。

但是我们也注意到,原始的聊天数据其实是一坨的🤣。

image.png

除去调整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分。

image.png

但是可以实现的操作不仅如此,进一步查阅完baseline2、3的实现,其实也还可以通过微调的方式进一步规范模型的输出。(2小时过去了,现在模型微调还在排队中...)

image.png

等待的过程中,我们可以进一步分析数据集的分布和示例。

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个字符,不禁怀疑,这么长的片段,大模型吃的下吗?

我们接着可以分析数据集中的其他字段,并计算其统计信息。下面是对不同的字段进行统计分析,以便于我们观察数据的输出风格 。

image.png

部分输出如下:

image.png

借此,我们可以挖掘几个重要信息!(其实是推测的)

  • 咨询类型类似于一种词语叠加的复合属性。
  • 性别极少会透露出是男是女
  • 年龄和生日完全没有展示在对话记录里面(但是不确保测试集有没有,亦或者这只是一个干扰项)
  • 客户意向是‘有意向’‘无意向’的单选或者留空
  • 竞品信息也没有什么参考价值。

还有两个分布回答具有一定的倾向性:

image.png

在百来条数据中,产品功能是占据了购买异议点的一半,而这异议点没有空缺的部分。几乎都是产品功能,客户内部问题。(是否可以根据此进行prompt追加示例,给模型输出带来倾向的改造呢?)

image.png

这里留一个念想,等着后续微调结果再给结果吧。