Sheldon镇楼~~~
贝叶斯定理是英国数学家贝叶斯提出的,当时的目标是要解决所谓「逆概率」问题。在前贝叶斯时代处理概率问题的时候,总是先取一定假设(比如抛硬币时,每次出现正反面的概率相同),然后在假设下讨论一定事件的概率(比如说连续出现 10 次正面的概率)。「逆概率」则反过来考虑问题,比如说,如果连续出现 10 次正面,我们想知道一次抛硬币时出现正反面的概率。贝叶斯定理的相关论文在贝叶斯去世后才发表,此后法国大数学家拉普拉斯对这一理论进行了深入的研究,使之成为我们今天使用的形式,如下图所示。
此公式表示两个互换的条件概率之间的关系,他们通过联合概率关联起来,这样使得我们知道P(D|H)的情况下去计算P(H|D)成为了可能,而我们的贝叶斯模型便是通过贝叶斯准则去计算某个样本在不同类别条件下的条件概率并取具有最大条件概率的那个类型作为分类的预测结果。
实现简单贝叶斯分类器
下面以进行文本分类为目的使用Python实现一个朴素贝叶斯文本分类器.
为了计算条件概率,我们需要计算各个特征的在不同类别下的条件概率以及类型的边际概率,这就需要我们通过大量的训练数据进行统计获取近似值了,这也就是我们训练我们朴素贝叶斯模型的过程.
针对不同的文本,我们可以将所有出现的单词作为数据特征向量,统计每个文本中出现词条的数目(或者是否出现某个词条)作为数据向量。这样一个文本就可以处理成一个整数列表,并且长度是所有词条的数目,这个向量也许会很长,用于本文的数据集中的短信词条大概一共3000多个单词。
def get_doc_vector(words, vocabulary):
''' 根据词汇表将文档中的词条转换成文档向量
:param words: 文档中的词条列表
:type words: list of str
:param vocabulary: 总的词汇列表
:type vocabulary: list of str
:return doc_vect: 用于贝叶斯分析的文档向量
:type doc_vect: list of int
'''
doc_vect = [0]*len(vocabulary)
for word in words:
if word in vocabulary:
idx = vocabulary.index(word)
doc_vect[idx] = 1
return doc_vect
统计训练的过程:
def train(self, dataset, classes):
''' 训练朴素贝叶斯模型
:param dataset: 所有的文档数据向量
:type dataset: MxN matrix containing all doc vectors.
:param classes: 所有文档的类型
:type classes: 1xN list
:return cond_probs: 训练得到的条件概率矩阵
:type cond_probs: dict
:return cls_probs: 各种类型的概率
:type cls_probs: dict
'''
# 按照不同类型记性分类
sub_datasets = defaultdict(lambda: []) # defaultdict:当字典里的key不存在但被查找时,返回的不是keyError而是一个默认值
cls_cnt = defaultdict(lambda: 0)
for doc_vect, cls in zip(dataset, classes):
sub_datasets[cls].append(doc_vect)
cls_cnt[cls] += 1
# 计算类型概率
cls_probs = {k: v/len(classes) for k, v in cls_cnt.items()}
# 计算不同类型的条件概率
cond_probs = {}
dataset = np.array(dataset)
for cls, sub_dataset in sub_datasets.items():
sub_dataset = np.array(sub_dataset)
# Improve the classifier.
cond_prob_vect = np.log((np.sum(sub_dataset, axis=0) + 1)/(np.sum(dataset) + 2))
cond_probs[cls] = cond_prob_vect
return cond_probs, cls_probs
注意这里对于基本的条件概率直接相乘有两处改进:
- 各个特征的概率初始值为1,分母上统计的某一类型的样本总数的初始值是1,这是为了避免如果有一个特征统计的概率为0,则联合概率也为零那自然没有什么意义了, 如果训练样本足够大时,并不会对比较结果产生影响.
- 由于各个独立特征的概率都是小于1的数,累积起来必然会是个更小的书,这会遇到浮点数下溢的问题,因此在这里我们对所有的概率都取了对数处理,这样在保证不会有损失的情况下避免了下溢的问题。
获取了统计概率信息后,我们便可以通过贝叶斯准则预测我们数据的类型了,这里我并没有直接计算每种情况的概率,而是通过统计得到的向量与数据向量进行内积获取条件概率的相对值并进行相对比较做出决策的。
def classify(self, doc_vect, cond_probs, cls_probs):
''' 使用朴素贝叶斯将doc_vect进行分类.
'''
pred_probs = {}
for cls, cls_prob in cls_probs.items():
cond_prob_vect = cond_probs[cls]
pred_probs[cls] = np.sum(cond_prob_vect*doc_vect) + np.log(cls_prob)
return max(pred_probs, key=pred_probs.get)
进行短信分类
已经构建好了朴素贝叶斯模型,我们就可以使用此模型来统计数据并用来预测了。这里我使用了SMS垃圾短信语料库中的垃圾短信数据, 并随机抽取90%的数据作为训练数据,剩下10%的数据作为测试数据来测试我们的贝叶斯模型预测的准确性。
当然在建立模型前我们需要将数据处理成模型能够处理的格式:
ENCODING = 'ISO-8859-1'
TRAIN_PERCENTAGE = 0.9
def get_doc_vector(words, vocabulary):
''' 根据词汇表将文档中的词条转换成文档向量
:param words: 文档中的词条列表
:type words: list of str
:param vocabulary: 总的词汇列表
:type vocabulary: list of str
:return doc_vect: 用于贝叶斯分析的文档向量
:type doc_vect: list of int
'''
doc_vect = [0]*len(vocabulary)
for word in words:
if word in vocabulary:
idx = vocabulary.index(word)
doc_vect[idx] = 1
return doc_vect
def parse_line(line):
''' 解析数据集中的每一行返回词条向量和短信类型.
'''
cls = line.split(',')[-1].strip()
content = ','.join(line.split(',')[: -1])
word_vect = [word.lower() for word in re.split(r'\W+', content) if word]
return word_vect, cls
def parse_file(filename):
''' 解析文件中的数据
'''
vocabulary, word_vects, classes = [], [], []
with open(filename, 'r', encoding=ENCODING) as f:
for line in f:
if line:
word_vect, cls = parse_line(line)
vocabulary.extend(word_vect)
word_vects.append(word_vect)
classes.append(cls)
vocabulary = list(set(vocabulary))
return vocabulary, word_vects, classes
有了上面三个函数我们就可以直接将我们的文本转换成模型需要的数据向量,之后我们就可以划分数据集并将训练数据集给贝叶斯模型进行统计。
# 训练数据 & 测试数据
ntest = int(len(classes)*(1-TRAIN_PERCENTAGE))
test_word_vects = []
test_classes = []
for i in range(ntest):
idx = random.randint(0, len(word_vects)-1)
test_word_vects.append(word_vects.pop(idx))
test_classes.append(classes.pop(idx))
train_word_vects = word_vects
train_classes = classes
train_dataset = [get_doc_vector(words, vocabulary) for words in train_word_vects]
训练模型:
cond_probs, cls_probs = clf.train(train_dataset, train_classes)
剩下我们用测试数据来测试我们贝叶斯模型的预测准确度:
# 测试模型
error = 0
for test_word_vect, test_cls in zip(test_word_vects, test_classes):
test_data = get_doc_vector(test_word_vect, vocabulary)
pred_cls = clf.classify(test_data, cond_probs, cls_probs)
if test_cls != pred_cls:
print('Predict: {} -- Actual: {}'.format(pred_cls, test_cls))
error += 1
print('Error Rate: {}'.format(error/len(test_classes)))
参考链接:
https://www.jiqizhixin.com/articles/2017-09-19-6
https://zhuanlan.zhihu.com/p/27906640