机器学习十大经典算法:另辟蹊径EM算法+高斯混合模型图像像素分割实战——Nemo鱼图像分割(python代码+详细注释)

前言


  今天程序员节,据说发博客会有1024勋章,所以就来整理一下笔者模式识别课第一次大作业的第二道题——用EM算法来做Nemo鱼图像像素分割。网上看了很多关于EM算法的分析,大多都涉及很复杂的数学推导,包括要用凸函数的不等关系,要证单调性收敛性等,看了很久也没有理太清楚。在这里分享一种形象化理解EM算法的方法,来源于笔者的模式识别老师。她将EM算法看成是反复进行贝叶斯决策和最大似然估计的过程,当参数变化小于某个阈值时,算法收敛到局部最优值。并附笔者所写代码,供大家参考。贝叶斯理论与实战和最大似然估计在以下两篇博客中已经详细介绍。由于本文严格继承于贝叶斯实战,故往下阅读前,最好先读它。

  贝叶斯理论:机器学习十大经典算法:深入浅出聊贝叶斯决策

  贝叶斯实战:朴素贝叶斯图像分割实战——Nemo鱼图像分割

  代码演示PPT及数据:下载


数据与任务


  • 数据

  图像fish.bmp与掩膜mask.mat,掩膜点乘图像,即可获得待分割区域ROI。小鱼区域主要有两种类型的区域(白色,橙色),以下就是要用EM算法+朴素贝叶斯把这两类区域分割出来——用不同的颜色表示不同类的区域。

在这里插入图片描述
  训练数据sample.mat,它是一个二维的matlab数组,第一列为灰度值,第2-4列为RGB值。由于EM算法属于无监督学习,所以较贝叶斯实战中的训练数据而言,抹去了标签。训练数据中依旧隐含了两类区域的灰度值和RGB值,及每类中它们的分布规律。但哪个值属于哪类区域(隐变量),以及每类区域的灰度值和RGB值的分布是我们要用EM算法去估计的。

在这里插入图片描述

  • 任务

  任务1:用EM算法估计出每个训练数据的软标签,并结合软标签和训练数据,估计出两类区域灰度值的概率密度函数,最后用最小错误贝叶斯对fish.bmpROI灰度图像逐像素进行分割。

  任务2:用EM算法估计出每个训练数据的软标签,并结合软标签和训练数据,估计出两类区域RGB值的概率密度函数,最后用最小错误贝叶斯对fish.bmpROI彩色图像逐像素进行分割。

copyright ©意疏:https://blog.csdn.net/sinat_35907936/article/details/109266603


理论与实现


  • EM算法

  E-M即期望最大化,它是一种通过迭代来估计隐变量的利器,常用在无类别标签的高斯混合模型(GMM)中,GMM如下图所示,图源。这里并不打算去强推EM算法公式,而旨在用另一种方法理解它——EM=贝叶斯公式+最大似然估计,以达到在GMM模型中使用它的目的。

  所谓高斯混合模型,笔者觉得就是先假定每个类别样本的产生都服从一个高斯分布,然后估计每类对应高斯分布的参数,最后,如果每个类别服从的高斯分布叠加(混合)起来的函数能很好拟合样本数据,说明就找到了一组好的参数。

  从贝叶斯实战一文中可知,估计参数可以直接用最大似然估计。但是在训练数据没有标签时,对于GMM模型做分类时,除了要估计均值方差,还要类别估计标签(隐变量),这样使得很难找到似然函数极值点的解析解,此时就需要用EM算法,用迭代的方式解决。

在这里插入图片描述

  

  • 换个角度看EM算法

  E过程: 估计隐变量,贝叶斯后验概率

  对于分类(聚类)而言,隐变量就是指每个样本可能属于的类别标签,但此处的标签不是决策时非0即1的硬标签,而是衡量各类别对产生该样本贡献率的软标签。这由我们的目的决定,EM中不是为了决策,而是为了迭代下去,以产生更好的软标签

  由高斯函数的特点,我们可以知道三个函数在每个点上都是互相叠加的。换句话说,所有取值的产生都与三个分布有关系——三个分布都有产生该取值的可能,或者说都对产生该取值有贡献,只是贡献有大小之分。

  通过贝叶斯理论,我们可以知道。在已知结果(采集到的样本是各类别的混合),并且知道结果有几个来源,要得到各来源对结果的贡献率,要用贝叶斯逆概公式。只有两个来源,即二分类时如下式:
在这里插入图片描述

  由贝叶斯逆概公式,计算出每个类别后验概率(贡献率)的过程就是E过程贡献率或者后验概率即软标签(soft_guess)。要用上述贝叶斯公式,需要知道先验概率P,类条件概率密度函数的PDF参数m和S。第一次迭代时,它们由我们给定初值,初值给定不是任意的,需要我们观察数据来给出,以保证EM算法尽快收敛。

  M过程: 更新先验概率和类条件概率密度函数参数,最大似然估计

  迭代的目的是为了得到尽可能好分类结果——软标签,而要得到新的软标签,在训练数据给定的情况下,必须要更新先验概率,类条件概率密度函数的均值和方差。由于此时已经有标签,所以可以直接得到先验概率,并可以使用极大似然估计估计出类条件概率密度函数的均值和方差

  由以上分析,每个样本中,属于每个类别的个数为1* 贡献率,或者1* 软标签。那么,每个类别的样本总个数即为其软标签的和,如式(2)。其中N为训练样本个数。

在这里插入图片描述
  先验概率:
在这里插入图片描述

  贝叶斯实战中,可以知道最大似然估计均值方差或者均值协方差的解析解,有标签时非此即彼,soft_guess为0或者1,没有标签时。可能为此也可能为彼,soft_guess为某类别在该样本上的后验概率。这里只给出多维时均值协方差的表达式,一维时类似。

  均值: 累加所有样本值中属于某个类别的成分,再除以该类别的样本总数。
在这里插入图片描述
在这里插入图片描述

  协方差: 累加所有样本值中属于某个类别的协方差成分,再除以该类别的样本数减一,减一的目的是无偏估计。
在这里插入图片描述
在这里插入图片描述

  E过程估计软标签,M过程估计先验概率和类条件概率密度函数。EM完成一次,即为一次迭代。如最大似然估计出的参数变化小于给定阈值,即可说EM算法收敛。上述几个式子,与EM算法推出来的是一致的,并且实践证明,它可以收敛。

copyright ©意疏:https://blog.csdn.net/sinat_35907936/article/details/109266603

  • EM实现流程

  左边部分为训练过程,即E与M的迭代过程。算法收敛后,用得到的类概率密度函数和先验概率,对Nemo图像ROI区域逐像素进行最小错误贝叶斯决策,为不同的决策分配不同的颜色即可得到分割结果,如图所示。
在这里插入图片描述

copyright ©意疏:https://blog.csdn.net/sinat_35907936/article/details/109266603

  • 算法实现

  数据的加载与解析,并假定一个先验概率——两类各占一半。

import os
from scipy import io
from scipy.stats import norm
import numpy as np
import PIL.Image as Image
import matplotlib.pyplot as plt
from scipy.stats import multivariate_normal

plot_dir = 'EM_out'
if os.path.exists(plot_dir) == 0:
    os.mkdir(plot_dir)
# 数据加载与解析
mask = io.loadmat('mask.mat')['Mask']                 # 数据为一个字典,根据key提取数据
sample = io.loadmat('sample.mat')['array_sample']
src_image = Image.open('fish.bmp')
RGB_img = np.array(src_image)
Gray_img = np.array(src_image.convert('L'))

# 通过mask,获取ROI区域
Gray_ROI = (Gray_img * mask)/256
RGB_mask = np.array([mask, mask, mask]).transpose(1, 2, 0)
RGB_ROI = (RGB_img * RGB_mask)/255

# 假设两类数据初始占比相同,即先验概率相同
P_pre1 = 0.5
P_pre2 = 0.5

# 假设每个数据来自两类的初始概率相同,即软标签相同
soft_guess1 = 0.5
soft_guess2 = 0.5

# 选择1维或是多维
gray_status = True
gray_status = False

  任务1:对灰度图像进行像素分割。经过试验,发现EM算法很容易过拟合,即将数据尽可能的分开,这样使得有些连续的区域分割出来不连续。所以笔者采用每迭代一次就进行一次Nemo与分割的测试,最后选择最好的一组分割结果。分割过程就是最小错误贝叶斯决策的过程。
在这里插入图片描述
  根据上图中两种颜色,观察出一个初值来。笔者均值假定的0.5,0.8,方差假定的0.1,0.3。

# 一维时的EM
# ----------------------------------------------------------------------------------------------------#
if gray_status:

    # 观察图像,肉眼估计初始值
    gray1_m = 0.5
    gray1_s = 0.1
    gray2_m = 0.8
    gray2_s = 0.3

    # 绘制假定的PDF
    x = np.arange(0, 1, 1/1000)
    gray1_pdf = norm.pdf(x, gray1_m, gray1_s)
    gray2_pdf = norm.pdf(x, gray2_m, gray2_s)
    plt.figure(0)
    ax = plt.subplot(1, 1, 1)
    ax.plot(x, gray1_pdf, 'r', x, gray2_pdf, 'b')
    ax.set_title('supposed PDF')
    plt.figure(1)
    ax1 = plt.subplot(1, 1, 1)
    ax1.imshow(Gray_ROI, cmap='gray')
    ax1.set_title('gray ROI')
    plt.show()

    gray = np.zeros((len(sample), 5))
    gray_s_old = gray1_s + gray2_s

    # 迭代更新参数
    for epoch in range(30):

        for i in range(len(sample)):

            # 贝叶斯计算每个数据的后验,即得到软标签(E过程)
            soft_guess1 = (P_pre1*norm.pdf(sample[i][0], gray1_m, gray1_s))/(P_pre1*norm.pdf(sample[i][0], gray1_m, gray1_s) +
                                                                             P_pre2*norm.pdf(sample[i][0], gray2_m, gray2_s))
            soft_guess2 = 1 - soft_guess1
            gray[i][0] = sample[i][0]
            gray[i][1] = soft_guess1*1                         # 当前一个数据中类别1占的个数,1*后验,显然是小数
            gray[i][2] = soft_guess2*1
            gray[i][3] = soft_guess1*sample[i][0]              # 对当前数据中属于类别1的部分,当前数据*后验
            gray[i][4] = soft_guess2*sample[i][0]

            # 根据软标签,再借助最大似然估计出类条件概率PDF参数——均值,标准差(M过程)

        gray1_num = sum(gray)[1]                                # 对每一个数据中类别1占的个数求和,就得到数据中类别1的总数
        gray2_num = sum(gray)[2]
        gray1_m = sum(gray)[3]/gray1_num                        # 对每一个数据中属于类别1的那部分求和,就得到类别1的x的和,用其除以类别1的个数就得到其均值
        gray2_m = sum(gray)[4]/gray2_num

        sum_s1 = 0.0
        sum_s2 = 0.0

        for i in range(len(gray)):
            sum_s1 = sum_s1 + gray[i][1]*(gray[i][0] - gray1_m)*(gray[i][0] - gray1_m)     # 每个数据的波动中,属于类别1的部分
            sum_s2 = sum_s2 + gray[i][2]*(gray[i][0] - gray2_m)*(gray[i][0] - gray2_m)
        gray1_s = pow(sum_s1/gray1_num, 0.5)                                               # 标准差
        gray2_s = pow(sum_s2/gray2_num, 0.5)

        # print(gray1_m, gray2_m, gray1_s, gray2_s)
        P_pre1 = gray1_num/(gray1_num + gray2_num)                                         # 更新先验概率
        P_pre2 = 1 - P_pre1

        gray1_pdf = norm.pdf(x, gray1_m, gray1_s)
        gray2_pdf = norm.pdf(x, gray2_m, gray2_s)
        gray_s_d = abs(gray_s_old - gray2_s - gray1_s)
        gray_s_old = gray2_s + gray1_s
        # if gray_s_d < 0.0001:                                                               # 迭代停止条件,如果两次方差变化较小则停止迭代
        #     break

        # 绘制更新参数后的pdf
        plt.figure(2)
        ax2 = plt.subplot(1, 1, 1)
        ax2.plot(x, gray1_pdf, 'r', x, gray2_pdf, 'b')
        ax2.set_title('epoch' + str(epoch + 1) + ' PDF')
        plt.savefig(plot_dir + '//' + 'PDF_' + str(epoch + 1) + '.jpg', dpi=100)
        plt.close()
        # plt.show()

        if epoch % 1 == 0:                                # 迭代2次进行一次分割测试

            gray_out = np.zeros_like(Gray_img)
            for i in range(len(Gray_ROI)):
                for j in range(len(Gray_ROI[0])):
                    if Gray_ROI[i][j] == 0:
                        continue
                    # 贝叶斯公式分子比较,等价于最大后验
                    elif P_pre1 * norm.pdf(Gray_ROI[i][j], gray1_m, gray1_s) > P_pre2 * norm.pdf(Gray_ROI[i][j],
                                                                                                 gray2_m, gray2_s):
                        gray_out[i][j] = 100
                    else:
                        gray_out[i][j] = 255
            # 显示分割结果
            plt.figure(3)
            ax3 = plt.subplot(1, 1, 1)
            ax3.imshow(gray_out, cmap='gray')
            ax3.set_title('epoch' + str(epoch + 1) + 'gray segment')
            plt.savefig(plot_dir + '//' + 'Gray_segment_' + str(epoch + 1) + '.jpg', dpi=100)
            plt.close()
            # plt.show()

  迭代过程概率分布变化情况,从图中可以看出,随着迭代进行,概率分布的交叉部分越来越少,这样会导致过拟合。
在这里插入图片描述
  分割结果动图展示:
在这里插入图片描述

copyright ©意疏:https://blog.csdn.net/sinat_35907936/article/details/109266603

  任务2:对彩色图像进行像素分割。协方差矩阵为3X3的实对称矩阵,对角线为方差。分割过程是最小错误贝叶斯决策的过程。


# 三维时的EM
# -------------------------------------------------------------------------------------------------------#
else:
    # 观察图像,肉眼估计初始值
    RGB1_m = np.array([0.5, 0.5, 0.5])
    RGB2_m = np.array([0.8, 0.8, 0.8])
    RGB1_cov = np.array([[0.1, 0.05, 0.04],
                        [0.05, 0.1, 0.02],
                        [0.04, 0.02, 0.1]])
    RGB2_cov = np.array([[0.1, 0.05, 0.04],
                        [0.05, 0.1, 0.02],
                        [0.04, 0.02, 0.1]])

    RGB = np.zeros((len(sample), 11))

    # 显示彩色ROI
    plt.figure(3)
    cx = plt.subplot(1, 1, 1)
    cx.set_title('RGB ROI')
    cx.imshow(RGB_ROI)
    plt.show()
    # 迭代更新参数
    for epoch in range(20):
        for i in range(len(sample)):

            # 贝叶斯计算每个数据的后验,即得到软标签
            soft_guess1 = P_pre1*multivariate_normal.pdf(sample[i][1:4], RGB1_m, RGB1_cov)/(P_pre1*multivariate_normal.pdf(sample[i][1:4], RGB1_m, RGB1_cov) + P_pre2*multivariate_normal.pdf(sample[i][1:4], RGB2_m, RGB2_cov))
            soft_guess2 = 1 - soft_guess1
            RGB[i][0:3] = sample[i][1:4]
            RGB[i][3] = soft_guess1*1
            RGB[i][4] = soft_guess2*1
            RGB[i][5:8] = soft_guess1*sample[i][1:4]
            RGB[i][8:11] = soft_guess2*sample[i][1:4]
        # print(RGB[0])

        # 根据软标签,再借助最大似然估计出类条件概率PDF参数——均值,标准差
        RGB1_num = sum(RGB)[3]
        RGB2_num = sum(RGB)[4]
        RGB1_m = sum(RGB)[5:8]/RGB1_num
        RGB2_m = sum(RGB)[8:11]/RGB2_num

        # print(RGB1_num+RGB2_num, RGB1_m, RGB2_m)
        cov_sum1 = np.zeros((3, 3))
        cov_sum2 = np.zeros((3, 3))

        for i in range(len(RGB)):
            # print(np.dot((RGB[i][0:3]-RGB1_m).reshape(3, 1), (RGB[i][0:3]-RGB1_m).reshape(1, 3)))
            cov_sum1 = cov_sum1 + RGB[i][3]*np.dot((RGB[i][0:3]-RGB1_m).reshape(3, 1), (RGB[i][0:3]-RGB1_m).reshape(1, 3))
            cov_sum2 = cov_sum2 + RGB[i][4]*np.dot((RGB[i][0:3]-RGB2_m).reshape(3, 1), (RGB[i][0:3]-RGB2_m).reshape(1, 3))
        RGB1_cov = cov_sum1/(RGB1_num-1)                                                    # 无偏估计除以N-1
        RGB2_cov = cov_sum2/(RGB2_num-1)

        P_pre1 = RGB1_num/(RGB1_num + RGB2_num)
        P_pre2 = 1 - P_pre1

        print(RGB1_cov, P_pre1)

        # 用贝叶斯对彩色图像进行分割

        RGB_out = np.zeros_like(RGB_ROI)

        for i in range(len(RGB_ROI)):
            for j in range(len(RGB_ROI[0])):
                if np.sum(RGB_ROI[i][j]) == 0:
                    continue
                # 贝叶斯公式分子比较
                elif P_pre1 * multivariate_normal.pdf(RGB_ROI[i][j], RGB1_m, RGB1_cov) > P_pre2 * multivariate_normal.pdf(
                        RGB_ROI[i][j], RGB2_m, RGB2_cov):
                    RGB_out[i][j] = [255, 0, 0]
                else:
                    RGB_out[i][j] = [0, 255, 0]
        # print(RGB_ROI.shape)

        # 显示彩色分割结果
        plt.figure(4)
        ax3 = plt.subplot(1, 1, 1)
        ax3.imshow(RGB_out)
        ax3.set_title('epoch' + str(epoch + 1) + ' RGB segment')
        plt.savefig(plot_dir + '//' + 'RGB_segment_' + str(epoch + 1) + '.jpg', dpi=100)
        plt.close()

  分割结果动图展示:

在这里插入图片描述

copyright ©意疏:https://blog.csdn.net/sinat_35907936/article/details/109266603


参考


  西瓜书162-163.

猜你喜欢

转载自blog.csdn.net/sinat_35907936/article/details/109266603