机器学习:朴素贝叶斯分类器代码实现,决策函数非向量化方式

102 篇文章 0 订阅
56 篇文章 0 订阅

朴素贝叶斯离散型的算法描述:

在这里插入图片描述

代码实现:

看算法描述比较好实现,但是实际实现起来,还是有一定难度喔

有一点需要注意,在看代码或者实现代码过程中,你必须知道矩阵的每一个纬度是什么含义,
这是写代码看代码的基础

注释比较详细,可以直接阅读。

实现一个NaiveBayes的基类,以便扩展:

# -*- coding: utf-8 -*-

import numpy as np

class NaiveBayes:
    def __init__(self):
        # 记录训练集的变量
        self._x = None
        self._y = None
        # 核心数组,储存实际使用的条件概率的相关信息
        self._data = None
        # 模型核心,决策函数,能够根据输入的x,y得到对应的后验概率
        self._func  = None
        # 记录各个维度特征值取值个数的数组
        self._n_possibilities = None
        # 记录按照类别分开后的输入数据的数组
        self._labeled_x = None
        # 记录类别的信息
        self._label_x_zip = None
        # 核心数组,记录第i类数据的个数,cat为category
        self._cat_counter = None
        # 核心数组,记录数据条件概率的原始极大似然估计
        # self._con_counter[d][c][p] = p(X^d = p | y = c) con为conditional
        self._con_counter = None
        # 核心数组用于记录数值化类别时的转换关系
        self.label_dic = None
        # 核心数组用于记录数值化features时的转换关系
        self._feat_dics = None
        
    # 重载__getitem__运算符避免定义大量的property
    def __getitem__(self,item):
        if isinstance(item,str):
            return getattr(self,"_"+item)
# =============================================================================
# 模型的训练        
# =============================================================================
    # 留下抽象方法让子类定义
    def feed_data(self,x,y,sample_weight = None):
        pass
    
    # 留下抽象方法让子类定义,sample_weight代表样本权重
    # 这个地方是为了后续使用提升的方法,样本的权重体现了各个样本的重要性
    def feed_sample_weight(self,sample_weight = None):
        pass
    
    # 定义计算先验概率的函数,lb为各个估计中的平滑项lambda
    # 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]
    
    # 定义具有普适的训练
    def fit(self,x=None,y=None,sample_weight=None,lb=1):
        # 如果有传入的x,y就把x,y传入初始化模型
        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
   
# =============================================================================
# 模型的评估和预测
# =============================================================================
# 定义预测单一样本的函数
# 参数get_raw_result为控制函数是输出类别(False)还是输出后验概率(True)
        
    def predict_one(self,x,get_raw_result=False):
        # 将输入的数据数值化,如果是numpy数组,转化成python的list
        # 这时因为python在数值化这个操作上list比较快
        if isinstance(x,np.ndarray):
            x = x.tolist()
        else:
            x = x[:]
            
        # 调用相关方法数值化,该方法具体的模型不同而不同
        x  = self._transfer_x(x)
        # 类别和该类别的后验概率,存的是当前最大的
        m_arg,m_probability = 0,0
        # 遍历各个类别找到最大的后验概率的类别
        for i in range(len(self._cat_counter)):
            # 决策函数
            p = self._func(x,i)
            if p > m_probability:
                m_arg,m_probability = i, p
            
        if not get_raw_result:
            return self.label_dic[m_arg]
        
        return m_probability
    
    # 预测多个样本,就是重复调用一个样本
    def predict(self,x,get_raw_result=False):
        return np.array([self.predict_one(xx,get_raw_result) for xx in x])
                
    
    # 定义评估方法,在这里使用准确率来定义
    def evaluate(self,x,y):
        y_pred = self.predict(x)
        print("Acc: {:12.6} %".format(100*np.sum(y==y_pred)/len(y)))

实现离散型朴素贝叶斯MultiomialNB类:

# -*- coding: utf-8 -*-
# =============================================================================
# 处理离散性的朴素贝叶斯
# =============================================================================

from Basic_bayes import NaiveBayes

import numpy as np

class MultiomialNB(NaiveBayes):
    # 定义数据预处理
    def feed_data(self,x,y,sample_weight=None):
        # 分情况将输入的向量转置,原始x每一行[feature1,feature2..]
        # 转置后,每一行为一个feature的那个样本的取值
        if isinstance(x,list):
            features = map(list,zip(*x))
        else:
            features = x.T
        
        # 一般的二维数据数值化的思路的,建立二维数值化的字典,注意之间的对应关系
        # 一般情况下特征数量*样本数量--》特征数量*每个特征的取值范围,就得到了特征特征值
        # 的数值化了,将原数据转化为数值化,遍历数据时,注意是遍历 特征:特征值,指针是特征
        # 这样就可以使用字典访问了,也就是一个一个数据的访问就可以了。
        
        
        # 使用set获取各个维度的特征和类别种类,
        # 使用bincount优化算法,将所有特征从0开始数值化
        # 将数值化的过程转化成字典,这样一一对应
        
        # 获取的是每一个feature的取值
        features = [set(feat) for feat in features]
        # feat_dics[第几个feature][这个feature的取值] = 这个feature的取值对应的数值化ID
        feat_dics = [{_:i for i,_ in enumerate(feats)} for feats in features ]
        label_dic = {_:i for i,_  in enumerate(set(y))}
        
        # 利用字典转换更新数据集
        # 这里可能有点难以理解,自己举个例子就能明白
        # feat_dics每一行代表一个feature纬度
        # 每一行里装的是:这个feature的取值:数值化这个feature的取值
        # 原始x每一行[feature1,feature2..],遍历它,就是第几个feature
        x = np.array([[feat_dics[i][_] for i,_ in enumerate(sample)] for sample in x])
        y = np.array([label_dic[yy] for yy in y])
        
            
        # 利用numpy里面的bitcount方法统计各个类别的数量
        cat_counter = np.bincount(y)
        # 记录各个纬度特征的取值个数
        n_possibilities = [len(feats) for feats in features]
        
        # 这里给出了一般的:将数据集按照类别分类的方法
        # 数值化之后处理,操作对象必须为numpy
        # y == value 就可以标记出出分为为value的数据,标记的数据为True
        # x[ci].T,ci为标记数组,就可以得到标记的数据
        
        # 获取各类别数据的下表
        # 这个得到的是len(cat_counter)个array,每个array look like y,里面都是True or False
        # 表明每个类别在y中的位置
        labels = [y == value for value in range(len(cat_counter))]
        # labels返回的是类别个数的表,每个表记录了每个类别在数据中的标记
        # x[labels[0]]可以得到,标记数组标记的数据,转置了,代表现在矩阵为feature数目*数据个数
        labeled_x = [x[ci].T for ci in labels]
        
        # 更新各个模型的属性
# =============================================================================
#       self._x:[datanum,feature] = featureValue ,
#       self._y:[datanum] = class
#       self._labeled_x:[class,feature,num_in_class] = featureValue
#       self._cat_counter:[class] = num_in_class
#       self._feat_dics:[feature,featureValue] = encodefeatureValue
#       self._n_possibilities:[feature] = num_in_feature       
# =============================================================================
        self._x,self._y = x,y
        self._labeled_x,self._label_x_zip = labeled_x,list(zip(labels,labeled_x))
        (self._cat_counter,self._feat_dics,self._n_possibilities) = (cat_counter,feat_dics,n_possibilities)
        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):
        # self._con_counter:[feature,class,featureValue] = p(x =(feature,featureValue)|y = class)
        self._con_counter = []
        # 用于求条件概率的极大似然估计,x =(feature,featureValue)的数量
        # dim = feature, _p = num_in_feature
        for dim,_p in enumerate(self._n_possibilities):
            if sample_weight is None:
                # xx:[feature,num_in_class],需要统计xx里面,不同featureValue的数量
                # 被添加进来的数据为:[class,featureValue] = num_in_featureValue
                # 外部循环后就为:[feature,class,featureValue]
                self._con_counter.append([
                        np.bincount(xx[dim],minlength=_p) for xx in self._labeled_x])
            else:
                self._con_counter.append([
                        np.bincount(xx[dim],weights=sample_weight[
                                label] / sample_weight[label].mean(),minlength=_p)
                        for label,xx in self._label_x_zip])
        
    # 定义核心处理函数
    def _fit(self,lb):
        # n_dim为feature的个数
        n_dim = len(self._n_possibilities)
        # n_category 为class的数目
        n_category = len(self._cat_counter)
        # 先验概率[class] = prior_probability
        p_category = self.get_prior_probability(lb)
        
        # data储存平滑处理后的条件概率数组
        data = [None]*n_dim
        # self._n_possibilities:[feature] = num_in_feature
        for dim,n_possibilities in enumerate(self._n_possibilities):
            data[dim] = [[
                    (self._con_counter[dim][c][p] + lb) / (self._cat_counter[c] + lb*n_possibilities)
                    for p in range(n_possibilities)
                        ] for c in range(n_category)]
        # 以上得到的data:[feature,class,featureValue]
        self._data = [np.array(dim_info) for dim_info in data]
        
        # 利用self._data生成决策函数
        def func(input_x,tar_category):
            rs =1
            # 遍历各个纬度,利用data和条件独立假设计算联合条件概率
            # d,xx:feature,featureValue
            for d,xx in enumerate(input_x):
                rs *= data[d][tar_category][xx]
            
            # 利用先验概率和联合条件概率计算后验概率
            return rs*p_category[tar_category]
        
        # fit里需要返回决策函数
        return func
    
    # 定义数值化数据的函数,就是预测的时候,需要处理以下预测的数据
    def _transfer_x(self,x):
        # 遍历每一个元素,利用转化字典进行数值化
        # self._feat_dics:[feature,featureValue] = encodefeatureValue
        # 这时对单个数据的处理
        # j,char :feature,featureValue
        for j,char in enumerate(x):
            x[j] = self._feat_dics[j][char]
        return x

实现从文件中读取数据:

# -*- coding: utf-8 -*-

# 定义一个将文件中的数据转化为数组的类
import numpy as np
class DataUtil:
# =============================================================================
# 从文件中读取数据
# 5个参数:数据集的名称,数据集的路径,训练样本数,类别所在列,是否打乱数据    
# =============================================================================
    def get_dataset(name,path,train_num=None,tar_index=None,shuffle=True):
        x =[]
        # 将编码设置为utf-8
        with open(path,"r",encoding="utf-8") as file:
            # 如果是气球数据集的话,使用逗号分割数据
            if "balloon" in name:
                # 文件读取是一行一行读取的
                for sample in file:
                    # 一行数据就是一个数组,strip()去空格,split(",")以逗号分隔
                    x.append(sample.strip().split(","))
            
            # 默认打乱数据
            if shuffle:
                np.random.shuffle(x)
            
            # 默认类别在最后一列
            tar_index = -1 if tar_index is None else tar_index
            y = np.array([xx.pop(tar_index) 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:])

测试数据:

https://pan.baidu.com/s/14ecC-61qXaCjyryz-PrepQ

代码测试:

if __name__ == '__main__':
    from Util import DataUtil
    for dataset in ("balloon1.0","balloon1.5","balloon2.0"):
        # 读取数据
        _x,_y = DataUtil.get_dataset(dataset,"_Data/{}.txt".format(dataset))
        
        # 实例化模型,并进行训练
        nb = MultiomialNB()
        nb.fit(_x,_y)
        
        # 评估模型
        nb.evaluate(_x,_y)

Acc:        100.0 %
Acc:      91.6667 %
Acc:        100.0 %
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值