0#03连续型朴素贝叶斯

0x00 数据准备

以下我我生成的随机数
前两列是向量,最后一列是标签

[[ -2.40190838  -9.46793749   0.        ]
 [  0.48501661  -1.64782819   1.        ]
 [ -1.13857628  -1.52379494   1.        ]
 [  0.03676704  -3.07497058   1.        ]
 [  0.58966412   2.05349804   1.        ]
 [  1.74603537  -2.5598727    1.        ]
 [  0.9932642   -0.48002329   1.        ]
 [ -3.97025533 -10.74409641   0.        ]
 [ -1.21779287 -11.15836353   0.        ]
 [ -0.52577983 -11.34940749   0.        ]
 [  0.52298726  -0.13703454   1.        ]
 [ -1.80888642   1.30322485   1.        ]
 [ -0.45292089  -6.04316334   0.        ]
 [ -2.65890181  -1.12446239   1.        ]
 [  1.54891636   0.74589865   1.        ]
 [ -1.3087977   -7.71897353   0.        ]
 [ -0.47151448 -10.37571491   0.        ]
 [ -0.89524628 -10.96464394   0.        ]
 [ -2.86703029 -10.84498679   0.        ]
 [ -2.5972638   -9.71612662   0.        ]]

在文末尾,会解释数据产生的来源。

0x01 笔算连续型朴素贝叶斯

连续型贝叶斯中每个向量的特征值是一个连续型数字,所以无法计算其概率,我们假设特征值服从高斯分布(正态分布,Gaussian),虽然我们无法计算具体的的概率,但是可以用概率密度替代。
回忆一下,计算正太分布需要哪些东西。

[1] 公式准备

正态分布

[1.1] 计算数学期望(平均值mu)
按标签分离后
标签:0的数学期望为

[-1.67085098, -9.83834141]

标签:1的数学期望为

[ 0.03162864, -0.64453651]

[1.2] 计算标准差(sigma)
标签:0的标准差为

[1.1524603 , 1.62677798]

标签:1的标准差为

[1.37551258, 1.57723297]

[1.3] 返回一个概率密度(gaussian(x,mu,sigma))
就套用上面的公式,计算每个维度的概率密度
所以在这里就和离散的朴素贝叶斯不一样,离散型朴素贝叶斯在能计算出确切的条件概率,然后根据输入去查找,而连续型只能给一个公式,然后根据输入去带入。

[2] 计算先验算概率

统计 y(标签)=0和y(标签)=1的概率

[0.5,0.5]

[3] 预测,利用MAP估计进行评估

还是拿第一个数据来尝试
[ -2.40190838 -9.46793749 0. ]
第1个维度的值为-2.40190838
第2个维度的值为 -9.46793749
标签为 0

利用我们有的数据
先估算标签为0:
对于第1个维度的值为
x = -2.40190838
mu=-1.67085098      数学期望
sigma=1.1524603     标准差
exp(-(x-mu)**2/(2*sigma**2))/(sqrt_pi*sigma) = 0.28307756002538126
对于第2个维度的值为
x = -9.46793749
mu=-9.83834141      数学期望
sigma=1.62677798     标准差
exp(-(x-mu)**2/(2*sigma**2))/(sqrt_pi*sigma)=0.2389593899107375
P(标签为0的估算)=0.2389593899107375*0.28307756002538126*0.5
=0.03382202052054264
同理进行估计标签为1:
对于第1个维度的值为
x = -2.40190838
mu=0.03162864      数学期望
sigma=1.37551258     标准差
np.exp(-(x-mu)**2/(2*sigma**2))/(sqrt_pi*sigma)=0.060641622215243135
对于第2个维度的值为
x = -9.46793749
mu = -0.64453651
sigma = 1.57723297
np.exp(-(x-mu)**2/(2*sigma**2))/(sqrt_pi*sigma)= 4.048620642274784e-08
P(标签为1的估算)=0.060641622215243135*4.048620642274784e-08*0.5
=4.048620642274784e-08
所以最后的结果为标签为0,而且很明显差距很大。
可以说明可行。

0x02 python伪代码

其实这里的伪代码和离散型朴素贝叶斯相似。就不一一过于详细的解释。

[0] 数据导入

[0.1] 文本导入
现在数据导入不需要制作特征值字典,因为数据已经是数值化了,但是有个小问题,数据是文本,我们要转为对应的浮点型。
[0.2] x,y数据分离
但是数据分离还是要滴
x为

In [130]: x
Out[130]: 
array([[ -2.40190838,  -9.46793749],
       [  0.48501661,  -1.64782819],
       [ -1.13857628,  -1.52379494],
       [  0.03676704,  -3.07497058],
       [  0.58966412,   2.05349804],
       [  1.74603537,  -2.5598727 ],
       [  0.9932642 ,  -0.48002329],
       [ -3.97025533, -10.74409641],
       [ -1.21779287, -11.15836353],
       [ -0.52577983, -11.34940749],
       [  0.52298726,  -0.13703454],
       [ -1.80888642,   1.30322485],
       [ -0.45292089,  -6.04316334],
       [ -2.65890181,  -1.12446239],
       [  1.54891636,   0.74589865],
       [ -1.3087977 ,  -7.71897353],
       [ -0.47151448, -10.37571491],
       [ -0.89524628, -10.96464394],
       [ -2.86703029, -10.84498679],
       [ -2.5972638 ,  -9.71612662]])

y为

In [132]: y
Out[132]: 
array([0., 1., 1., 1., 1., 1., 1., 0., 0., 0., 1., 1., 0., 1., 1., 0., 0.,
       0., 0., 0.])

[0.3] 制作字典
对于y(标签)我们还是要制作字典,对其转化。
label_dic

{"第0类":0,"第1类":1}

[0.4] 统计标签(y)的出现频次
为了后面计算先验概率
cat_counter

[10,10]

[0.5] 按标签分类数据
为了计算数学期望(mu)和标准差(sigma),带入公式更加方便
labelled_x

In [136]: labelled_x
Out[136]: 
[array([[ -2.40190838,  -3.97025533,  -1.21779287,  -0.52577983,
          -0.45292089,  -1.3087977 ,  -0.47151448,  -0.89524628,
          -2.86703029,  -2.5972638 ],
        [ -9.46793749, -10.74409641, -11.15836353, -11.34940749,
          -6.04316334,  -7.71897353, -10.37571491, -10.96464394,
         -10.84498679,  -9.71612662]]),
 array([[ 0.48501661, -1.13857628,  0.03676704,  0.58966412,  1.74603537,
          0.9932642 ,  0.52298726, -1.80888642, -2.65890181,  1.54891636],
        [-1.64782819, -1.52379494, -3.07497058,  2.05349804, -2.5598727 ,
         -0.48002329, -0.13703454,  1.30322485, -1.12446239,  0.74589865]])]

第一部数据准备也就算完成了

[1] 计算公式

[1.1] 计算平均值
使用np.mean函数
mu = [np.mean(labelled_x[c][dim])for c in range(n_category)]

            标签为(y=0)             标签为(y=1)
第一个维度[-1.670850984534065, 0.03162864419482627]
第二个维度[-9.838341405638564, -0.6445365102599536]

[1.2] 计算标准差
使用np.std函数
sigma = [np.std(labelled_x[c][dim]) for c in range(n_category)]

            标签为(y=0)             标签为(y=1)
第一个维度[1.1524603038120147, 1.3755125846542104]
第二个维度[1.6267779834169904, 1.5772329705692298]

[1.3] 最后的公式为
gaussian_maximum_likelihood

np.exp(-(x - mu) ** 2 / (2 * sigma ** 2) / (sqrt_pi * sigma))
其中sqrt_pi = (2 *np. pi) ** 0.5

[2] 计算先验算概率

使用上面准备好的cat_counter来计算
p_category

[0.5,0.5]

[3] 预测,利用MAP估计进行评估

给我们一个数据,带入我们

-2.40190838  -9.46793749
我们要带入上面的公式
但是我们想像离散那样随拿随取 就像离散型朴素贝叶斯一样。
那么我们在data中储存函数吧。
        data = [
            gaussian_maximum_likelihood(self._labelled_x, n_category, dim) \
            for dim in range(len(x))
        ]

到这里伪代码写的差不多了。发现连续型与离散型有好多的重复代码,毕竟大家都是贝叶斯家族的。就会有个小想法
我们扔出来一个类吧,让连续型和离散型都继承自那个类。

0x03python的实现

那个被扔出来的基类

先考虑一下那些是重复的

  1. __init__初始化
  2. 计算先验概率的函数
  3. 训练的框架
  4. 预测的算法
    额,这部就是贝叶斯算法的框架吗?
    可能你没懂不过没事
    看代码
class NaiveBayes:
    def __init__(self):
        # 记录训练集的变量
        self._x = None
       
        # 记录训练集结果的变量
        self._y = None

        # 存储实际使用的条件概率的相关信息
        self._data = None
        
        # 决策函数
        self._func = None
        # 记录各个维度特征取值个数的数组
        self._n_possibilities = None
    
        # 记录按类别分开后的输入数据的数组
        self._labelled_x = None
     
        # 记录按类别相关信息的数组,定义有所不同
        self._label_zip = None

        # 第i类数据的个数
        self._cat_counter = None

        # 记录数据条件概率的原始极大似然法
        self._con_counter = None

        # 记录数值话类别时的转换关系
        self._label_dic = None

        # 数值化各个唯独特征时的转换关系
        self._feat_dic = None

    # 为了避免定义大量的property
    def __getitem__(self, item):
        if isinstance(item, str):
            return getattr(self, '_' + item)

    # 留下抽象方法让子类定义
    def feed_data(self, x, y, sample_weight=None):
        pass

    def feed_sample_weight(self, sample_weight=None):
        pass

    # TODO:计算先验概率的函数,lb就是估计中的平滑项
    # lb为1表示拉普拉斯平滑
    def get_prior_probability(self, lb=1):
        return [(_c_num + lb) / (len(self._y) + lb * len(self._cat_counter)) \
                for _c_num in self._cat_counter]

    # TODO:进行训练
    def fit(self, x=None, y=None, sample_weight=None, lb=1):
        if x is not None and y is not None:
            self.feed_data(x, y, sample_weight)

        self._func = self._fit(lb)

    # 重写了
    def _fit(self, lb):
        pass

    @staticmethod
    def _transfer_x(x):
        pass

    # TODO:预测单一样本
    def predict_one(self, x, get_raw_result=False):
        if isinstance(x, np.ndarray):
            x = x.tolist()
        else:
            x = x[:]
        # 相关数值化方法进行数值话
        x = self._transfer_x(x)
        m_arg = 0
        m_probability = 0
        for i in range(len(self._cat_counter)):
            p = self._func(x, i)
            logging.debug("p"+str(i))
            logging.debug(p)
            if p > m_probability:
                m_arg = i
                m_probability = p
        if not get_raw_result:
            return self._label_dic[m_arg]
        return m_probability

    # TODO:多样本预测
    def predict(self, x, get_raw_result=False):
        return np.array([self.predict_one(xx, get_raw_result) for xx in x])

    # TODO:对预测进行评估
    def evaluate(self, x, y):
        y_pred = self.predict(x)
        print("正确率:{:12.6}%".format(100 * np.sum(y_pred == y) / len(y)))

引用大佬的github
接下来就是填空题了

from ml.bys.b_NaiveBayes.Basic import *


class DataUtil:
    # [0] 数据导入
    def get_dataset(path, train_num=None, tar_idx=None, shuffle=True):
        x = []
        # [0.1] 文本导入
        with open(path, "r", encoding="utf-8") as f:
            for sample in f:
                x.append(sample.strip().split(","))
        if shuffle:
            np.random.shuffle(x)
        tar_idx = -1 if tar_idx is None else tar_idx
        # [0.2] x, y数据分离
        y = np.array([xx.pop(tar_idx) for xx in x])
        x = np.array(x)
        if train_num is None:
            return x, y
        return (x[:train_num], y[:train_num]), (x[train_num:], y[train_num:])


sqrt_pi = (2 * np.pi) ** 0.5


# [1] 计算公式
class NBFunctions:

    @staticmethod
    def gaussian_maximum_likelihood(labelled_x, n_category, dim):
        # [1.1] 计算平均值
        mu = [np.mean(labelled_x[c][dim]) for c in range(n_category)]
        # [1.2] 计算标准差
        sigma = [np.std(labelled_x[c][dim]) for c in range(n_category)]

        # [1.3] 最后的公式为
        def func(_c):
            def sub(xx):
                return NBFunctions.gaussian(xx, mu[_c], sigma[_c])

            return sub

        return [func(_c=c) for c in range(n_category)]

    @staticmethod
    # [1.3] 最后的公式为
    def gaussian(x, mu, sigma):
        return np.exp(-(x - mu) ** 2 / (2 * sigma ** 2) / (sqrt_pi * sigma))


class GaussianNB(NaiveBayes):
    def feed_data(self, x, y, sample_weight=None):
        # []注意文本转浮点
        x = np.array([list(map(lambda c: float(c), sample)) for sample in x])
        # [0.3] 制作字典
        labels = list(set(y))
        label_dic = {label: i for i, label in enumerate(labels)}
        y = np.array([label_dic[yy] for yy in y])
        # [0.4] 统计标签(y)的出现频次
        cat_counter = np.bincount(y)
        labels = [y == value for value in range(len(cat_counter))]
        # [0.5] 按标签分类数据
        labelled_x = [x[label].T for label in labels]
        self._x = x.T
        self._y = y
        self._labelled_x = labelled_x
        self._label_zip = labels
        self._cat_counter = cat_counter
        self._label_dic = {i: _l for _l, i in label_dic.items()}
        self.feed_sample_weight(sample_weight)

    def feed_sample_weight(self, sample_weight=None):
        if sample_weight is not None:
            local_weight = sample_weight * len(sample_weight)
            for i, label in enumerate(self._label_zip):
                self._labelled_x[i] *= local_weight[label]

    def _fit(self, lb):
        n_category = len(self._cat_counter)
        # [2] 计算先验算概率
        p_category = self.get_prior_probability(lb)
        data = [
            NBFunctions.gaussian_maximum_likelihood(self._labelled_x, n_category, dim) \
            for dim in range(len(self._x))
        ]
        # data为一个函数,不信可以打印一下
        self._data = data

        def func(input_x, tar_category):
            rs = 1
            for d, xx in enumerate(input_x):
                rs *= data[d][tar_category](xx)
            return rs * p_category[tar_category]

        return func

    @staticmethod
    def _transfer_x(x):
        return x

0x04 数据是怎么来的

对了,还是要解释一下数字是怎么来的,其实这里是为了把连续型朴素贝叶斯的内部原理讲清楚才是这么复杂的.
如果可以调用库就不一样了
在sklearn.datasets中有一个 make_blobs函数可以生成随机的聚类数据

import numpy as np
if __name__ == '__main__':
    # 我们随机生成一些数据吧
    from sklearn.datasets import make_blobs
    import time
    """
    聚类数据生成器
        Parameters
    ----------
    n_samples : int, optional (default=100)
        The total number of points equally divided among clusters.
        待生成的样本的总数
    n_features : int, optional (default=2)
        The number of features for each sample.
        是每个样本的特征数。
    centers : int or array of shape [n_centers, n_features], optional
        (default=3)
        The number of centers to generate, or the fixed center locations.
        表示类别数
    cluster_std : float or sequence of floats, optional (default=1.0)
        The standard deviation of the clusters.
        表示每个类别的方差
    center_box : pair of floats (min, max), optional (default=(-10.0, 10.0))
        The bounding box for each cluster center when centers are
        generated at random.

    shuffle : boolean, optional (default=True)
        Shuffle the samples.

    random_state : int, RandomState instance or None, optional (default=None)
        If int, random_state is the seed used by the random number generator;
        If RandomState instance, random_state is the random number generator;
        If None, the random number generator is the RandomState instance used
        by `np.random`.
    """
    # 100个数据,2个向量,2个标签,随机数种子2(为了能够生成一样的数据),方差1.5
    x, y = make_blobs(20, 2, centers=2, random_state=2, cluster_std=1.5)
    from sklearn.naive_bayes import GaussianNB

    model = GaussianNB()
    model.fit(x, y)
    # 这一步偷偷把数据拿出来
    print(np.concatenate([x, np.array([y]).T], axis=1))

    y_new = model.predict(x)
    print("正确率:{:12.6}%".format(100 * np.sum(y_new == y) / len(y)))

当然啦,这两种方法的正确率为100%.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352

推荐阅读更多精彩内容