跳至主要内容

在电商分类数据集上微调 Phi-3.5

了解微软全新 LLM 系列,并通过在电商分类数据集上进行微调,将模型准确率从 65% 提升至 86%。
· 12分钟 阅读

使用 AI 探索

微软推出 Phi-3.5 系列,与 Meta AI 等共同角逐大语言模型赛道。该系列包含小型语言模型、视觉语言模型,并采用专家混合(Mixture-of-Experts)方法,带来一流表现。

在本教程中,我们将探索微软 Phi-3.5 模型族。我们将加载 Phi-3.5-mini-instruct 模型,并对其进行微调,使其能基于文本描述对电商产品进行分类。最后,我们将演示如何将 LoRA(低秩适配)与基础模型合并并推送至 Hugging Face,以便高效地在云端部署,使模型可用于各种应用场景。

选修《精通大语言模型(LLMs)概念》课程,了解 LLM 的应用、训练方法、伦理考量及最新研究。

Getting Started with Phi-3.5 feature images

图片作者自制

认识 Microsoft Phi-3.5

此次Microsoft Phi-3.5 发布带来了三款创新模型:Phi-3.5-mini、Phi-3.5-vision,以及最新的 Phi-3.5-MoE(专家混合模型)。

Phi-3.5-mini 针对多语言支持进行了优化,具备令人印象深刻的 128K 上下文长度。尽管体量更小,但凭借监督微调、近端策略优化和直接偏好优化等严格强化,性能可与更大模型比肩,且能严格遵循指令。

Phi-3.5-vision 是一款前沿、轻量的多模态模型,使用合成数据与筛选的公共网站数据进行训练。它擅长多帧图像理解与推理,适用于细致的图像对比、多图总结/讲述与视频摘要,应用前景广阔。

其中的亮点模型 Phi-3.5-MoE 采用包含 16 个专家、66 亿激活参数的专家混合架构,在保证强大安全性的同时,提供低时延与卓越性能,并具备完善的多语言支持。

Phi-3.5 系列为开源社区提供了高性价比且高性能的解决方案,推动小型语言模型与生成式 AI 的发展。

如需了解 Phi-3 的架构、特性与应用,请参阅《Phi-3 教程:上手微软最小 AI 模型》指南。

访问 Microsoft Phi-3.5 模型

本节我们将在 Kaggle 平台加载 Phi-3.5-mini-instruct 模型并运行推理。

  1. 启动启用 T4x2 GPU 的会话。

setting the GPU on Kaggle

  1. 使用 pip 命令安装所有必需的 Python 包。
%%capture
%pip install -U transformers accelerate
  1. 使用 Transformers 库加载完整模型与分词器。
  2. 使用模型与分词器创建 text-generation 管道。
from transformers import AutoTokenizer,AutoModelForCausalLM,pipeline
import torch

base_model = "microsoft/Phi-3.5-mini-instruct"

tokenizer = AutoTokenizer.from_pretrained(base_model)

model = AutoModelForCausalLM.from_pretrained(
        base_model,
        return_dict=True,
        low_cpu_mem_usage=True,
        torch_dtype=torch.float16,
        device_map="auto",
        trust_remote_code=True,
)


pipe = pipeline(
    "text-generation",
    model=model,
    tokenizer=tokenizer,
    torch_dtype=torch.float16,
    device_map="auto",
)
  1. 用系统提示与用户问题创建消息,并通过聊天模板转换为对话提示。
  2. 将提示提供给管道以生成回复。
messages = [
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": "What is the tallest building in the world?"}
]

# Apply the chat template to the messages
prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)

# Generate the output using the pipeline
outputs = pipe(prompt, max_new_tokens=120, do_sample=True)

# Print the generated text
print(outputs[0]["generated_text"])

我们得到了准确且详细的结果。

<|system|>
You are a helpful assistant.<|end|>
<|user|>
What is the tallest building in the world?<|end|>
<|assistant|>
 As of my knowledge cutoff in 2023, the tallest building in the world is the Burj Khalifa, located in Dubai, United Arab Emirates. It stands at a remarkable 828 meters (2,716.5 feet) tall once its antenna is included. Completed in January 2010, the Burj Khalifa marks a significant achievement in architecture and engineering, setting numerous records. It provides office space, luxury condominiums, and various leisure facilities. This landmark continues to
  1. 我们还可以基于说明与通话转写编写自定义提示,交给文本生成管道,以检查模型是否遵循用户指令。
prompt = """
        In a call center environment, classify customer interactions as 'Fraudulent' or 'Non-Fraudulent'. 
        Consider factors such as the nature of the inquiry, caller verification details, transaction history, and any red flags raised during the call.
        [Lisa Adams, contacts the call center claiming unauthorized transactions on her credit card statement. She demands a full refund, asserting that she has never visited the merchant in question.] =
        """
outputs = pipe(prompt, max_new_tokens=120, do_sample=True)
print(outputs[0]["generated_text"])

如您所见,模型表现不错,将该通话标记为欺诈并给出了理由。

 In a call center environment, classify customer interactions as 'Fraudulent' or 'Non-Fraudulent'. 
        Consider factors such as the nature of the inquiry, caller verification details, transaction history, and any red flags raised during the call.
        [Lisa Adams, contacts the call center claiming unauthorized transactions on her credit card statement. She demands a full refund, asserting that she has never visited the merchant in question.] =
        
        Call Interaction Classification: Fraudulent
        
        Explanation: 
        The situation described by Lisa Adams indicates a potential case of credit card fraud. There are several red flags in this interaction that suggest the customer might be reporting unauthorized transactions:

1. The caller claims unauthorized transactions - This is a common indicator of fraud, especially if the transactions were for places or services the customer did not recognize or didn't patronize according to their personal knowledge or documented transaction history (e.g., no visits to the

如果您在 Kaggle 平台运行模型时遇到问题,请参考《Phi-3.5 简单模型推理》Kaggle 笔记本。它包含预构建的环境与代码及其输出。

微调 Microsoft Phi-3.5-mini-instruct

本指南将学习如何加载并处理电商文本分类数据;加载模型与分词器;在微调前于测试集上评估模型;构建训练器;在训练集上微调模型;并在微调后进行测试。

如果您对流程还不熟悉,请先阅读我们的《LLM 微调入门指南》,了解微调背后的理论。

1. 环境设置

启动新的 Kaggle 笔记本并启用 GPU 加速。然后,使用 Kaggle secrets 将 Hugging Face 与 Weights & Biases 的 API token 设置为环境变量。

setting up environment variable in Kaggle.

安装所有必需的 Python 包。

%%capture
%pip install -U bitsandbytes
%pip install -U transformers
%pip install -U accelerate
%pip install -U peft
%pip install -U trl

登录 Weights & Biases 服务,签署 API key,并新建项目。

import wandb

from kaggle_secrets import UserSecretsClient
user_secrets = UserSecretsClient()

wb_token = user_secrets.get_secret("wandb")

wandb.login(key=wb_token)
run = wandb.init(
    project='Fine-tune Phi-3.5-it on Ecommerce Text Classification', 
    job_type="training", 
    anonymous="allow"

Weights and basis run link

最后,加载微调与评估过程中将用到的所有 Python 包与函数。

import numpy as np
import pandas as pd
import os
from tqdm import tqdm
import bitsandbytes as bnb
import torch
import torch.nn as nn
import transformers
from datasets import Dataset
from peft import LoraConfig, PeftConfig
from trl import SFTTrainer
from trl import setup_chat_format
from transformers import (AutoModelForCausalLM, 
                          AutoTokenizer, 
                          BitsAndBytesConfig, 
                          TrainingArguments, 
                          pipeline, 
                          logging)
from sklearn.metrics import (accuracy_score, 
                             classification_report, 
                             confusion_matrix)
from sklearn.model_selection import train_test_split

2. 加载与处理数据集

将E-commerce Text Classification 数据集添加到您的笔记本,如下所示。该数据集包含两列:标签(电商类别)与产品的文本描述。

adding the dataset

加载 CSV 文件、进行处理,并查看前 5 行。

df = pd.read_csv("/kaggle/input/ecommerce-text-classification/ecommerceDataset.csv")
df.columns = ["label","text"]
df.loc[:,'label'] = df.loc[:,'label'].str.replace('Clothing & Accessories','Clothing')
df.head()

数据集由产品描述与类别标签构成。

viewing the dataset

将数据集打乱并仅选择前 2000 行。本指南以示例为主,通过在有限样本上微调加快流程。

接下来,我们将数据划分为训练集、验证集与测试集。

# Shuffle the DataFrame and select only 2000 rows
df = df.sample(frac=1, random_state=85).reset_index(drop=True).head(2000)

# Split the DataFrame
train_size = 0.8
eval_size = 0.1

# Calculate sizes
train_end = int(train_size * len(df))
eval_end = train_end + int(eval_size * len(df))

# Split the data
X_train = df[:train_end]
X_eval = df[train_end:eval_end]
X_test = df[eval_end:]

我们将创建两个函数。generate_prompt 会将文本列转换为提示,包含指令、文本描述与标签;generate_test_prompt 与之相同,但不包含标签。

# Define the prompt generation functions
def generate_prompt(data_point):
    return f"""
            Classify the E-commerce text into Electronics, Household, Books and Clothing.
text: {data_point["text"]}
label: {data_point["label"]}""".strip()

def generate_test_prompt(data_point):
    return f"""
            Classify the E-commerce text into Electronics, Household, Books and Clothing.
text: {data_point["text"]}
label: """.strip()

# Generate prompts for training and evaluation data
X_train.loc[:,'text'] = X_train.apply(generate_prompt, axis=1)
X_eval.loc[:,'text'] = X_eval.apply(generate_prompt, axis=1)

# Generate test prompts and extract true labels
y_true = X_test.loc[:,'label']
X_test = pd.DataFrame(X_test.apply(generate_test_prompt, axis=1), columns=["text"])

将 pandas 的训练与验证 DataFrame 转换为 Hugging Face 数据集。

# Convert to datasets
train_data = Dataset.from_pandas(X_train[["text"]])
eval_data = Dataset.from_pandas(X_eval[["text"]])

train_data['text'][3]

文本包含系统指令、产品描述与标签。

sample from dataset

3. 加载模型与分词器

使用仓库 ID 从 Hugging Face Hub 加载 4-bit 量化模型与分词器。随后完成必要设置,准备就绪。

base_model_name = "microsoft/Phi-3.5-mini-instruct"

bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_use_double_quant=False,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype="float16",
)

model = AutoModelForCausalLM.from_pretrained(
    base_model_name,
    device_map="auto",
    torch_dtype="float16",
    quantization_config=bnb_config, 
)

model.config.use_cache = False
model.config.pretraining_tp = 1

tokenizer = AutoTokenizer.from_pretrained(base_model_name)

tokenizer.pad_token_id = tokenizer.eos_token_id

4. 微调前的模型评估

在微调前需要评估基础模型,以判断微调是否带来提升。为此,我们将创建 predict 函数,接收测试数据集,并基于产品文本描述生成电商类别。

def predict(test, model, tokenizer):
    y_pred = []
    categories = ["Electronics", "Household", "Books", "Clothing"]
    
    for i in tqdm(range(len(test))):
        prompt = test.iloc[i]["text"]
        pipe = pipeline(task="text-generation", 
                        model=model, 
                        tokenizer=tokenizer, 
                        max_new_tokens=4, 
                        temperature=0.1)
        
        result = pipe(prompt)
        answer = result[0]['generated_text'].split("label:")[-1].strip()
        
        # Determine the predicted category
        for category in categories:
            if category.lower() in answer.lower():
                y_pred.append(category)
                break
        else:
            y_pred.append("none")
    
    return y_pred

y_pred = predict(X_test, model, tokenizer)

我们已得到预测类别列表,接下来将其与真实类别对比,生成模型评估报告。该 “evaluate” 函数接收预测与真实类别列表,输出详细评估,包括平均准确率、各类别单独准确率、分类报告与混淆矩阵。

def evaluate(y_true, y_pred):
    labels = ["Electronics", "Household", "Books", "Clothing"]
    mapping = {label: idx for idx, label in enumerate(labels)}
    
    def map_func(x):
        return mapping.get(x, -1)  # Map to -1 if not found, but should not occur with correct data
    
    y_true_mapped = np.vectorize(map_func)(y_true)
    y_pred_mapped = np.vectorize(map_func)(y_pred)
    
    # Calculate accuracy
    accuracy = accuracy_score(y_true=y_true_mapped, y_pred=y_pred_mapped)
    print(f'Accuracy: {accuracy:.3f}')
    
    # Generate accuracy report
    unique_labels = set(y_true_mapped)  # Get unique labels
    
    for label in unique_labels:
        label_indices = [i for i in range(len(y_true_mapped)) if y_true_mapped[i] == label]
        label_y_true = [y_true_mapped[i] for i in label_indices]
        label_y_pred = [y_pred_mapped[i] for i in label_indices]
        label_accuracy = accuracy_score(label_y_true, label_y_pred)
        print(f'Accuracy for label {labels[label]}: {label_accuracy:.3f}')
        
    # Generate classification report
    class_report = classification_report(y_true=y_true_mapped, y_pred=y_pred_mapped, target_names=labels, labels=list(range(len(labels))))
    print('\nClassification Report:')
    print(class_report)
    
    # Generate confusion matrix
    conf_matrix = confusion_matrix(y_true=y_true_mapped, y_pred=y_pred_mapped, labels=list(range(len(labels))))
    print('\nConfusion Matrix:')
    print(conf_matrix)


evaluate(y_true, y_pred)

我们获得了 65% 的平均准确率。看看微调是否能进一步提升。

Accuracy: 0.645
Accuracy for label Electronics: 0.950
Accuracy for label Household: 0.531
Accuracy for label Books: 0.561
Accuracy for label Clothing: 0.658

Classification Report:
              precision    recall  f1-score   support

 Electronics       0.46      0.95      0.62        40
   Household       0.83      0.53      0.65        81
       Books       0.96      0.56      0.71        41
    Clothing       0.86      0.66      0.75        38

   micro avg       0.69      0.65      0.67       200
   macro avg       0.78      0.67      0.68       200
weighted avg       0.79      0.65      0.67       200


Confusion Matrix:
[[38  0  1  0]
 [33 43  0  2]
 [ 9  6 23  2]
 [ 2  3  0 25]]

5. 模型设置

从模型中提取线性模块名称。

def find_all_linear_names(model):
    cls = bnb.nn.Linear4bit
    lora_module_names = set()
    for name, module in model.named_modules():
        if isinstance(module, cls):
            names = name.split('.')
            lora_module_names.add(names[0] if len(names) == 1 else names[-1])
    if 'lm_head' in lora_module_names:  # needed for 16 bit
        lora_module_names.remove('lm_head')
    return list(lora_module_names)
modules = find_all_linear_names(model)
modules
['gate_up_proj', 'down_proj', 'qkv_proj', 'o_proj']

使用线性模块名称来创建 LoRA。我们仅微调 LoRA,保留其余模型参数不变,以节省内存并加快训练。

接着,为 Kaggle 环境配置模型超参数。您可根据机器配置调整这些参数,以提升准确率或缩短训练时间。关于各超参数的说明,请参阅《微调 Llama 2》教程。

现在设置监督微调(SFT)训练器,提供训练与验证数据集、LoRA 配置、训练参数、分词器与模型。

output_dir="Phi-3.5-mini-instruct"

peft_config = LoraConfig(
    lora_alpha=16,
    lora_dropout=0,
    r=64,
    bias="none",
    task_type="CAUSAL_LM",
    target_modules=modules,
)

training_arguments = TrainingArguments(
    output_dir=output_dir,                    # directory to save and repository id
    num_train_epochs=1,                       # number of training epochs
    per_device_train_batch_size=1,            # batch size per device during training
    gradient_accumulation_steps=4,            # number of steps before performing a backward/update pass
    gradient_checkpointing=True,              # use gradient checkpointing to save memory
    optim="paged_adamw_8bit",
    logging_steps=1,                         
    learning_rate=2e-5,                       # learning rate, based on QLoRA paper
    weight_decay=0.001,
    fp16=False,
    bf16=False,
    max_grad_norm=0.3,                        # max gradient norm based on QLoRA paper
    max_steps=-1,
    warmup_ratio=0.03,                        # warmup ratio based on QLoRA paper
    group_by_length=False,
    lr_scheduler_type="cosine",               # use cosine learning rate scheduler
    report_to="wandb",                  # report metrics to w&b
    eval_strategy="steps",              # save checkpoint every epoch
    eval_steps = 0.2
)

trainer = SFTTrainer(
    model=model,
    args=training_arguments,
    train_dataset=train_data,
    eval_dataset=eval_data,
    peft_config=peft_config,
    dataset_text_field="text",
    tokenizer=tokenizer,
    max_seq_length=512,
    packing=False,
    dataset_kwargs={
    "add_special_tokens": False,
    "append_concat_token": False,
    }
)

6. 模型训练

使用 .train 函数启动微调过程。

# Train model
trainer.train()

损失在逐步下降,如果增加训练轮次,结果可能更好。

model training

结束 Weights & Biases 运行以生成评估报告。

wandb.finish()
model.config.use_cache = True

training summary

您可以访问 Weights & Biases 网站,选择您的项目并查看训练分析,以评估模型表现。

training summary on W&B

将模型与分词器保存到本地,便于后续合并并推送到远端。

# Save trained model and tokenizer
trainer.save_model(output_dir)
tokenizer.save_pretrained(output_dir)

本指南代码量较多。如果您想要更简便的方案,可按照《LlaMA-Factory WebUI 新手指南:微调 LLM》微调一款开源模型。若希望进一步简化且无需操心硬件,我们强烈推荐《微调 GPT-4o Mini:分步指南》,可在云端以最少代码完成微调。

7. 微调后的模型测试

测试微调后模型的性能是否提升。我们先生成预测标签列表,并与真实标签一起传入 evaluate 函数。

y_pred = predict(X_test, model, tokenizer)
evaluate(y_true, y_pred)

模型准确率提升约 32.31%,其余性能指标同样出色。除 Books 类别外,其余类别的识别均相当准确。

Accuracy: 0.860
Accuracy for label Electronics: 0.825
Accuracy for label Household: 0.926
Accuracy for label Books: 0.683
Accuracy for label Clothing: 0.947

Classification Report:
              precision    recall  f1-score   support

 Electronics       0.97      0.82      0.89        40
   Household       0.88      0.93      0.90        81
       Books       0.90      0.68      0.78        41
    Clothing       0.88      0.95      0.91        38

   micro avg       0.90      0.86      0.88       200
   macro avg       0.91      0.85      0.87       200
weighted avg       0.90      0.86      0.88       200


Confusion Matrix:
[[33  6  1  0]
 [ 1 75  2  3]
 [ 0  3 28  2]
 [ 0  1  0 36]]

请务必通过右上角的 “Save Version” 按钮保存 Kaggle 笔记本。选择快速保存选项,并将保存输出设置为包含模型文件与全部代码。

Saving the Kaggle notebook.

如果您在微调过程中遇到问题,请参考《Fine-tune Phi-3.5 for Text Classification》Kaggle 笔记本。

合并并导出微调后的模型

要将模型合并并导出至 Hugging Face,我们将先创建一个新的 Kaggle 笔记本,并添加已保存的笔记本,以访问微调后的模型与分词器。

添加另一个 Kaggle 笔记本的方式与添加数据集或模型类似。点击 “+ Add Input” 按钮,粘贴笔记本链接,然后点击添加。

Adding the saved notebook to the Kaggle envrnoment.

使用 Kaggle secrets 将 Hugging Face API 设置为环境变量,并安装加载与合并模型所需的全部 Python 包。

%%capture
%pip install -U bitsandbytes
%pip install -U transformers
%pip install -U accelerate
%pip install -U peft

设置基础模型与微调后模型变量,分别为模型 ID 与适配器的位置。

# Model
base_model = "microsoft/Phi-3.5-mini-instruct"
fine_tuned_model = "/kaggle/input/fine-tune-phi-3-5-for-text-classification/Phi-3.5-mini-instruct/"

从 Hugging Face hub 加载完整基础模型与分词器。

from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
from peft import PeftModel
import torch
# Reload tokenizer and model
tokenizer = AutoTokenizer.from_pretrained(base_model)

base_model_reload = AutoModelForCausalLM.from_pretrained(
        base_model,
        return_dict=True,
        low_cpu_mem_usage=True,
        torch_dtype=torch.float16,
        device_map="auto",
        trust_remote_code=True,
)

仅用两行代码即可将基础模型与适配器合并。

# Merge adapter with base model
model = PeftModel.from_pretrained(base_model_reload, fine_tuned_model)
model = model.merge_and_unload()

为测试是否合并成功,我们将使用合并后的模型与分词器创建文本生成管道,并传入示例提示以生成响应。

text = "Inalsa Dazzle Glass Top, 3 Burner Gas Stove with Rust Proof Powder Coated Body, Black Toughened Glass Top, 2 Medium and 1 Small High Efficiency Brass Burners, Aluminum Mixing Tubes, Powder Coated Body, Inbuilt Stainless Steel Drip Trays, 360 degree Swivel Nozzle,Bigger Legs to Facilitate Cleaning Under Cooktop"
prompt = f"""Classify the E-commerce text into Electronics, Household, Books and Clothing.
text: {text}
label: """.strip()

pipe = pipeline(
    "text-generation",
    model=model,
    tokenizer=tokenizer,
    torch_dtype=torch.float16,
    device_map="auto",
)

outputs = pipe(prompt, max_new_tokens=4, do_sample=True, temperature=0.1)
print(outputs[0]["generated_text"].split("label: ")[-1].strip())1].strip())

模型准确预测了该产品的类别。

Household

我们将提供目录名,将完整模型保存到本地。

model_dir = "Phi-3.5-mini-instruct-Ecommerce-Text-Classification"
model.save_pretrained(model_dir)
tokenizer.save_pretrained(model_dir)

将已保存的模型推送到 Hugging Face hub。首先使用从 Kaggle secrets 中获取的 API key 登录 Hugging Face CLI,然后使用 push_to_hub 将模型与分词器一并推送。

from huggingface_hub import login
from kaggle_secrets import UserSecretsClient
user_secrets = UserSecretsClient()

hf_token = user_secrets.get_secret("HUGGINGFACE_TOKEN")
login(token = hf_token)


model.push_to_hub(model_dir, use_temp_dir=False)
tokenizer.push_to_hub(model_dir, use_temp_dir=False)

已创建新的模型仓库,并将所有文件推送至 Hugging Face 模型库。

Finetuned model on Hugging Face

来源:kingabzpro/Phi-3.5-mini-instruct-Ecommerce-Text-Classification

若您在合并与导出模型时遇到问题,请查看《将 Phi-3.5 微调适配器合并到完整模型》Kaggle 笔记本。

另外,欢迎查看我们的最新教程《微调 Llama 3.2 并本地使用:分步指南》。在该教程中,您将学习如何使用免费 GPU 在自定义数据集上微调模型,将模型合并并导出至 Hugging Face Hub,并转换为 GGUF 格式,以便配合 Jan 应用在本地使用。

总结

大语言模型正变得更小更高效,从而降低运营成本,并在各领域具备更强适应性。在本教程中,我们探索了 Phi-3.5 Mini、Vision 与 MoE 模型,并学习了如何通过 Kaggle 笔记本访问 Phi-3.5 Mini 模型。

随后我们在分类数据上对模型进行微调并评估表现,将准确率从 65% 大幅提升至 86%——这一成果相当可观。仅靠 RAG 或函数调用无法达到这样的效果。

最后,我们将 LoRA 与基础模型集成,并将完整模型导出至 Hugging Face Hub,供所有人使用。

下一步,按照这份《12 个适合各水平的 LLM 项目》清单动手搭建项目。内容覆盖初学者、中级学习者、毕业年级与专家级别。每个项目都配有源码、可视化指南与参考链接。

Abid Ali Awan

作者

Abid Ali Awan

作为一名持证的数据科学家,我热衷于利用前沿技术打造创新的机器学习应用。凭借在语音识别、数据分析与报告、MLOps、对话式人工智能以及自然语言处理方面的扎实背景,我不断打磨构建智能系统的能力,力求带来切实影响。除技术专长外,我也擅长沟通,能够将复杂概念提炼为清晰、简明的表述。因此,我成为数据科学领域备受关注的博主,与不断壮大的数据专业人士社区分享洞见与实践经验。目前,我专注于内容创作与编辑,借助大语言模型打造有力且吸引人的内容,帮助企业与个人更好地发挥数据价值。

主题