MultinomialNB的实现与评估
对于离散型朴素贝叶斯模型的实现,由于核心算法都是在进行“计数”工作,所以问题的关键就转换为了如何进行计数。
幸运的是,Numpy中的一个方法:bincount就是专门明来计数的,它能够非常快速地数出一个数组中各个数字出现的频率;
而且由于它是 Numpy自带的方法,其速度比 Python标准库 collections中的计数器 Counter还要快上非常多,
不幸的是,该方法有如下两个缺点:
- 只能处理非负整数型的数组
- 向量中的最大值即为返回的数组的长度,换句话说,如果用 bincount方法对一个长度为1、元素为1000的数组计数,返回的结果就是999个0加1个1
所以做数据预处理时就要充分考虑到这两点,具体代码如下:
1 #导入基本架构 2 from Basic import * 3 4 class MultinomialNB(NaiveBayes): 5 #定义预处理数据的方法 6 def feed_data(self,x,y,sample_weight=None): 7 #分情况将输入向量x进行转置 8 if isinstance(x,list): 9 features = map(list,zip(*x)) 10 else: 11 features = x.T 12 13 #利用python中内置的高级数据结构----集合,获取各个维度的特征和类别种类 14 #为了利用bincount方法来优化算法,将所有特征从0开始数值化 15 #PS:需要将数值化过程中的转换关系记录成字典,否则无法对新数据进行判断 16 features = [set(feat) for feat in features] 17 feat_dics = [{_l: i for i,_l in enumerate(feats)} for feats in features] 18 label_dic = {_l: i for i ,_l in enumerate(set(y))} 19 20 #利用转换字典更新训练集 21 x = np.array([[feat_dics[i][_l] for i,_l in enumerate(sample)] for sample in x]) 22 y = np.array([label_dic[yy] for yy in y]) 23 24 #利用numpy中的bincounter方法,获得各类别的数据的个数 25 cat_counter = np.bincounter(y) 26 #记录各维度特征的取值个数 27 n_possibilities = [len(feats) for feats in features] 28 #获得各类别数据的下标 29 labels = [y == value for value in range(len(cat_counter))] 30 #利用下标获取记录按类别分开后的输入数据的数组 31 labelled_x = [x[ci].T for ci in labels] 32 33 #更新模型的各个属性 34 self._X , self_y = x,y 35 self._labelled_x , self._label_zip = labelled_x,list(zip(labels,labelled_x)) 36 (self._cat_counter, self._feat_dics, self._n_possibilities) = ( 37 cat_counter, feat_dics, n_possibilities) 38 self.label_dic = {i: _l for _l, i in label_dic.items()} 39 40 #调用处理样本权重的函数,以更新记录条件概率的数组 41 self.feed_samle_weight(sample_weight) 42 43 #定义处理样本权重的函数 44 def feed_samle_weight(self,sample_weight=None): 45 self._con_counter = [] 46 #利用numpy的bincounter方法获取带权重的条件概率的极大似然估计 47 for dim, _p in enumerate(self._n_possibilities): 48 if sample_weight is None: 49 self._con_counter.append([np.bincount(xx[dim], minlength=_p) for xx in self._labelled_x]) 50 else: 51 self._con_counter.append([ 52 np.bincount(xx[dim], weights=sample_weight[label] / sample_weight[label].mean(), minlenght= _p) 53 for label, xx in self._label_zip])
PS:这样做会让训练过程加速很多但是同时也会使预测过程的速度下降一些(原因在于预测时要先将输入数据数值化);视具体情况不同,数据预处理可以有不同实现。
由于在数据预处理这一块做了大量工作,核心函数就变为调用与整合数据预处理时记录下来的信息的过程:
1 #定义核心训练函数 2 def _fit(self,lb): 3 n_dim = len(self._n_possibilities) 4 n_category = len(self._cat_counter) 5 p_category = self.get_prior_probability(lb) 6 #data即为存储加了平滑项后的条件概率的数组 7 data = [None] * n_dim 8 for dim, n_possibilities in enumerate(self._n_possibilities): 9 data[dim] = [[ 10 (self._con_counter[dim][c][p] + lb) / (self._cat_counter[c] + lb * n_possibilities) 11 for p in range(n_possibilities) 12 ]for c in range(n_category)] 13 self._data = [np.array(dim.info) for dim_info in data] 14 15 #利用data生成决策函数 16 def func(input_x, tar_category): 17 rs = 1 18 #遍历各个维度,利用data和条件独立性假设计算联合条件概率 19 for d, xx in enumerate(input_x): 20 rs *= data[d][tar_category][xx] 21 #利用先验概率和联合条件概率计算后验概率 22 return rs * p_category[tar_category] 23 #返回决策函数 24 return func 25 26 #定义数值化数据的函数 27 def _transfer_x(self,x): 28 #遍历每个元素,利用转换字典进行数值化 29 for j,char in enumerate(x): 30 x[j] = self._feat_dics[j][char] 31 return x
至此,第一个能用的朴素贝叶斯模型就完全搭建完毕了,可以拿气球数据集1.0和1.5来简单的评估模型。
接下来定义一个能够将文件中的数据转化为python数组的类:
class DataUtil: #定义一个方法使其能从文件中读取数据 #该方法接受五个参数: #数据集的名字、数据集的路径、训练样本数、类别所在列、是否打乱数据 def get_dataset(name,path,train_num=None,tar_idx=None,shuffle=True): x = [] #将编码设为,以便读入中文等特殊字符 #... with open(path,'r') as file: if 'balloon' in name: for sample in file: x.append(sample.strip().split(',')) #默认打乱数据 if shuffle: np.random.shuffle(x) #默认类别在最后一列 tar_idx = -1 if tar_idx is None else tar_idx 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])
接下来就是评估用的代码:
1 if __name__ == '__main__': 2 #导入标准库time以计时,导入DataUtil类以获取数据 3 import time 4 from Util import DataUtil 5 #遍历1.0、1.5两个版本的气球数据集 6 for dataset in (' balloon1.0.txt','balloon1.5.txt'): 7 #读入数据 8 _x, _y = DataUtil.get_dataset(dataset, "{}".format(dataset)) 9 #实例化模型并进行训练、同时记录整个过程花费的时间 10 learning_time = time.time() 11 nb = MultinomialNB() 12 nb.fit(_x,_y) 13 learning_time = time.time() 14 15 #评估模型的表现,同时记录评估过程花费的时间 16 estimation_time = time.time() - estimation_time 17 18 #将记录下来的耗时输出 19 print( 20 'Model building : {:12.6} s\n' 21 'Estimation : {:12.6} s\n' 22 'Total : {:12.6} s'.format( 23 learning_time, estimation_time, learning_time + estimation_time) 24 )