文本分类任务预处理模板

文本分类模板

文本分类是很常见的NLP任务,二分类任务如舆情分析,而电商文本的的类目判别,新闻类目判断就属于多分类了。文本分类算法很多,从基于统计打车Bayes再发展到线性模型判断,如LR,再到非线性SVM;目前用的比较多,能有效减少特征工程的是深度神经网络DNN。
本文并没有关注模型的选择和调参部分,是对工作中文本分类任务中公用部分方法总结,给算法部分打个基础。
一个完整的文本分类任务通常包含以下任务:

  1. 定义数据清洗原则
  2. 读取文本
  3. 建立词典
  4. 训练集/开发集/测试集的切分
  5. 建模
  6. 评估
  7. 调参

注意到其中1, 2, 3, 4, 6是共同的,这些共同的步骤提出来做成一个文本分类模板,之后就仅关注模型的选择和调参会。
文本分类算法孰优孰劣没有定论,先选定一个baseline,再更改算法看是否有提高,实际工作中,我通常使用TFIDF灌进LR作为baseline,TFIDF是文本算法中通俗易懂特征,LR可解释性强。再开始使用Keras搭建DNN,看比LR线性模型提高了多少。

数据清洗

对输入doc的乱七八糟符号要去除,再用jiaba分词对输入doc进行分词,注意,不需要对停用词做处理,因为1. LSTM,CNN等结构对可能能捕捉到“的得地”词影响;2.我们可以控制词典过滤超过n%文档出现过的词语,出现小于m次的词语。

import jieba
import re

etl_regex = re.compile(r"[\s+\!\/_,$%^*(+\"\']+|[+——!,。?、~@#¥%……&*():]+")

# 去掉符号
def delete_symbol(content):
    content = etl_regex.sub('', content)
    return content
    
def clean_doc(doc, vocab):
    tokens = jieba.lcut(doc)
    tokens = [word for word in filter(lambda x: len(x) > 0, map(delete_symbol, jieba.cut(document, cut_all=True)))]
    tokens = [w for w in tokens if w in vocab]
    tokens = ' '.join(tokens)
    return tokens

建立词典

词典就是一个Counter,遍历清洗后过的文本token list,得到{word1:count, word2:count, ... , wordn:count}的Counter结构。遍历Counter结构,可以过滤出现次数m次的词语,类似于TFIDF的思想。
Counter使用比较原始,可以from gensim.corpora import Dictionary, 把分完词的二维list 填进,用它的方法过滤词更加方便。

from collections import Counter

def add_doc_to_vocab(filename, vocab):
    doc = load_doc(filename)
    tokens = clean_doc(doc)
    vocab.update(tokens)
    
def process_docs(directory, vocab):
    for filename in listdir(directory):
        if not filename.endwith('.txt'):
            continue
        path = directory + '/' + filename
        add_doc_to_vocab(path, vocab)

vocab = Counter()
process_docs('pos/', vocab)
process_docs('neg/', vocab)
tokens = [k for k,c in vocab.items() if c >= min_occurrence]

----------------------------------------
from gensim.corpora import Dictionary

dictionary = corpora.Dictionary(texts)
dictionary.filter_extremes(no_below=1, no_above=0.9, keep_n=None)

读文本

def load_doc(filename):
    text = ""
    with open(filename, "r", encoding="utf-8") as fp:
        for line in fp:
            text += line
    return text
    
def process_docs(directory, vocab, is_train):
    documents = list()
    for filename in listdir(directory):
        if not is_train and not filename.startswith('.txt'):
            continue
        path = directory + '/' + filename
        doc = load_doc(path)
        tokens = clean_doc(doc, vocab)
        documents.append(tokens)
    return documents

def load_clean_dataset(vocab, is_train):
    neg = process_docs('txt_sentoken/neg', vocab, is_train)
    pos = process_docs('txt_sentoken/pos', vocab, is_train)
    docs = neg + pos
    labels = array([0 for _ in range(len(neg))] + [1 for _ in range(len(pos))])
    return docs, labels

对数据编码

DNN输入得是定长vector,对于超过定义长度的输入,我们要做trunc截断,对于小于的,我们要前/后paddinhg。

def create_tokenizer(lines):
    tokenizer = Tokenizer()
    tokenizer.fit_on_texts(lines)
    return tokenizer

# integer encode and pad documents
def encode_docs(tokenizer, max_length, docs):
    encoded = tokenizer.texts_to_sequences(docs)
    padded = pad_sequences(encoded, maxlen=max_length, padding='post')
    return padded

建模

这儿就是该干嘛想干嘛的地方了。

def define_model(vocab_size, max_length):
    pass

评估

测试集派上用场了,对测试集做相同的预处理流程读到内存里,使用keras model自带的evaluate()会输出一份详尽的报告。

Xtest = encode_docs(tokenizer, max_length, test_docs)
test_docs, ytest = load_clean_dataset(vocab, False)
_, acc = model.evaluate(Xtest, ytest, verbose=0)

完整流程

#建词典
vocab = Counter()
process_docs('pos/', vocab)
process_docs('neg/', vocab)

#读训练文本
train_docs, ytrain = load_clean_dataset(vocab, True)

# 创建分词器
tokenizer = create_tokenizer(train_docs)

vocab_size = len(tokenizer.word_index) + 1
max_length = max([len(s.split()) for s in train_docs])

# 文本编码,做DNN输入
Xtrain = encode_docs(tokenizer, max_length, train_docs)

# 定义模型
model = define_model(vocab_size, max_length)

# 训练
model.fit(Xtrain, ytrain, epochs=10, verbose=2)
_, acc = model.evaluate(Xtrain, ytrain, verbose=0)

# 测试集评估Xtest = encode_docs(tokenizer, max_length, test_docs)
test_docs, ytest = load_clean_dataset(vocab, False)
_, acc = model.evaluate(Xtest, ytest, verbose=0)
Comments
Write a Comment