本文将介绍朴素贝叶斯分类器的基本原理,并将其应用到图像分类任务中。(数据集为字母大小写灰度图,大小为16×16,每种字母1016张,总共26×2×1016张图片,数据集下载链接:github仓库)
贝叶斯公式描述了在已知P(X)发生的条件下,导致其发生的原因是 C i C_i Ci的概率,称为条件 X X X下 C i C_i Ci的后验概率, P ( C i ∣ X ) P(C_i |X) P(Ci∣X) 即表示 X X X属于 C i C_i Ci类的概率。假设有 m m m个类,则分别求出在条件 X X X下, C i ( i = 1 , 2 , . . . . . . , m ) C_i (i=1,2,......,m) Ci(i=1,2,......,m) 的后验概率,最大后验概率对应的类别即为X所属的类别。
P ( C i │ X ) = P ( C i ) P ( X ∣ C i ) ( P ( X ) P(C_i│X) = \frac{P(C_i)P(X|C_i)}{(P(X)} P(Ci│X)=(P(X)P(Ci)P(X∣Ci)
在分类任务中,每个数据样本用一个 n n n维特征向量表示: X = x 1 , x 2 , … … , x n X = {x_1, x_2,……, x_n} X=x1,x2,……,xn。假设有 m m m个类: C 1 , C 2 , . . . . . . , C m C_1, C_2,......, C_m C1,C2,......,Cm,分离器依次假设 X X X属于 C i ( i = 1 , 2 , . . . . . . , m ) C_i (i=1,2,......,m) Ci(i=1,2,......,m),并求得各后验概率并找出最大后验概率对应的类。对于 P ( X ) , P ( C i ) P(X),P(C_i) P(X),P(Ci)一类直接分析数据得到的概率称为先验概率。如果 C i C_i Ci类的先验概率未知,一般设 C i C_i Ci的先验概率是相等的,即如果有 m m m个类, P ( C i ) = 1 m P(C_i) = \frac1m P(Ci)=m1,或者如果样本有三类 C 1 , C 2 , C 3 C_1, C_2, C_3 C1,C2,C3三类的训练样本数为 1 : 2 : 3 1:2:3 1:2:3,可以设置 P ( C i ) , i = 1 , 2 , 3 P(C_i),i=1,2,3 P(Ci),i=1,2,3 的值为 1 6 , 1 3 , 1 2 \frac16,\frac13,\frac12 61,31,21。 P ( X ) P(X) P(X)为归一化因子,对于所有类来说是一致的。 P ( X ∣ C i ) P(X|C_i) P(X∣Ci)称为给的 C i C_i Ci时 X X X的似然度。如果训练样本各类数目均相等,则可将问题简化为求最大似然度的问题。
这里是数据预处理过程,包括将数据分割为训练数据和测试数据,删除非图片文件(.db文件),为图片打标签等
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 sklearn.decomposition import PCA
大津法求阈值
大津法利用求最大类间方差,按照图像的灰度特性,将图片划分为前景和背景。最大类间方差对应的分割点即为阈值。
假设阈值为 T ( T = 0 , 1 , 2 , . . . . . . , 255 ) T(T=0,1,2,......,255) T(T=0,1,2,......,255),图像大小为 M × N M×N M×N(这里为16×16), N 0 N_0 N0为小于 T T T的像素个数, μ 0 μ_0 μ0为小于T的像素值的均值, N 1 N_1 N1为大于 T T T的像素个数, μ 1 μ_1 μ1为大于 T T T的像素值的均值。
ω 0 = N 0 M × N ω_0 = \frac{N_0}{M×N} ω0=M×NN0 , ω 1 = N 1 M × N ω_1 = \frac{N_1}{M×N} ω1=M×NN1 ,其中 N 0 + N 1 = M × N N_0+ N_1=M×N N0+N1=M×N
μ = μ 0 × ω 0 + μ 1 × ω 1 μ = μ_0×ω_0 + μ_1×ω_1 μ=μ0×ω0+μ1×ω1 ,
g = ω 0 × ( μ 0 − μ ) 2 + ω 1 × ( μ 1 − μ ) 2 g = ω_0×(μ_0-μ)^2+ ω_1×(μ_1-μ)^2 g=ω0×(μ0−μ)2+ω1×(μ1−μ)2
遍历0-255,求出 g g g最大时对应的 T T T即为最佳阈值。
def myOtsu(img_array):
height = 16
width = 16
size = height*width
T = 0
g_0 = 0
for i in range(size+1):
back = np.where(img_array>=i)[0]
front = np.where(img_array<i)[0]
front_cnt = len(front)
back_cnt = size-front_cnt
w_f = front_cnt/size
w_b = back_cnt/size
if len(img_array[front]) == 0:
u_f = 0
else:
u_f = img_array[front].mean()
if len(img_array[back]) == 0:
u_b = 0
else:
u_b = img_array[back].mean()
g = w_f*w_b*np.square(u_f-u_b)
if g>g_0:
g_0 = g
T = i
front = np.where(img_array<T)[0]
back = np.where(img_array>=T)[0]
img_array[front] = 255
img_array[back] = 0
return img_array,T
train_data()
和test_data()
负责加载数据,将图片展开为256维的向量,使用大津法二值化图像,前景为0,后景为1。最后返回一个字典,键为字母(即标签),值为一个二维数组,行向量为一张图片的特征向量。
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.float32)
im = myOtsu(im)[0]/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.float32)
im = myOtsu(im)[0]/255
imgs.append(im)
data[label]=imgs
return data
计算分量的概率
这里需要利用拉普拉斯平滑,以防止某个分量概率为0导致其联合分布概率为0的情况。具体做法使假设该训练图片中该分量为0、1的图片至少有一张,最后求概率时除以(训练图片数+2)即可。
def cal_prob(data):
prob = {}
for label in data:
cnt = np.ones(16*16)
imgs = data[label]
for img in imgs:
ones = np.where(img==1)[0]
cnt[ones]+=1
prob[label] = cnt/(len(imgs)+2)
return prob
分类过程中,假设在 C i C_i Ci条件下, C i C_i Ci类图片第k个分量为1的概率为 p k 1 p_{k1} pk1, p k 1 + p k 0 = 1 p_{k1}+p_{k0}=1 pk1+pk0=1。对于测试图片的特征向量 X = { x 1 , x 2 , … , x n } X = \{x_1, x_2,…, x_n\} X={x1,x2,…,xn}, P ( x k = a ) = p k a P(x_k=a)=p_{ka} P(xk=a)=pka。其中, a = 1 , 0 , k = 0 , 1 , … 255 a=1,0,k=0,1,…255 a=1,0,k=0,1,…255。通过遍历每一种图片的概率模型,可求得测试图片的特征向量 X = { x 1 , x 2 , … , x n } X = \{x_1, x_2,…, x_n\} X={x1,x2,…,xn}在给定 C i C_i Ci类下的似然度:
P ( X ∣ C i ) = ∏ k = 0 255 P ( x k = a ) P(X|C_i) = ∏_{k=0}^{255}P(x_k=a) P(X∣Ci)=∏k=0255P(xk=a)
遍历各个类的概率模型,求出该测试图片的最大似然度对应的类即为分类结果。
class bayes_classifier:
def __init__(self,data):
self.__probs = cal_prob(data)
def classfy(self,img):
results = {}
for label in self.__probs:
res = 1
for i in range(len(img)):
if img[i] == 1:
res=res*self.__probs[label][i]
else:
res=res*(1-self.__probs[label][i])
results[label] = res
res_label = max(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.classfy(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)))
测试数据中每种字母100张图片,共26×2×100张测试图片。以下为输出结果,分别为每个字母的识别准确率和整体的识别准确率:(最高为 K : 91%,最低为 I : 29%,整体为 66.03846153846153%)
H:0.65
z:0.58
h:0.7
G:0.65
c:0.56
Q:0.52
n:0.77
D:0.69
P:0.82
V:0.52
f:0.72
Y:0.73
w:0.71
y:0.68
U:0.57
q:0.78
u:0.66
m:0.78
o:0.52
J:0.47
F:0.68
Z:0.56
M:0.71
A:0.75
R:0.74
S:0.52
K:0.91
C:0.68
a:0.7
r:0.68
L:0.74
l:0.72
v:0.47
T:0.72
g:0.69
j:0.78
I:0.29
k:0.79
N:0.84
d:0.8
E:0.77
X:0.59
B:0.77
e:0.7
x:0.58
p:0.66
t:0.68
W:0.41
b:0.73
i:0.71
O:0.43
s:0.46
correct_prob:0.6603846153846153