讓計算機能夠理解、解釋、操縱和生成人類語言,從而執行有價值的任務。
關注社區:Hugging Face、Papers With Code、GitHub 是現代NLP學習不可或缺的資源。許多最新模型和代碼都在這里開源。
①、安裝庫
pip install numpy pandas matplotlib nltk scikit-learn tensorflow torch transformers
②、傳統NLP方法
Ⅰ文本預處理
import nltk
from nltk.tokenize import word_tokenize
from nltk.corpus import stopwords
from nltk.stem import PorterStemmer, WordNetLemmatizer
import re# 下載所需nltk數據(第一次運行需要)
nltk.download('punkt')
nltk.download('stopwords')
nltk.download('wordnet')# 示例文本
text = "NLP is amazing! It involves analyzing and processing human language with computers."# a) 小寫化 & 去除標點/數字
text_clean = re.sub(r'[^a-zA-Z\s]', '', text.lower())
print("Cleaned Text:", text_clean)# b) 分詞
tokens = word_tokenize(text_clean)
print("Tokens:", tokens)# c) 去除停用詞
stop_words = set(stopwords.words('english'))
filtered_tokens = [word for word in tokens if word not in stop_words]
print("Filtered Tokens:", filtered_tokens)# d) 詞干提取
stemmer = PorterStemmer()
stemmed_tokens = [stemmer.stem(word) for word in filtered_tokens]
print("Stemmed Tokens:", stemmed_tokens)# e) 詞形還原(比詞干提取更高級)
lemmatizer = WordNetLemmatizer()
lemmatized_tokens = [lemmatizer.lemmatize(word) for word in filtered_tokens]
print("Lemmatized Tokens:", lemmatized_tokens)
Ⅱ 文本表示(TF-IDF)與機器學習模型訓練
from sklearn.feature_extraction.text import TfidfVectorizer #使用TF-IDF向量化文本
from sklearn.model_selection import train_test_split # 劃分訓練集和測試集
from sklearn.naive_bayes import MultinomialNB # 訓練一個樸素貝葉斯分類器
from sklearn.metrics import accuracy_score# 示例數據:電影評論和情感標簽(0-負面, 1-正面)
corpus = ["This movie is great and I love it!","The plot was terrible and boring.","Amazing acting by the lead actor.","Waste of time, horrible script.","I enjoyed it a lot, highly recommend.","The worst movie I have ever seen."
]
labels = [1, 0, 1, 0, 1, 0]# 使用TF-IDF向量化文本(自動處理了大部分預處理)
vectorizer = TfidfVectorizer(stop_words='english', max_features=50)
X = vectorizer.fit_transform(corpus).toarray()
print("TF-IDF Matrix Shape:", X.shape)
print("Feature names:", vectorizer.get_feature_names_out())# 劃分訓練集和測試集
X_train, X_test, y_train, y_test = train_test_split(X, labels, test_size=0.33, random_state=42)# 訓練一個樸素貝葉斯分類器
model = MultinomialNB()
model.fit(X_train, y_train)# 預測并評估
predictions = model.predict(X_test)
print("Predictions:", predictions)
print("True Labels:", y_test)
print("Accuracy:", accuracy_score(y_test, predictions))# 預測新樣本
new_review = ["This film is absolutely fantastic!"]
new_review_vec = vectorizer.transform(new_review)
prediction_new = model.predict(new_review_vec)
print(f"Review: '{new_review[0]}' -> Sentiment: {'Positive' if prediction_new[0] == 1 else 'Negative'}")
③、深度學習NLP代碼 (使用Keras)
使用LSTM進行情感分析
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Embedding, LSTM, Dense, Dropout# 假設我們使用和上面一樣的corpus和labels,但這里需要更大量的數據,僅作示例# 構建詞匯表并序列化文本
tokenizer = Tokenizer(num_words=500, oov_token="<OOV>")
tokenizer.fit_on_texts(corpus)
word_index = tokenizer.word_index
sequences = tokenizer.texts_to_sequences(corpus)# 填充序列以保證相同長度
padded_sequences = pad_sequences(sequences, maxlen=10, padding='post', truncating='post')
print("Padded Sequences:\n", padded_sequences)# 構建一個簡單的LSTM模型
model = Sequential([Embedding(input_dim=500, output_dim=16, input_length=10), # 500詞匯量,16維嵌入向量,輸入長度10LSTM(units=32, dropout=0.2),Dense(1, activation='sigmoid') # 二分類輸出
])model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
model.summary()# 訓練模型 (這里數據量太小,僅為演示格式)
# model.fit(padded_sequences, labels, epochs=10, validation_split=0.2)
④、現代NLP代碼 (使用Hugging Face Transformers)
from transformers import AutoTokenizer, TFAutoModelForSequenceClassification
from transformers import pipeline
import tensorflow as tf# ------------------ 方法一:直接使用Pipeline(零樣本學習,無需訓練)------------------
# Hugging Face提供的pipeline API,開箱即用
classifier = pipeline('sentiment-analysis')
results = classifier("I really enjoyed this movie! The acting was superb.")
print(results) # 輸出:[{'label': 'POSITIVE', 'score': 0.9998}]# ------------------ 方法二:加載模型和分詞器進行微調 ------------------
# 假設我們有一個更大的數據集,這里僅展示加載和準備步驟# a. 加載預訓練模型和對應的分詞器
model_name = "distilbert-base-uncased-finetuned-sst-2-english" # 一個在SST-2情感分析任務上微調過的DistilBERT模型
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = TFAutoModelForSequenceClassification.from_pretrained(model_name)# b. 對輸入文本進行編碼(Tokenization)
inputs = tokenizer("This is a fantastic example of NLP!", return_tensors="tf", padding=True, truncation=True)
print("Input IDs:", inputs['input_ids'])
print("Attention Mask:", inputs['attention_mask'])# c. 模型預測
outputs = model(inputs)
predictions = tf.nn.softmax(outputs.logits, axis=-1)
print("Predictions:", predictions)
# 輸出類似:tf.Tensor([[0.0012 0.9988]], shape=(1, 2), dtype=float32) -> 第二類概率高,代表積極# d. (可選)在自己的數據集上微調
# ... 需要準備數據集、定義優化器、訓練循環等,步驟較為復雜,但Hugging Face提供了完整的示例代碼。
構建一個新聞文本分類器,判斷一篇新聞屬于體育、科技還是娛樂類別
- 數據準備:加載并探索數據 成功加載了三個類別的20 Newsgroups數據集
- 文本預處理:清洗和標準化文本 轉換為小寫 移除標點符號、數字,移除停用詞,進行詞干提取或詞形還原
- 文本表示:將文本轉換為數值特征(TF-IDF) 將數據轉換為易于操作的Pandas DataFrame格式
- 模型訓練:使用機器學習算法訓練分類器,使用MultinomialNB, SVC, LogisticRegression等模型在訓練集上進行訓練,并在測試集上進行預測。最后使用accuracy_score, classification_report, confusion_matrix(并配合seaborn和matplotlib進行可視化)來全面評估和比較各個模型的性能
- 評估與預測:評估模型性能并用其預測新數據,可能會使用cross_val_score或更高級的超參數調優工具(如GridSearchCV)來尋找最佳模型
一、安裝必要的庫并加載數據。我們將使用scikit-learn內置的新聞數據集
# 導入所需庫
import numpy as np
import pandas as pd #用于數值計算和數據操作(如DataFrame)
from sklearn.datasets import fetch_20newsgroups #sklearn (Scikit-learn):機器學習核心庫,從20 Newsgroups數據集中加載數據
from sklearn.feature_extraction.text import TfidfVectorizer # 將文本數據轉換為TF-IDF特征向量
from sklearn.naive_bayes import MultinomialNB #三個不同的分類器模型(多項式樸素貝葉斯、支持向量機、邏輯回歸)
from sklearn.svm import SVC
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split, cross_val_score #用于劃分訓練集/測試集和進行交叉驗證
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score #用于評估模型性能的指標
import matplotlib.pyplot as plt
import seaborn as sns #用于數據可視化和繪制圖表(如混淆矩陣)
import nltk #自然語言處理工具庫
from nltk.corpus import stopwords #常用停用詞列表(如 “the”, “is”, “and”)
from nltk.stem import PorterStemmer, WordNetLemmatizer #用于詞干提取和詞形還原
import re #用于處理正則表達式和字符串操作,常用于文本清洗
import string# 下載NLTK所需數據 下載NLTK模塊運行所需的語料庫和數據文件
nltk.download('stopwords') #下載停用詞列表
nltk.download('wordnet') #下載WordNet詞典,這是詞形還原器(WordNetLemmatizer)所必需的
nltk.download('omw-1.4')#下載Open Multilingual WordNet,為WordNet提供多語言支持(對于英語處理同樣重要)# 從著名的20 Newsgroups數據集中加載一個子集 加載數據:我們選擇3個類別
categories = ['rec.sport.hockey', 'comp.graphics', 'sci.space'] #冰球、計算機圖形學和太空科學
#分別加載官方的訓練集和測試集
#確保數據在加載時被隨機打亂,但同時通過固定隨機種子(42)保證每次運行的結果是可復現的
#此時,newsgroups_train和newsgroups_test是包含數據、標簽、類別名稱等信息的Bunch對象
newsgroups_train = fetch_20newsgroups(subset='train', categories=categories, shuffle=True, random_state=42)
newsgroups_test = fetch_20newsgroups(subset='test', categories=categories, shuffle=True, random_state=42)# 轉換為DataFrame便于處理 將Scikit-learn的Bunch對象轉換為Pandas DataFrame。這是一種非常常見的操作,因為DataFrame提供了更直觀、更方便的數據查看和處理方式(例如,使用iloc, value_counts等)
df_train = pd.DataFrame({'text': newsgroups_train.data, 'target': newsgroups_train.target})#'text': 包含原始的新聞文本內容 (newsgroups_train.data)
df_test = pd.DataFrame({'text': newsgroups_test.data, 'target': newsgroups_test.target})#'target': 包含對應的類別標簽(數字形式,newsgroups_train.target)# 查看數據信息 輸出訓練集和測試集的樣本數量(行數)和特征數量(列數,這里應該是2列:text和target)
print(f"訓練集大小: {df_train.shape}")
print(f"測試集大小: {df_test.shape}")
print("\n類別分布:")
#查看訓練集中每個類別的樣本數量,檢查數據是否平衡(即每個類別的樣本數是否大致相同)。平衡的數據集通常有助于模型更好地學習
#輸出數字標簽(target)與實際類別名稱的對應關系。例如,0可能對應'comp.graphics',1對應'rec.sport.hockey',2對應'sci.space'
print(df_train['target'].value_counts())
print("\n類別名稱:", newsgroups_train.target_names)# 查看前3條數據 定性地查看原始數據的樣子
for i in range(3):#根據數字標簽獲取對應的類別名稱print(f"\n--- 樣本 {i} (類別: {newsgroups_train.target_names[df_train['target'].iloc[i]]}) ---")print(df_train['text'].iloc[i][:200] + "...") # 只顯示前200個字符 獲取第i個樣本的文本內容,并只打印前200個字符。這有助于了解數據的原始格式(通常包含郵件頭、發件人、主題等)、語言風格和需要進行哪些清洗工作。
二、文本預處理
- 清洗、分詞、去停用詞、詞形還原
- 減少噪聲、標準化詞匯、降低特征空間的維度,從而幫助模型專注于真正有信息量的詞匯上,提升其泛化能力和性能
# 初始化工具
stemmer = PorterStemmer()# 初始化波特詞干提取器。詞干提取(Stemming)是一種粗粒度地還原單詞到其詞根形式的方法,通常會直接砍掉詞綴,可能產生不是真正單詞的結果(如 "running" → "run", "happily" → "happili")
lemmatizer = WordNetLemmatizer() #初始化詞形還原器。詞形還原(Lemmatization)是一種更精細的方法,它使用詞典將單詞還原為其字典形式( lemma),并且會考慮單詞的詞性(如 "running" → "run", "better" (adj.) → "good")
stop_words = set(stopwords.words('english'))#獲取英語停用詞列表,并將其轉換為集合(set)。集合的查找速度(O(1))遠快于列表(O(n)),這對于處理大量文本時的性能提升至關重要。停用詞是那些極其常見但攜帶信息量很少的詞(如 "the", "a", "an", "in")def preprocess_text(text):"""文本預處理函數步驟: 小寫 -> 去標點/數字 -> 分詞 -> 去停用詞 -> 詞干提取/詞形還原"""# 1. 轉換為小寫text = text.lower()# 2. 移除標點符號和數字 用正則表達式清理文本中的非字母字符text = re.sub(r'[^a-zA-Z\s]', '', text)# 3. 分詞 將連續的文本字符串分割成單個單詞(或稱為"詞元/tokens")的列表tokens = text.split() #對于更復雜的語言(如中文)或需要保留特定結構的情況,會使用更高級的分詞器(如NLTK的word_tokenize)# 4. 去除停用詞 過濾掉無意義的信息和噪聲#for word in tokens: 遍歷上一步得到的所有詞元#if word not in stop_words: 只保留不在停用詞集合中的詞。#and len(word) > 2: 同時只保留長度大于2的詞。這一步可以過濾掉很多殘留的噪音(如單個字母、縮寫等),這些短詞通常信息量也很低tokens = [word for word in tokens if word not in stop_words and len(word) > 2]# 5. 詞形還原 (比詞干提取更常用) 將單詞的各種屈折形式還原為其基本形式(lemma)# 例如,"running" → "run", "mice" → "mouse", "is" → "be"。這可以進一步減少特征的維度,將相關詞歸并為同一個特征,幫助模型更好地學習tokens = [lemmatizer.lemmatize(word) for word in tokens]# 6. 重新組合為文本 將處理后的詞元列表重新連接成一個干凈的字符串 下一步使用的TfidfVectorizer的輸入通常是字符串序列(每個文檔是一個字符串),而不是詞元列表。return ' '.join(tokens)# 應用預處理
print("原始文本示例:")
print(df_train['text'].iloc[0][:200], "...") #將preprocess_text函數應用到df['text']列的每一個元素上。df_train['cleaned_text'] = df_train['text'].apply(preprocess_text)
df_test['cleaned_text'] = df_test['text'].apply(preprocess_text)print("\n清洗后文本示例:")
print(df_train['cleaned_text'].iloc[0][:200], "...")
三、文本表示(TF-IDF)
將文本轉換為機器學習模型可以理解的數值特征(將文本數據數值化),使用TF-IDF將文本轉換為數值向量
- 初始化:通過配置TfidfVectorizer的參數,設定了特征提取的規則,旨在保留信息量最大的詞匯,剔除噪聲。
- 擬合:在訓練集上學習這些規則,建立從文本到特征向映射關系。
- 轉換:應用學到的規則,分別將訓練集和測試集文本轉換為數值矩陣(X_train_tfidf, X_test_tfidf)
- 輸出:生成的X_train_tfidf和X_test_tfidf就是可以直接輸入到后續分類模型(如MultinomialNB, SVC)中的特征矩陣。它們的每一行代表一個文檔,每一列代表一個特征詞(或n-gram)的TF-IDF權重
# 初始化TF-IDF向量化器 TfidfVectorizer對象,并設置了幾個關鍵參數來控制和優化特征提取過程
# max_features: 只考慮最常見的5000個詞
# min_df: 忽略出現次數少于5次的詞
# max_df: 忽略出現在90%以上文檔中的詞(如"the", "and")
vectorizer = TfidfVectorizer(max_features=5000, #限制特征詞匯表的大小,只考慮整個語料庫中TF-IDF權重最高的5000個詞,降維min_df=5, #忽略那些在所有文檔中出現次數少于5次的詞 去除稀有詞。出現次數極少的詞很可能是拼寫錯誤、非常專業的術語或噪音,它們對于大多數文檔的分類沒有幫助,反而會增加特征空間的噪聲max_df=0.9, #忽略那些出現在超過90%的文檔中的詞,去除常見詞。雖然代碼之前已經用NLTK的停用詞列表過濾過一次,但此參數作為一個額外的安全網。有些詞可能不在標準停用詞列表中,但在特定數據集中出現得過于頻繁(例如,在這個新聞數據集里可能常見的"writes"、"article"等),這些詞攜帶的類別區分信息非常少,類似于停用詞。stop_words='english',#指定使用內置的英語停用詞列表進行過濾,這里與之前NLTK的預處理是冗余的。因為cleaned_text已經移除了停用詞,所以此處的參數可能不會起到額外作用。通常,只需在一處做停用詞移除即可ngram_range=(1, 2)) # 同時使用單個詞和雙詞組合 #指定不僅要提取單個詞(unigrams),還要提取相鄰的兩個詞組合(bigrams)捕獲上下文信息。有些概念由多個詞組成,例如:"machine" (1-gram) 和 "learning" (1-gram) 單獨出現時含義廣泛。 "machine learning" (2-gram) 作為一個整體則是一個信息量高度集中的特定術語。# 在訓練集上擬合并轉換 fit():學習訓練數據的特征。具體來說,它基于df_train['cleaned_text']
# 構建詞匯表(所有不同的詞) 計算每個詞的文檔頻率(DF)(即有多少個文檔包含這個詞) 根據max_features, min_df, max_df等參數過濾詞匯表,確定最終要使用的特征詞
X_train_tfidf = vectorizer.fit_transform(df_train['cleaned_text'])
# 在測試集上轉換(注意:只transform,不fit)#應用剛才學到的規則,轉換訓練數據。它使用學習到的詞匯表和DF值,為每一篇訓練文檔計算TF-IDF特征向量,最終生成一個稀疏矩陣 X_train_tfidf。
X_test_tfidf = vectorizer.transform(df_test['cleaned_text'])y_train = df_train['target']
y_test = df_test['target']print(f"訓練集特征維度: {X_train_tfidf.shape}")#輸出稀疏矩陣的形狀,格式為(樣本數量, 特征數量)
print(f"測試集特征維度: {X_test_tfidf.shape}")# 查看特征詞 獲取最終向量化器所采用的特征名稱(即詞匯表)
feature_names = vectorizer.get_feature_names_out()
print(f"\n前20個特征詞: {feature_names[:20]}")
四、模型訓練與評估
- 使用準確率、分類報告、混淆矩陣
# 初始化不同模型 創建一個字典,以便于循環遍歷和比較多個不同的模型
models = {'Naive Bayes': MultinomialNB(),# (樸素貝葉斯): 非常適合文本分類的經典模型,特別適用于像TF-IDF這樣的離散特征。它計算高效,通常在文本任務上有一個不錯的基線表現。'SVM': SVC(kernel='linear', random_state=42),#(支持向量機)kernel='linear': 指定使用線性核。對于高維文本數據(如TF-IDF)random_state=42: 設置隨機種子,確保結果可復現'Logistic Regression': LogisticRegression(max_iter=1000, random_state=42)#(邏輯回歸):增加最大迭代次數。邏輯回歸使用迭代優化算法,對于大型數據集,默認的100次迭代可能不足以收斂。這個參數防止算法因未收斂而發出警告
}# 訓練并評估每個模型
results = {}
for name, model in models.items():print(f"\n=== 訓練 {name} ===")# 訓練模型 在訓練數據上訓練模型。模型學習TF-IDF特征(X_train_tfidf)和對應標簽(y_train)之間的關系,并調整其內部參數model.fit(X_train_tfidf, y_train)# 預測 使用訓練好的模型對從未見過的測試集(X_test_tfidf)進行預測,生成預測標簽(y_pred)y_pred = model.predict(X_test_tfidf)# 評估 計算準確率:(預測正確的樣本數) / (總樣本數)accuracy = accuracy_score(y_test, y_pred)results[name] = accuracyprint(f"準確率: {accuracy:.4f}")print("\n分類報告:")#提供更詳細的、按類別劃分的評估指標print(classification_report(y_test, y_pred, target_names=newsgroups_train.target_names))# 比較模型性能
print("\n=== 模型性能比較 ===")
for name, acc in results.items():print(f"{name}: {acc:.4f}")# 選擇最佳模型(這里假設Naive Bayes最好)
best_model = MultinomialNB()
best_model.fit(X_train_tfidf, y_train)
五、可視化與深入分析
# 繪制混淆矩陣 創建一個可視化函數來顯示模型的詳細分類性能,超越簡單的準確率
def plot_confusion_matrix(y_true, y_pred, class_names):cm = confusion_matrix(y_true, y_pred) #計算混淆矩陣。這是一個N×N的矩陣(N為類別數),其中:行代表真實標簽 列代表預測標簽 對角線上的值表示正確分類的樣本數 非對角線上的值表示誤分類的樣本數plt.figure(figsize=(8, 6))sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', #使用Seaborn繪制熱力圖 annot=True: 在每個單元格中顯示數值 fmt='d': 將數值格式化為整數(decimal)cmap='Blues': 使用藍色系顏色映射(顏色越深表示數值越大)xticklabels=class_names, yticklabels=class_names: 使用類別名稱而不是數字作為坐標軸標簽xticklabels=class_names, yticklabels=class_names)plt.title('混淆矩陣')plt.ylabel('真實標簽')plt.xlabel('預測標簽')plt.show()# 使用最佳模型的預測結果
y_pred_best = best_model.predict(X_test_tfidf)
plot_confusion_matrix(y_test, y_pred_best, newsgroups_train.target_names)#使用最佳模型對測試集進行預測,然后調用上面的函數繪制混淆矩陣
# 分析最重要的特征(詞) 可視化每個類別中最具判別性的詞匯,幫助理解模型是如何區分不同類別的
def plot_top_features(model, vectorizer, class_names, n=10):"""可視化每個類別最重要的特征詞"""fig, axes = plt.subplots(1, len(class_names), figsize=(15, 5))if len(class_names) == 1:axes = [axes]for i, class_label in enumerate(class_names):# 獲取該類別的特征重要性(對數概率)feature_importance = model.feature_log_prob_[i]# 獲取最重要的特征索引top_indices = np.argsort(feature_importance)[-n:][::-1]top_features = [feature_names[idx] for idx in top_indices]top_scores = [feature_importance[idx] for idx in top_indices]axes[i].barh(range(n), top_scores[::-1])axes[i].set_yticks(range(n))axes[i].set_yticklabels(top_features[::-1])axes[i].set_title(f'Top features for: {class_label}')plt.tight_layout()plt.show()# 繪制特征重要性(適用于Naive Bayes)
plot_top_features(best_model, vectorizer, newsgroups_train.target_names, n=8)
六、使用模型進行新預測
# 準備新的新聞文本進行預測 創建三條新的新聞文本,分別對應于之前定義的三個類別(太空、冰球、計算機圖形學)
#NASA, Mars, orbit, space → 應該預測為 sci.space
#hockey, championship, goal, game → 應該預測為 rec.sport.hockey
#computer graphics, rendering, 3D → 應該預測為 comp.graphics
new_news = ["The NASA spacecraft successfully entered Mars orbit today after a seven-month journey through space.","The hockey team won the championship with an amazing final goal in the last seconds of the game.","New computer graphics technology allows for real-time rendering of complex 3D environments with stunning detail."
]# 預處理新文本 對新文本應用與訓練數據完全相同的預處理流程
#使用列表推導式,對new_news中的每個文本調用之前定義的preprocess_text()函數。
cleaned_news = [preprocess_text(text) for text in new_news]# 轉換為TF-IDF特征 transform():使用之前從訓練集學習到的詞匯表和IDF權重來轉換新數據。
#這意味著:如果新文本中包含訓練時未見過的詞,它們會被忽略;如果包含訓練時見過的詞,會使用訓練時計算好的IDF值。
new_news_tfidf = vectorizer.transform(cleaned_news)# 預測 predict():返回每個樣本的預測類別(數字標簽)
predictions = best_model.predict(new_news_tfidf)
#predict_proba():返回每個樣本屬于各個類別的概率值。這是一個形狀為(3, 3)的數組(3個樣本,3個類別)
prediction_proba = best_model.predict_proba(new_news_tfidf)# 顯示預測結果
print("=== 新新聞預測結果 ===")
for i, (text, pred, proba) in enumerate(zip(new_news, predictions, prediction_proba)):predicted_class = newsgroups_train.target_names[pred]print(f"\n新聞 {i+1}:")print(f"文本: {text[:80]}...")print(f"預測類別: {predicted_class}")print(f"各類別概率: {dict(zip(newsgroups_train.target_names, np.round(proba, 3)))}")
預測輸出案例:
=== 新新聞預測結果 ===新聞 1:
文本: The NASA spacecraft successfully entered Mars orbit today after a seven-month journe...
預測類別: sci.space
各類別概率: {'comp.graphics': 0.001, 'rec.sport.hockey': 0.002, 'sci.space': 0.997}新聞 2:
文本: The hockey team won the championship with an amazing final goal in the last seconds...
預測類別: rec.sport.hockey
各類別概率: {'comp.graphics': 0.003, 'rec.sport.hockey': 0.995, 'sci.space': 0.002}新聞 3:
文本: New computer graphics technology allows for real-time rendering of complex 3D envir...
預測類別: comp.graphics
各類別概率: {'comp.graphics': 0.996, 'rec.sport.hockey': 0.002, 'sci.space': 0.002}
使用LSTM進行情感分析(分類任務)
一、環境準備與數據加載
import tensorflow as tf #TensorFlow/Keras: 主深度學習框架
from tensorflow.keras.models import Sequential #Sequential: 順序模型,用于堆疊神經網絡層
#Embedding: 詞嵌入層,將單詞轉換為密集向量
#LSTM: 長短期記憶網絡,處理序列數據
#Bidirectional: 雙向LSTM,從兩個方向處理序列
#Dense: 全連接層
#Dropout: 防止過擬合的正則化層
from tensorflow.keras.layers import Embedding, LSTM, Dense, Dropout, Bidirectional
from tensorflow.keras.preprocessing.text import Tokenizer #Tokenizer: 文本分詞和向量化
from tensorflow.keras.preprocessing.sequence import pad_sequences #pad_sequences: 將序列填充到相同長度
from tensorflow.keras.callbacks import EarlyStopping#EarlyStopping: 提前停止訓練回調import numpy as np #numpy: 數值計算
import pandas as pd #pandas: 數據處理和分析
from sklearn.model_selection import train_test_split #train_test_split: 數據集劃分
from sklearn.metrics import classification_report, confusion_matrix#classification_report: 分類性能報告 #confusion_matrix: 混淆矩陣import matplotlib.pyplot as plt#matplotlib/seaborn: 數據可視化
import seaborn as sns #stopwords: 停用詞列表
import re
from nltk.corpus import stopwords #nltk: 自然語言處理工具包
import nltknltk.download('stopwords') #停用詞下載: nltk.download('stopwords') - 獲取常見無意義詞匯# 設置隨機種子確保結果可重現
tf.random.set_seed(42)
np.random.seed(42)
二、加載和預處理IMDb電影評論數據集
- 加載原始數據并限制詞匯表大小
- 創建詞匯表映射以便查看原始文本
- 將變長序列填充/截斷為固定長度
- 準備用于訓練神經網絡的數據格式
# 加載IMDb數據集
from tensorflow.keras.datasets import imdb# 只保留數據集中最常用的10000個詞
vocab_size = 10000 #vocab_size = 10000: 限制詞匯表大小,只使用數據集中最常用的10,000個單詞
max_length = 200 # 每條評論最大長度 max_length = 200: 設置每條評論的最大長度,超過此長度的將被截斷,不足的將被填充# 加載數據 num_words=vocab_size: 只保留出現頻率最高的10,000個單詞
(X_train, y_train), (X_test, y_test) = imdb.load_data(num_words=vocab_size)# 獲取詞匯表
word_index = imdb.get_word_index()#獲取單詞到整數的映射字典
reverse_word_index = {value: key for key, value in word_index.items()} #創建整數到單詞的反向映射字典,用于將數字序列解碼回文本# 解碼一條評論看看 這個函數將整數序列轉換回可讀的文本
def decode_review(encoded_review):#i - 3: 因為IMDb數據集中的索引偏移了3(0、1、2分別用于填充、序列開始和未知詞)#reverse_word_index.get(i - 3, '?'): 如果找不到對應的單詞,使用'?'代替return ' '.join([reverse_word_index.get(i - 3, '?') for i in encoded_review])print("示例評論:")
print(decode_review(X_train[0]))
print(f"情感標簽: {y_train[0]} (0=負面, 1=正面)")# 填充序列到相同長度 pad_sequences: 將所有序列填充/截斷到相同長度
#axlen=max_length: 設置序列的最大長度為200
#padding='post': 在序列末尾進行填充(而不是開頭)
#truncating='post': 從序列末尾截斷(而不是開頭)
X_train = pad_sequences(X_train, maxlen=max_length, padding='post', truncating='post')
X_test = pad_sequences(X_test, maxlen=max_length, padding='post', truncating='post')#處理后,訓練集和測試集都變為形狀為(樣本數量, 200)的二維數組
#例如:(25000, 200)表示25,000條評論,每條評論長度為200
print(f"\n訓練集形狀: {X_train.shape}")
print(f"測試集形狀: {X_test.shape}")
三、 構建LSTM模型
- 輸入: 形狀為(batch_size, 200)的整數序列
- 嵌入層: 轉換為(batch_size, 200, 128)的詞向量矩陣
- 雙向LSTM: 處理序列,輸出(batch_size, 128)的特征向量(64×2,雙向)
- Dropout: 隨機丟棄50%的連接
- 全連接層: 進一步提取特征,32維輸出
- Dropout: 隨機丟棄30%的連接
- 輸出層: 輸出0-1之間的情感概率
# 構建LSTM模型
embedding_dim = 128 # 詞向量維度 詞向量的維度,每個單詞將被表示為128維的密集向量
lstm_units = 64 # LSTM單元數量 LSTM層中隱藏單元的數量,控制模型的容量和復雜度model = Sequential([# 嵌入層:將整數索引轉換為密集向量 將每個單詞的整數索引轉換為密集的詞向量表示Embedding(input_dim=vocab_size, #詞匯表大小(10,000)output_dim=embedding_dim, #輸出向量維度(128維)input_length=max_length),#輸入序列長度(200)# 雙向LSTM層:從兩個方向捕捉上下文信息 捕捉文本中的雙向上下文信息,提高對語義的理解#lstm_units=64: LSTM單元數量#return_sequences=False: 只返回最后一個時間步的輸出(不是整個序列)#Bidirectional(): 包裝器,使LSTM從正向和反向兩個方向處理序列Bidirectional(LSTM(lstm_units, return_sequences=False)),# Dropout層:防止過擬合 作用:正則化技術,防止模型過擬合訓練數據Dropout(0.5),#rate=0.5: 丟棄50%的神經元連接# 全連接層 Dense(32, activation='relu'), #units=32: 32個神經元 activation='relu': 使用ReLU激活函數Dropout(0.3), #第二個Dropout層:丟棄30%的連接,進一步防止過擬合# 輸出層:二分類 units=1: 1個神經元(二分類問題) activation='sigmoid': Sigmoid激活函數,輸出0-1之間的概率值Dense(1, activation='sigmoid')
])# 編譯模型
model.compile(optimizer='adam',#使用Adam優化器,適合大多數深度學習任務loss='binary_crossentropy', #二元交叉熵損失函數,適合二分類問題metrics=['accuracy', 'precision', 'recall'] #評估指標 accuracy: 準確率 precision: 精確率(預測為正例中真正正例的比例) recall: 召回率(真正正例中被預測為正例的比例)
)# 顯示模型結構 顯示每層的類型、輸出形狀、參數數量 幫助理解模型結構和參數規模 便于調試和優化模型
model.summary()
四、訓練模型
- 數據分割:訓練開始時,自動將X_train的20%作為驗證集
- 訓練循環:每個epoch使用64個樣本的批次進行訓練
- 驗證評估:每個epoch結束后在驗證集上評估性能
- 早停監控:持續監控驗證集損失,決定是否提前停止
- 權重恢復:訓練結束后自動恢復到最佳性能的權重
# 設置早停回調 在訓練過程中監控驗證集性能,當性能不再提升時自動停止訓練
#訓練結束后恢復最佳模型的權重(而不是最后一個epoch的權重)
#防止過擬合:避免在訓練集上表現過好但在驗證集上性能下降
#節省時間:自動停止不必要的訓練輪次
#獲得最佳模型:通過restore_best_weights確保得到泛化能力最好的模型
early_stopping = EarlyStopping(monitor='val_loss',#監控驗證集損失值(validation loss)patience=3, #允許性能不提升的輪次數。如果連續3個epoch驗證損失沒有改善,則停止訓練restore_best_weights=True #訓練結束后恢復最佳模型的權重(而不是最后一個epoch的權重)
)# 訓練模型 包含訓練過程中所有指標的歷史記錄
history = model.fit(X_train, y_train, #訓練數據(填充后的整數序列) 訓練標簽(0或1的情感標簽)epochs=15, #最大訓練輪次為15 實際可能因早停而提前結束batch_size=64, #每個批次的樣本數量 將25,000個訓練樣本分成每批64個的小批次 每epoch需要約391個批次(25000/64≈391)validation_split=0.2, # 從訓練集中劃分20%作為驗證集callbacks=[early_stopping],#使用早停回調函數 可以添加多個回調函數,如[early_stopping, checkpoint]verbose=1 #顯示訓練進度條 0: 不顯示輸出 1: 顯示進度條 2: 每個epoch顯示一行信息
)
五、評估模型
# 評估模型
test_loss, test_accuracy, test_precision, test_recall = model.evaluate(X_test, y_test, verbose=0)
print(f"測試集準確率: {test_accuracy:.4f}")
print(f"測試集精確率: {test_precision:.4f}")
print(f"測試集召回率: {test_recall:.4f}")# 繪制訓練歷史
def plot_training_history(history):fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))# 繪制準確率ax1.plot(history.history['accuracy'], label='訓練準確率')ax1.plot(history.history['val_accuracy'], label='驗證準確率')ax1.set_title('模型準確率')ax1.set_xlabel('Epoch')ax1.set_ylabel('Accuracy')ax1.legend()# 繪制損失ax2.plot(history.history['loss'], label='訓練損失')ax2.plot(history.history['val_loss'], label='驗證損失')ax2.set_title('模型損失')ax2.set_xlabel('Epoch')ax2.set_ylabel('Loss')ax2.legend()plt.tight_layout()plt.show()plot_training_history(history)# 預測并生成分類報告
y_pred_proba = model.predict(X_test)
y_pred = (y_pred_proba > 0.5).astype(int)print("\n分類報告:")
print(classification_report(y_test, y_pred, target_names=['負面', '正面']))# 繪制混淆矩陣
cm = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(6, 5))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=['負面', '正面'], yticklabels=['負面', '正面'])
plt.title('混淆矩陣')
plt.ylabel('真實標簽')
plt.xlabel('預測標簽')
plt.show()
六、對新文本進行預測
# 對新評論進行情感分析
def predict_sentiment(text):# 文本預處理函數def preprocess_text(text):text = text.lower()text = re.sub(r'[^a-zA-Z\s]', '', text)tokens = text.split()return ' '.join(tokens)# 將文本轉換為序列processed_text = preprocess_text(text)words = processed_text.split()# 將單詞轉換為索引sequence = []for word in words:if word in word_index and word_index[word] < vocab_size:sequence.append(word_index[word] + 3) # +3 是因為IMDb數據集的偏移# 填充序列padded_sequence = pad_sequences([sequence], maxlen=max_length, padding='post', truncating='post')# 預測prediction = model.predict(padded_sequence)[0][0]sentiment = "正面" if prediction > 0.5 else "負面"confidence = prediction if prediction > 0.5 else 1 - predictionreturn sentiment, confidence, prediction# 測試新評論
test_reviews = ["This movie was absolutely fantastic! The acting was superb and the story was engaging.","I hated this film. It was boring and poorly made. Waste of time.","The movie was okay, not great but not terrible either. Some good scenes but overall mediocre.","An amazing masterpiece that deserves all the awards. Brilliant direction and performances!"
]print("=== 新評論情感分析 ===")
for i, review in enumerate(test_reviews):sentiment, confidence, score = predict_sentiment(review)print(f"\n評論 {i+1}:")print(f"內容: {review[:80]}...")print(f"預測: {sentiment} (置信度: {confidence:.3f}, 原始分數: {score:.3f})")
使用LSTM進行文本生成(序列生成任務)
一、準備文本數據
# 下載莎士比亞文本數據
import tensorflow as tf
import numpy as nppath_to_file = tf.keras.utils.get_file('shakespeare.txt', 'https://storage.googleapis.com/download.tensorflow.org/data/shakespeare.txt'
)# 讀取數據
text = open(path_to_file, 'rb').read().decode(encoding='utf-8')
print(f'文本長度: {len(text)} 字符')
print(f'文本前500字符:\n{text[:500]}')
二、文本預處理
# 創建詞匯表
vocab = sorted(set(text))
print(f'唯一字符數: {len(vocab)}')# 創建字符到索引和索引到字符的映射
char2idx = {char: idx for idx, char in enumerate(vocab)}
idx2char = {idx: char for idx, char in enumerate(vocab)}# 將文本轉換為數字序列
text_as_int = np.array([char2idx[char] for char in text])print(f'文本:\n"{text[:50]}"')
print(f'\n對應的索引:\n{text_as_int[:50]}')# 創建訓練樣本
seq_length = 100 # 輸入序列長度
examples_per_epoch = len(text) // (seq_length + 1)# 創建訓練數據集
char_dataset = tf.data.Dataset.from_tensor_slices(text_as_int)# 將字符序列轉換為輸入-目標對
sequences = char_dataset.batch(seq_length + 1, drop_remainder=True)def split_input_target(chunk):input_text = chunk[:-1]target_text = chunk[1:]return input_text, target_textdataset = sequences.map(split_input_target)# 批處理和打亂數據
BATCH_SIZE = 64
BUFFER_SIZE = 10000dataset = dataset.shuffle(BUFFER_SIZE).batch(BATCH_SIZE, drop_remainder=True)
三、構建文本生成模型
# 模型參數
vocab_size = len(vocab)
embedding_dim = 256
rnn_units = 1024# 構建模型
def build_model(vocab_size, embedding_dim, rnn_units, batch_size):model = tf.keras.Sequential([tf.keras.layers.Embedding(vocab_size, embedding_dim,batch_input_shape=[batch_size, None]),tf.keras.layers.LSTM(rnn_units,return_sequences=True,stateful=True,recurrent_initializer='glorot_uniform'),tf.keras.layers.Dropout(0.2),tf.keras.layers.LSTM(rnn_units,return_sequences=True,stateful=True,recurrent_initializer='glorot_uniform'),tf.keras.layers.Dropout(0.2),tf.keras.layers.Dense(vocab_size)])return modelmodel = build_model(vocab_size, embedding_dim, rnn_units, BATCH_SIZE)
model.summary()
四、訓練模型
# 定義損失函數
def loss(labels, logits):return tf.keras.losses.sparse_categorical_crossentropy(labels, logits, from_logits=True)# 編譯模型
model.compile(optimizer='adam', loss=loss)# 配置檢查點
checkpoint_dir = './training_checkpoints'
checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt_{epoch}")checkpoint_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_prefix,save_weights_only=True)# 訓練模型(為了演示,只訓練少量輪次)
EPOCHS = 10history = model.fit(dataset, epochs=EPOCHS, callbacks=[checkpoint_callback])
五、文本生成函數
# 為了生成文本,我們需要重建模型用于單批次輸入
model = build_model(vocab_size, embedding_dim, rnn_units, batch_size=1)
model.load_weights(tf.train.latest_checkpoint(checkpoint_dir))
model.build(tf.TensorShape([1, None]))# 文本生成函數
def generate_text(model, start_string, num_generate=1000, temperature=1.0):# 將起始字符串轉換為數字input_eval = [char2idx[s] for s in start_string]input_eval = tf.expand_dims(input_eval, 0)# 空列表用于存儲結果text_generated = []# 重置模型狀態model.reset_states()for i in range(num_generate):predictions = model(input_eval)# 移除批次維度predictions = tf.squeeze(predictions, 0)# 用溫度參數調整預測分布predictions = predictions / temperaturepredicted_id = tf.random.categorical(predictions, num_samples=1)[-1, 0].numpy()# 將預測的字符和之前的輸入一起傳遞給模型作為下一個輸入input_eval = tf.expand_dims([predicted_id], 0)text_generated.append(idx2char[predicted_id])return (start_string + ''.join(text_generated))# 生成文本
print(generate_text(model, start_string=u"ROMEO: ", temperature=0.8))