赞
踩
在应用机器学习方法的时候,通常,模型接受的输入是数值向量。但是自然语言处理中原始数据是文本,或者说是字符串。所以,在做自然语言处理的一些问题时,首先需要将输入的文本转换成向量。最基本的方法有: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两个特征。所以这两个功能需要自己实现。
首先,根据训练集构建需要的词典。原始的数据(文本)中包含了一些没有意义而且出现频率比较高的词,这些叫停用词,在预处理的时候需要去掉。同时,文本中的标点符号也要去掉。
- 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
'运行
对原始训练数据进行处理之后,我们就可以统计训练数据(文本)中出现过的单词及其出现的次数,然后选取出现次数前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
'运行
在得到了词典之后,我们就可以进行特征抽取。首先,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
'运行
相比于word count,tf-idf计算就较为繁琐,其中tf是指某个词w在某一篇文档中出现的频率,其中
是w在文档d中出现的次数,
为文档d中单词的总数。需要注意的是如果采用频率作为tf的值,最后得到的tf-idf值会非常小。还有就是在scikit-learn中计算tf-idf使用
来作为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。需要的话,可以参考一下。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。