首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >使用经典ML方法和LSTM方法检测灾难tweet

使用经典ML方法和LSTM方法检测灾难tweet

作者头像
磐创AI
发布2021-08-05 10:14:20
发布2021-08-05 10:14:20
1.2K00
代码可运行
举报
运行总次数:0
代码可运行

在本文中,我将对分类任务应用两种不同的方法。我将首先应用一个经典的机器学习分类算法-梯度增强分类器。

在代码的后面,我将使用LSTM技术来训练RNN模型。因为我们正在处理tweets,所以这是一个NLP任务,我将与大家分享一些技巧,以便大家更加熟悉大多数NLP项目中的一些常见步骤。

我将使用Kaggle挑战赛的数据,名为“自然语言处理-灾难推文”。你可以在“data”部分的链接下面找到“train.csv文件

https://www.kaggle.com/c/nlp-getting-started/overview

数据集有5列。列“target”是标签列,这意味着我将训练一个模型,该模型可以使用其他列(如“text”、“location”和“keyword”)预测列“target”的值。现在我们先来了解一下每一列的含义:

  • id-每个tweet的唯一标识符
  • text-推特的文本
  • location-发送推文的位置(可能为空)
  • keyword-推文中的特定关键字(可能为空)
  • target-输入文件为train.csv,这表示tweet是关于一个真正的灾难(1)还是不是(0)

对于这个任务,我将使用Sklearn和Keras等库来训练分类器模型。Sklearn用于使用梯度增强分类器训练模型,Keras用于训练LSTM模型。

代码语言:javascript
代码运行次数:0
运行
复制
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import re

import nltk 
nltk.download('stopwords')
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize 
from nltk.stem import SnowballStemmer

from sklearn import model_selection, metrics, preprocessing, ensemble, model_selection, metrics
from sklearn.feature_extraction.text import CountVectorizer


import tensorflow as tf
from tensorflow.keras.models import Model
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.layers import Conv1D, Bidirectional, LSTM, Dense, Dropout, Input
from tensorflow.keras.optimizers import Adam

了解数据

对于这个任务,我们只使用'train.csv“并将其分解为训练和测试数据集。我将把数据加载到Pandas Dataframe并查看前几行。

代码语言:javascript
代码运行次数:0
运行
复制
# 读取训练数据集
file_path = "./train.csv"
raw_data = pd.read_csv(file_path)
print("Data points count: ", raw_data['id'].count())
raw_data.head()

首先,我想更加熟悉数据集,以便理解这些特征(列)。“目标”列是我们的模型要学习预测的列。因为它只有0和1这两个唯一的值,所以这是一个二分类任务。

我想知道token为0和1的tweet的分布,所以让我们基于列“target”绘制数据。

如你所见,标签0表示非灾难tweets的数据点较多,标签1表示与灾难相关tweets的数据点较少。通常,对于有一些倾斜标签的数据,建议使用F1分数而不是准确率来进行模型评估,我们将在本文末尾讨论这个问题。

接下来,我想知道我们的数据集中每一列缺失的数据点是怎样的。下面的热图显示“keyword”这一列缺少的数据点很少,我将填补这些缺失的数据点,并将这一列作为一个特征使用。

列“location”数据非常缺失,数据质量非常差。所以我决定不使用这个列。列“text”,这是tweet的实际文本,它没有丢失数据。

我也注意到有一些tweet包含的单词不到3个,我认为两个单词的句子可能无法很好地传递内容。为了弄清楚句子的字数分布,我可视化每个句子的字数直方图。

正如我们所看到的,大多数tweet都在11到19个单词之间,所以我决定删除少于2个单词的tweet。我相信用三个字的句子就足以说明这条微博了。删除超过25-30个单词的tweet可能是个好主意,因为它们可能会减慢训练时间。

数据清理和预处理:

在处理tweet的NLP任务中,清除数据的常见步骤是删除特殊字符、删除停用词、删除url、删除数字和进行词干分析。但我们先来熟悉一些NLP数据预处理的概念:

向量化:

单词向量化是一种将单词映射到实数的技术,或者更好地说是实数向量。我使用了Sklearn和Keras库的向量化。

token化:

token化是将一个短语(可以是句子、段落或文本)分解成更小的部分,如一系列单词、一系列字符或一系列子单词,它们被称为token。token化的一个用途是从文本生成token,然后将token转换为数字(向量化)。

padding:

神经网络模型要求输入具有相同的形状和大小,这意味着一个接一个地输入到模型中的所有tweet必须具有完全相同的长度,所以我们要用上填充(padding)。

数据集中的每条tweet都有不同的字数,我们将为每条tweet设置一个最大字数,如果一条tweet较长,那么我们可以删除一些字数,如果tweet的字数少于max,我们可以用固定值(如“0”)填充tweet的开头或结尾。

词干:

词干分析的任务是将多余的字符从一个词减少到词干形式。例如,将“working”和“worked”这两个词词干化为“work”。

我使用了Snowball词干分析器,这是一种词干算法(也称为Porter2词干算法)。它是波特词干分析器的一个更好的版本,因为一些问题在这个词干分析器中得到了解决。

词嵌入:

词嵌入是对文本的一种学习表示,其中具有相同含义的单词具有相似的表示。每个单词被映射到一个向量,向量值以类似于神经网络的方式学习。

现在让我们看看整个数据清理代码:

代码语言:javascript
代码运行次数:0
运行
复制
def clean_text(each_text):

    # 从文本中删除URL
    each_text_no_url = re.sub(r"http\S+", "", each_text)

    # 从文本中删除数字
    text_no_num = re.sub(r'\d+', '', each_text_no_url)

    # token化每个文本
    word_tokens = word_tokenize(text_no_num)

    # 删除特殊字符
    clean_text = []
    for word in word_tokens:
        clean_text.append("".join([e for e in word if e.isalnum()]))

    # 删除停用词并小写化
    text_with_no_stop_word = [w.lower() for w in clean_text if not w in stop_words]  

    # 词干化
    stemmed_text = [stemmer.stem(w) for w in text_with_no_stop_word]

    return " ".join(" ".join(stemmed_text).split())


raw_data['clean_text'] = raw_data['text'].apply(lambda x: clean_text(x) )
raw_data['keyword'] = raw_data['keyword'].fillna("none")
raw_data['clean_keyword'] = raw_data['keyword'].apply(lambda x: clean_text(x) )

为了能够同时使用“text”和“keyword”列,有多种方法可以应用,但我应用的一种简单方法是将这两种特征结合到一个新特征中,称为“keyword_text

代码语言:javascript
代码运行次数:0
运行
复制
# #将“clean_keyword”列和“clean_text”列合并为一个列
raw_data['keyword_text'] = raw_data['clean_keyword'] + " " + raw_data["clean_text"]

我使用了Sklearn的“train_test_split”函数来执行训练和测试集的划分。

代码语言:javascript
代码运行次数:0
运行
复制
feature = "keyword_text"
label = "target"

# 分割训练测试
X_train, X_test,y_train, y_test = model_selection.train_test_split(raw_data[feature],raw_data[label],test_size=0.3,random_state=0,shuffle=True)

正如我已经提到的向量化,我们必须将文本转换成数字,因为机器学习模型只能处理数字,所以我们在这里使用“Countervectorize”。我们对训练数据进行拟合和变换,只对测试数据进行变换。确保测试数据没有拟合。

代码语言:javascript
代码运行次数:0
运行
复制
# 向量化文本
vectorizer = CountVectorizer()
X_train_GBC = vectorizer.fit_transform(X_train_GBC)
x_test_GBC = vectorizer.transform(x_test_GBC)

GradientBoostingClassifier:

梯度Boosting分类器是一种机器学习算法,它将决策树等弱学习模型结合起来,形成一个强预测模型。

代码语言:javascript
代码运行次数:0
运行
复制
model = ensemble.GradientBoostingClassifier(learning_rate=0.1,                                            
                                            n_estimators=2000,
                                            max_depth=9,
                                            min_samples_split=6,
                                            min_samples_leaf=2,
                                            max_features=8,
                                            subsample=0.9)
model.fit(X_train_GBC, y_train)

评价模型性能的一个很好的指标是F-score。在计算F分数之前,让我们先熟悉精确度和召回率。

精度:在我们正确标记为阳性的数据点中,有多少点我们正确标记为阳性。

召回率:在我们正确标记为阳性的数据点中,有多少是阳性的。

F1分数:是召回率和精确度的调和平均值。

代码语言:javascript
代码运行次数:0
运行
复制
# 评估模型
predicted_prob = model.predict_proba(x_test_GBC)[:,1]
predicted = model.predict(x_test_GBC)

accuracy = metrics.accuracy_score(predicted, y_test)
print("Test accuracy: ", accuracy)
print(metrics.classification_report(y_test, predicted, target_names=["0", "1"]))
print("Test F-scoare: ", metrics.f1_score(y_test, predicted))
Test accuracy:  0.7986784140969163
              precision    recall  f1-score   support

           0       0.79      0.88      0.83      1309
           1       0.81      0.69      0.74       961

    accuracy                           0.80      2270
   macro avg       0.80      0.78      0.79      2270
weighted avg       0.80      0.80      0.80      2270

Test F-scoare:  0.7439775910364146

混淆矩阵是一个表,它显示了分类模型相对于两个类的性能。从图中可以看出,我们的模型在检测目标值“0”时比检测目标值“1”时有更好的性能。


LSTM:

LSTM(Long-Short-Term Memory network)是一种递归神经网络(RNN,Recurrent Neural network),具有学习长期依赖性和记忆信息的能力。

我已经在上面谈到了词嵌入,现在是时候将其用于我们的LSTM方法了。我使用了斯坦福大学的GloVe嵌入技术。读取GloVe嵌入文件之后,我们使用Keras创建一个嵌入层。

代码语言:javascript
代码运行次数:0
运行
复制
# 读取词嵌入
embeddings_index = {}
with open(path_to_glove_file) as f:
    for line in f:
        word, coefs = line.split(maxsplit=1)
        coefs = np.fromstring(coefs, "f", sep=" ")
        embeddings_index[word] = coefs

print("Found %s word vectors." % len(embeddings_index))

# 在Keras中定义嵌入层
embedding_matrix = np.zeros((vocab_size, embedding_dim))
for word, i in word_index.items():
    embedding_vector = embeddings_index.get(word)
    if embedding_vector is not None:
        embedding_matrix[i] = embedding_vector

embedding_layer = tf.keras.layers.Embedding(vocab_size,embedding_dim,weights[embedding_matrix],input_length=sequence_len,trainable=False)

对于LSTM模型,我从一个嵌入层开始,为每个输入序列生成一个嵌入向量。然后我使用卷积模型来减少特征的数量,然后是一个双向LSTM层。最后一层是Dense层。因为它是一个二分类,所以我们使用sigmoid作为激活函数。

代码语言:javascript
代码运行次数:0
运行
复制
# 定义模型体系结构
sequence_input = Input(shape=(sequence_len, ), dtype='int32')
embedding_sequences = embedding_layer(sequence_input)

x = Conv1D(128, 5, activation='relu')(embedding_sequences)
x = Bidirectional(LSTM(128, dropout=0.5, recurrent_dropout=0.2))(x)
x = Dense(512, activation='relu')(x)
x = Dropout(0.5)(x)
x = Dense(512, activation='relu')(x)
outputs = Dense(1, activation='sigmoid')(x)
model = Model(sequence_input, outputs)
model.summary()

对于模型优化,我使用了以二元交叉熵作为损失函数的Adam优化。

代码语言:javascript
代码运行次数:0
运行
复制
# 优化模型
model.compile(optimizer=Adam(learning_rate=learning_rate), loss='binary_crossentropy', metrics=['accuracy'])

模型训练完成后,我想看看训练精度和损失的学习曲线。该图显示,模型精度的不断提高和损失的不断减少

现在我已经训练了模型,所以现在是时候评估它的模型性能了。我将得到模型的准确率和测试数据的F1分数。因为预测值是介于0和1之间的浮点值,所以我使用0.5作为阈值来分隔“0”和“1”。

代码语言:javascript
代码运行次数:0
运行
复制
# 评估模型
predicted = model.predict(X_test, verbose=1, batch_size=10000)

y_predicted = [1 if each > 0.5 else 0 for each in predicted]

score, test_accuracy = model.evaluate(X_test, y_test, batch_size=10000)

print("Test Accuracy: ", test_accuracy)
print(metrics.classification_report(list(y_test), y_predicted))

Test Accuracy:  0.7726872
              precision    recall  f1-score   support

           0       0.78      0.84      0.81      1309
           1       0.76      0.68      0.72       961

    accuracy                           0.77      2270
   macro avg       0.77      0.76      0.76      2270
weighted avg       0.77      0.77      0.77      2270

正如我们在混淆矩阵中看到的那样,RNN方法的性能与梯度增强分类器方法非常相似。该模型在检测“0”方面比检测“1”做得更好。


结论

如你所见,两种方法的输出非常接近。梯度增强分类器的训练速度比LSTM模型快得多。

有许多方法可以提高模型的性能,如修改输入数据,应用不同的训练方法,或使用超参数搜索算法,如GridSearch或RandomizedSearch来寻找超参数的最佳值。


参考文献

https://keras.io/examples/nlp/pretrained_word_embeddings/

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-07-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 磐创AI 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 了解数据
  • 数据清理和预处理:
    • 向量化:
    • token化:
    • padding:
    • 词干:
    • 词嵌入:
  • GradientBoostingClassifier:
  • LSTM:
  • 结论
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档