文本分类实战----数据处理篇

最近在参加kaggle上的一个文本分类的比赛,因为持续时间比较长,有两个月的时间,想在这期间详细的学习一下文本分类的方法和知识,所以会持续更新一些博客来记录参赛的过程。在比赛结束后,我会将参赛过程中使用的代码放到我的Github上面,大家相互学习。主要会包括如何处理数据,参赛中使用的模型,一些重要方法的原理等方面的东西。本篇主要记录数据的处理方法。

任务介绍

首先我们介绍一下这个比赛的任务,是对输入的英文文本做二分类任务。具体来说,输入一个英文的问句,需要我们的模型来判断这个问句是不是有意义的,最后输出对应的标签。“有意义”是指这个问句是一个正常的问题,而不是一个带有感情色彩的声明或者不是基于事实的问题,并且这个问题不涉及低俗政治敏感等因素。

数据集

首先我们介绍一下比赛官方给出的数据集。数据集由Excel表格给出。训练集的数据1.31million行,测试集数据有56.4k行。每一条训练数据由[qid, question text, target]组成 ,其中qid是每一条数据的唯一id标识,question text是英文问句文本,target是0/1,0代表正常问句,1则相反。下图是示例。测试数据集是需要提交到后台进行判定的,因此是没有target这一项的。
在这里插入图片描述

另外,比赛官方还给了embedding好的词向量作为支撑数据。四种词向量:Google-vectors-negative300, glove.84B.300d, paragram_300_sl999, wiki-news-300d-1M。都是300维的词向量。

数据处理
方法一:embedding—三种词向量的串联

这里用的方法是使用了官方给出的词向量,将每一个问句中的单词都转化为词向量的形式。不过,为了更为准确的描述一个词,我们这里同时使用了三个词向量(glove.840B.300d,wiki-news-300d-1M,paragram_300_sl999)标识单词,具体做法是将三个词向量串联到一起,形成一个3*300=900维的向量。下面的代码展示了将单词转化为一个300维向量的过程,我们只要重复这个步骤,就可以得到多个词向量。

EMBEDDING_FILE = '../input/embeddings/glove.840B.300d/glove.840B.300d.txt'
def get_coefs(word,*arr): return word, np.asarray(arr, dtype='float32')
embeddings_index = dict(get_coefs(*o.split(" ")) for o in open(EMBEDDING_FILE))

all_embs = np.stack(embeddings_index.values()) # 二维数组 2096016*300
print(all_embs.shape[0])
emb_mean,emb_std = all_embs.mean(), all_embs.std()  # scalar 均值,标准差
print("mean=",emb_mean)
embed_size = all_embs.shape[1]  # 300

word_index = tokenizer.word_index  # 单词对应的整数编号形成的列表
nb_words = min(max_features, len(word_index))  # 只取两者中较小者的单词数量
# 结合下面的代码。对于embeddings中没有的单词,使用随机初始化的词向量
embedding_matrix_1 = np.random.normal(emb_mean, emb_std, (nb_words, embed_size))
for word, i in word_index.items():
    if i >= max_features: 
        continue
    embedding_vector = embeddings_index.get(word)
    if embedding_vector is not None: 
        embedding_matrix_1[i] = embedding_vector

del embeddings_index; gc.collect() # 内存空间清理

我们对另外两个embeddings使用同样的代码就可以得到所用的另外两个词向量矩阵。代码就不重复贴了。下面的代码将三种词向量串联起来。

embedding_matrix = np.concatenate((embedding_matrix_1, embedding_matrix_2, embedding_matrix_3), axis=1)  
del embedding_matrix_1, embedding_matrix_2, embedding_matrix_3
gc.collect()
np.shape(embedding_matrix)  # (50000, 900)

这样,我们就得到了最终的词向量,每个单词由一个900维的向量表示。后面我们只需要将词向量投入分类模型中进行分类就可以了。

方法二:TFIDF+统计特征

首先对于TFIDF不熟悉的同学可以看一下这篇博客,对TFIDF原理做了介绍。
这种方法分为两个部分,分别是TFIDF和统计特征,但是在实际中是同时使用了两者,并且都比较简单,所以放在一起说了。
先说TFIDF部分,我们知道TFIDF类似Word2Vec,将句子转化为向量表示的形式,不同的是Word2Vec将每个单词转化为一个多维向量,整个句子就是一个二维的矩阵。TFIDF则是对词频表示单词的优化,每个单词表示为一个浮点数,最终一个句子会被表示成一个多维向量。从这里也可以看出来,TFIDF没有Word2Vec表示的内容丰富。
这个方法中对TFIDF的使用有两个途径,一个是直接将每个单词进行TFIDF,另一个是先将句子进行ngram,然后在对每一个gram进行TFIDF。这样最终会形成一个句子的两个向量表示,内容就相对丰富一些。下面是ngram级别的代码:

char_vector = TfidfVectorizer(
    ngram_range=(2,4),     # 对ngram进行TFIDF。
    max_features=20000,
    stop_words='english',   # list类型
    analyzer='char_wb',
    token_pattern=r'\w{1,}',
    strip_accents='unicode',
    sublinear_tf=True, 
    max_df=0.98,
    min_df=2  # 上下词频阈值之外的单词不计入
)
char_vector.fit(X_train[:85000])  
# 返回TFIDF矩阵。tocsr存储稀疏矩阵
# X_train X_val X_test 分别是原始数据进行零填充后的pandas dataFrame形式
train_char_vector = char_vector.transform(X_train).tocsr()
valid_char_vector = char_vector.transform(X_val).tocsr()
test_char_vector = char_vector.transform(X_test).tocsr()

word级别的TFIDF:

word_vector = TfidfVectorizer(
    ngram_range=(1,1),  # 对每一个单词进行TFIDF
    max_features=9000,
    sublinear_tf=True, 
    strip_accents='unicode', 
    analyzer='word', 
    token_pattern="\w{1,}", 
    stop_words="english",
    max_df=0.95,
    min_df=2
)
all_text = list(X_train) + list(X_test)
word_vector.fit(all_text)
train_word_vector = word_vector.transform(X_train).tocsr()
valid_word_vector = word_vector.transform(X_val).tocsr()
test_word_vector = word_vector.transform(X_test).tocsr()

统计特征部分
我们对原始数据进行特征的统计,每个句子会形成一个对应的特征向量,与上面TFIDF得出的两个特征向量一起放入分类模型中进行分类。具体来说,我们对原始数据dataFrame中每一条数据做统计,包括单词数量,标点数量等等统计特征,把这些统计特征每一个都生成新的一列加入到dataFrame中,然后将原始数据dataFrame列删除,也就是我们对于这部分统计的特征,我们不使用原始数据了。最终,我们用csr压缩矩阵这部分数据,形成一种特征向量。下面是代码:

def get_features(data):
    # data = [train_df, val_df, test_df]  (3,)
    # 经过本函数的处理后 data的第一维[1175509 rows x 3 columns]变为[1175509 rows x 16 columns]
    # 第二维由[130613 rows x 3 columns] 变为 [130613 rows x 16 columns]
    # 第三维由[56370 rows x 2 columns] 变为 [56370 rows x 15 columns]
    for dataframe in data:
        # dataFrame 添加列
        dataframe["text_size"] = dataframe["question_text"].apply(len).astype('uint16')  # 句子长度
        dataframe["capital_size"] = dataframe["question_text"].apply(lambda x: sum(1 for c in x if c.isupper())).astype('uint16')  # 大写字母的个数
        dataframe["capital_rate"] = dataframe.apply(lambda x: float(x["capital_size"]) / float(x["text_size"]), axis=1).astype('float16')  # 大写字母率
        dataframe["exc_count"] = dataframe["question_text"].apply(lambda x: x.count("!")).astype('uint16')  # 感叹号数量
        dataframe["quetion_count"] = dataframe["question_text"].apply(lambda x: x.count("?")).astype('uint16')  # 问号数量
        dataframe["unq_punctuation_count"] = dataframe["question_text"].apply(lambda x: sum(x.count(p) for p in '∞θ÷α•à−β∅³π‘₹´°£€\×™√²')).astype('uint16') # 不同标点符号数量
        dataframe["punctuation_count"] = dataframe["question_text"].apply(lambda x: sum(x.count(p) for p in '.,;:^_`')).astype('uint16')  # 标点符号数量
        dataframe["symbol_count"] = dataframe["question_text"].apply(lambda x: sum(x.count(p) for p in '*&$%')).astype('uint16')  # ??
        dataframe["words_count"] = dataframe["question_text"].apply(lambda x: len(x.split())).astype('uint16')  # 单词数量
        dataframe["unique_words"] = dataframe["question_text"].apply(lambda x: (len(set(1 for w in x.split())))).astype('uint16')  # 不同单词的数量
        dataframe["unique_rate"] = dataframe["unique_words"] / dataframe["words_count"]  
        dataframe["word_max_length"] = dataframe["question_text"].apply(lambda x: max([len(word) for word in x.split()]) ).astype('uint16')  # 最大单词长度
        dataframe["mistake_count"] = dataframe["question_text"].apply(lambda x: sum(x.count(w) for w in mistake_list)).astype('uint16')  # 错误拼写数量
    print("data shape = ", np.array(data).shape)
    return data

data = get_features(data)

feature_cols = ["text_size", "capital_size", "capital_rate", "exc_count", "quetion_count", "unq_punctuation_count", "punctuation_count", "symbol_count", "words_count", "unique_words", "unique_rate", "word_max_length", "mistake_count"]

# 不取qid,question text,target列的数据,只取feature_cols列的数据。
X_train = csr_matrix(train_df[feature_cols].values)
X_val = csr_matrix(val_df[feature_cols].values)
X_test = csr_matrix(test_df[feature_cols].values)

最终,我们将TFIDF得到的两个特征向量和统计方法得到的一个特征向量组合,作为最终的输入数据。

# 按列将数组堆叠
input_train = hstack([X_train, train_word_vector, train_char_vector])
input_valid = hstack([X_val, valid_word_vector, valid_char_vector])
input_test = hstack([X_test, test_word_vector, test_char_vector])

猜你喜欢

转载自blog.csdn.net/pnnngchg/article/details/84953258
今日推荐