使用 DistilBERT LoRA、TF-IDF 基準線、校準、可解釋性、穩健性測試與半監督學習進行 IMDb 情感分析
重點摘要
本教學建立從頭到尾的情感分析流程,使用Stanford NLP IMDb資料集比較傳統機器學習(TF-IDF結合邏輯迴歸)與參數高效微調(DistilBERT結合LoRA)。流程涵蓋資料審計、機率校準、可解釋性分析,並利用未標記資料進行半監督學習。最後比較半監督模型與基準線,並儲存合併後的Transformer以供後續推論使用。
In this tutorial, we develop an end-to-end sentiment analysis workflow using the Stanford NLP IMDb Large Movie Review Dataset and compare classical machine learning with parameter-efficient transformer fine-tuning.
We begin by establishing a reproducible environment and auditing the dataset for class ordering, review-length skew, duplicate leakage, and preprocessing artifacts before training a strong TF-IDF and Logistic Regression baseline.
We then fine-tune DistilBERT with LoRA through PEFT, evaluate it using accuracy, macro-F1, ROC-AUC, confusion matrices, and ROC curves, and examine threshold selection and probability calibration through Expected Calibration Error and reliability analysis.
Beyond headline metrics, we investigate confident errors, performance across review lengths, word-level occlusion saliency, and head-versus-tail truncation to understand how the model reaches its predictions and where long-context limitations affect performance.
Finally, we use the unlabeled IMDb split for confidence-based pseudo-labeling, compare the resulting semi-supervised model against our baseline, and save the merged transformer for reusable sentiment inference.Copy CodeCopiedUse a different Browserimport importlib.
util, subprocess, sys, os, time, random, warnings, inspect, hashlib warnings.filterwarnings("ignore") os.environ["TOKENIZERS_PARALLELISM"] = "false" os.
environ["WANDB_DISABLED"] = "true" _REQUIRED = { "transformers": "transformers", "datasets": "datasets", "peft": "peft", "accelerate": "accelerate", "sklearn": "scikit-learn", } _missing = [pkg for mod, pkg in _REQUIRED.items() if importlib.util.
find_spec(mod) is None] if _missing: print(f"Installing: {', '.join(_missing)} ...") subprocess.run([sys.executable, "-m", "pip", "install", "-q", *_missing], check=True) print("Done.(If imports fail below, restart the runtime and re-run.
)\n") import numpy as np import pandas as pd import torch import matplotlib.pyplot as plt from datasets import load_dataset from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.pipeline import make_pipeline from sklearn.
metrics import (accuracy_score, f1_score, roc_auc_score, classification_report, confusion_matrix, roc_curve) from transformers import (AutoTokenizer, AutoModelForSequenceClassification, TrainingArguments, Trainer, DataCollatorWithPadding, EarlyStoppingCallback, set_seed) from peft import LoraConfig, get_peft_model, TaskType def _disable_torchao_probe(): patched = [] try: import peft.
import_utils as _piu _piu.is_torchao_available = lambda: False patched.append("peft.import_utils") except Exception: pass for _name, _mod in list(sys.modules.items()): if _name.startswith("peft") and hasattr(_mod, "is_torchao_available"): _mod.is_torchao_available = lambda: False patched.
append(_name) return patched try: import torchao as _tao _v = getattr(_tao, "__version__", "?") if tuple(int(x) for x in _v.split(".")[:2]) < (0, 16): print(f"[compat] torchao {_v} < 0.16 -> disabling PEFT's torchao probe: " f"{', '.
join(_disable_torchao_probe())}") except Exception: _disable_torchao_probe() SEED = 42 MODEL_NAME = "distilbert-base-uncased" MAX_LEN = 256 N_TRAIN = 5000 N_EVAL = 2000 N_UNSUP = 3000 EPOCHS = 2 BATCH = 16 LR = 3e-4 FULL_RUN = False if FULL_RUN: N_TRAIN, N_EVAL, EPOCHS = 25000, 25000, 3 set_seed(SEED); random.
seed(SEED); np.random.seed(SEED); torch.manual_seed(SEED) DEVICE = "cuda" if torch.cuda.is_available() else "cpu" print("=" * 79) print(f"device={DEVICE} | torch={torch.__version__} | " f"gpu={torch.cuda.get_device_name(0) if DEVICE=='cuda' else 'n/a'}") print("=" * 79) t0 = time.
time() raw = load_dataset("stanfordnlp/imdb") print(raw, f"\nloaded in {time.time()-t0:.1f}s\n") print("--- example (truncated) ---") print("label:", raw["train"][0]["label"], "|", raw["train"][0]["text"][:300], "...\n") first_labels = np.array(raw["train"]["label"][:5]) last_labels = np.
array(raw["train"]["label"][-5:]) print(f"TRAP #1 - split ordering: first 5 labels {first_labels}, " f"last 5 labels {last_labels} -> ALWAYS shuffle before subsampling.") train_full = raw["train"].shuffle(seed=SEED) test_full = raw["test"].shuffle(seed=SEED) train_ds = train_full.
select(range(min(N_TRAIN, len(train_full)))) eval_ds = test_full.select(range(min(N_EVAL, len(test_full)))) print(f" after shuffle+subsample: train balance = " f"{np.bincount(train_ds['label'])}, eval balance = {np.bincount(eval_ds['label'])}") lens = np.array([len(t.
split()) for t in train_full["text"]]) q = np.percentile(lens, [50, 75, 90, 95, 99]) print(f"\nTRAP #2 - length (words): median={q[0]:.0f} p75={q[1]:.0f} p90={q[2]:.0f} " f"p95={q[3]:.0f} p99={q[4]:.0f} max={lens.max()}") print(f" ~{(lens > MAX_LEN*0.75).mean()*100:.
1f}% of reviews exceed MAX_LEN={MAX_LEN} " f"tokens (rough words->tokens factor 1.3).Section 9 measures what that costs.") h_tr = {hashlib.md5(t.encode()).hexdigest() for t in raw["train"]["text"]} h_te = {hashlib.md5(t.encode()).
hexdigest() for t in raw["test"]["text"]} print(f"\nTRAP #3 - leakage: {len(h_tr & h_te)} exact duplicate reviews across " f"train/test; {len(raw['train'])-len(h_tr)} dupes inside train itself.") def clean(t): return t.replace("<br />", " ").replace("<br/>", " ").strip() plt.figure(figsize=(11, 3.
2)) plt.subplot(1, 2, 1) plt.hist(np.clip(lens, 0, 1000), bins=60) plt.axvline(MAX_LEN, ls="--", color="k", label=f"MAX_LEN={MAX_LEN}") plt.title("Review length (words, clipped at 1000)"); plt.legend() plt.subplot(1, 2, 2) plt.bar(["neg", "pos"], np.bincount(raw["train"]["label"])) plt.
title("Train class balance (perfectly balanced)") plt.tight_layout(); plt.show() We configure the Colab environment, install the required libraries, apply the PEFT–torchao compatibility fix, and set deterministic seeds for reproducible experiments.
We load the Stanford IMDb dataset, shuffle and subsample the train and test splits, and inspect class balance, review-length distributions, duplicate leakage, and HTML artifacts.We also visualize review lengths and label frequencies so we understand the dataset structure before building any models.
Copy CodeCopiedUse a different Browserprint("\n" + "=" * 79 + "\n3.TF-IDF BASELINE\n" + "=" * 79) Xtr = [clean(t) for t in train_ds["text"]]; ytr = np.array(train_ds["label"]) Xte = [clean(t) for t in eval_ds["text"]]; yte = np.array(eval_ds["label"]) t0 = time.
time() tfidf_clf = make_pipeline( TfidfVectorizer(ngram_range=(1, 2), min_df=2, max_features=300_000, sublinear_tf=True, strip_accents="unicode"), LogisticRegression(C=8.0, max_iter=2000, n_jobs=-1), ) tfidf_clf.fit(Xtr, ytr) p_tfidf = tfidf_clf.
predict_proba(Xte)[:, 1] acc_tfidf = accuracy_score(yte, p_tfidf > 0.5) auc_tfidf = roc_auc_score(yte, p_tfidf) print(f"trained in {time.time()-t0:.1f}s -> acc={acc_tfidf:.4f} auc={auc_tfidf:.4f}") vec, lr = tfidf_clf.steps[0][1], tfidf_clf.steps[1][1] feats, coefs = np.array(vec.
get_feature_names_out()), lr.coef_[0] order = np.argsort(coefs) print("\nmost NEGATIVE n-grams:", ", ".join(feats[order[:12]])) print("most POSITIVE n-grams:", ", ".join(feats[order[-12:]][::-1])) print("\n" + "=" * 79 + "\n4.LoRA FINE-TUNING\n" + "=" * 79) tok = AutoTokenizer.
from_pretrained(MODEL_NAME) def tokenize(batch): return tok([clean(t) for t in batch["text"]], truncation=True, max_length=MAX_LEN) tr_tok = (train_ds.map(tokenize, batched=True, remove_columns=["text"]) .rename_column("label", "labels")) ev_tok = (eval_ds.
map(tokenize, batched=True, remove_columns=["text"]) .rename_column("label", "labels")) base = AutoModelForSequenceClassification.from_pretrained( MODEL_NAME, num_labels=2, id2label={0: "NEGATIVE", 1: "POSITIVE"}, label2id={"NEGATIVE": 0, "POSITIVE": 1}, ) lora_cfg = LoraConfig( task_type=TaskType.
SEQ_CLS, r=16, lora_alpha=32, lora_dropout=0.05, target_modules=["q_lin", "v_lin"], modules_to_save=["pre_classifier", "classifier"], ) try: model = get_peft_model(base, lora_cfg) except ImportError as e: _disable_torchao_probe() print(f"[compa
Related
相關文章

蘋果中國官網刪除 Apple 智能接入阿里千問使用手冊
蘋果中國官網一度上線《在Mac上配合Apple智能使用千問》支援文件,說明可搭配阿里巴巴千問模型,但該文件現已遭到刪除,原連結無法存取。該文件原先指出,千問擴充功能支援macOS 26.6以上版本,並可於系統設定中啟用,用於寫作工具與Siri。目前蘋果仍未對刪除原因做出說明。

重磅!蘋果國行AI突然發佈,首次官宣牽手阿里,Mac用上千問了
蘋果官方更新Mac使用手冊,首次公開確認與阿里巴巴千問合作,將AI能力整合進國行Apple智能。用戶可透過Siri與寫作工具直接在Mac上使用千問的文本理解與生成功能,合作僅限中國大陸地區。
認識 Shepherd:一個開源 Python 基底,讓元代理能分叉、重播及還原任何代理執行
Shepherd 是一個開源的 Python 執行基底,能將代理程式的執行過程記錄成類似 Git 的型別事件追蹤,讓元代理可以分叉、重播和還原任意的代理狀態。研究顯示,其分叉速度比 Docker 快 5 倍,重播時 prompt 快取重用率超過 95%,並在即時監督與反事實最佳化等應用中顯著提升效能。

【數智周報】張一鳴:字節跳動“拒絕蒸餾”,不用別人輸出換榜單排名;三星發佈下一代AI存儲路線圖,展示zHBM和400層以上V10 NAND技術;閃迪第四財季營收超預期增長372%,數據中心收入增近13倍,擬豪擲140億回購
字節跳動創辦人張一鳴在內部會議強調公司堅持長期主義,拒絕蒸餾他人模型以換取榜單排名。三星發佈下一代AI存儲路線圖,展示zHBM與400層以上V10 NAND技術。閃迪第四財季營收年增372%,數據中心收入成長近13倍,並計劃回購140億美元股票。
Pokee AI 推出 Pokee-Isaac 28B:百萬 Token 上下文長度的代理模型,專為客戶內部部署設計
長時程代理在累積上下文的速度上,遠快於解決任務的效率。每項工具輸出、觀察結果及中間推理步驟都會保留在上下文視窗中,而兩個關鍵能力——保留上下文並在其中保持連貫——目前幾乎僅能透過雲端端點實現。這排除了受監管行業、公共部門機構及裝置端應用,因為這些場景的資料完全不允許離開內部邊界。Pokee AI 發布了 Pokee-Isaac 28B,這是一款擁有 280 億參數、僅支援文字的基礎模型,具備 1000 萬 Token 的上下文長度,專為在客戶內部邊界內運行而設計。Pokee 研究團隊聲稱,在 RULER 基準測試中,該模型在 1000 萬 Token 長度下達到 93.3% 的準確率,與代理基準測試中最具成本效益的雲端基準模型表現持平,且可單靠一張 GPU 完成部署。

專為 AI 智能體打造的雲端瀏覽器,Cloudflare 發佈 Kitesurf
Cloudflare 推出專為 AI 智能體打造的雲端瀏覽器 Kitesurf,運行於 Cloudflare Workers 平台,支援瀏覽網站與填寫表單。該瀏覽器較 Chromium 消耗更少計算資源,能降低運作成本,目前處於測試階段。