最大熵模型及其python实现

刚开始学习最大熵模型的时候,自以为书中的推导都看明白了。等到自己实现时才发现问题多多。因此,这篇博客将把重点放在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. 最大熵原理与最大熵模型
我们都知道太阳东升西落,这是一个必然事件,所以这一事实并不会引起你的注意,因为这种“正确的废话”能带给你的信息量是很少的。同样的,一个不可能事件能带给你的信息量也是很少的。从这里我们可以看出,信息量与概率存在有一定关联。信息熵公式所描述的也就是这样一种关联。

S=ipilogpi

而最大熵原理认为,在众多符合已知条件的概率模型中,使得熵最大的模型是最好的。我们来依据最大熵原理推导一下文章开头的骰子问题。
设掷出1~6的概率分别为 。那么可以写出基于最大熵原理建立的优化模型:
maxS=i=16pilogpis.t.i=16pi=1

构造拉格朗日函数:
L(p,λ)=i=16pilogpi+λ(1i=16pi)

分别对各个p求偏导
Lpi=λ(1+logpi)

pi=eλ1 (常数),所以基于最大熵原理得到的概率模型服从均匀分布。所有事件是等概率发生的,这一点符合人们的认知,即在不知道更多的条件时,不对模型做进一步假设。由于实际问题中可能包含更多的约束(例如文章开头提到了骰子质量分布不均匀的情况)使得最大熵模型中可能包含更多约束,故求解得到的概率模型并不一定就严服从均匀分布,最大熵模型只是在基于已知的条件下,使得模型接近均匀分布。关于信息熵和最大熵原理本文不做过多阐述,网上的解释都更有参考价值。
考虑训练数据集 T={(x1,y1),(x2,y2),...(xN,yN)} ,如何训练一个概率模型p(y|x)实现分类任务?首先,我们可以根据训练练样本得到特征x与标签y的经验联合概率分布 p˜(x,y) ,也就是,统计每个特征-标签对在训练样本中出现的概率。由于是基于样本得到的概率所以是经验概率。同样的,可以根据样本得到特征的经验概率 p˜(x) 。训练得到的条件概率应当满足以下条件
Ep=p˜(x)p(y|x)f(x,y)=p˜(x,y)f(x,y)=Ep˜

其中,f(x,y)是特征函数。该特定特征-标签对出现时,取1,否则取0。(也不一定就要取1,特征函数有多种形式,可以参考 https://www.zhihu.com/question/52925275)。
除了让期望相等,该概率模型还应当满足最大熵准则和概率和为1的准则。所以训练该概率模型转化为如下的优化问题:
minS(Y|X)=S(X,Y)+S(X)=i=1Np˜(x,y)logp(y|x)=i=1Np(y|x)p˜(x)logp(y|x)s.t.yp(y|x)=1p˜(x,y)f(x,y)=p(y|x)p˜(x)f(x,y)

需要注意的是上面的模型是对条件熵取最大。而条件熵代表着随机变量X发生的情况下随机变量Y发生带来熵。衡量已知X的情况下Y的不确定性,具体计算公式与推导参考 http://blog.csdn.net/xiangyong58/article/details/51290393
构造拉格朗日函数
L(p(y|x),λi)=i=1Np(y|x)p˜(x)logp(y|x)+λ0[1yp(y|x)]+i=1Nλi[x,yp˜(x,y)fi(x,y)x,yp(y|x)p˜(x)fi(x,y)]

可以对原始问题进行转化,原始问题为
minp(y|x)maxλL(p(y|x),λ)

由于L是关于P的凸函数,原始问题可转化为其对偶问题解决。
maxλminp(y|x)L(p(y|x),λ)

考虑最小化对偶函数
minp(y|x)ψ=i=1Np(y|x)p˜(x)logp(y|x)+λ0[1i=1Npi]+i=1Nλi[x,yp˜(x,y)fi(x,y)x,yp(y|x)p˜(x)fi(x,y)]


ψp(y|x)=x,yp˜(x)[1+logp(y|x)λ0i=1Nλifi(x,y)]=0

所以
p(y|x)=exp[i=1Nλifi(x,y)]exp(1λ0)

yp(y|x)=1 ,上式进一步简化成
p(y|x)=exp[i=1Nλifi(x,y)]yexp[i=1Nλifi(x,y)]

再对该式中的w进一步优化即可得到最终的模型。
2. 最大熵模型的求解IIS算法
这一部分的推导十分繁琐,不过李航博士的《统计学习方法》有十分详细的介绍,这里就不搬运了。直接贴结果。
IIS计算流程如下:
Step1:初始化权重w为全0向量。
Step2:求解方程
x,yp˜(x)p(y|x)fi(x,y)exp[δif#(x,y)]=x,yp˜(x,y)fi(x,y)

中的 δi ,也就是权重w的改变量。其中 f#(x,y)=ifi(x,y) ,如果该值是一个常数M,那么可以写出方程的解析解
δi=1MlogEp˜(fi)Ep(fi)

否则,就要数值求解 δi 。更新w值。
Setp3:判断w迭代前后是否稳定,如果没稳定,就重复1~3.
整个求解流程较为清晰,但是待到实现时,你就会发现,这个特征函数到底是什么东东?怎么实现?
特征函数其实可以看作模型的眼睛,对于给定的训练样本,可以统计出所有样本的特征-标签对,这样的特征-标签对只要存在,那么按照定义,特征函数值就变为1。如果不存在,那么就为0。这样的操作将所有可能出现的特征-标签对减少到出现在样本集中的特征-标签对,使得模型的训练集中在训练样本而不是所有可能出现的情况上。这一点我也是参考了 https://www.zhihu.com/question/52925275之后意识到的。下面解说python代码加深理解。
3. Python实现最大熵模型
首先整理一下思路。要根据训练集获得p(y|x)的模型,我们需要得到训练集的所有特征以及特征函数f。特征函数可以根据训练集统计得到。之后,我们需要根据样本得到经验联合概率 p˜(x,y) 和每个特征的概率 p˜(x) 以便计算他们的期望在求得期望后,根据IIS中Step2计算w改变量。更新权重直到权重变化不大或者达到最大迭代次数为止。
下面放代码

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] 多篇博客,文中已标注。

  • 2
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
以下是最大熵模型Python实现示例: 1. 安装必要的库 ``` pip install numpy pip install scikit-learn ``` 2. 导入库 ``` import numpy as np from sklearn.linear_model import LogisticRegression ``` 3. 定义最大熵模型类 ``` class MaxEnt: def __init__(self, eps=1e-5): self._eps = eps self._model = LogisticRegression(penalty='none', solver='lbfgs', max_iter=1000) def _get_features(self, x): # 获取特征函数 features = [1.0] for i in range(len(x)): for j in range(i+1, len(x)): features.append(x[i] + '_' + x[j]) return features def _create_dataset(self, data): # 创建数据集 X = [] y = [] for d in data: x = self._get_features(d[:-1]) X.append(x) y.append(d[-1]) return np.array(X), np.array(y) def train(self, data): # 训练模型 X, y = self._create_dataset(data) self._model.fit(X, y) def predict(self, x): # 预测 x = self._get_features(x) proba = self._model.predict_proba([x])[0] return proba[1] def evaluate(self, data): # 评估模型 X, y = self._create_dataset(data) return self._model.score(X, y) ``` 4. 使用示例 ``` # 创建数据集 data = [ ['sunny', 'hot', 'high', 'weak', 0], ['sunny', 'hot', 'high', 'strong', 0], ['overcast', 'hot', 'high', 'weak', 1], ['rainy', 'mild', 'high', 'weak', 1], ['rainy', 'cool', 'normal', 'weak', 1], ['rainy', 'cool', 'normal', 'strong', 0], ['overcast', 'cool', 'normal', 'strong', 1], ['sunny', 'mild', 'high', 'weak', 0], ['sunny', 'cool', 'normal', 'weak', 1], ['rainy', 'mild', 'normal', 'weak', 1], ['sunny', 'mild', 'normal', 'strong', 1], ['overcast', 'mild', 'high', 'strong', 1], ['overcast', 'hot', 'normal', 'weak', 1], ['rainy', 'mild', 'high', 'strong', 0] ] # 训练模型 model = MaxEnt() model.train(data) # 预测 x = ['sunny', 'hot', 'high', 'weak'] proba = model.predict(x) print(proba) # 评估模型 score = model.evaluate(data) print(score) ``` 输出结果: ``` 0.376030006505 1.0 ``` 说明: - 预测结果为0.376030006505,表示该样本属于第0类的概率为0.376030006505,属于第1类的概率为1-0.376030006505=0.623969993495。 - 评估结果为1.0,表示模型在训练集上的准确率为100%。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值