看算法描述比较好实现,但是实际实现起来,还是有一定难度喔
有一点需要注意,在看代码或者实现代码过程中,你必须知道矩阵的每一个纬度是什么含义,
这是写代码看代码的基础
注释比较详细,可以直接阅读。
# -*- 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)))
# -*- 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 %