I have translated the provided text.

Here is the translated result:

设置

要完成以下指南,您需要安装以下软件包:

  • anthropic
  • voyageai
  • pandas
  • matplotlib
  • sklearn
  • numpy

您还需要:

  • Anthropic API 密钥
  • VoyageAI API 密钥(可选)
    • 嵌入已预先计算,但如果您进行任何更改,则需要 API 密钥
!pip install anthropic
!pip install voyageai
!pip install pandas
!pip install numpy
!pip install matplotlib
!pip install -U scikit-learn
import os

os.environ['VOYAGE_API_KEY'] = "VOYAGE KEY HERE"
os.environ['ANTHROPIC_API_KEY'] = "ANTHROPIC KEY HERE"
# 设置我们的环境
import anthropic
import os

client = anthropic.Anthropic(
    # 这是默认值,可以省略
    api_key=os.getenv("ANTHROPIC_API_KEY"),
)

使用 Claude 进行分类

大型语言模型(LLM)彻底改变了分类领域,特别是在传统机器学习系统面临挑战的领域。LLM 在处理具有复杂业务规则以及低质量或有限训练数据的分类问题方面取得了显著成功。此外,LLM 能够为其操作提供自然语言解释和理由,从而增强了分类过程的可解释性和透明度。通过利用 LLM 的强大功能,我们可以构建超越传统机器学习方法能力的分类系统,并在数据稀缺或业务需求复杂的情况下表现出色。

在本指南中,我们将探讨如何利用 LLM 来处理高级分类任务。我们将涵盖以下关键组件和步骤:

  1. 数据准备:我们将首先准备我们的训练和测试数据。训练数据将用于构建分类模型,而测试数据将用于评估其性能。正确的数据准备对于确保我们分类系统的有效性至关重要。

  2. 提示工程:提示工程在利用 LLM 进行分类方面发挥着至关重要的作用。我们将设计一个提示模板,该模板定义了用于分类的提示的结构和格式。提示模板将结合用户查询、类别定义以及来自向量数据库的相关示例。通过精心设计提示,我们可以指导 LLM 生成准确且符合上下文的分类。

  3. 实施检索增强生成(RAG):为了增强分类过程,我们将使用向量数据库来存储和高效检索我们训练数据的嵌入。向量数据库支持相似性搜索,使我们能够为给定查询查找最相关的示例。通过用检索到的示例增强 LLM,我们可以提供额外的上下文并提高生成分类的准确性。

  4. 测试和评估:构建好分类系统后,我们将使用转换后的测试数据对其性能进行严格测试。我们将遍历测试查询,使用分类函数对每个查询进行分类,并将预测的类别与预期的类别进行比较。通过分析分类结果,我们可以评估我们系统的有效性并确定需要改进的领域。

问题定义:保险支持工单分类器

注意:此示例中使用的问题定义、数据和标签由 Claude 3 Opus 合成生成

在保险行业,客户支持在确保客户满意度和留存率方面发挥着至关重要的作用。保险公司每天收到大量支持工单,涵盖广泛的主题,例如账单、保单管理、理赔协助等。手动对这些工单进行分类可能耗时且效率低下,导致响应时间延长,并可能影响客户体验。

类别定义

  1. 账单咨询 - 关于发票、费用、收费和保费的问题 - 要求澄清账单明细 - 关于付款方式和到期日的咨询

  2. 保单管理 - 请求更改、更新或取消保单 - 关于保单续订和恢复的查询 - 关于添加或删除保险选项的咨询

  3. 理赔协助 - 关于理赔流程和提交程序的疑问 - 请求协助提交理赔文件 - 关于理赔状态和赔付时间表的咨询

  4. 保险范围解释 - 关于特定保单类型涵盖内容的疑问 - 请求澄清保险范围限制和排除项 - 关于免赔额和自付费用的咨询

  5. 报价和提案 - 新保单报价和价格比较请求 - 关于可用折扣和捆绑选项的查询 - 关于从其他保险公司转换的咨询

  6. 账户管理 - 登录凭证或密码重置请求 - 关于在线账户功能和用途的查询 - 关于更新联系方式或个人信息的咨询

  7. 账单争议 - 对意外或不正确收费的投诉 - 退款或保费调整请求 - 关于滞纳金或催款通知的咨询

  8. 理赔争议 - 对被拒或赔付不足的理赔的投诉 - 要求重新考虑理赔决定 - 关于上诉理赔结果的咨询

  9. 保单比较 - 关于保单选项之间差异的问题 - 请求帮助在不同保险级别之间做出选择 - 关于保单与竞争对手产品比较的咨询

  10. 一般咨询 - 关于公司联系信息或营业时间的疑问 - 一般性产品或服务信息请求 - 不属于其他类别的查询

标记数据

数据可以在此 Google 表格 中找到,您也可以在 data 文件夹中找到相同的数据。

我们将使用以下数据集:

  • ./data/test.tsv
  • ./data/train/tsv
import pandas as pd

data = {
    'train': [],
    'test': [],
    'test_2': []
}

# 将 DataFrame 转换为字典列表的辅助函数
def dataframe_to_dict_list(df):
    return df.apply(lambda x: {'text': x['text'], 'label': x['label']}, axis=1).tolist()


# 将 TSV 文件读入 DataFrame
test_df = pd.read_csv("./data/test.tsv", sep='\t')
data['test'] = dataframe_to_dict_list(test_df)

train_df = pd.read_csv("./data/train.tsv", sep='\t')
data['train'] = dataframe_to_dict_list(train_df)


# 理解数据集中的标签
labels = list(set(train_df['label'].unique()))

# 打印第一个训练示例和训练示例的数量
print(data['train'][0], len(data['train']))

# 创建测试集
X_test = [example['text'] for example in data['test']]
y_test = [example['label'] for example in data['test']]

# 打印测试集的长度
print(len(X_test), len(y_test))
{'text': 'I just got my auto policy renewal bill and the cost seems to be more than what I usually pay. Could you explain the reason for the increase?', 'label': 'Billing Inquiries'} 68
68 68

评估每个分类模型

evaluate 函数接受以下参数:

  • X:输入特征。
  • y:真实标签。
  • classifier:要评估的分类器函数。
  • batch_size:分类的每个批次的大小(默认为该层允许的最大批次大小)。

plot_confusion_matrix 函数接受以下参数:

  • cm:混淆矩阵。
  • labels:类别的标签。

通过使用此评估代码,您可以评估分类器的性能并可视化混淆矩阵,以深入了解模型的预测。

调整 MAXIMUM_CONCURRENT_REQUESTS 以匹配您的 Anthropic 账户的速率限制,参见此处

import matplotlib.pyplot as plt
from sklearn.metrics import classification_report, confusion_matrix
import concurrent.futures
import numpy as np

#您可以增加此数字以加快评估速度,但请记住您可能需要更高的 API 速率限制
#有关更多详细信息,请参阅 https://docs.anthropic.com/en/api/rate-limits#rate-limits
MAXIMUM_CONCURRENT_REQUESTS = 1

def plot_confusion_matrix(cm, labels):
    # 可视化混淆矩阵
    fig, ax = plt.subplots(figsize=(8, 8))
    im = ax.imshow(cm, cmap='Blues')

    # 添加颜色条
    cbar = ax.figure.colorbar(im, ax=ax)

    # 设置刻度标签和位置
    ax.set_xticks(np.arange(len(labels)))
    ax.set_yticks(np.arange(len(labels)))
    ax.set_xticklabels(labels, rotation=45, ha='right')
    ax.set_yticklabels(labels)

    # 为每个单元格添加标签
    thresh = cm.max() / 2.
    for i in range(len(labels)):
        for j in range(len(labels)):
            ax.text(j, i, cm[i, j],
                    ha='center', va='center',
                    color='white' if cm[i, j] > thresh else 'black')

    # 设置标签和标题
    plt.xlabel('预测标签')
    plt.ylabel('真实标签')
    plt.title('混淆矩阵')
    plt.tight_layout()
    plt.show()

def evaluate(X, y, classifier, batch_size=MAXIMUM_CONCURRENT_REQUESTS):
    # 初始化存储预测和真实标签的列表
    y_true = []
    y_pred = []

    # 创建一个 ThreadPoolExecutor
    with concurrent.futures.ThreadPoolExecutor() as executor:
        # 将分类任务分批提交给执行程序
        futures = []
        for i in range(0, len(X), batch_size):
            batch_X = X[i:i+batch_size]
            batch_futures = [executor.submit(classifier, x) for x in batch_X]
            futures.extend(batch_futures)

        # 按原始顺序检索结果
        for i, future in enumerate(futures):
            predicted_label = future.result()
            y_pred.append(predicted_label)
            y_true.append(y[i])

    # 规范化 y_true 和 y_pred
    y_true = [label.strip() for label in y_true]
    y_pred = [label.strip() for label in y_pred]

    # 计算分类指标
    report = classification_report(y_true, y_pred, labels=labels, zero_division=1)
    cm = confusion_matrix(y_true, y_pred, labels=labels)
    print(report)
    plot_confusion_matrix(cm, labels)

随机分类器

为了演示我们的 evaluate 函数的输出,我们可以定义一个随机分类器。

import random

def random_classifier(text):
    return random.choice(labels)
print("正在评估随机分类方法在测试集上的表现...")
evaluate(X_test, y_test, random_classifier)
正在评估随机分类方法在测试集上的表现...
                       精确率    召回率  f1-score   支持度

Coverage Explanations       0.00      0.00      0.00         9
    Billing Inquiries       0.00      0.00      0.00         6
   Account Management       0.29      0.33      0.31         6
     Billing Disputes       0.29      0.22      0.25         9
Policy Administration       0.09      0.17      0.12         6
    Claims Assistance       0.00      0.00      0.00         6
      Claims Disputes       0.17      0.11      0.13         9
 Quotes and Proposals       0.00      0.00      0.00         5
   Policy Comparisons       0.00      0.00      0.00         5
    General Inquiries       0.14      0.14      0.14         7

             准确率                           0.10        68
            宏平均       0.10      0.10      0.10        68
         加权平均       0.11      0.10      0.10        68

png

简单分类测试

现在让我们使用 Claude 构建一个简单的分类器。

首先,我们将使用 XML 格式对类别进行编码。这将使 Claude 更容易理解信息。使用 XML 编码信息是一种通用的提示策略,有关更多信息,请参见此处

import textwrap
categories = textwrap.dedent("""<category>
    <label>Billing Inquiries</label>
    <content> Questions about invoices, charges, fees, and premiums Requests for clarification on billing statements Inquiries about payment methods and due dates
    </content>
</category>
<category>
    <label>Policy Administration</label>
    <content> Requests for policy changes, updates, or cancellations Questions about policy renewals and reinstatements Inquiries about adding or removing coverage options
    </content>
</category>
<category>
    <label>Claims Assistance</label>
    <content> Questions about the claims process and filing procedures Requests for help with submitting claim documentation Inquiries about claim status and payout timelines
    </content>
</category>
<category>
    <label>Coverage Explanations</label>
    <content> Questions about what is covered under specific policy types Requests for clarification on coverage limits and exclusions Inquiries about deductibles and out-of-pocket expenses
    </content>
</category>
<category>
    <label>Quotes and Proposals</label>
    <content> Requests for new policy quotes and price comparisons Questions about available discounts and bundling options Inquiries about switching from another insurer
    </content>
</category>
<category>
    <label>Account Management</label>
    <content> Requests for login credentials or password resets Questions about online account features and functionality Inquiries about updating contact or personal information
    </content>
</category>
<category>
    <label>Billing Disputes</label>
    <content> Complaints about unexpected or incorrect charges Requests for refunds or premium adjustments Inquiries about late fees or collection notices
    </content>
</category>
<category>
    <label>Claims Disputes</label>
    <content> Complaints about denied or underpaid claims Requests for reconsideration of claim decisions Inquiries about appealing a claim outcome
    </content>
</category>
<category>
    <label>Policy Comparisons</label>
    <content> Questions about the differences between policy options Requests for help deciding between coverage levels Inquiries about how policies compare to competitors' offerings
    </content>
</category>
<category>
    <label>General Inquiries</label>
    <content> Questions about company contact information or hours of operation Requests for general information about products or services Inquiries that don't fit neatly into other categories
    </content>
</category>""")

接下来,我们将构建一个分类函数,该函数执行以下操作:

  • 定义提示模板
  • 输入我们提示模板中的变量
  • 提取规范化响应

请注意,我们利用 role: assistant 消息和 stop_sequences 参数来重复提取结果。

def simple_classify(X):
    prompt = textwrap.dedent("""
    您将把客户支持工单分类到以下类别之一:
    <categories>
        {{categories}}
    </categories>

    这是客户支持工单:
    <ticket>
        {{ticket}}
    </ticket>

    仅在类别标签之间响应。
    """).replace("{{categories}}", categories).replace("{{ticket}}", X)
    response = client.messages.create(
        messages=[{"role":"user", "content": prompt}, {"role":"assistant", "content": "<category>"}],
        stop_sequences=["</category>"],
        max_tokens=4096,
        temperature=0.0,
        model="claude-3-haiku-20240307"
    )

    # 从响应中提取结果
    result = response.content[0].text.strip()
    return result
print("正在评估简单分类方法在测试集上的表现...")
evaluate(X_test, y_test, simple_classify)
正在评估简单分类方法在测试集上的表现...
                       精确率    召回率  f1-score   支持度

Coverage Explanations       0.47      1.00      0.64         9
    Billing Inquiries       0.44      0.67      0.53         6
   Account Management       1.00      1.00      1.00         6
     Billing Disputes       0.80      0.44      0.57         9
Policy Administration       0.50      0.33      0.40         6
    Claims Assistance       1.00      1.00      1.00         6
      Claims Disputes       1.00      1.00      1.00         9
 Quotes and Proposals       1.00      0.60      0.75         5
   Policy Comparisons       0.71      1.00      0.83         5
    General Inquiries       1.00      0.00      0.00         7

             准确率                           0.71        68
            宏平均       0.79      0.70      0.67        68
         加权平均       0.79      0.71      0.67        68

png

这些结果比随机的要好,但肯定可以改进!让我们在提示中添加 RAG 和 K-shot 示例。

为此,我们将需要利用向量数据库,这将允许我们将给定的查询与训练数据中的相似示例进行匹配。这些示例有望提高我们分类器的准确性。

我们将构建一个简单的 VectorDB 类,该类利用 VoyageAI 创建的嵌入模型。

import os
import numpy as np
import voyageai
import pickle
import json

class VectorDB:
    def __init__(self, api_key=None):
        if api_key is None:
            api_key = os.getenv("VOYAGE_API_KEY")
        self.client = voyageai.Client(api_key=api_key)
        self.embeddings = []
        self.metadata = []
        self.query_cache = {}
        self.db_path = "./data/vector_db.pkl"

    def load_data(self, data):
        # 检查向量数据库是否已加载
        if self.embeddings and self.metadata:
            print("向量数据库已加载。跳过数据加载。")
            return
        # 检查 vector_db.pkl 是否存在
        if os.path.exists(self.db_path):
            print("正在从磁盘加载向量数据库。")
            self.load_db()
            return

        texts = [item["text"] for item in data]

        # 使用 for 循环嵌入超过 128 个文档
        batch_size = 128
        result = [
            self.client.embed(
                texts[i : i + batch_size],
                model="voyage-2"
            ).embeddings
            for i in range(0, len(texts), batch_size)
        ]

        # 展平嵌入
        self.embeddings = [embedding for batch in result for embedding in batch]
        self.metadata = [item for item in data]
        self.save_db()
        # 将向量数据库保存到磁盘
        print("向量数据库已加载并保存。")

    def search(self, query, k=5, similarity_threshold=0.75):
        query_embedding = None
        if query in self.query_cache:
            query_embedding = self.query_cache[query]
        else:
            query_embedding = self.client.embed([query], model="voyage-2").embeddings[0]
            self.query_cache[query] = query_embedding

        if not self.embeddings:
            raise ValueError("向量数据库中没有加载数据。")

        similarities = np.dot(self.embeddings, query_embedding)
        top_indices = np.argsort(similarities)[::-1]
        top_examples = []

        for idx in top_indices:
            if similarities[idx] >= similarity_threshold:
                example = {
                    "metadata": self.metadata[idx],
                    "similarity": similarities[idx],
                }
                top_examples.append(example)

                if len(top_examples) >= k:
                    break
        self.save_db()
        return top_examples

    def save_db(self):
        data = {
            "embeddings": self.embeddings,
            "metadata": self.metadata,
            "query_cache": json.dumps(self.query_cache),
        }
        with open(self.db_path, "wb") as file:
            pickle.dump(data, file)

    def load_db(self):
        if not os.path.exists(self.db_path):
            raise ValueError("未找到向量数据库文件。请使用 load_data 创建新数据库。")

        with open(self.db_path, "rb") as file:
            data = pickle.load(file)

        self.embeddings = data["embeddings"]
        self.metadata = data["metadata"]
        self.query_cache = json.loads(data["query_cache"])

我们可以定义向量数据库并加载我们的训练数据。

VoyageAI 对没有关联信用卡账户的账户的费率为 3RPM。为了便于演示,我们将利用缓存。

vectordb = VectorDB()
vectordb.load_data(data["train"])
正在从磁盘加载向量数据库。
向量数据库已加载并保存。

RAG 分类提示

在此提示中,我们利用检索增强生成(RAG)来插入与给定查询在语义上相似的训练数据示例。

def rag_classify(X):
    rag = vectordb.search(X,5)
    rag_string = ""
    for example in rag:
        rag_string += textwrap.dedent(f"""
        <example>
            <query>
                "{example["metadata"]["text"]}"
            </query>
            <label>
                {example["metadata"]["label"]}
            </label>
        </example>
        """)
    prompt = textwrap.dedent("""
    您将把客户支持工单分类到以下类别之一:
    <categories>
        {{categories}}
    </categories>

    这是客户支持工单:
    <ticket>
        {{ticket}}
    </ticket>

    请使用以下示例来帮助您分类查询:
    <examples>
        {{examples}}
    </examples>

    仅在类别标签之间响应。
    """).replace("{{categories}}", categories).replace("{{ticket}}", X).replace("{{examples}}", rag_string)
    response = client.messages.create(
        messages=[{"role":"user", "content": prompt}, {"role":"assistant", "content": "<category>"}],
        stop_sequences=["</category>"],
        max_tokens=4096,
        temperature=0.0,
        model="claude-3-haiku-20240307"
    )

    # 从响应中提取结果
    result = response.content[0].text.strip()
    return result
print("正在评估 RAG 方法在测试集上的表现...")
evaluate(X_test, y_test, rag_classify)
正在评估 RAG 方法在测试集上的表现...
                       精确率    召回率  f1-score   支持度

Coverage Explanations       0.75      1.00      0.86         9
    Billing Inquiries       1.00      0.67      0.80         6
   Account Management       1.00      1.00      1.00         6
     Billing Disputes       0.82      1.00      0.90         9
Policy Administration       1.00      1.00      1.00         6
    Claims Assistance       1.00      1.00      1.00         6
      Claims Disputes       1.00      1.00      1.00         9
 Quotes and Proposals       1.00      1.00      1.00         5
   Policy Comparisons       1.00      1.00      1.00         5
    General Inquiries       1.00      0.57      0.73         7

             准确率                           0.93        68
            宏平均       0.96      0.92      0.93        68
         加权平均       0.94      0.93      0.92        68

png

RAG 分类与思维链提示

此提示通过允许 Claude 反思结果来构建,从而提高分类的准确性。

def rag_chain_of_thought_classify(X):
    rag = vectordb.search(X,5)
    rag_string = ""
    for example in rag:
        rag_string += textwrap.dedent(f"""
        <example>
            <query>
                "{example["metadata"]["text"]}"
            </query>
            <label>
                {example["metadata"]["label"]}
            </label>
        </example>
        """)
    prompt = textwrap.dedent("""
    您将把客户支持工单分类到以下类别之一:
    <categories>
        {{categories}}
    </categories>

    这是客户支持工单:
    <ticket>
        {{ticket}}
    </ticket>

    请使用以下示例来帮助您分类查询:
    <examples>
        {{examples}}
    </examples>

    首先,您将在 scratchpad 标签中逐步思考问题。
    您应该考虑所有提供的信息,并为您的分类创建一个具体的论点。

    请使用以下格式进行响应:
    <response>
        <scratchpad>您的想法和分析在此处</scratchpad>
        <category>您选择的类别标签在此处</category>
    </response>
    """).replace("{{categories}}", categories).replace("{{ticket}}", X).replace("{{examples}}", rag_string)
    response = client.messages.create(
        messages=[{"role":"user", "content": prompt}, {"role":"assistant", "content": "<response><scratchpad>"}],
        stop_sequences=["</category>"],
        max_tokens=4096,
        temperature=0.0,
        model="claude-3-haiku-20240307"
    )

    # 从响应中提取结果
    result = response.content[0].text.split("<category>")[1].strip()
    return result
print("正在评估带思维链的 RAG 方法在测试集上的表现...")
evaluate(X_test, y_test, rag_chain_of_thought_classify)
正在评估带思维链的 RAG 方法在测试集上的表现...
                       精确率    召回率  f1-score   支持度

Coverage Explanations       0.82      1.00      0.90         9
    Billing Inquiries       1.00      0.67      0.80         6
   Account Management       1.00      1.00      1.00         6
     Billing Disputes       0.82      1.00      0.90         9
Policy Administration       1.00      1.00      1.00         6
    Claims Assistance       1.00      1.00      1.00         6
      Claims Disputes       1.00      1.00      1.00         9
 Quotes and Proposals       1.00      1.00      1.00         5
   Policy Comparisons       1.00      1.00      1.00         5
    General Inquiries       1.00      0.71      0.83         7

             准确率                           0.94        68
            宏平均       0.96      0.94      0.94        68
         加权平均       0.95      0.94      0.94        68

png

评估

本指南说明了在进行提示工程时,通过经验衡量提示性能的重要性。您可以在此处阅读有关我们经验性提示工程方法的更多信息。使用 Jupyter Notebook 是开始提示工程的好方法,但随着数据集的增长和提示数量的增加,利用能够与您一起扩展的工具非常重要。

在本指南部分,我们将探讨使用 Promptfoo,一个开源 LLM 评估工具包。要开始,请前往 ./evaluation 目录并查看 ./evaluation/README.md

当您成功运行评估后,请回到此处查看结果。

import json
import pandas as pd

promptfoo_results = pd.read_csv("./data/results.csv")
examples_columns = promptfoo_results.columns[2:]

number_of_providers = 5
number_of_prompts = 3

prompts = ["Simple", "RAG", "RAG w/ CoT"]

columns = ["label", "text"] + [
    json.loads(examples_columns[prompt * number_of_providers + provider])["provider"]

    + " Prompt: "
    + str(prompts[prompt])
    for prompt in range(number_of_prompts)
    for provider in range(number_of_providers)
]

promptfoo_results.columns = columns

result = (
    promptfoo_results.iloc[:, 2:]
    .astype(str)
    .apply(lambda x: x.str.count("PASS"))
    .sum()
    / len(promptfoo_results)

    * 100
).sort_values(ascending=False)

print(result)
Haiku: T-0.0 Prompt: RAG w/ CoT    95.588235
Haiku: T-0.2 Prompt: RAG w/ CoT    95.588235
Haiku: T-0.8 Prompt: RAG w/ CoT    95.588235
Haiku: T-0.0 Prompt: RAG           94.117647
Haiku: T-0.4 Prompt: RAG           94.117647
Haiku: T-0.6 Prompt: RAG           94.117647
Haiku: T-0.4 Prompt: RAG w/ CoT    94.117647
Haiku: T-0.6 Prompt: RAG w/ CoT    94.117647
Haiku: T-0.2 Prompt: RAG           92.647059
Haiku: T-0.8 Prompt: RAG           89.705882
Haiku: T-0.6 Prompt: Simple        72.058824
Haiku: T-0.0 Prompt: Simple        70.588235
Haiku: T-0.2 Prompt: Simple        70.588235
Haiku: T-0.8 Prompt: Simple        70.588235
Haiku: T-0.4 Prompt: Simple        69.117647
dtype: float64