文本分类

以前写的:《python自然语言处理》第六章 学习分类文本

1.引言

文本分类是归类文本或文本片段的一种方式。通过检查一段文字中的单词用法,分类器可以决定分配给这个单词何种标签。

二元分类器可以在两个标签(如正,负)之间做决定,文本可以是其中一个标签(多标签分类器可以给一段文本分配多个标签)

分类器在有标签的特征集(训练数据)中学习,然后对没有标签的特征集进行分类:

特征集 训练集
(feature,label) feature

 在文本分类的情况下,feature通常是是单词,值全为true。

2.词袋特征提取

文本特征提取本质: 将单词列表转变为特征集,从而让分类器可以使用这个特征集(NLTK分类器期望得到dict形式的特征集)

单词列表 ['I', 'love', 'the', 'blue', 'sky', '.'] 
特征集 {'I': True, 'love': True, 'the': True, 'blue': True, 'sky': True, '.': True} 

2.1 词袋模型

词袋模型

 词袋模型从一个实例的所有单词中构建出单词的特征集。

这种方法不在乎单词的顺序,或单词出现多少次,只关心该单词是否出现

# bag_of_words() 函数
def bag_of_words(words):
    return dict([(word, True) for word in words])

# 函数的参数是 words 单词列表 , 函数的功能是将单词列表转化为 (单词,是否出现) 的字典
# 得到的词包不在乎单词顺序或单词出现的次数,只关心单词至少出现一次
# 下面是个例子
sentence = 'I love the blue sky.'
words = nltk.word_tokenize(sentence) # 单词列表
print(bag_of_words(words)) 
# {'I': True, 'love': True, 'the': True, 'blue': True, 'sky': True, '.': True}

2.2 过滤停用词

 有些单词毫无意义,就要排除他们。

# 过滤:从单词集中排除某些单词集
def bag_of_words_not_in_set(words,badwords):
    words = [word for word in words if word not in badwords]
    return bag_of_words(words)
    # return bag_of_words(set(words)-set(badwords))
    # set是集合,可以做加减法

print(bag_of_words_not_in_set(words,'the'))
# {'I': True, 'love': True, 'blue': True, 'sky': True, '.': True}  没有了the


# 过滤停用词
from nltk.corpus import stopwords
def bag_of_non_stopwords(words,stopfile='english'):
    badwords = stopwords.words(stopfile)
    return bag_of_words_not_in_set(words,badwords)
print(bag_of_non_stopwords(words))
# {'I': True, 'love': True, 'blue': True, 'sky': True, '.': True}  没有了the
  • 其中参数stopfile是语言文件名。

2.3 添加有意义的双连词

有意义的双连词比起单词不太常见,因此将它们包括在词袋模型中有利于分类器做出更好的选择。

# 给词包添加有意义的双连词(给分类器提供有意义的特征)
from nltk.collocations import BigramCollocationFinder
from nltk.metrics import BigramAssocMeasures
def bag_of_bigram_words(words,score_fn=BigramAssocMeasures.chi_sq,n=200):
    bigram_finder = BigramCollocationFinder.from_words(words)
    bigrams = bigram_finder.nbest(score_fn,n)
    return bag_of_words(words+bigrams)

print(bag_of_bigram_words(words))
# {'I': True, 'love': True, 'the': True, 'blue': True, 'sky': True, '.': True, ('I', 'love'): True, ('blue', 'sky'): True, ('love', 'the'): True, ('sky', '.'): True, ('the', 'blue'): True}
  • bag_of_bigram_words()函数会返回所有单词+200个最有意义的双连词组成的dict。


3.朴素贝叶斯分类器

贝叶斯

3.1 读取语料库

3.1.1 获取从标签到特征集的映射

这里我们使用 nltk.corpus.movie_review 这个语料库,它包含两类文本:pos和neg,这两个类别是互斥的,要在二元分类器上训练

形状为   { label : [features] } 

本例中形式:   {  'pos' = [从pos的txt中提取的所有单词] ,   'neg'  =  [从neg的txt中提取的所有单词]   }

import collections
def label_feats_from_corpus(corp,feature_detector=bag_of_words):
    label_feats = collections.defaultdict(list)
    for label in corp.categories():
        for fileid in corp.fileids(categories=[label]):
            # fileid 是文本名字,每个fileid都是一个txt文件,如 corp.fileids(categories=[label])[0] = ’neg/cv000_29416.txt‘
            feats = feature_detector(corp.words(fileids=[fileid]))
            # feats 是提取出来的特征,即词包(特征集)
            label_feats[label].append(feats)
            # label_feats['pos'] 是一个词包(特征集),它包含从pos文本提取出来的所有词
    return label_feats
  • collections.defaultdict(list) 将 label_feats 的形状初始化为 ( <class 'list'> ,  { } )
  • >>> movie_reviews.categories()
    ['neg', 'pos']
  • label_feats_from_corpus(corp,feature_detector=bag_of_words) 这个函数嘉定语料库已经分好类了,并且单个文件(如corp.fileids(categories=[label])[0])表示用于特征提取的单个实例
  •     feature_detector 从某个类别的某个文件fileid中提取特征,默认设置为bag_of_words(), 返回 dict 类型的 

3.1.2 划分训练集和测试集

一旦获得了标签到特征集的映射,就可以构建加标签的训练和测试实例列表。

# 划分train数据和test数据,train:test = 3:1
def split_label_feats(lfeats,split=0.75):
    train_feats = []
    test_feats = []
    for label,feats in lfeats.items():
        cutoff = int(len(feats) * split)
        train_feats.extend([(feat,label) for feat in feats[:cutoff]])
        test_feats.extend([(feat,label) for feat in feats[cutoff:]])
    return train_feats,test_feats
  •     pos_train / pos_test =  neg_train / neg_test  =  3/1

3.2 利用处理过的数据训练朴素贝叶斯分类器

# 训练贝叶斯分类器
from nltk.classify import NaiveBayesClassifier
nb_classifier = NaiveBayesClassifier.train(test_feats)
nb_classifier.labels()

3.3 使用朴素贝叶斯分类器

# 使用贝叶斯分类器
negSentence = 'I hate you, you are ugly.'
negWords = nltk.word_tokenize(negSentence)
negfeat = bag_of_words(negWords)
print(nb_classifier.classify(negfeat))
# neg

4.决策树分类器 

DecisionTreeClassifier 类通过创建树结构进行工作,其中树个每个结点对应于特征名,分支对应特征值。沿着分支,向下追踪,可以得到树的叶子,也就是分类标签(结果)。

基于决策树算法,如 ID3 , C4.5 , C5 , CART

决策树ID3算法

 4.1实现

import nltk
from feature import label_feats_from_corpus , split_label_feats from feature import bag_of_words # 获取数据 from nltk.corpus import movie_reviews lfeats = label_feats_from_corpus(movie_reviews) train_feats , test_feats = split_label_feats(lfeats) # 训练决策树分类器 from nltk.classify import DecisionTreeClassifier dt_classifier = DecisionTreeClassifier.train(train_feats,binary=True,entropy_cutoff=0.8,depth_cutoff=5,support_cutoff=30) # 测试分类器 from nltk.classify.util import accuracy print(accuracy(dt_classifier,test_feats))
  • 参数binary=True指的是特征值(feature value)不是Ture就是False,因此单词特征是二元的。注意于二元分类器作区分:非二元特征可以使用二元分类器。
  • 决策树不适合特征数量较多的情况,运行速度慢,通过给定下面三个参数来决定树什么时候停止细分。
  • 使用entropy_cutoff控制不确定性:当树种标签选择的概率分布熵比 entropy_cutoof 大, 那么树就需要通过创建更多的分支进行细化
  • 使用depth_cutoff控制树的深度
  • 使用support_cutoff控制与决策:当标签的特征集数目<=support_cutff时,表明没有足够的特征实例来让树做出明智的决定,所以停止树这一部分的进一步细分。

5.最大熵分类器

 最大熵分类器 MaxentClassifier 也叫逻辑回归分类器。

 它使用编码,将加标签的特征集转化为向量,然后利用这些编码向量计算每个特征的权重。

 接着组合这些权重以确定某个特征集最有可能的标签。

import nltk
from feature import label_feats_from_corpus , split_label_feats
from feature import bag_of_words



# 获取数据
from nltk.corpus import movie_reviews
lfeats = label_feats_from_corpus(movie_reviews)
train_feats , test_feats = split_label_feats(lfeats)
# 训练分类器
from nltk.classify import MaxentClassifier
from nltk.classify.util import accuracy
me_classifier = MaxentClassifier.train(train_feats,algorithm='gis',trace=0,max_iter=10,min_lldelta=0.5)
print(accuracy(me_classifier,test_feats))  # 0.722
# 查看信息量最大的特征
most_informatice_features=me_classifier.show_most_informative_features(n=4)
print(most_informatice_features)
  • 最大熵模型背后的基本思想是构建一些适合所观测数据的概率分布,然后悬着具有最大熵的任何一个概率分布。
  • 默认算法是iis, 它和gis一样通过迭代地改进用于分类特征的权重
  • max_iter 变量指定更新权重的最大迭代次数
  • min_lldelta 变量指定了在对数似然(log likelihood)中为提高权重继续迭代所需的最小改变

6. 训练 scikit-learn 分类器

scikit-learn是最好的机器学习库之一,下面使用NLTK的 SklearnClassifier 类(以scikit-learn 模型为中心的包装类,scikit-learn 模型符合NLTK的Classifier 接口)

训练 SklearnClassifier 类与上述分类器的训练步骤略有不同:

  1. 创建训练特征
    # 获取数据
    from nltk.corpus import movie_reviews
    lfeats = label_feats_from_corpus(movie_reviews)
    train_feats , test_feats = split_label_feats(lfeats)
  2. 选择并导入sklearn算法,并构建SklearnClassifier类
    sk_classifier = SklearnClassifier(要用的算法)
  3. 训练分类器

6.1 SklearnClassifier类

class SklearnClassifier(ClassifierI):
    """Wrapper for scikit-learn classifiers."""

    def __init__(self, estimator, dtype=float, sparse=True):
        """
        :param estimator: scikit-learn classifier object.

        :param dtype: data type used when building feature array.
            scikit-learn estimators work exclusively on numeric data. The
            default value should be fine for almost all situations.

        :param sparse: Whether to use sparse matrices internally.
            The estimator must support these; not all scikit-learn classifiers
            do (see their respective documentation and look for "sparse
            matrix"). The default value is True, since most NLP problems
            involve sparse feature sets. Setting this to False may take a
            great amount of memory.
        :type sparse: boolean.
        """
        self._clf = estimator
        self._encoder = LabelEncoder()
        self._vectorizer = DictVectorizer(dtype=dtype, sparse=sparse)

    def __repr__(self):
        return "<SklearnClassifier(%r)>" % self._clf

    def classify_many(self, featuresets):
        """Classify a batch of samples.

        :param featuresets: An iterable over featuresets, each a dict mapping
            strings to either numbers, booleans or strings.
        :return: The predicted class label for each input sample.
        :rtype: list
        """
        X = self._vectorizer.transform(featuresets)
        classes = self._encoder.classes_
        return [classes[i] for i in self._clf.predict(X)]

    def prob_classify_many(self, featuresets):
        """Compute per-class probabilities for a batch of samples.

        :param featuresets: An iterable over featuresets, each a dict mapping
            strings to either numbers, booleans or strings.
        :rtype: list of ``ProbDistI``
        """
        X = self._vectorizer.transform(featuresets)
        y_proba_list = self._clf.predict_proba(X)
        return [self._make_probdist(y_proba) for y_proba in y_proba_list]

    def labels(self):
        """The class labels used by this classifier.

        :rtype: list
        """
        return list(self._encoder.classes_)

    def train(self, labeled_featuresets):
        """
        Train (fit) the scikit-learn estimator.

        :param labeled_featuresets: A list of ``(featureset, label)``
            where each ``featureset`` is a dict mapping strings to either
            numbers, booleans or strings.
        """

        X, y = list(zip(*labeled_featuresets))
        X = self._vectorizer.fit_transform(X)
        y = self._encoder.fit_transform(y)
        self._clf.fit(X, y)

        return self

    def _make_probdist(self, y_proba):
        classes = self._encoder.classes_
        return DictionaryProbDist(dict((classes[i], p) for i, p in enumerate(y_proba)))
View Code
  •  使用估计器estimator进行初始化,也就是传入的算法,如 MultinomialNB 离散型朴素贝叶斯。
  •  LabelEncoder 对象将标签字符串转换为数字。例如pos转换为1,neg转换为0
  •  DictVectorize 对象用于将NLTK特征字典转换为 sklearn 兼容的特征向量
  •  SklearnClassifier 类使用稀疏向量,因此并不是所有分类算法与SklearnClassifier兼容

6.2 离散型朴素贝叶斯分类器

from nltk.classify.scikitlearn import SklearnClassifier
from sklearn.naive_bayes import MultinomialNB
sk_classifier = SklearnClassifier(MultinomialNB)
sk_classifier.train(train_feats)
  • MultinomialNB 可以用离散的特征值(如词频),而 NaiveBayesClassifier 类假定了如字符串或布尔变量的小特征集

6.3 逻辑回归分类器

也就是最大熵分类器

from nltk.classify.scikitlearn import SklearnClassifier
from sklearn.linear_model import LogisticRegression
sk_classifier = SklearnClassifier(LogisticRegression())
sk_classifier.train(train_feats)
  • 常用于线性模型

6.4 LinearSVC


from nltk.classify.scikitlearn import SklearnClassifier
from sklearn.svm import *

sk_classifier = SklearnClassifier(SVC)
# sk_classifier = SklearnClassifier(LinearSVC)
# sk_classifier = SklearnClassifier(NuSVC)

sk_classifier.train(train_feats)

7 计算高信息量单词

7.1 精准率和召回率

7.2 使用高信息单词的分类器

8 投票组合分类器

9 多标签分类器(多个二元分类器)

10 使用NLTK训练器

原文地址:https://www.cnblogs.com/lvjingyuan/p/14630617.html