窃·格瓦拉

只有坚强起来,才能不丧失温柔 在家一个人很无聊,都没有朋友、女友玩 ,进了局子里面个个是人才,说话又好听,我超喜欢在里面。...

最大熵用于文本分类

原始数据集和完整的代码见 http://download.csdn.net/detail/u012176591/8675665

一个相关的论文《使用最大熵模型进行中文文本分类》

1.改进的迭代尺度算法IIS:

输入:特征函数f1,f2,,fn;经验分布P~(X,Y),模型Pw(y|x)

输出:最优参数值wi;最优模型Pw

  1. 对所有i{1,2,,n},取初值wi=0
  2. 对每一i{1,2,,n}

    • (a)令δi是方程
      x,yP~(x)P(y|x)fi(x,y)exp(δif#(x,y))=EP~(fi)
      的解,这里
      f#(x,y)=ni=1fi(x,y)
    • (b) 更新wi值:wiwi+δi
  3. 如果不是所有的wi都收敛,重复步骤2.

这一算法关键的一步是2(a),即求方程中的δi。如果f#(x,y)是常数,即对任何x,y,有f#(x,y)=M,那么δi=1MlogEP~(fi)EP(fi)

2.怎么用最大熵做文本分类

  • 代码中的prob就是Pw(y|x),代表文本x属于类别的概率,prob是一个文本数*类别的矩阵。

    Pw(y|x)=1Zw(x)exp(ni=1wifi(x,y))

    其中Zw(x)=yexp(ni=1wifi(x,y))

  • EP_prior就是EP~(f)=x,yP~(x,y)f(x,y),是特征函数f(x,y)关于先验分布P~(x,y)的期望值。

    EP_post就是EP(f)=x,yP~(x)P(y|x)f(x,y),是特征函数f(x,y)关于后验分布P(x,y)=P~(x)P(y|x)的期望值,其中P(y|x)是我们的模型。

    最大熵模型P(y|x)要求EP~(f)=EP(f),这意味着wordNumctgyNum个约束条件,每个特征有一个约束条件。

  • 预测文本类别是所用的公式Pw(y|x)=exp(ni=1wifi(x,y))yexp(ni=1wifi(x,y)),对于一个文本xPw(y|x)最大的类y就是判断的类。可以发现分母部分yexp(ni=1wifi(x,y))与类别的判断无关,所以只要计算分子部分exp(ni=1wifi(x,y)即可。

  • 对于词w和类别C,它的特征函数

    fw,C(C,t)={#(t,w)#(t)0C=CCC

    其中#(t,w)#(t)表示词w在文档t中出现的先验概率。

    假设所有文本的单词的不重复集合的元素数目是wordNum,类别的个数是ctgyNum,那么特征一共有wordNumctgyNum个,,组成一个wordNumctgyNum的特征矩阵。由特征函数公式,可知对每个文本t,其单词(包括在该文本中出现和未出现的)与类别C的数目在特征函数的作用下形成一个特征矩阵。易知该矩阵在此文本非所属的类别所在的列元素全为0,该文本中不出现的单词所在的行也全为0,非0元素的和为1。在程序中对每个文本只记录其非0特征及其特征值(texts中的元素是字典,就是反映的这种思路,字典中隐含的是该文本所属的类别C)。EP~(f)EP(f)与特征矩阵的维度大小相同,而且EP~(f)本质上是对所有文本的特征矩阵的求和(本例中所有的文本的出现概率视为均等,故EP~(f)EP(f)表达式中的P~(x)相同,忽略其不影响)。

  • 本例中EP~(f)=x,yP~(x,y)f(x,y)=xf(x,y),因为只有y等于x的类别时P~(x,y)1textNum,即单个文本的出现概率,其余都为0,这就是为什么说EP~(f)本质上是对所有文本的特征矩阵的求和。

    P~(x)就是单个文本的概率,这里每个文本都不相同且仅属于一个类别,故P~(x)=1textNum

    故由EP~(f)=EP(f)可推知xf(x,y)=x,yP(y|x)f(x,y),这是在特定应用场景下的模型约束,下面的分析中就是这个模型。

  • 更新量δi=1MlogEP~(fi)EP(fi),其中M=maxki=1f(xi),即所有文本中特征值的和的最大值,由fw,C(C,t)的公式知这里M的值肯定为1。

3.训练和测试效果

训练效果:

迭代0次后的模型效果:
训练总文本个数:2741 总错误个数:1 总错误率:0.000364830353885
迭代1次后的模型效果:
训练总文本个数:2741 总错误个数:5 总错误率:0.00182415176943
迭代2次后的模型效果:
训练总文本个数:2741 总错误个数:7 总错误率:0.0025538124772
迭代3次后的模型效果:
训练总文本个数:2741 总错误个数:8 总错误率:0.00291864283108
迭代4次后的模型效果:
训练总文本个数:2741 总错误个数:7 总错误率:0.0025538124772
迭代5次后的模型效果:
训练总文本个数:2741 总错误个数:7 总错误率:0.0025538124772
迭代6次后的模型效果:
训练总文本个数:2741 总错误个数:7 总错误率:0.0025538124772
迭代7次后的模型效果:
训练总文本个数:2741 总错误个数:5 总错误率:0.00182415176943
迭代8次后的模型效果:
训练总文本个数:2741 总错误个数:4 总错误率:0.00145932141554
迭代9次后的模型效果:
训练总文本个数:2741 总错误个数:3 总错误率:0.00109449106166

测试效果:

迭代0次后的模型效果:
测试总文本个数:709 总错误个数:129 总错误率:0.181946403385
迭代1次后的模型效果:
测试总文本个数:709 总错误个数:121 总错误率:0.170662905501
迭代2次后的模型效果:
测试总文本个数:709 总错误个数:118 总错误率:0.166431593794
迭代3次后的模型效果:
测试总文本个数:709 总错误个数:118 总错误率:0.166431593794
迭代4次后的模型效果:
测试总文本个数:709 总错误个数:118 总错误率:0.166431593794
迭代5次后的模型效果:
测试总文本个数:709 总错误个数:119 总错误率:0.16784203103
迭代6次后的模型效果:
测试总文本个数:709 总错误个数:120 总错误率:0.169252468265
迭代7次后的模型效果:
测试总文本个数:709 总错误个数:117 总错误率:0.165021156559
迭代8次后的模型效果:
测试总文本个数:709 总错误个数:117 总错误率:0.165021156559
迭代9次后的模型效果:
测试总文本个数:709 总错误个数:118 总错误率:0.166431593794

将历次迭代中的训练和测试错误率可视化如下图:
这里写图片描述
发现在训练集上模型的效果很好,但是在测试集上效果差得太多,可能是模型有偏差吧。

特征权值的分布

这里写图片描述

从上图可以看出绝大部分的特征的权值都为0.
为了更清晰地分析非0权值,做出下面这张图,可以看到特征的权值遵循正态分布。
这里写图片描述

4.Python源码

已经有了足够的注释,如果你还看不懂,你或者去揣摩最大熵模型的原理,或者去学习Python编程。

def get_ctgy(fname):#根据文件名称获取类别的数字编号
        index = {'fi':0,'lo':1,'co':2,'ho':3,'ed':4,'te':5,
                 'ca':6,'ta':7,'sp':8,'he':9,'ar':10,'fu':11}
        return index[fname[:2]]

def updateWeight():

        #EP_post是 单词数*类别 的矩阵
        for i in range(wordNum):
            for j in range(ctgyNum):
                EP_post[i][j] = 0.0 #[[0.0 for x in range(ctgyNum)] for y in range(wordNum)]
        # prob是 文本数*类别 的矩阵,记录每个文本属于每个类别的概率
        cond_prob_textNum_ctgyNum = [[0.0 for x in range(ctgyNum)] for y in range(textNum)]
        #计算p(类别|文本)

        for i in range(textNum):#对每一个文本
                zw = 0.0  #归一化因子
                for j in range(ctgyNum):#对每一个类别
                        tmp = 0.0
                        #texts_list_dict每个元素对应一个文本,该元素的元素是单词序号:频率所组成的字典。
                        for (feature,feature_value) in texts_list_dict[i].items():
                            #v就是特征f(x,y),非二值函数,而是实数函数,
                            #k是某文本中的单词,v是该单词的次数除以该文本不重复单词的个数。
                                #feature_weight是 单词数*类别 的矩阵,与EP_prior相同
                                tmp+=feature_weight[feature][j]*feature_value #feature_weight是最终要求的模型参数,其元素与特征一一对应,即一个特征对应一个权值
                        tmp = math.exp(tmp)
                        zw+=tmp #zw关于类别求和
                        cond_prob_textNum_ctgyNum[i][j]=tmp                        
                for j in range(ctgyNum):
                        cond_prob_textNum_ctgyNum[i][j]/=zw
        #上面的部分根据当前的feature_weight矩阵计算得到prob矩阵(文本数*类别的矩阵,每个元素表示文本属于某类别的概率),
        #下面的部分根据prob矩阵更新feature_weight矩阵。


        for x in range(textNum):
                ctgy = category[x] #根据文本序号获取类别序号
                for (feature,feature_value) in texts_list_dict[x].items(): #该文本中的单词和对应的频率
                        EP_post[feature][ctgy] += (cond_prob_textNum_ctgyNum[x][ctgy]*feature_value)#认p(x)的先验概率相同        
        #更新特征函数的权重w
        for i in range(wordNum):
                for j in range(ctgyNum):
                        if (EP_post[i][j]<1e-17) |  (EP_prior[i][j]<1e-17) :
                                continue                        

                        feature_weight[i][j] += math.log(EP_prior[i][j]/EP_post[i][j])        

def modelTest():
        testFiles = os.listdir('data\\test\\')
        errorCnt = 0
        totalCnt = 0

        #matrix是类别数*类别数的矩阵,存储评判结果
        matrix = [[0 for x in range(ctgyNum)] for y in range(ctgyNum)]
        for fname in testFiles: #对每个文件

                lines = open('data\\test\\'+fname)
                ctgy = get_ctgy(fname) #根据文件名的前两个字符给出类别的序号
                probEst = [0.0 for x in range(ctgyNum)]         #各类别的后验概率
                for line in lines: #该文件的每一行是一个单词和该单词在该文件中出现的频率
                        arr = line.split('\t')
                        if not words_dict.has_key(arr[0]) : 
                            continue        #测试集中的单词如果在训练集中没有出现则直接忽略
                        word_id,freq = words_dict[arr[0]],float(arr[1])
                        for index in range(ctgyNum):#对于每个类别
                            #feature_weight是单词数*类别墅的矩阵
                            probEst[index] += feature_weight[word_id][index]*freq
                ctgyEst = 0
                maxProb = -1
                for index in range(ctgyNum):
                        if probEst[index]>maxProb:
                            ctgyEst = index
                            maxProb = probEst[index]
                totalCnt+=1
                if ctgyEst!=ctgy: 
                    errorCnt+=1
                matrix[ctgy][ctgyEst]+=1
                lines.close()
        #print "%-5s" % ("类别"),
        #for i in range(ctgyNum):
        #    print "%-5s" % (ctgyName[i]),  
        #print '\n',
        #for i in range(ctgyNum):
        #    print "%-5s" % (ctgyName[i]), 
        #    for j in range(ctgyNum):
        #        print "%-5d" % (matrix[i][j]), 
        #    print '\n',
        print "测试总文本个数:"+str(totalCnt)+"  总错误个数:"+str(errorCnt)+"  总错误率:"+str(errorCnt/float(totalCnt))

def prepare():
        i = 0
        lines = open('data\\words.txt').readlines()
        #words_dict给出了每一个中文词及其对应的全局统一的序号,是字典类型,示例:{'\xd0\xde\xb5\xc0\xd4\xba': 0}
        for word in lines:
                word = word.strip()
                words_dict[word] = i
                i+=1
        #计算约束函数f的经验期望EP(f)
        files = os.listdir('data\\train\\') #train下面都是.txt文件
        index = 0
        for fname in files: #对训练数据集中的每个文本文件
                file_feature_dict = {}
                lines = open('data\\train\\'+fname)
                ctgy = get_ctgy(fname) #根据文件名的前两个汉字,也就是中文类别来确定类别的序号

                category[index] = ctgy #data/train/下每个文本对应的类别序号
                for line in lines: #每行内容:古迹  0.00980392156863
                        # line的第一个字符串是中文单词,第二个字符串是该单词的频率
                        arr = line.split('\t')
                        #获取单词的序号和频率
                        word_id,freq= words_dict[arr[0]],float(arr[1])

                        file_feature_dict[word_id] = freq
                        #EP_prior是单词数*类别的矩阵
                        EP_prior[word_id][ctgy]+=freq
                texts_list_dict[index] = file_feature_dict
                index+=1
                lines.close()
def train():
        for loop in range(4):
            print "迭代%d次后的模型效果:" % loop
            updateWeight()
            modelTest()


textNum = 2741  # data/train/下的文件的个数
wordNum = 44120 #data/words.txt的单词数,也是行数
ctgyNum = 12


#feature_weight是单词数*类别墅的矩阵
feature_weight = np.zeros((wordNum,ctgyNum))#[[0 for x in range(ctgyNum)] for y in range(wordNum)]

ctgyName = ['财经','地域','电脑','房产','教育','科技','汽车','人才','体育','卫生','艺术','娱乐']
words_dict = {}

# EP_prior是个12(类)* 44197(所有类的单词数)的矩阵,存储对应的频率
EP_prior = np.zeros((wordNum,ctgyNum))
EP_post = np.zeros((wordNum,ctgyNum))
#print np.shape(EP_prior)
texts_list_dict = [0]*textNum #所有的训练文本 
category = [0]*textNum        #每个文本对应的类别

print "初始化:......"
prepare()
print "初始化完毕,进行权重训练....."
train()

源码中出现的文件数据格式的解析

这里写图片描述

其中words.txt 里是所有在训练数据集中出现的单词的不重复集合,每行一个单词,如下:

这里写图片描述

test和train文件夹中分别是测试数据集和训练数据集,这两个文件夹下的文件格式和内容格式是没有区别的。每个文件对应一个文本,文件名的前两个字符表示对应的文本所属的类别,文件内容是该文本中所有出现的单词在该文本中的概率,如下:
这里写图片描述


作hist图的代码:

weight = feature_weight.reshape((1,44120*12))[0]
#weight = np.log10(weight+1)
plt.hist(weight, 100)
plt.title(u'特征的权值分布图',{'fontname':'STFangsong','fontsize':18})
plt.xlabel(u'特征的权值',{'fontname':'STFangsong','fontsize':18})
plt.ylabel(u'个数',{'fontname':'STFangsong','fontsize':18})


作错误率的代码:

train_precision = [0.000364830353885,0.00182415176943,0.0025538124772,0.00291864283108,0.0025538124772,
                  0.0025538124772,0.0025538124772,0.00182415176943,0.00145932141554,0.00109449106166]

test_precision = [0.181946403385,0.170662905501,0.166431593794,0.166431593794,0.166431593794,0.16784203103,
                 0.169252468265,0.165021156559,0.165021156559,0.166431593794]
iteration = range(1,11)

fig,ax = plt.subplots(nrows=1,ncols=1)
ax.plot(iteration,train_precision,'-og',label=u'训练误差')
ax.plot(iteration,test_precision,'-sr',label=u'测试误差')


ax.legend(prop={'family':'SimHei','size':15})

ax.set_xlabel(u'迭代次数',{'fontname':'STFangsong','fontsize':18})

ax.set_ylabel(u'误判比例',{'fontname':'STFangsong','fontsize':18})
ax.set_title(u'训练和测试误差曲线',{'fontname':'STFangsong','fontsize':18})

进一步阅读

阅读更多
文章标签: 最大熵 文本分类
个人分类: DataMining
所属专栏: 机器学习理论与实践
想对作者说点什么? 我来说一句

最大熵分类器 java

2012年06月21日 2.08MB 下载

最大熵文本分类

2015年05月08日 19.9MB 下载

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭