机器学习算法原理实现——最大熵模型

【写在前面】

在sklearn库中,没有直接称为"最大熵模型"的类,但是有一个与之非常相似的模型,那就是LogisticRegression。逻辑回归模型可以被视为最大熵模型的一个特例,当问题是二分类问题,且特征函数是输入和输出的线性函数时,最大熵模型就等价于逻辑回归模型。

【最大熵模型的原理】

最大熵模型是一种概率模型,它的基本思想是在满足给定约束条件下,使模型的熵最大。熵是一个衡量不确定性的量,熵越大,不确定性越大,模型越平滑,对未知数据的适应性越强。

最大熵模型的基本原理可以从以下几个方面来理解:

1. 特征函数:最大熵模型通过特征函数来描述输入和输出之间的关系。特征函数是一个二元函数,如果输入和输出满足某种关系,则特征函数的值为1,否则为0。

2. 模型的形式:最大熵模型是一个对数线性模型,它的形式为:P(Y|X) = exp(∑λi fi(X,Y)) / Z(X),其中,λi是特征函数fi对应的权重,Z(X)是归一化因子,保证概率之和为1。【见后实际例子】

3. 训练目标:最大熵模型的训练目标是最大化训练数据的对数似然函数,这等价于最小化交叉熵。通过优化算法(如梯度下降、牛顿法等)来求解最优的权重参数。

4. 约束条件:最大熵模型的约束条件通常是特征函数关于输入的期望等于特征函数关于模型的期望,这保证了模型能够尽可能地符合训练数据的统计特性。

最大熵模型广泛应用于自然语言处理、信息检索等领域,如文本分类、词性标注、命名实体识别等任务。

 

【如何理解其中提到的对数线性模型?】

最大熵模型是一个对数线性模型,这是因为它的形式可以写成一个线性函数的指数形式,并且这个线性函数的参数是特征函数的权重。具体来说,最大熵模型的形式可以写成:

P(Y|X) = exp(∑λifi(X,Y)) / Z(X)

其中,λi是特征函数fi对应的权重,Z(X)是归一化因子,保证概率之和为1。这个模型是线性的,因为对数概率log P(Y|X)是特征函数和权重的线性组合:

log P(Y|X) = ∑λi fi(X,Y) - log Z(X)

这就是为什么最大熵模型被称为对数线性模型。

举一个实际的例子,假设我们有一个二分类问题,输入X是一个人的年龄,输出Y是这个人是否喜欢吃冰淇淋。我们可以定义两个特征函数:f1(X,Y) = X (Y == 0)表示年龄和不喜欢吃冰淇淋的关系,f2(X,Y) = X (Y == 1)表示年龄和喜欢吃冰淇淋的关系。然后,我们可以通过训练数据来学习特征函数的权重λ1和λ2,得到最大熵模型:

P(Y=0|X) = exp(λ1X) / (exp(λ1X) + exp(λ2X))
P(Y=1|X) = exp(λ2X) / (exp(λ1X) + exp(λ2X))

这个模型就是一个对数线性模型,因为对数概率是特征函数和权重的线性组合。

【最大熵模型和softmax函数关系】

最大熵模型和Softmax函数都是处理多分类问题的常用工具,它们之间有一些关联,但也有一些关键的区别。

1. 最大熵模型:最大熵模型是一种概率模型,它的目标是在满足给定约束条件下,使模型的熵最大。在最大熵模型中,我们通常定义一组特征函数和对应的权重,然后使用这些特征函数和权重来计算每个类别的概率。最大熵模型可以用于多分类问题,其模型形式为P(Y|X) = exp(w·f(X,Y)) / Z(X),其中Z(X)是归一化因子。

2. Softmax函数:Softmax函数是一种将一组实数映射到概率分布的函数。给定一组实数,Softmax函数可以计算出每个实数对应的概率,使得所有概率之和为1。Softmax函数常用于多分类问题的最后一层,用于输出每个类别的概率。

最大熵模型和Softmax函数的关系在于,最大熵模型的输出形式和Softmax函数的形式非常相似。实际上,如果我们将最大熵模型的特征函数定义为输入和类别的线性函数,那么最大熵模型的输出就等价于Softmax函数的输出。

总的来说,最大熵模型和Softmax函数都是处理多分类问题的工具,它们在形式上有一些相似之处,但在定义和使用方式上有一些关键的区别。

 

【推导最大熵模型的实现步骤最大熵模型的求解步骤主要包括以下几个步骤

1. 定义特征函数:特征函数是一个二元函数,如果输入和输出满足某种关系,则特征函数的值为1,否则为0。

2. 定义模型:最大熵模型是一个对数线性模型,它的形式为:P(Y|X) = exp(∑λifi(X,Y)) / Z(X),其中,λi是特征函数fi对应的权重,Z(X)是归一化因子,保证概率之和为1。

3. 定义约束条件:最大熵模型的约束条件通常是特征函数关于输入的期望等于特征函数关于模型的期望,这保证了模型能够尽可能地符合训练数据的统计特性。

4. 定义目标函数:最大熵模型的目标函数是最大化训练数据的对数似然函数,这等价于最小化交叉熵。

5. 求解模型:通过优化算法(如梯度下降、牛顿法等)来求解最优的权重参数。

以下是最大熵模型的求解步骤的详细推导:

首先,我们定义模型的对数似然函数为:

L(λ) = ∑y,x]∈D P̂(x,y) log P(y|x;λ)

其中,P̂(x,y)是训练数据中(x,y)的经验分布,P(y|x;λ)是模型的概率分布。我们的目标是最大化对数似然函数,即:

max L(λ)

然后,我们定义模型的约束条件为:

∑y P̂(x,y)fi(x,y) = ∑y P(y|x;λ)fi(x,y)

这个约束条件保证了模型的期望等于训练数据的期望。

最后,我们使用拉格朗日乘子法和KKT条件,可以得到最优解的条件:

∑x∈D P̂(x)P(y|x;λ)fi(x,y) = ∑x∈D P̂(x,y)fi(x,y)

这个条件可以通过迭代算法(如改进的迭代尺度法、拟牛顿法等)来求解。
 
然后:

 

后续的推导见:https://blog.csdn.net/weixin_41566471/article/details/106319467

总之,就是梯度下降的迭代算法:

 

python实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import numpy as np
 
 
class MaxEntropy(object):
    def __init__(self, lr=0.01,epoch = 1000):
        self.lr = lr # 学习率
        self.N = None  # 数据个数
        self.n = None # Xy对
        self.hat_Ep = None
        # self.sampleXY = []
        self.labels = None
        self.xy_couple = {}
        self.xy_id = {}
        self.id_xy = {}
        self.epoch = epoch
 
    def _rebuild_X(self,X):
        X_result = []
        for x in X:
            print(x,self.X_columns)
            X_result.append([y_s + '_' + x_s for x_s, y_s in zip(x, self.X_columns)])
        return X_result
         
    def build_data(self,X,y,X_columns):
        self.X_columns = X_columns
        self.y = y
         
        self.X = self._rebuild_X(X)
        self.N = len(X)
        self.labels = set(y)
        for x_i,y_i in zip(self.X,y):
            for f in  x_i:
                self.xy_couple[(f,y_i)]  = self.xy_couple.get((f,y_i),0) + 1
        self.n = len(self.xy_couple.items())
 
    def fit(self,X,y,X_columns):
        self.build_data(X,y,X_columns)
        self.w = [0] * self.n
        for _ in range(self.epoch):
            for i in range(self.n):
                # self.w[i]  += 1/self.n * np.log(self.get_hat_Ep(i) / self.get_Ep(i) )  # 此处乘1/self.n,或者乘一个较小的学习率
                self.w[i]  += self.lr * np.log(self.get_hat_Ep(i) / self.get_Ep(i) )  # 此处乘1/self.n,或者乘一个较小的学习率
                # print(_,np.log(self.get_hat_Ep(i) / self.get_Ep(i) ) )
     
    def predict(self,X):
        print(X)
        X = self._rebuild_X(X)
        print(X)
         
        result = [{} for _ in range(len(X))]
        for i,x_i in enumerate (X):
            for y in self.labels:
                # print(x_i)
                result[i][y] = self.get_Pyx(x_i,y)
        return result
        
    def get_hat_Ep(self,index):
        self.hat_Ep = [0]*(self.n)
        for i,xy in enumerate(self.xy_couple):
            self.hat_Ep[i] = self.xy_couple[xy] / self.N
            self.xy_id[xy] = i
            self.id_xy[i] = xy
        return self.hat_Ep[index]
 
    def get_Zx(self,x_i):
        Zx = 0
        for y in self.labels:
            count = 0
            for f in x_i :
                if (f,y) in self.xy_couple:
                    count += self.w[self.xy_id[(f,y)]]
            Zx +=  np.exp(count)
        return  Zx<br>
    def get_Pyx(self,x_i,y):
        count = 0
        for f in x_i :
            if (f,y) in self.xy_couple:
                count += self.w[self.xy_id[(f,y)]]
        return np.exp(count) / self.get_Zx(x_i)
 
    def get_Ep(self,index):
        f,y = self.id_xy[index]
        # print(f,y)
        ans = 0
        # print(self.X)
        for x_i in self.X:
            if f not in x_i:
                continue
            pyx = self.get_Pyx(x_i,y)
            ans += pyx / self.N
            # print("ans",ans,pyx)
        return ans
         
data_set = [['youth', 'no', 'no', '1', 'refuse'],
               ['youth', 'no', 'no', '2', 'refuse'],
               ['youth', 'yes', 'no', '2', 'agree'],
               ['youth', 'yes', 'yes', '1', 'agree'],
               ['youth', 'no', 'no', '1', 'refuse'],
               ['mid', 'no', 'no', '1', 'refuse'],
               ['mid', 'no', 'no', '2', 'refuse'],
               ['mid', 'yes', 'yes', '2', 'agree'],
               ['mid', 'no', 'yes', '3', 'agree'],
               ['mid', 'no', 'yes', '3', 'agree'],
               ['elder', 'no', 'yes', '3', 'agree'],
               ['elder', 'no', 'yes', '2', 'agree'],
               ['elder', 'yes', 'no', '2', 'agree'],
               ['elder', 'yes', 'no', '3', 'agree'],
               ['elder', 'no', 'no', '1', 'refuse'],
               ]
columns = ['age', 'working', 'house', 'credit_situation','labels']
X = [i[:-1] for i in data_set]
X_columns = columns[:-1]
Y = [i[-1] for i in data_set]
 
train_X = X[:12]
test_X = X[12:]
train_Y = Y[:12]
test_Y = Y[12:]
  
 
X_columns = columns[:-1]
 
 
 
mae = MaxEntropy()
mae.fit(train_X,train_Y,X_columns)
 
mae.predict(test_X)

  


注意:
1
2
3
4
5
6
7
def get_hat_Ep(self,index):
    self.hat_Ep = [0]*(self.n)
    for i,xy in enumerate(self.xy_couple):
        self.hat_Ep[i] = self.xy_couple[xy] / self.N
        self.xy_id[xy] = i
        self.id_xy[i] = xy
    return self.hat_Ep[index]

  

get_hat_Ep函数的功能是计算特征函数关于经验分布的期望值。在最大熵模型中,这是一个重要的步骤,因为我们需要确保模型的期望等于训练数据的期望。

在给定的代码中,get_hat_Ep函数通过遍历self.xy_couple(即特征函数和类别的组合)来计算每个特征函数关于经验分布的期望值。具体来说,对于每个特征函数和类别的组合,它计算该组合在训练数据中出现的次数(即self.xy_couple[xy]),然后除以总的数据个数(即self.N),得到该组合的经验概率。然后,它将这个经验概率存储在self.hat_Ep中,以便后续使用。

总的来说,get_hat_Ep函数的功能是计算并存储每个特征函数关于经验分布的期望值,这是最大熵模型训练过程中的一个重要步骤。
 
1
2
3
4
5
6
7
8
9
10
11
12
def get_Ep(self,index):
    f,y = self.id_xy[index]
    # print(f,y)
    ans = 0
    # print(self.X)
    for x_i in self.X:
        if f not in x_i:
            continue
        pyx = self.get_Pyx(x_i,y)
        ans += pyx / self.N
        # print("ans",ans,pyx)
    return ans

  

get_Ep函数的功能是计算特征函数关于模型分布的期望值。在最大熵模型中,这是一个重要的步骤,因为我们需要确保模型的期望等于训练数据的期望。

在给定的代码中,get_Ep函数通过遍历self.X(即输入数据)来计算每个特征函数关于模型分布的期望值。具体来说,对于每个输入数据点,它首先检查该数据点是否包含特征函数对应的特征。如果包含,它则计算该数据点对应的类别的条件概率(即self.get_Pyx(x_i,y)),然后将这个条件概率累加到ans中。最后,它将ans除以总的数据个数(即self.N),得到该特征函数关于模型分布的期望值。

总的来说,get_Ep函数的功能是计算并返回每个特征函数关于模型分布的期望值,这是最大熵模型训练过程中的一个重要步骤。
 
代码输出:
['youth', 'no', 'no', '1'] ['age', 'working', 'house', 'credit_situation']
['youth', 'no', 'no', '2'] ['age', 'working', 'house', 'credit_situation']
['youth', 'yes', 'no', '2'] ['age', 'working', 'house', 'credit_situation']
['youth', 'yes', 'yes', '1'] ['age', 'working', 'house', 'credit_situation']
['youth', 'no', 'no', '1'] ['age', 'working', 'house', 'credit_situation']
['mid', 'no', 'no', '1'] ['age', 'working', 'house', 'credit_situation']
['mid', 'no', 'no', '2'] ['age', 'working', 'house', 'credit_situation']
['mid', 'yes', 'yes', '2'] ['age', 'working', 'house', 'credit_situation']
['mid', 'no', 'yes', '3'] ['age', 'working', 'house', 'credit_situation']
['mid', 'no', 'yes', '3'] ['age', 'working', 'house', 'credit_situation']
['elder', 'yes', 'no', '2'] ['age', 'working', 'house', 'credit_situation']
['elder', 'yes', 'no', '3'] ['age', 'working', 'house', 'credit_situation']
['elder', 'no', 'no', '1'] ['age', 'working', 'house', 'credit_situation']
[['age_elder', 'working_yes', 'house_no', 'credit_situation_2'], ['age_elder', 'working_yes', 'house_no', 'credit_situation_3'], ['age_elder', 'working_no', 'house_no', 'credit_situation_1']]
 
 
【最大熵模型和逻辑回归的区别】
最大熵模型和逻辑回归确实都可以被视为线性指数族函数,但它们之间还是存在一些关键的区别:

1. 特征选择:在逻辑回归中,特征通常是输入变量的函数,而在最大熵模型中,特征可以是输入和输出变量的联合函数。这使得最大熵模型可以更灵活地建模输入和输出之间的复杂关系。

2. 模型形式:虽然两者都可以被视为线性指数族函数,但它们的具体形式有所不同。逻辑回归通常用于二分类问题,其模型形式为P(Y=1|X) = 1 / (1 + exp(-w·X))。而最大熵模型可以用于多分类问题,其模型形式为P(Y|X) = exp(w·f(X,Y)) / Z(X),其中Z(X)是归一化因子。

3. 训练目标:逻辑回归的训练目标是最大化对数似然函数,而最大熵模型的训练目标是在满足特征函数期望等于经验期望的约束条件下,最大化模型的熵。

4. 应用领域:逻辑回归广泛应用于各种分类问题,如信用评分、疾病预测等。而最大熵模型主要应用于自然语言处理领域,如文本分类、词性标注、命名实体识别等。

总的来说,虽然最大熵模型和逻辑回归在形式上有一些相似之处,但它们在特征选择、模型形式、训练目标和应用领域等方面都有各自的特点。
 

 

posted @   bonelee  阅读(377)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
历史上的今天:
2018-09-30 如何迭代pandas dataframe的行
2018-09-30 风控用户识别方法
2018-09-30 数据预处理-异常值识别
2017-09-30 Xshell高级后门完整分析报告
2017-09-30 DNS SOA NS区别
2017-09-30 [转]Python UnicodeEncodeError: ‘ascii’ codec can’t encode characters in position 的解决办法
2016-09-30 ThoughtWorks微服务架构交流心得
点击右上角即可分享
微信分享提示