5.預訓練模型跑分
回顧賽題
回顧賽題任務
挑戰與難點:
-
標注數據少 ——> 半監督學習 or 數據增強
-
聚類分析噪點影響嚴重
回顧Baseline
問題:
- TF-IDF無法捕捉以下語義。
- 聚類分析粗糙,未評估聚類質量。
提升方案:
- 分類任務(任務一和任務二)
- 使用BERT模型
- 數據增強(對于任務一)
- 微調方式
- 聚類任務
預訓練模型步驟
任務一:商品識別
- 數據準備: 我們把文字標簽(比如 “Xfaiyx Smart Translator”)映射成數字(0, 1),因為模型只能理解數字。
- 模型和分詞器:
AutoTokenizer
負責把漢字句子切分成模型認識的“詞元”(Token)。AutoModelForSequenceClassification
是一個專門用于分類任務的BERT模型結構。 - 訓練:
TrainingArguments
用來設置訓練的超參數(比如訓練幾輪、每批次用多少數據等)。Trainer
是一個高級封裝,我們把模型、參數、數據都喂給它,它就會自動幫我們完成整個復雜的訓練過程。 - 預測: 訓練好后,我們用
pipeline
這個便捷工具對所有視頻的文本進行預測,得到商品名稱。
任務二:情感分析
- 這個過程和任務一非常類似,但是我們用一個
for
循環來為四個不同的情感維度分別獨立地訓練四個模型。 - 因為每個維度的分類任務都不同(比如
sentiment_category
是5分類,而user_scenario
是2分類),所以為每個任務單獨訓練一個模型效果最好。 - 注意,這里我們將
1,2,3,4,5
這樣的原始標簽也轉換成了從0
開始的0,1,2,3,4
,訓練完再轉換回去。這是Hugging Face模型的標準要求。
任務三:評論聚類
- 句向量模型: 我們加載
SentenceTransformer
模型,它會把每個評論變成一個包含384或768個數字的向量,這個向量精準地捕捉了評論的語義。 - 尋找最佳K: 這是關鍵的改進!代碼會遍歷 K 從 5 到 8,對每個 K 值都進行一次KMeans聚類,并計算輪廓系數。輪廓系數越高,代表聚類效果越好(類內越緊密,類間越疏遠)。最后,代碼會選用分數最高的那個 K 值。
- 最終聚類: 使用找到的最佳 K,進行最后一次聚類,并把每個評論分到的簇標簽(比如屬于第0簇,第1簇…)記錄下來。
- 主題生成: 我們為每個簇生成了一個簡單的名字,比如
positive_主題_1
。這樣做的好處是清晰明了,并且百分百符合提交格式。
步驟
前期準備
- 安裝所需要的庫
!pip install --upgrade transformers accelerate sentence-transformers -q
2. 導入
import pandas as pd
import numpy as np
import torch
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
from transformers import (AutoTokenizer,AutoModelForSequenceClassification,TrainingArguments,Trainer,pipeline,
)
from sentence_transformers import SentenceTransformer
from datasets import Dataset
import os
import zipfile
- 全局設置(模型定義)和準備數據
# ---------------------------------
# 1. 全局設置和模型定義
# ---------------------------------
print("\n--> 1. 開始進行全局設置...")
CLASSIFICATION_MODEL = 'bert-base-multilingual-cased'
EMBEDDING_MODEL = 'sentence-transformers/paraphrase-multilingual-mpnet-base-v2'
DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'
print(f"將使用設備: {DEVICE}")# ---------------------------------
# 2. 加載和準備數據
# ---------------------------------
print("\n--> 2. 開始加載和準備數據...")
video_data = pd.read_csv("origin_videos_data.csv")
comments_data = pd.read_csv("origin_comments_data.csv")
video_data["text"] = video_data["video_desc"].fillna("") + " " + video_data["video_tags"].fillna("")
因為評論有多個國家的語言,所以分類模型選的bert-base-multilingual-cased
,多語言句向量模型sentence-transformers/paraphrase-multilingual-mpnet-base-v2
(也可以嘗試其它模型)
任務一:商品識別
- 數據準備:處理數據和標簽
# 1. 篩選出有商品名的數據
train_video_df = video_data[~video_data["product_name"].isnull()].copy()# 2. 獲取所有不重復的商品名,并排序
labels_list = sorted(train_video_df["product_name"].unique())# 3. 創建“商品名” -> “數字ID” 的映射 (字典)
label2id = {label: i for i, label in enumerate(labels_list)}# 4. 創建“數字ID” -> “商品名” 的映射 (反向字典,方便以后查看結果)
id2label = {i: label for i, label in enumerate(labels_list)}# 5. 在數據中創建新的一列 "label",存放轉換后的數字ID
train_video_df["label"] = train_video_df["product_name"].map(label2id)
- 分詞與編碼
# 1. 加載一個預訓練好的分詞器
# CLASSIFICATION_MODEL 是一個預訓練模型的名字,比如 "bert-base-chinese"
tokenizer = AutoTokenizer.from_pretrained(CLASSIFICATION_MODEL)# 2. 對所有文本進行分詞和編碼
# - tolist(): 把一列文本轉換成一個列表
# - truncation=True: 如果句子太長,就截斷
# - padding=True: 如果句子太短,就用特殊數字填充,讓所有句子一樣長
# - max_length=128: 指定句子的最大長度
train_encodings = tokenizer(train_video_df["text"].tolist(), truncation=True, padding=True, max_length=128)# 3. 把我們之前轉換好的數字標簽也放進這個編碼結果里
train_encodings['label'] = train_video_df["label"].tolist()# 4. 將整個編碼結果(包含文本編碼和標簽)封裝成一個標準的數據集對象
train_dataset = Dataset.from_dict(train_encodings)
- 加載模型與配置訓練
# 1. 加載預訓練模型
model = AutoModelForSequenceClassification.from_pretrained(CLASSIFICATION_MODEL, # 模型的名字,要和Tokenizer一致num_labels=len(labels_list), # 告訴模型我們總共有多少個分類id2label=id2label, # 把我們之前創建的ID->標簽映射告訴模型label2id=label2id # 也把標簽->ID的映射告訴模型
).to(DEVICE) # 2. 設置訓練參數
training_args = TrainingArguments(output_dir='./results', # 訓練結果存到哪里num_train_epochs=3, # 所有數據要學習3遍per_device_train_batch_size=8, # 每次看8個樣本logging_dir='./logs', # 日志存到哪里logging_steps=10, # 每訓練10步就打印一次日志report_to="none" # 不上報到第三方平臺
)trainer = Trainer(model=model,args=training_args,train_dataset=train_dataset
)
4.訓練與預測
# 1. 開始訓練
trainer.train()# 2. 使用 pipeline(管道)
classifier = pipeline("text-classification", # 任務類型是文本分類model=model, # 用我們剛訓練好的模型tokenizer=tokenizer, # 用我們之前加載的分詞器device=0 # 0代表使用第一塊GPU,-1代表使用CPU
)# 3. 把所有視頻的文本都扔給它進行預測
predictions = classifier(video_data["text"].tolist())# 4. 從預測結果中提取出標簽名字,并更新回原數據
video_data["product_name"] = [pred['label'] for pred in predictions]
pipeline
是 Hugging Face 提供的最高度封裝的預測工具。它把“輸入原始文本 -> 分詞 -> 轉換成ID -> 模型預測 -> 輸出 logits -> Softmax -> 翻譯回標簽”這一整套繁瑣的流程,壓縮成了一步
classifier = pipeline("任務名稱", model=訓練好的模型, tokenizer=配套的分詞器)
優化嘗試
因為我用BERT微調之后,看了一下分數只有80左右,
數據太少: 生成偽標簽
+ 5折交叉驗證
假設標簽是“答案”,有標簽的數據是“教材”,就是讓5個專家做沒有答案的練習冊,如果5個專家的答案都一樣,就把這道題收入到教材,這樣教材的內容就更多了,最后再讓學生學習這本“教材”
- 分離數據: 一部分有標簽(答案)和一部分沒標簽
# 有答案的“教材”
train_video_df = video_data[~video_data["product_name"].isnull()].copy()
# 沒答案的“練習題”
unlabeled_video_df = video_data[video_data["product_name"].isnull()].copy()# ... (標簽數字化的部分和之前一樣) ...
- 訓練5個模型(專家),并讓每個專家做一次“練習冊”,收集“答案”
- 把教材(
train_video_df
)平均分成5份。 - 第1輪:用第1、2、3、4份當教材訓練模型,第5份當模擬考(這里代碼省略了驗證,直接訓練)。
- 第2輪:用第1、2、3、5份當教材訓練模型,第4份當模擬考。
- …以此類推,一共訓練5個模型。
- 每個模型都學習了80%的數據,而且學習的內容都不完全相同,這樣就組成了我們的“專家委員會”。
- 把教材(
# 引入分層K折交叉驗證工具,它能保證每一折里各類別的比例都差不多
from sklearn.model_selection import StratifiedKFold# 設定交叉驗證:分成5份,打亂順序
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
unlabeled_preds = [] # 用來存放5個專家對“練習題”的答案# skf.split(...) 會自動循環5次,每次都生成不同的訓練集索引(train_idx)
for fold, (train_idx, val_idx) in enumerate(skf.split(train_video_df['text'], train_video_df['label'])):print(f"\n===== 開始訓練第 {fold+1} 位專家 =====")# 1. 準備當前這位專家的教材train_fold_df = train_video_df.iloc[train_idx]# (數據編碼過程,和之前一樣,只是數據源是 train_fold_df)train_dataset = ... # 2. 請來一位全新的專家(模型)model = AutoModelForSequenceClassification.from_pretrained(...)# 3. 對這位專家進行特訓trainer = Trainer(model=model, args=training_args, train_dataset=train_dataset)trainer.train()# 4. 專家學成!讓他去做“練習題”(unlabeled_video_df)unlabeled_dataset = ... # 把練習題也編碼成模型能讀懂的格式# trainer.predict 返回原始的、未經處理的預測分數(logits)raw_preds, _, _ = trainer.predict(unlabeled_dataset)# 5. 將該專家的答案(處理成0-1之間的概率后)存起來unlabeled_preds.append(F.softmax(torch.from_numpy(raw_preds), dim=-1).numpy())
- 生成“新教材”
# 1. 計算平均意見:對5位專家的預測概率取平均值
# axis=0 表示在“專家”這個維度上求平均
avg_preds = np.mean(unlabeled_preds, axis=0)# 2. 確定最終投票結果:取平均概率最高的那個類別作為預測結果(第一題選A, 對應標簽0)
pred_labels = np.argmax(avg_preds, axis=1) # 得到數字標簽,如 0, 1, 2# 3. 取最高的那個平均概率作為置信度分數
pred_scores = np.max(avg_preds, axis=1) # 得到分數,如 0.98, 0.75, 0.91# 4. 只有信心超過90%的答案,我們才采納
confidence_threshold = 0.90
pseudo_df = pd.DataFrame({'text': unlabeled_video_df['text'], 'label': pred_labels, 'score': pred_scores})
high_confidence_pseudo_df = pseudo_df[pseudo_df['score'] > confidence_threshold].copy()# 5. 將“新教材”和“老教材”合并
if not high_confidence_pseudo_df.empty:print(f"成功篩選出 {len(high_confidence_pseudo_df)} 條新教材!")combined_train_df = pd.concat([train_video_df, high_confidence_pseudo_df], ignore_index=True)
else:# 如果沒篩出來,就還用老教材combined_train_df = train_video_df
? 4.得到新的數據集,進行訓練
# 1. 準備最全的教材
final_dataset = Dataset.from_pandas(combined_train_df)
final_dataset = final_dataset.map(...) # 編碼# 2. 加載模型
final_model = AutoModelForSequenceClassification.from_pretrained(...)# 3. 用所有數據進行訓練
final_trainer = Trainer(model=final_model, args=training_args, train_dataset=final_dataset)
final_trainer.train()print("\n--- 開始預測所有視頻... ---")
final_classifier = pipeline("text-classification", model=final_model, ...)
final_predictions = final_classifier(video_data["text"].tolist())# 更新最終結果
video_data["product_name"] = [pred['label'] for pred in final_predictions]
任務二:情感分析
任務二與任務一類似
,并且數據夠多。用一個 for
循環來為四個不同的情感維度分別獨立地訓練四個模型。
任務三:評論聚類
上期使用循環選擇聚類個數得分50+, 本來打算使用UMAP降維試試,但是分數還是50+(暫時沒有提高分數的頭緒,等提高了再補充)
結果
心得
算是第一次參加這類大賽,通過Datawhale的教程,很輕松的入門,寫的很詳細。遇到不會的也可以在群里交流,對自己提升很多。