基于机器学习的文本分类

在应用机器学习方法的时候,通常,模型接受的输入是数值向量。但是自然语言处理中原始数据是文本,或者说是字符串。所以,在做自然语言处理的一些问题时,首先需要将输入的文本转换成向量。最基本的方法有:word count和TF-IDF,这两种方法是最基本的,但有很大的缺陷。word count和TF-IDF仅仅只是考虑了某个词在文本中出现的次数或者频率,没有考虑词的上下文结构信息。关于word count和TF-IDF可以参考我的另一篇博客词袋模型(Bag of Words Model)

本文采用的数据集是:https://www.cs.cornell.edu/people/pabo/movie-review-data/,它是二分类的数据集。所以,这里就采用的是Logistic Regression方法。同时,不采用scikit-learn这样的工具获取word count和TF-IDF两个特征。所以这两个功能需要自己实现。

构建词典

首先,根据训练集构建需要的词典。原始的数据(文本)中包含了一些没有意义而且出现频率比较高的词,这些叫停用词,在预处理的时候需要去掉。同时,文本中的标点符号也要去掉。

1、预处理(清洗数据)

def clean_data(train_path, stop_words):
    '''
    清洗数据:将字符串转换成小写、去掉标点符号和停用词
    :param train_path:
    :param stop_words:
    :return:
    '''
    # pattern = re.compile(r'\b[A-Za-z][A-Za-z]+\b')
    data = []
    with open(train_path, 'r', encoding='utf-8') as f:
        for line in f.readlines():
            line = line.strip().lower()
            content = line[1:-3]
            label = line[-1]
            sentence = [word for word in content.split(' ') if word not in stop_words and word not in string.punctuation]
            # word_list = pattern.findall(content)
            # sentence = [word for word in word_list if word not in stop_words]
            data.append([sentence, label])
    return data

2、根据处理后的数据构建词典

对原始训练数据进行处理之后,我们就可以统计训练数据(文本)中出现过的单词及其出现的次数,然后选取出现次数前5000的单词作为词典,并为每一个词赋予一个id。那么,后面就可以根据这个词典生成特征。

def build_vocab(data, n_gram):
    vocab = dict()
    for sentence, _ in data:
        for i in range(len(sentence)):
            if i+n_gram <= len(sentence):
                word_group = ' '.join(sentence[i:i+n_gram])
                vocab[word_group] = vocab.get(word_group, 0) + 1
    vocab_list = sorted([_ for _ in vocab.items() if _[1] > MIN_FREQ], key=lambda x: x[1], reverse=True)[:MAX_VOCAB_SIZE]
    vocab = {_[0]: idx for idx, _ in enumerate(vocab_list)}
    return vocab

特征抽取

1、Word Count

在得到了词典之后,我们就可以进行特征抽取。首先,Word Count是指某个词在某一篇文档中出现的次数,比如:有一篇文档“i have a book book”,其中i出现了1次,那么i这个词的count就为1;book出现了两次,则它的count为2。那这一句话可以表示为一个特征向量,这个向量的大小就是上面构建的词典的大小,在这里也就是5000,即这篇文档会被表示为1个大小为5000的向量,向量中每个位置的值为该位置对应单词的count,怎么找每个词对应的位置,就根据词典就行,因为词典中每个词都对应一个id。比如:book这个词它在词典中对应的id为100,那么特征向量第100的位置就是book的出现次数(count)。

def feature_extraction(data, vocab, n_gram):
    feature = []
    word_idf = {}
    for sentence, label in data:
        text_feature = [0] * len(vocab)
        for word in sentence:
            if word in vocab:
                text_feature[vocab[word]] = sentence.count(word)
                '''
                if word in word_idf:
                    idf_of_word = word_idf[word]
                    text_feature[vocab[word]] = tf(word, sentence) * idf_of_word
                else:
                    idf_of_word = idf(word, data)
                    text_feature[vocab[word]] = tf(word, sentence) * idf_of_word
                    word_idf[word] = idf_of_word
                '''
        feature.append([text_feature, int(label)])
    return feature

2、TF-IDF

相比于word count,tf-idf计算就较为繁琐,其中tf是指某个词w在某一篇文档中出现的频率tf_{w}=\frac{N_{w}}{N_{d}},其中N_{w}是w在文档d中出现的次数,N_{d}为文档d中单词的总数。需要注意的是如果采用频率作为tf的值,最后得到的tf-idf值会非常小。还有就是在scikit-learn中计算tf-idf使用N_{w}来作为tf的值,然后在乘以idf的值。同时scikit-learn中计算了tf-idf之后,还对每个文档中的每个词的tf-idf做了归一化(分母是tf-idf的L2范数)。比如:某一篇文档都有表示成了一个tf-idf向量,然后归一化就是将其中每一个word的tf-idf值除以这个tf-idf向量的L2范数。参考:https://scikit-learn.org/stable/modules/feature_extraction.html#text-feature-extraction

在获得了特征向量之后,就可以将特征传入模型开始训练,对于二分类问题,本文采用的是Logistic Regression。

def feature_extraction(data, vocab, n_gram):
    feature = []
    word_idf = {}
    for sentence, label in data:
        text_feature = [0] * len(vocab)
        for word in sentence:
            if word in vocab:
                #text_feature[vocab[word]] = sentence.count(word)
                if word in word_idf:
                    idf_of_word = word_idf[word]
                    text_feature[vocab[word]] = tf(word, sentence) * idf_of_word
                else:
                    idf_of_word = idf(word, data)
                    text_feature[vocab[word]] = tf(word, sentence) * idf_of_word
                    word_idf[word] = idf_of_word

        feature.append([text_feature, int(label)])
    return feature

训练结果

word count feature:     test accuracy: 0.8350     test loss: 0.4215

tf-idf feature:     test accuracy: 0.8525     test loss: 0.4372

小结

相比于深度学习的文本分类方法,基于机器学习的方法需要特征提取这个步骤,同时对原始数据所需的处理要更多一点。word count和TF-IDF这两个特征在这个数据集上的表现,后者要更好一点。除此之外,自己实现word count和TF-IDF特征提取的代码比较慢,所以尽量选择使用scikit-learn这些工具来处理,这样做速度会更快。这篇文章对应的代码放在这里:https://github.com/helin1995/NLP-Beginner-Practices。另外,我还写了一个多分类(3类)的代码,采用scikit-learn来进行特征抽取,用Pytorch实现的,代码放在:https://github.com/helin1995/NLP-Beginner-Practices。需要的话,可以参考一下。

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值