Natural Language Processing with Disaster Tweets | Kaggle
任务 判断一句话是否关于灾难predicting whether a given tweet is about a real disaster or not
预处理清洗掉网页信息中的无效符号;
编码方式有:词袋模型、TF-IDF、GloVe、BERT
再利用机器学习分类模型如岭回归、逻辑回归、支持向量机、随机森林;也可以用双头LSTM等深度学习模型
目录
[优化一 text清洗:删除无效符号](#优化一 text清洗:删除无效符号)
[1. TF-IDF编码 + 预处理 + 三种分类模型](#1. TF-IDF编码 + 预处理 + 三种分类模型)
[1.1 TF-IDF向量化](#1.1 TF-IDF向量化)
[1.2 三种分类模型比较](#1.2 三种分类模型比较)
[1.3 交叉验证评估分数](#1.3 交叉验证评估分数)
[2. GloVe+双头LSTM](#2. GloVe+双头LSTM)
[2.1 text所有单词->词库corpus](#2.1 text所有单词->词库corpus)
[2.2 用GloVe 构建embedding_dict 词->向量 字典](#2.2 用GloVe 构建embedding_dict 词->向量 字典)
[2.3 text -> 数值序列](#2.3 text -> 数值序列)
[2.4 embedding_matrix 数值->向量 准备词嵌入层](#2.4 embedding_matrix 数值->向量 准备词嵌入层)
[2.5 双向LSTM文本分类模型](#2.5 双向LSTM文本分类模型)
[2.6 划分训练数据+训练](#2.6 划分训练数据+训练)
[3. BERT编码 + 训练微调](#3. BERT编码 + 训练微调)
[3.1 文本编码函数](#3.1 文本编码函数)
[3.2 模型构建函数](#3.2 模型构建函数)
[3.3 加载预训练数据](#3.3 加载预训练数据)
[3.4 模型训练](#3.4 模型训练)
[3.5 加载参数 预测0-1 & 提交](#3.5 加载参数 预测0-1 & 提交)
简单版:词袋模型编码+岭分类器拟合
词袋模型 (Bag-of-Words):把每条推文拆成单词(或 n-gram),统计每个词在语料中的出现次数。
得到 稀疏矩阵 使用 岭回归分类器 (Ridge Classifier):线性模型 + L2 正则化,适合高维稀疏数据。
导入数据 并分别查看 非灾难推文和灾难推文 的前5条
python
import numpy as np
import pandas as pd
train_df = pd.read_csv("/kaggle/input/nlp-getting-started/train.csv")
test_df = pd.read_csv("/kaggle/input/nlp-getting-started/test.csv")
# 查看非灾难推文的前5条
non_disaster_tweets = train_df[train_df["target"] == 0]["text"]
print("非灾难推文示例:")
print(non_disaster_tweets.head())
# 查看灾难推文的前5条
disaster_tweets = train_df[train_df["target"] == 1]["text"]
print("\n灾难推文示例:")
print(disaster_tweets.head())

词袋模型(Bag-of-Words) 词频统计 进行 fit + transform
python
from sklearn import feature_extraction, linear_model, model_selection, preprocessing
# CountVectorizer() 词袋模型 fit+transform
count_vectorizer = feature_extraction.text.CountVectorizer()
train_vectors = count_vectorizer.fit_transform(train_df["text"])
test_vectors = count_vectorizer.transform(test_df["text"])
岭分类器 (Ridge Classifier) 进行对 train 向量 fit ; 对 test 向量 predict 并保存结果
python
# 岭分类器 拟合+预测
clf = linear_model.RidgeClassifier(solver='lsqr')
clf.fit(train_vectors, train_df["target"])
sample_submission = pd.read_csv("/kaggle/input/nlp-getting-started/sample_submission.csv")
sample_submission["target"] = clf.predict(test_vectors)
sample_submission.to_csv("submission.csv", index=False)
优化一 text清洗:删除无效符号
文本清理函数:移除URL + HTML标签 + 表情符号 + 标点符号
python
import re
import string
def clean_text(text):
if not isinstance(text, str):
return text
# 1. 移除URL
url_pattern = re.compile(r'https?://\S+|www\.\S+')
text = url_pattern.sub(r'', text)
# 2. 移除HTML标签
html_pattern = re.compile(r'<.*?>')
text = html_pattern.sub(r'', text)
# 3. 移除表情符号
emoji_pattern = re.compile("["
u"\U0001F600-\U0001F64F" # emoticons
u"\U0001F300-\U0001F5FF" # symbols & pictographs
u"\U0001F680-\U0001F6FF" # transport & map symbols
u"\U0001F1E0-\U0001F1FF" # flags (iOS)
u"\U00002702-\U000027B0"
u"\U000024C2-\U0001F251"
"]+", flags=re.UNICODE)
text = emoji_pattern.sub(r'', text)
# 4. 移除标点符号
table = str.maketrans('', '', string.punctuation)
text = text.translate(table)
return text
df['text'] = df['text'].apply(lambda x: clean_text(x))
进行数据导入和预处理text之后 还可以有以下3种进阶处理方式。
1. TF-IDF编码 + 预处理 + 三种分类模型
Disaster Tweets-tfidf | Kaggle
1.1 TF-IDF向量化
python
# TF-IDF向量化(带参数调优)
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf_vectorizer = TfidfVectorizer(
max_features=10000,
ngram_range=(1, 2), # 包含unigram和bigram
stop_words='english',
min_df=2,
max_df=0.8
)
train_vectors = tfidf_vectorizer.fit_transform(train_df['cleaned_text'])
test_vectors = tfidf_vectorizer.transform(test_df['cleaned_text'])
1.2 三种分类模型比较
转化后的向量 用三种模型分类(逻辑回归 支持向量机 随机森林)
python
from sklearn.linear_model import LogisticRegression
from sklearn.svm import LinearSVC
from sklearn.ensemble import RandomForestClassifier
# 1. Logistic Regression(逻辑回归)
print("训练Logistic Regression...")
lr_clf = LogisticRegression(
C=1.0,
solver='liblinear',
random_state=42,
max_iter=1000
)
lr_clf.fit(train_vectors, train_df["target"])
# 2. Linear SVC(支持向量机)
print("训练Linear SVC...")
svc_clf = LinearSVC(
C=0.5,
random_state=42,
max_iter=1000
)
svc_clf.fit(train_vectors, train_df["target"])
# 3. 随机森林(适合集成学习)
print("训练Random Forest...")
rf_clf = RandomForestClassifier(
n_estimators=100,
max_depth=20,
random_state=42,
n_jobs=-1
)
rf_clf.fit(train_vectors, train_df["target"])
1.3 交叉验证评估分数
交叉验证比较模型F1分数
python
from sklearn.model_selection import cross_val_score
from sklearn.metrics import accuracy_score
# 交叉验证评估
print("\n模型交叉验证得分:")
models = {
'LogisticRegression': lr_clf,
'LinearSVC': svc_clf,
'RandomForest': rf_clf
}
for name, model in models.items():
scores = cross_val_score(model, train_vectors, train_df["target"], cv=5, scoring='f1')
print(f"{name}: F1得分 = {scores.mean():.4f} (±{scores.std():.4f})")

LinearSVC 支持向量机分数最高,用它预测最后结果
python
sample_submission = pd.read_csv("/kaggle/input/nlp-getting-started/sample_submission.csv")
sample_submission["target"] = svc_clf.predict(test_vectors) # 使用svc
sample_submission.to_csv("submission.csv", index=False)
2. GloVe+双头LSTM
Disaster Tweets GloVe+Bidirectional LSTM | Kaggle
2.1 text所有单词->词库corpus
对每条tweet进行分词处理word_tokenize(tweet) ; 转换为小写word.lower();
将处理后的单词列表添加到词库corpus中
python
from tqdm import tqdm
from nltk.tokenize import word_tokenize
corpus=[]
for tweet in tqdm(df['text']):
words=[word.lower() for word in word_tokenize(tweet)]
corpus.append(words)
2.2 用GloVe 构建embedding_dict 词->向量 字典
python
embedding_dict={} # 字典
with open('/kaggle/input/glove-global-vectors-for-word-representation/glove.6B.100d.txt','r') as f:
for line in f:
values=line.split()
word = values[0] # 词
vectors=np.asarray(values[1:],'float32') # 向量
embedding_dict[word]=vectors
f.close()
2.3 text -> 数值序列
Tokenizer()根据词库corpus构建词汇表;
将文本转换为数值序列 并填充为相同的长度tweet_pad
python
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
MAX_LEN = 50
tokenizer_obj = Tokenizer()
tokenizer_obj.fit_on_texts(corpus)
sequences = tokenizer_obj.texts_to_sequences(corpus)
tweet_pad = pad_sequences(sequences, maxlen=MAX_LEN, truncating='post', padding='post')
tweet_pad[0][0:] # 第一句话示例
2.4 embedding_matrix 数值->向量 准备词嵌入层
word_index 为词汇表中 词->数值 的映射;embedding_dict 为 词->向量
结合得embedding_matrix 数值->向量的映射,作为后续深度学习的词嵌入层
python
word_index=tokenizer_obj.word_index
num_words=len(word_index)+1 # 词汇表词个数
embedding_matrix=np.zeros((num_words,100)) # 每个词 长度为100的向量
for word,i in tqdm(word_index.items()):
if i < num_words:
emb_vec=embedding_dict.get(word)
if emb_vec is not None: # 在GloVe中有对应向量 就填入
embedding_matrix[i]=emb_vec
2.5 双向LSTM文本分类模型
Embedding + Dropout + Bidirectional LSTM + Dense Layer + Output Layer

python
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Embedding, LSTM, Dense, SpatialDropout1D, Bidirectional, Dropout
from tensorflow.keras.initializers import Constant
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.regularizers import l2
model = Sequential()
# 嵌入层 利用之前的embedding_matrix
embedding = Embedding(
num_words,
100,
embeddings_initializer=Constant(embedding_matrix),
trainable=False # 保持预训练词向量不变
)
model.add(embedding)
# 空间dropout
model.add(SpatialDropout1D(0.3))
# 双向LSTM
model.add(Bidirectional(LSTM(
64,
dropout=0.3,
recurrent_dropout=0.3,
return_sequences=False # 只返回最后输出
)))
# 添加全连接层
model.add(Dense(32, activation='relu', kernel_regularizer=l2(0.001)))
model.add(Dropout(0.5))
# 输出层
model.add(Dense(1, activation='sigmoid'))
# 优化器
optimizer = Adam(
learning_rate=1e-4, # 降低学习率
beta_1=0.9,
beta_2=0.999,
epsilon=1e-7
)
# 编译模型
model.compile(
loss='binary_crossentropy',
optimizer=optimizer,
metrics=['accuracy', 'precision', 'recall']
)
model.build(input_shape=(None, MAX_LEN))
model.summary()
2.6 划分训练数据+训练
python
from sklearn.model_selection import train_test_split
train=tweet_pad[:tweet.shape[0]]
test=tweet_pad[tweet.shape[0]:]
X_train,X_test,y_train,y_test=train_test_split(train,tweet['target'].values,test_size=0.2)
print('Shape of train',X_train.shape)
print("Shape of Validation ",X_test.shape)
history=model.fit(X_train,y_train,batch_size=4,epochs=15,validation_data=(X_test,y_test),verbose=2)

预测+提交
python
pred_GloVe = model.predict(test)
sample_submission = pd.read_csv("/kaggle/input/nlp-getting-started/sample_submission.csv")
sample_submission["target"] = pred_GloVe.round().astype('int') # 使用LSTM预测结果
sample_submission.to_csv("submission.csv", index=False)
3. BERT编码 + 训练微调
Disaster NLP: Keras BERT using TFHub | Kaggle BERT using TFHub
需要先构造 文本-> BERT格式-> 模型结构
(原kaggle代码 使用的那个网页的tokenizer现在停止服务了 仅学习思路)
3.1 文本编码函数
把一批文本转成 BERT 输入格式:
-
input_ids
:单词对应的词表ID。 -
input_mask
:是否是padding。 有效位置为1,padding为0。 -
segment_ids
:句子编号(这里只有一个句子,所以全0)。
句子前后分别补上 [CLS]和[SEP] ;统一长度为max_len 缺失部分padding补0
python
def bert_encode(texts, tokenizer, max_len=512):
all_tokens = []
all_masks = []
all_segments = []
for text in texts:
text = tokenizer.tokenize(text) # 分词
text = text[:max_len-2] # 截断,留出[CLS]和[SEP]位置
input_sequence = ["[CLS]"] + text + ["[SEP]"] # 加上特殊符号
pad_len = max_len - len(input_sequence)
tokens = tokenizer.convert_tokens_to_ids(input_sequence) # 转换成词表ID
tokens += [0] * pad_len # padding
pad_masks = [1] * len(input_sequence) + [0] * pad_len # mask:真实token=1, pad=0
segment_ids = [0] * max_len # 句子分段标记(单句子任务全0)
all_tokens.append(tokens)
all_masks.append(pad_masks)
all_segments.append(segment_ids)
return np.array(all_tokens), np.array(all_masks), np.array(all_segments)
3.2 模型构建函数
模型接受了bert_encode 形式的三个输入之后;送到bert_layer 得到sequence_outout
第0个token 为原始的**[CLS]向量** 被用作句子级别特征,并给sigmoid + binary_crossentropy 进行二分类
python
def build_model(bert_layer, max_len=512):
# 1) 三个输入:token id, attention mask, token type ids(segment ids)
input_word_ids = Input(shape=(max_len,), dtype=tf.int32, name="input_word_ids")
input_mask = Input(shape=(max_len,), dtype=tf.int32, name="input_mask")
segment_ids = Input(shape=(max_len,), dtype=tf.int32, name="segment_ids")
# 2) 把这三个输入送入 BERT 层,得到 BERT 的输出
# 通常 bert_layer(...) 返回 (pooled_output, sequence_output)
# pooled_output: shape (batch, hidden_size) --- [CLS] 的一个投影(有时经过 dense + tanh)
# sequence_output: shape (batch, seq_len, hidden_size) --- 每个 token 的最后一层隐藏向量
_, sequence_output = bert_layer([input_word_ids, input_mask, segment_ids])
# 3) 取 sequence_output 的第 0 个 token(对应 [CLS])作为句子级别表示
# clf_output shape -> (batch_size, hidden_size)
clf_output = sequence_output[:, 0, :] # 取[CLS]向量
# 4) 在 [CLS] 向量上接一个全连接单元,输出一个值并通过 sigmoid -> 概率(用于二分类)
out = Dense(1, activation='sigmoid')(clf_output) # 二分类
# 5) 把输入和输出组装成 Keras Model,并编译
model = Model(inputs=[input_word_ids, input_mask, segment_ids], outputs=out)
model.compile(Adam(lr=1e-5), loss='binary_crossentropy', metrics=['accuracy'])
return model
3.3 加载预训练数据
BERT参数;分词器加载;训练测试数据加载
python
# BERT参数 并允许训练微调
module_url = "https://tfhub.dev/tensorflow/bert_en_uncased_L-24_H-1024_A-16/1"
bert_layer = hub.KerasLayer(module_url, trainable=True)
# 从BERT层拿到词表和是否转换为小写。初始化分词器。
vocab_file = bert_layer.resolved_object.vocab_file.asset_path.numpy() # 词表
do_lower_case = bert_layer.resolved_object.do_lower_case.numpy() # 是否转换为小写
tokenizer = tokenization.FullTokenizer(vocab_file, do_lower_case) # 分词器
# 加载训练和测试数据;输入进行encode
train = pd.read_csv("/kaggle/input/nlp-getting-started/train.csv")
test = pd.read_csv("/kaggle/input/nlp-getting-started/test.csv")
submission = pd.read_csv("/kaggle/input/nlp-getting-started/sample_submission.csv")
train_input = bert_encode(train.text.values, tokenizer, max_len=160)
test_input = bert_encode(test.text.values, tokenizer, max_len=160)
train_labels = train.target.values
3.4 模型训练
python
model = build_model(bert_layer, max_len=160)
checkpoint = ModelCheckpoint('model.h5', monitor='val_loss', save_best_only=True)
train_history = model.fit(
train_input, train_labels,
validation_split=0.2,
epochs=3,
callbacks=[checkpoint],
batch_size=16
)

3.5 加载参数 预测0-1 & 提交
python
model.load_weights('model.h5')
test_pred = model.predict(test_input)
submission['target'] = test_pred.round().astype(int)
submission.to_csv('submission.csv', index=False)