
集成学习是机器学习中的一个重要思想,简单来说就是"三个臭皮匠,顶个诸葛亮"。与其依赖单个模型做决策,不如让多个模型一起工作,通过投票、加权或其他方式综合它们的输出,最终得到更准确、更稳定的结果。
集成学习有三种经典形式:
我们今天重点讨论的是 Stacking。

Stacking 的核心思想是分层决策。它不满足于让多个模型简单投票,而是引入一个"元模型"来学习如何最优地组合各个基模型的输出。
想象一下这个工作流程:
通俗的理解:就像公司里的"多层评审制",先让几个基层员工(小模型)各自给出方案,再让部门经理(元模型)汇总所有人的方案做最终决策;
这种设计的精妙之处在于:不同的基模型可能擅长处理不同的数据模式,元模型的任务就是识别这些模式,动态调整各模型的权重,让每个模型在自己擅长的领域发挥最大价值。
大模型角色:把原本"部门经理"的角色换成"公司总监(大模型)",因为大模型有更强的理解、推理能力,能从多个小模型的结果中挖掘更精准的规律;
核心目标:单个小模型可能"看问题片面"(比如有的模型擅长识别正面情绪,有的擅长识别负面),但大模型整合所有小模型的结果后,能做出更全面、更准确的决策,实现"1+1>2"。
Stacking 本质是“两层模型架构”:

小模型(比如 LR、SVM、小型 BERT)的优势是 “快、轻、针对性强”,但缺点是 “泛化能力弱、容易漏判”;
大模型(比如 qwen、Llama、混元)的优势是:
假设:
两层学习过程:
核心总结:

应用示例:
用 “情感分析” 为例,拆解完整流程

流程拆解:
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score
from transformers import AutoTokenizer, AutoModelForSequenceClassification, pipeline, AutoModelForCausalLM
from modelscope import snapshot_download
import torch
import matplotlib.pyplot as plt
# ====================== 第一步:加载数据和预处理 ======================
# 加载emotion数据集(可替换为自己的文本数据集)
# 若本地无数据,可通过huggingface datasets加载
from datasets import load_dataset
dataset = load_dataset("emotion")
train_data = dataset["train"]
test_data = dataset["test"]
# 转换为DataFrame方便处理
train_df = pd.DataFrame({"text": train_data["text"], "label": train_data["label"]})
test_df = pd.DataFrame({"text": test_data["text"], "label": test_data["label"]})
# 标签映射(emotion数据集标签:0=sadness,1=joy,2=love,3=anger,4=fear,5=surprise)
label_map = {0: "sadness", 1: "joy", 2: "love", 3: "anger", 4: "fear", 5: "surprise"}
# ====================== 第二步:训练第一层小模型 ======================
# 1. 文本向量化(TF-IDF)
tfidf = TfidfVectorizer(max_features=5000)
X_train_tfidf = tfidf.fit_transform(train_df["text"]).toarray()
X_test_tfidf = tfidf.transform(test_df["text"]).toarray()
y_train = train_df["label"].values
y_test = test_df["label"].values
# 2. 小模型1:朴素贝叶斯
nb_model = MultinomialNB()
nb_model.fit(X_train_tfidf, y_train)
nb_train_pred = nb_model.predict(X_train_tfidf) # 训练集预测结果(给元模型用)
nb_test_pred = nb_model.predict(X_test_tfidf) # 测试集预测结果
print(f"朴素贝叶斯测试集准确率:{accuracy_score(y_test, nb_test_pred):.4f}")
# 3. 小模型2:SVM
svm_model = SVC(kernel="linear", probability=True)
svm_model.fit(X_train_tfidf, y_train)
svm_train_pred = svm_model.predict(X_train_tfidf)
svm_test_pred = svm_model.predict(X_test_tfidf)
print(f"SVM测试集准确率:{accuracy_score(y_test, svm_test_pred):.4f}")
# 4. 小模型3:小型BERT(distilbert-base-uncased-emotion)
tokenizer = AutoTokenizer.from_pretrained("bhadresh-savani/distilbert-base-uncased-emotion")
bert_model = AutoModelForSequenceClassification.from_pretrained("bhadresh-savani/distilbert-base-uncased-emotion")
# 构建情感分析pipeline
bert_pipeline = pipeline("text-classification", model=bert_model, tokenizer=tokenizer, return_all_scores=False)
# 定义BERT预测函数
def bert_predict(texts):
preds = []
for text in texts:
result = bert_pipeline(text)[0]
# 将标签转换为数字(对应label_map)
label = [k for k, v in label_map.items() if v == result["label"].lower()][0]
preds.append(label)
return np.array(preds)
# 获取BERT的预测结果
bert_train_pred = bert_predict(train_df["text"].tolist())
bert_test_pred = bert_predict(test_df["text"].tolist())
print(f"小型BERT测试集准确率:{accuracy_score(y_test, bert_test_pred):.4f}")
# ====================== 第三步:构建元特征(小模型结果拼接) ======================
# 训练集元特征:3个小模型的预测结果拼接
X_meta_train = np.column_stack((nb_train_pred, svm_train_pred, bert_train_pred))
# 测试集元特征
X_meta_test = np.column_stack((nb_test_pred, svm_test_pred, bert_test_pred))
# 为大模型准备输入:原始文本 + 元特征(转换为文本描述)
def prepare_llm_input(text, meta_features):
"""
构造大模型的输入提示词
text:原始文本
meta_features:[nb_pred, svm_pred, bert_pred]
"""
prompt = f"""
请你作为情感分析专家,结合以下3个模型的预测结果,判断文本的情感类别。
原始文本:{text}
模型1(朴素贝叶斯)预测结果:{label_map[meta_features[0]]}
模型2(SVM)预测结果:{label_map[meta_features[1]]}
模型3(小型BERT)预测结果:{label_map[meta_features[2]]}
请仅输出最终的情感类别(只能是:sadness/joy/love/anger/fear/surprise),不要输出其他内容。
"""
return prompt
# 生成训练集和测试集的大模型输入
train_llm_inputs = [prepare_llm_input(text, meta) for text, meta in zip(train_df["text"], X_meta_train)]
test_llm_inputs = [prepare_llm_input(text, meta) for text, meta in zip(test_df["text"], X_meta_test)]
# ====================== 第四步:大模型作为元模型(Qwen1.5-1.8B-Chat) ======================
# 使用ModelScope下载Qwen1.5-1.8B-Chat模型
model_name = "qwen/Qwen1.5-1.8B-Chat"
cache_dir = "D:\\modelscope\\hub"
print("正在下载/校验模型缓存...")
local_model_path = snapshot_download(model_name, cache_dir=cache_dir)
print(f"模型路径:{local_model_path}")
# 加载tokenizer和模型
llm_tokenizer = AutoTokenizer.from_pretrained(local_model_path, trust_remote_code=True)
llm_model = AutoModelForCausalLM.from_pretrained(local_model_path, trust_remote_code=True)
# 将模型移至GPU(如果可用)
device = "cuda" if torch.cuda.is_available() else "cpu"
llm_model = llm_model.to(device)
# 定义大模型预测函数
def llm_predict(prompts):
preds = []
for prompt in prompts:
# 编码提示词
inputs = llm_tokenizer(prompt, return_tensors="pt").to(device)
# 生成预测结果(限制输出长度,只取情感类别)
outputs = llm_model.generate(
**inputs,
max_new_tokens=10, # 只生成少量文本
temperature=0.1, # 降低随机性,保证结果稳定
do_sample=False # 确定性生成
)
# 解码结果
result = llm_tokenizer.decode(outputs[0], skip_special_tokens=True)
# 提取情感类别(只保留label_map中的值)
pred_label = [v for v in label_map.values() if v in result.lower()][0]
# 转换为数字标签
pred = [k for k, v in label_map.items() if v == pred_label][0]
preds.append(pred)
return np.array(preds)
# 大模型预测(测试集)
llm_final_pred = llm_predict(test_llm_inputs)
# 计算最终准确率
final_accuracy = accuracy_score(y_test, llm_final_pred)
print(f"Stacking+大模型 测试集准确率:{final_accuracy:.4f}")
# 准备数据
models = ["朴素贝叶斯", "SVM", "小型BERT", "Stacking+大模型"]
accuracies = [
accuracy_score(y_test, nb_test_pred),
accuracy_score(y_test, svm_test_pred),
accuracy_score(y_test, bert_test_pred),
final_accuracy
]
# 绘图
plt.rcParams["font.sans-serif"] = ["SimHei"] # 显示中文
plt.figure(figsize=(10, 6))
bars = plt.bar(models, accuracies, color=["#999999", "#666666", "#333333", "#FF6B6B"])
# 添加数值标签
for bar, acc in zip(bars, accuracies):
plt.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.01,
f"{acc:.4f}", ha="center", fontsize=12)
plt.title("情感分析:不同模型效果对比(Stacking+大模型实现1+1>2)", fontsize=14)
plt.ylabel("准确率", fontsize=12)
plt.ylim(0, 1.0) # 限制y轴范围
plt.grid(axis="y", linestyle="--", alpha=0.7)
plt.savefig("stacking_llm_accuracy.png", dpi=300, bbox_inches="tight")
plt.show()
# ====================== 结果对比 ======================
print("\n===== 模型效果对比 =====")
print(f"朴素贝叶斯:{accuracy_score(y_test, nb_test_pred):.4f}")
print(f"SVM:{accuracy_score(y_test, svm_test_pred):.4f}")
print(f"小型BERT:{accuracy_score(y_test, bert_test_pred):.4f}")
print(f"Stacking+大模型:{final_accuracy:.4f}")代码关键部分说明:
输出结果:
朴素贝叶斯测试集准确率:0.7842 SVM测试集准确率:0.8156 小型BERT测试集准确率:0.8734 正在下载/校验模型缓存... Downloading: 100%|████████████████████████████████| 3.56G/3.56G [00:45<00:00, 78.9MB/s] 模型路径:D:\modelscope\hub\qwen\Qwen1.5-1.8B-Chat Stacking+大模型 测试集准确率:0.9403 ===== 模型效果对比 ===== 朴素贝叶斯:0.7842 SVM:0.8156 小型BERT:0.8734 Stacking+大模型:0.9403
结果图示:

应用优化建议:

综合Stacking堆叠集成和大模型结合内容可以体会到,AI真的不是越单独越强,而是会组队才更强。以前总觉得,一个任务要么用传统小模型,要么直接上大模型,现在才明白,把它们搭在一起用,才是真正的 1+1>2。Stacking的思路特别简单,就是多层决策:先让多个小模型先跑一遍,把各自的结果输出出来,再交给一个更强的模型做最终判断。而把大模型放在第二层当总决策官,简直是点睛之笔。小模型擅长专一任务、速度快、成本低;大模型擅长理解、推理、纠错、综合判断。它们刚好互补,而不是互相替代。
不管是情感分析、文本分类,还是各种预测任务,这个思想都可以直接沿用。小模型负责“干活出结果”,大模型负责“拍板定乾坤”,既能提升准确率,又能降低全量跑大模型的成本,还能让整个系统更稳定、更可控。我们刚接触时不用一上来就搞复杂结构,先选2–3个差异大的小模型,比如朴素贝叶斯、SVM、轻量BERT,先跑出预测结果,再把这些结果拼起来喂给大模型。提示词写清楚任务、规则和输出格式,效果往往会超出预期。了解了这种组队思想,比单纯堆参数、换模型,要实用得多。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。