刚开始学习最大熵模型的时候,自以为书中的推导都看明白了。等到自己实现时才发现问题多多。因此,这篇博客将把重点放在python程序的解读上,为什么说是解读呢,因为这个程序不是我写的(轻点喷~~),这个程序参考了网上的一篇博客,地址:http://blog.csdn.net/moonzjaw/article/details/39552333。在此,对他的贡献表示诚挚的谢意。
进入正题,假设我们有一颗六面骰子,如何求出骰子投出“1”的概率?在没有其他条件的情况下,我们很自然地会让骰子每个面出现的概率均等,从而得出投出“1”的概率为1/6的结论。如果已知该骰子质量分布不均匀,使得投出“2”和“6”的概率和为1/2,那么投出“1”的概率又是多少?在这种情况下,我们仍然下意识的认为投出“1”、“3”、“4”、“5”概率相等。在这两个案例中,本来符已知条件的投出“1”的概率分布有无限多种,但是我们更倾向于使每个事件等可能发生,这其实使用到了最大熵原理。本博客首先介绍最大熵原理的具体含义,并基于最大熵原理,推导最大熵模型,介绍求解最大熵模型的一种常用方法—IIS,最后,解读基于python实现的最大熵模型简单案例。
1. 最大熵原理与最大熵模型
我们都知道太阳东升西落,这是一个必然事件,所以这一事实并不会引起你的注意,因为这种“正确的废话”能带给你的信息量是很少的。同样的,一个不可能事件能带给你的信息量也是很少的。从这里我们可以看出,信息量与概率存在有一定关联。信息熵公式所描述的也就是这样一种关联。
from collections import defaultdict
import numpy as np
class maxEntropy(object):
def __init__(self):
self.trainset = [] # 训练数据集
self.features = defaultdict(int) # 用于获得(标签,特征)键值对
self.labels = set([]) # 标签
self.w = []
def loadData(self, fName):
for line in open(fName):
fields = line.strip().split()
# at least two columns
if len(fields) < 2: continue # 只有标签没用
# the first column is label
label = fields[0]
self.labels.add(label) # 获取label
for f in set(fields[1:]): # 对于每一个特征
# (label,f) tuple is feature
self.features[(label, f)] += 1 # 每提取一个(标签,特征)对,就自加1,统计该特征-标签对出现了多少次
self.trainset.append(fields)
self.w = [0.0] * len(self.features) # 初始化权重
self.lastw = self.w
# 对于该问题,M是一个定值,所以delta有解析解
def train(self, max_iter=1000):
self.initP() # 主要计算M以及联合分布在f上的期望
# 下面计算条件分布及其期望,正式开始训练
for i in range(max_iter): # 计算条件分布在特诊函数上的期望
self.ep = self.EP()
self.lastw = self.w[:]
for i, w in enumerate(self.w):
self.w[i] += (1.0 / self.M) * np.log(self.Ep_[i] / self.ep[i])
if self.convergence():
break
def initP(self):
# 获得M
self.M = max([len(feature[1:]) for feature in self.trainset])
self.size = len(self.trainset)
self.Ep_ = [0.0] * len(self.features)
# 获得联合概率期望
for i, feat in enumerate(self.features):
self.Ep_[i] += self.features[feat] / (1.0 * self.size)
# 更改键值对为(label-feature)-->id
self.features[feat] = i
# 准备好权重
self.w = [0.0] * len(self.features)
self.lastw = self.w
def EP(self):
# 计算pyx
ep = [0.0] * len(self.features)
for record in self.trainset:
features = record[1:]
# cal pyx
prob = self.calPyx(features)
for f in features: # 特征一个个来
for pyx, label in prob: # 获得条件概率与标签
if (label, f) in self.features:
id = self.features[(label, f)]
ep[id] += (1.0 / self.size) * pyx
return ep
# 获得最终单一样本每个特征的pyx
def calPyx(self, features):
# 传的feature是单个样本的
wlpair = [(self.calSumP(features, label), label) for label in self.labels]
Z = sum([w for w, l in wlpair])
prob = [(w / Z, l) for w, l in wlpair]
return prob
def calSumP(self, features, label):
sumP = 0.0
# 对于这单个样本的feature来说,不存在于feature集合中的f=0所以要把存在的找出来计算
for showedF in features:
if (label, showedF) in self.features:
sumP += self.w[self.features[(label, showedF)]]
return np.exp(sumP)
def convergence(self):
for i in range(len(self.w)):
if abs(self.w[i] - self.lastw[i]) >= 0.001:
return False
return True
def predict(self, input):
features = input.strip().split()
prob = self.calPyx(features)
prob.sort(reverse=True)
return prob
if __name__ == '__main__':
mxEnt = maxEntropy()
mxEnt.loadData('gameLocation.dat')
mxEnt.train()
print mxEnt.predict('Sunny')
训练样本集为:
Outdoor Sunny Happy
Outdoor Sunny Happy Dry
Outdoor Sunny Happy Humid
Outdoor Sunny Sad Dry
Outdoor Sunny Sad Humid
Outdoor Cloudy Happy Humid
Outdoor Cloudy Happy Humid
Outdoor Cloudy Sad Humid
Outdoor Cloudy Sad Humid
Indoor Rainy Happy Humid
Indoor Rainy Happy Dry
Indoor Rainy Sad Dry
Indoor Rainy Sad Humid
Indoor Cloudy Sad Humid
Indoor Cloudy Sad Humid
第一列为标签。对于训练过程来说。代码首先调用loadData方法读入数据,并生成特征-标签对feature以及训练集合trainset。然后,调用initP方法初始化M以及联合分布的期望Ep_。注意该模型中特征对数目固定所以M为定值。求解Ep_时,从训练集中一个个取出特征-标签对并计数。最终可得到每个特征对以及对应的概率和期望。然后,算法执行Ep方法计算在当前w情况下p(y|x)值(调用了calPyx方法)。并计算期望Ep。计算期望Ep那一步本人一直有疑问,关于计算P(x)使用1/self.size感觉并不妥当。因为这里P(x)是可以根据样本集算出来的,不知道原作者这里用均匀分布意图何在?如果忽略这一点的话,经过前面的计算我们就得到了经验联合概率的期望Ep_以及模型与经验分布P(x)经验期望值。可根据IIS算法Step2提供的公式求解 δi 并更新w。重复直到w稳定或者达到最大迭代次数为止。贴一张结果图
那么最大熵模型就介绍到这里,欢迎讨论。特别是,对于李航书中所说“M为一常数时可以获得解析解”那么M不为常数代表了什么情况?欢迎讨论。
参考文献
[1] 李航. 统计学习方法 [M]. 北京:清华大学出版社, 2012: 65-70.
[2] 多篇博客,文中已标注。