机器学习 - Kaggle项目实践(7)NLP with Disaster Tweets 灾难消息

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 正则化,适合高维稀疏数据。

Disaster Tweets | Kaggle

导入数据 并分别查看 非灾难推文和灾难推文 的前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)
相关推荐
AngelPP2 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年2 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼2 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS2 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区3 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈3 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
Ray Liang4 小时前
被低估的量化版模型,小身材也能干大事
人工智能·ai·ai助手·mindx
shengjk15 小时前
NanoClaw 深度剖析:一个"AI 原生"架构的个人助手是如何运转的?
人工智能
西门老铁7 小时前
🦞OpenClaw 让 MacMini 脱销了,而我拿出了6年陈的安卓机
人工智能