https://blog.youkuaiyun.com/golden1314521/article/details/45576089
https://github.com/doubleEN/Maxent一个实例
原始数据集和完整的代码见 http://download.youkuaiyun.com/detail/u012176591/8675665
一个相关的论文《使用最大熵模型进行中文文本分类》
1.改进的迭代尺度算法IIS:
输入:特征函数f1,f2,⋯,fnf1,f2,⋯,fn;经验分布P~(X,Y)P~(X,Y),模型Pw(y|x)Pw(y|x)
输出:最优参数值wiwi;最优模型PwPw
对所有i∈{1,2,⋯,n}i∈{1,2,⋯,n},取初值wi=0wi=0
对每一i∈{1,2,⋯,n}i∈{1,2,⋯,n}:
(a)令δiδi是方程
∑x,yP~(x)P(y|x)fi(x,y)exp(δif#(x,y))=EP~(fi)∑x,yP~(x)P(y|x)fi(x,y)exp(δif#(x,y))=EP~(fi)
的解,这里
f#(x,y)=∑ni=1fi(x,y)f#(x,y)=∑i=1nfi(x,y)
(b) 更新wiwi值:wi←wi+δiwi←wi+δi
如果不是所有的wiwi都收敛,重复步骤2.
这一算法关键的一步是2(a),即求方程中的δiδi。如果f#(x,y)f#(x,y)是常数,即对任何x,yx,y,有f#(x,y)=Mf#(x,y)=M,那么δi=1MlogEP~(fi)EP(fi)δi=1MlogEP~(fi)EP(fi)
2.怎么用最大熵做文本分类
代码中的prob就是Pw(y|x)Pw(y|x),代表文本x属于类别的概率,prob是一个文本数*类别的矩阵。
Pw(y|x)=1Zw(x)exp(∑ni=1wifi(x,y))Pw(y|x)=1Zw(x)exp(∑i=1nwifi(x,y))
其中Zw(x)=∑yexp(∑ni=1wifi(x,y))Zw(x)=∑yexp(∑i=1nwifi(x,y))
EP_prior就是EP~(f)=∑x,yP~(x,y)f(x,y)EP~(f)=∑x,yP~(x,y)f(x,y),是特征函数f(x,y)f(x,y)关于先验分布P~(x,y)P~(x,y)的期望值。
EP_post就是EP(f)=∑x,yP~(x)P(y|x)f(x,y)EP(f)=∑x,yP~(x)P(y|x)f(x,y),是特征函数f(x,y)f(x,y)关于后验分布P(x,y)=P~(x)P(y|x)P(x,y)=P~(x)P(y|x)的期望值,其中P(y|x)P(y|x)是我们的模型。
最大熵模型P(y|x)P(y|x)要求EP~(f)=EP(f)EP~(f)=EP(f),这意味着wordNum∗ctgyNumwordNum∗ctgyNum个约束条件,每个特征有一个约束条件。
预测文本类别是所用的公式Pw(y|x)=exp(∑ni=1wifi(x,y))∑yexp(∑ni=1wifi(x,y))Pw(y|x)=exp(∑i=1nwifi(x,y))∑yexp(∑i=1nwifi(x,y)),对于一个文本xx,Pw(y|x)Pw(y|x)最大的类yy就是判断的类。可以发现分母部分∑yexp(∑ni=1wifi(x,y))∑yexp(∑i=1nwifi(x,y))与类别的判断无关,所以只要计算分子部分exp(∑ni=1wifi(x,y)exp(∑i=1nwifi(x,y)即可。
对于词ww和类别CC,它的特征函数
fw,C′(C,t)={#(t,w)#(t)0C=C′C≠C′
fw,C′(C,t)={#(t,w)#(t)C=C′0C≠C′
其中#(t,w)#(t)#(t,w)#(t)表示词ww在文档tt中出现的先验概率。
假设所有文本的单词的不重复集合的元素数目是wordNumwordNum,类别的个数是ctgyNumctgyNum,那么特征一共有wordNum∗ctgyNumwordNum∗ctgyNum个,,组成一个wordNum∗ctgyNumwordNum∗ctgyNum的特征矩阵。由特征函数公式,可知对每个文本tt,其单词(包括在该文本中出现和未出现的)与类别CC的数目在特征函数的作用下形成一个特征矩阵。易知该矩阵在此文本非所属的类别所在的列元素全为0,该文本中不出现的单词所在的行也全为0,非0元素的和为1。在程序中对每个文本只记录其非00特征及其特征值(texts中的元素是字典,就是反映的这种思路,字典中隐含的是该文本所属的类别CC)。EP~(f)EP~(f)和EP(f)EP(f)与特征矩阵的维度大小相同,而且EP~(f)EP~(f)本质上是对所有文本的特征矩阵的求和(本例中所有的文本的出现概率视为均等,故EP~(f)EP~(f)和EP(f)EP(f)表达式中的P~(x)P~(x)相同,忽略其不影响)。
本例中EP~(f)=∑x,yP~(x,y)f(x,y)=∑xf(x,y)EP~(f)=∑x,yP~(x,y)f(x,y)=∑xf(x,y),因为只有yy等于xx的类别时P~(x,y)P~(x,y)为1textNum1textNum,即单个文本的出现概率,其余都为00,这就是为什么说EP~(f)EP~(f)本质上是对所有文本的特征矩阵的求和。
P~(x)P~(x)就是单个文本的概率,这里每个文本都不相同且仅属于一个类别,故P~(x)=1textNumP~(x)=1textNum
故由EP~(f)=EP(f)EP~(f)=EP(f)可推知∑xf(x,y)=∑x,yP(y|x)f(x,y)∑xf(x,y)=∑x,yP(y|x)f(x,y),这是在特定应用场景下的模型约束,下面的分析中就是这个模型。
更新量δi=1MlogEP~(fi)EP(fi)δi=1MlogEP~(fi)EP(fi),其中M=max∑ki=1f(xi)M=max∑i=1kf(xi),即所有文本中特征值的和的最大值,由fw,C′(C,t)fw,C′(C,t)的公式知这里MM的值肯定为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})
进一步阅读
自然语言处理的最大熵模型
http://icl.pku.edu.cn/ICLseminars/2003spring/%E6%9C%80%E5%A4%A7%E7%86%B5%E6%A8%A1%E5%9E%8B.pdf
MaxEntModel.ppt
http://read.pudn.com/downloads131/ebook/557682/MaxEntModel%20.ppt
里面有对称硬币问题(表达能力、不确定度、为什么取对数、一次取三个最小值的霍夫曼编码),条件熵的公式定义的理解,马鞍点问题(详细见鞍点
http://zh.wikipedia.org/wiki/%E9%9E%8D%E9%BB%9E ,黑塞矩阵 http://zh.wikipedia.org/wiki/%E6%B5%B7%E6%A3%AE%E7%9F%A9%E9%98%B5
),极大似然函数由联合熵变成条件熵的推导过程
最大熵的Java实现
http://www.hankcs.com/nlp/maximum-entropy-java-implementation.html
最大熵文本分类 算法实现(有代码)
http://www.blogbus.com/myjuno-logs/240428649.html
数据集 http://www.searchforum.org.cn/tansongbo/corpus.htm
最大熵模型的简单实现(有两份代码,其中一个是另一个的简版)
http://yjliu.net/blog/2012/07/22/easy-implementation-on-maxent.html
张乐:Maximum Entropy Modeling
http://homepages.inf.ed.ac.uk/lzhang10/maxent.html
Jar包(没有源码)的maxent: Maxent software for species habitat modeling
https://www.cs.princeton.edu/~schapire/maxent/
A simple C++ library for maximum entropy classification
http://www.logos.ic.i.u-tokyo.ac.jp/~tsuruoka/maxent/
最大熵模型的图模型(factor graph):
概率图模型-贝叶斯-最大熵-隐马-最大熵马-马随机场