机器学习笔记(二)贝叶斯分类器(下)
本文介绍正态贝叶斯分类器的是实现,并将上一篇文章的图像分类任务中的朴素贝叶斯分类器修改为正态贝叶斯分类器。
1 原理概述
朴素贝叶斯分类器通过朴素假定将问题简化,即假设特征向量个分量之间相互独立分布,由此一来,分量分布的乘积即为其联合分布。正态贝叶斯分类器认为分量的分布不是独立的,且各分量分别服从正态分布,如此以来更符合一般情况。
假设特征向量服从
n
n
n维正态分布,其中
μ
\mu
μ为均值向量,
Σ
\Sigma
Σ为协方差矩阵,则特征向量的类条件概率密度为:
p
(
x
∣
c
)
=
1
(
2
π
)
n
2
∣
Σ
∣
e
−
1
2
(
x
−
μ
)
T
Σ
−
1
(
x
−
μ
)
p(x|c) = \frac1{(2\pi)^{\frac n2}|\Sigma|}e^{-\frac12(x-\mu)^T\Sigma^{-1}(x-\mu)}
p(x∣c)=(2π)2n∣Σ∣1e−21(x−μ)TΣ−1(x−μ)
在某些时候时候协方差矩阵
Σ
\Sigma
Σ为奇异矩阵,其行列式为0。故进行奇异值分解,即可求出
∣
Σ
∣
|\Sigma|
∣Σ∣及
Σ
−
1
\Sigma^{-1}
Σ−1。求解如下:
Σ = U W U T \Sigma = UWU^T Σ=UWUT Σ − 1 = ( U W U T ) − 1 = U W − 1 U T \Sigma^{-1} = (UWU^T)^{-1} = UW^{-1}U^T Σ−1=(UWUT)−1=UW−1UT
根据贝叶斯公式
p
(
c
i
∣
x
⃗
)
=
p
(
c
i
)
p
(
x
⃗
∣
c
)
p
(
x
⃗
)
p(c_i|\vec x)=\frac{p(c_i)p(\vec x|c)}{p(\vec x)}
p(ci∣x)=p(x)p(ci)p(x∣c)
同样的,
p
(
c
i
)
可
以
认
为
相
等
p(c_i)可以认为相等
p(ci)可以认为相等,
p
(
x
⃗
)
p(\vec x)
p(x)对每个类相等,所以问题简化为求
p
(
x
⃗
∣
c
)
p(\vec x|c)
p(x∣c)最大值对应的标签。
对概率密度密度函数两边取对数:
l
n
(
p
(
x
⃗
∣
c
)
)
=
l
n
(
1
(
2
π
)
n
2
∣
Σ
∣
1
2
)
−
1
2
(
(
x
⃗
−
μ
⃗
)
T
Σ
−
1
(
x
⃗
−
μ
⃗
)
)
ln(p(\vec x|c))=ln(\frac 1{(2\pi)^{\frac n2}|\Sigma|^{\frac 12}})-\frac1 2((\vec x-\vec\mu)^T\Sigma^{-1}(\vec x-\vec\mu))
ln(p(x∣c))=ln((2π)2n∣Σ∣211)−21((x−μ)TΣ−1(x−μ))
求上式的最大值,等价于求下式的最小值
l
n
(
∣
Σ
∣
)
+
(
(
x
⃗
−
μ
⃗
)
T
Σ
−
1
(
x
⃗
−
μ
⃗
)
)
ln(|\Sigma|)+((\vec x-\vec\mu)^T\Sigma^{-1}(\vec x -\vec\mu))
ln(∣Σ∣)+((x−μ)TΣ−1(x−μ))
2 关键代码
代码大多与上次重复,这里只给出关键代码的注释。完整代码会在最后给出。
这里有一点需要注意的是,贝叶斯分类器在处理高维数据时会遇到一些问题。由于我们的图片展开后为256维向量,这里求行列式时会导致累乘积很小超出而被认为为0,如此取对数时则会导致出现负无穷。在openCV的源码中的处理特征值乘积为0的办法是将特征值与一个很小的数比较,取较大者。但这里256维实在太高,此方法并不能解决问题。我们输出特征值可以看到最大的到1e5数量级,最小的到1e-12数量级,这里通过乘方的方式使特征值的数量级相近来弥补,经测试(瞎试),0.07次方效果最好。
class normal_model:
def __init__(self,data): #参数为一个二维数组,数组的每个元素为训练图片的特征向量
self.__mat = np.mat(data)
self.__mean = np.mean(self.__mat,axis=0) #求均值向量
self.__cov = np.mat(np.cov(self.__mat.T)) #求协方差矩阵
self.__U,self.__W,self.__UT= np.linalg.svd(self.__cov) #奇异值分解
self.__W=np.array(self.__W)**0.07 #这里乘方处理一下
self.__log=np.log(np.prod(self.__W)) #特征值累乘求行列式
def calculate(self,img_vec):
#判别函数
res = self.__log+(img_vec-self.__mean)*(self.__U*(np.diag(1/self.__W))*self.__UT)*(img_vec-self.__mean).T
return res
3 改进后的输出结果
G:0.69
V:0.53
r:0.78
u:0.73
T:0.68
H:0.74
W:0.49
p:0.78
S:0.55
A:0.8
a:0.74
m:0.86
v:0.65
X:0.53
h:0.75
N:0.78
e:0.68
k:0.81
b:0.79
P:0.81
O:0.48
Q:0.56
I:0.35
B:0.79
K:0.82
j:0.79
Z:0.55
l:0.7
x:0.62
E:0.74
y:0.7
U:0.67
L:0.71
n:0.83
M:0.71
d:0.73
C:0.58
w:0.77
f:0.79
o:0.53
t:0.74
R:0.74
J:0.38
F:0.72
D:0.77
i:0.74
g:0.77
z:0.63
Y:0.74
s:0.62
q:0.8
c:0.62
correct_prob:0.6896153846153846
可以看到相比于朴素贝叶斯分类器准确度可提高2.9%左右(大约3×26×2张)
4 完整代码(大部分与前一篇文章重复)
import os
import shutil
import zipfile
if not os.path.exists('work/recognise'):
os.makedirs('work/recognise')
#os.chdir('../work')
extracting = zipfile.ZipFile('/home/aistudio/data/data32588/letters.zip')
extracting.extractall('work/')
folders = os.listdir('work/letters')
#文件夹重命名为字母
for folder in folders:
i = int(folder[-3:])
if i<=36:
os.rename('work/letters/%s'%folder,'work/letters/%s'%chr(54+i))
else:
os.rename('work/letters/%s'%folder,'work/letters/%s'%chr(60+i))
print(os.listdir('work/letters'))
#图片重命名为数字
for path in os.listdir('work/letters'):
path = 'work/letters/%s'%path
count = 0
for f in os.listdir(path):
file_path = '%s%s'%(path,'/')
if f[-3:]=='png':
os.rename('%s%s'%(file_path,f),'%s%s%s'%(file_path,str(count),'.png'))
count+=1
else:
os.remove('%s%s'%(file_path,f))
print(f)
#重命名为数字
for folder in os.listdir('work/letters'):
os.makedirs('work/recognise/%s' % folder)
for i in range(916,1016):
shutil.move('work/letters/%s/%s.png' % (folder,str(i)),'work/recognise/%s/%s.png' % (folder,str(i-916)))
#-----------------------------------------------------
from PIL import Image,ImageOps
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import os
from operator import itemgetter
def train_data():
data = {}
for folder in os.listdir('work/letters'):
data[folder]=[]
file_list = os.listdir('work/letters/%s'%folder)
for i in range(900):
im = Image.open("work/letters/%s/%s.png" % (folder,str(i))).convert('L')
im = im.resize((16, 16), Image.ANTIALIAS)
im = np.array(im).reshape(256).astype(np.float64)
#im = im/255
data[folder].append(im)
return data
def test_data():
data={}
for label in os.listdir('work/recognise'):
imgs = []
for i in range(100):
im = Image.open("work/recognise/%s/%s.png" % (label,str(i))).convert('L')
im = im.resize((16, 16), Image.ANTIALIAS)
im = np.array(im).reshape(256).astype(np.float64)
#im = im/255
imgs.append(im)
data[label]=imgs
return data
class normal_model:
def __init__(self,data): #参数为一个二维数组,数组的每个元素为训练图片的特征向量
self.__mat = np.mat(data)
self.__mean = np.mean(self.__mat,axis=0) #求均值向量
self.__cov = np.mat(np.cov(self.__mat.T))
self.__U,self.__W,self.__UT= np.linalg.svd(self.__cov)
self.__W=np.array(self.__W)**0.07
self.__log=np.log(np.prod(self.__W))
def calculate(self,img_vec):
res = self.__log+(img_vec-self.__mean)*(self.__U*(np.diag(1/self.__W))*self.__UT)*(img_vec-self.__mean).T
return res
class bayes_classifier:
def __init__(self,data):
self.__model={}
for label in data:
self.__model[label]=normal_model(data[label])
def classify(self,img):
results = {}
for label in self.__model:
results[label]=self.__model[label].calculate(img)
res_label = min(results, key=results.get)
return res_label
classifier = bayes_classifier(train_data())
test = test_data()
correct_cnt ={}
for label in test:
correct=0
for i in test[label]:
res_label = classifier.classify(i)
if res_label == label:
correct+=1
correct_cnt[label]=correct
print('%s:%s' % (label,str(correct/100)))
print('correct_prob:%s'% str(sum(correct_cnt.values())/(26*2*100)))