代码可在Github上下载:代码下载
前言
由于使用极大似然估计会出现概率值为0的情况,这会影响后续的计算。比如当有一个后验概率为0的时候,那么会使我们最后计算先验概率P(y=1)或者P(y=-1)出错。
为了解决这个问题我们一般引入一个
λ
\lambda
λ (>>0,但是通常设为1)。
算法理论
然后条件概率的贝叶斯估计是:
P
λ
(
X
(
j
)
=
a
j
i
∣
Y
=
c
k
)
=
∑
i
=
1
N
I
(
x
i
(
j
)
=
a
j
i
,
y
i
=
c
k
)
+
λ
∑
i
=
1
K
I
(
y
i
=
c
k
)
+
S
j
λ
P_\lambda(X^{(j)} = a_{ji} | Y = c_k) = \frac {\sum_{i=1}^N I(x_i^{(j) = a_{ji}, y_i = c_k}) + \lambda} {\sum_{i=1}^K I(y_i = c_k) + S_j\lambda}
Pλ(X(j)=aji∣Y=ck)=∑i=1KI(yi=ck)+Sjλ∑i=1NI(xi(j)=aji,yi=ck)+λ
先验概率的贝叶斯估计是:
P
λ
(
Y
=
c
k
)
=
∑
i
=
1
N
I
(
y
i
=
c
k
)
+
λ
N
+
K
λ
P_\lambda(Y=c_k) = \frac {\sum_{i=1}^{N}I(y_i=c_k)+\lambda} {N+K\lambda}
Pλ(Y=ck)=N+Kλ∑i=1NI(yi=ck)+λ
算法实现
我们这次主要修改了上次代码的2个地方,一个是创建词条的函数,一个是训练函数,并增加了一个构造函数用来初始化我们的 λ \lambda λ
# coding:utf-8
# Designed by Chen
# 2017-8-19
import numpy as np
class bayes:
def __init__(self, lamb = 1):
if (lamb < 0): #因为lamb大于等于0
self._lamb = 1
else:
self._lamb = lamb
def create_vocab_list(self, dataSet): #创建词汇表
vocab_set = set([])
m, n = np.shape(dataSet) #获得数据集的行和列
self._S = [] #每个特征在自己的维度里出现的次数
for i in range(n): #按维度来创建词条,第一维,第二维这样
column = set([row[i] for row in dataSet])
vocab_set = vocab_set | set(column)
self._S.extend(list(np.ones(len(column)) * len(column)))
return list(vocab_set)
def set_of_word2vec(self, vocab_list, input_set): #词条向量 return feature vector, like a feature vector is [1, 'S'],if vocab_list is [1, 2, 3, 'S', 'M', 'L'],return vocab vector [1, 0, 0, 1, 0, 0]
vocab_vec = [0] * len(vocab_list) #vocablist大小的零向量 zero vector
for word in input_set: #遍历输入样本的每个特征 iterating every feature
if word in vocab_list:
vocab_vec[vocab_list.index(word)] = 1 #如果发现有匹配的值就设置为1
return vocab_vec
def train(self, dataSet, labels): #训练样本
self._vocab_list = self.create_vocab_list(dataSet) # 创建特征词汇表 create vocab list
train_matrix = [] # 多条词条向量的矩阵(一个词条向量代表着一个样本在词条中出现的次数) matrix consists of vocab vector
for line in dataSet: # 将每个训练样本转换为词条向量 feature vector to vocab vector
train_matrix.append(self.set_of_word2vec(self.vocab_list, line))
n = len(self.vocab_list) # 词条的特征数 feature num
print(n)
negative_feature_num = np.zeros(n) # 在类别为-1时,出现特征的次数向量(n1 means negative 1),the vector of counting num of every feature when label equal -1
positve_feature_num = np.zeros(n) # 在类别为1时,出现特征的次数向量()
negative_num = 0 # 标签中出现-1的次数 counting the number of negative label
positive_num = 0
for i in range(len(train_matrix)):
if labels[i] == 1:
positive_num += 1
positve_feature_num += train_matrix[i]
else:
negative_feature_num += train_matrix[i] # 与词条向量相加
negative_num += 1
self._positive_vec = (positve_feature_num + self.lamb) / (positive_num + np.array(self.S) * self.lamb) #p1的各个随机向量(特征)的概率分布
self._negative_vec = (negative_feature_num + self.lamb) / (negative_num + np.array(self.S) * self.lamb)
self._p_positive = (positive_num + self.lamb) / float(len(labels) + len(set(labels)) * self.lamb) #p(y=1)的概率
# return self.p1Vect, self.pN1Vect, self.pClass1
def predict(self, input_data): #预测函数
input_vec = self.set_of_word2vec(self.vocab_list, input_data) # 测试样本的词条向量
# np.multiply(self.p1Vect ,inputVec)
p_positive = self.p_positive # 按照公式需要乘以p(y=1)的值,我们就以此为初始值
p_negative = (1 - self.p_positive)
for num in np.multiply(self.positive_vec, input_vec): # 概率分布和词条向量进行相乘,得出p(x=xi|y=1)的概率,然后相乘
if (num > 0):
p_positive *= num
for num in np.multiply(self.negative_vec, input_vec):
if (num > 0):
p_negative *= num
print(p_positive, p_negative)
if (p_positive > p_negative): # 相比,谁大就倾向谁 up to max probability
return 1
else:
return -1
@property
def lamb(self):
return self._lamb
@property
def vocab_list(self):
return self._vocab_list
@property
def positive_vec(self):
return self._positive_vec
@property
def negative_vec(self):
return self._negative_vec
@property
def p_positive(self):
return self._p_positive
@property
def S(self):
return self._S
if __name__ == "__main__":
dataSet = [[1, "S"], [1, "M"], [1, "M"], [1, "S"], [1, "S"],
[2, "S"], [2, "M"], [2, "M"], [2, "L"], [2, "L"],
[3, "L"], [3, "M"], [3, "M"], [3, "L"], [3, "L"]]
labels = [-1, -1, 1, 1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, -1]
bayes = bayes()
bayes.train(dataSet, labels)
print("prediction is:", bayes.predict([2, "S"]))
Line6~10 主要是将
λ
\lambda
λ进行初始化,对于非法的
λ
\lambda
λ值,我们也要进行一些处理,就是把它初始化成我们常用的1。当
λ
\lambda
λ等于0时是极大似然估计,取1时则为拉普拉斯平滑。
Line12~20 这里把之前创建词条的方式给改了,之前是按行读取数据,然后用set()把所有特征都写到词条里。
这次做法是按列来进行特征选择,读取出列的数据后,用set()提取特征,并写入词条。这次的数据集是2维的,第一维的特征有{1, 2, 3},第二维的特征有{‘S’, ‘M’, ‘L’}。然后我加了一个S列表,对应着条件概率的
S
j
S_j
Sj ,不过我是以向量的形式存放,到时好用向量来相乘获得概率分布的向量。
Line22~27 将特征向量转为词条向量,比如特征向量为[1, ‘S’],那么词条是[1, 2, 3, ‘S’, ‘M’, ‘L’],那么词条向量为[1, 0, 0, 1, 0, 0]。
Line47~49 对比极大似然估计,主要是多了
λ
\lambda
λ
Line52~67 最后我们求出p(y=1)的概率之后,就可以,这里为什么不求出p(y=-1)的概率,是因为就2个类别,p(y=-1) = 1-p(y=1)。好了,我们有了概率分布和类别的概率之后就可以用这个来进行预测了。我们要判别的样本是[2, “S”]。我们有了概率分布,再跟词条向量点乘,就得出了后面p(x=xi|y=1)的概率了。按照公式,我们需要计算2个类别的概率,然后判断谁大就属于谁。