朴素贝叶斯分类器简介及C++实现(性别分类)
貝葉斯分類器是一種基于貝葉斯定理的簡單概率分類器。
在機(jī)器學(xué)習(xí)中,樸素貝葉斯分類器是一系列以假設(shè)特征之間強(qiáng)(樸素)獨(dú)立下運(yùn)用貝葉斯定理為基礎(chǔ)的簡單概率分類器。樸素貝葉斯是文本分類的一種熱門(基準(zhǔn))方法,文本分類是以詞頻為特征判斷文件所屬類別或其它(如垃圾郵件、合法性、體育或政治等等)的問題。通過適當(dāng)?shù)念A(yù)處理,它可以與這個領(lǐng)域更先進(jìn)的方法(包括支持向量機(jī))相競爭。
樸素貝葉斯分類器是高度可擴(kuò)展的,因此需要數(shù)量與學(xué)習(xí)問題中的變量(特征/預(yù)測器)成線性關(guān)系的參數(shù)。
在統(tǒng)計(jì)學(xué)和計(jì)算機(jī)科學(xué)文獻(xiàn)中,樸素貝葉斯模型有各種名稱,包括簡單貝葉斯和獨(dú)立貝葉斯。所有這些名稱都參考了貝葉斯定理在該分類器的決策規(guī)則中的使用,但樸素貝葉斯不一定用到貝葉斯方法。
樸素貝葉斯是一種構(gòu)建分類器的簡單方法。該分類器模型會給問題實(shí)例分配用特征值表示的類標(biāo)簽,類標(biāo)簽取自有限集合。它不是訓(xùn)練這種分類器的單一算法,而是一系列基于相同原理的算法:所有樸素貝葉斯分類器都假定樣本每個特征與其它特征都不相關(guān)。舉個例子,如果一種水果其具有紅,圓,直徑大概3英寸等特征,該水果可以被判定為是蘋果。盡管這些特征相互依賴或者有些特征由其他特征決定,然而樸素貝葉斯分類器認(rèn)為這些屬性在判定該水果是否為蘋果的概率分布上獨(dú)立的。
對于某些類型的概率模型,在監(jiān)督式學(xué)習(xí)的樣本集中能獲取得非常好的分類效果。在許多實(shí)際應(yīng)用中,樸素貝葉斯模型參數(shù)估計(jì)使用最大似然估計(jì)方法;換而言之,在不用到貝葉斯概率或者任何貝葉斯模型的情況下,樸素貝葉斯模型也能奏效。
樸素貝葉斯分類器的一個優(yōu)勢在于只需要根據(jù)少量的訓(xùn)練數(shù)據(jù)估計(jì)出必要的參數(shù)(變量的均值和方差)。由于變量獨(dú)立假設(shè),只需要估計(jì)各個變量的方法,而不需要確定整個協(xié)方差矩陣。
樸素貝葉斯概率模型:理論上,概率模型分類器是一個條件概率模型:p(C|F1,…,Fn)。獨(dú)立的類別變量C有若干類別,條件依賴于若干特征變量F1,F2,…,Fn。但問題在于如果特征數(shù)量n較大或者每個特征能取大量值時,基于概率模型列出概率表變得不現(xiàn)實(shí)。所以我們修改這個模型使之變得可行。貝葉斯定理有以下式子:
用樸素的語言可以表達(dá)為:
實(shí)際中,我們只關(guān)心分式中的分子部分,因?yàn)榉帜覆灰蕾囉贑而且特征Fi的值是給定的,于是分母可以認(rèn)為是一個常數(shù)。這樣分子就等價(jià)于聯(lián)合分布(在概率論中,對兩個隨機(jī)變量X和Y,其聯(lián)合分布是同時對于X和Y的概率分布。對離散隨機(jī)變量而言,聯(lián)合分布概率質(zhì)量函數(shù)為Pr(X=x& Y=y),即P(X=x and Y=y)=P(Y=y|X=x)P(X=x)=P(X=x|Y=y)P(Y=y))模型:p(C,F1,…,Fn)。重復(fù)使用鏈?zhǔn)椒▌t(chain rule,是求復(fù)合函數(shù)導(dǎo)數(shù)的一個法則),可將該式寫成條件概率(conditional probability,就是事件A在另外一個事件B已經(jīng)發(fā)生條件下的發(fā)生概率,條件概率表示為P(A|B),讀作”在B條件下A的概率”)的形式,如下所示:
其中Z(證據(jù)因子)是一個只依賴于F1,…,Fn等的縮放因子,當(dāng)特征變量的值已知時是一個常數(shù)。由于分解成所謂的類先驗(yàn)概率p(C)和獨(dú)立概率分布p(Fi|C),上述概率模型的可掌控性得到很大的提高。如果這是一個k分類問題,且每個p(Fi|C=c)可以表達(dá)為r個參數(shù),于是相應(yīng)的樸素貝葉斯模型有(k-1)+nrk個參數(shù)。實(shí)際應(yīng)用中,通常取k=2(二分類問題),r=1(伯努利分布作為特征),因此模型的參數(shù)個數(shù)為2n+1,其中n是二值分類特征的個數(shù)。
從概率模型中構(gòu)造分類器:以上導(dǎo)出了獨(dú)立分布特征模型,也就是樸素貝葉斯概率模型。樸素貝葉斯分類器包括了這種模型和相應(yīng)的決策規(guī)則。一個普通的規(guī)則就是選出最優(yōu)可能的那個:這就是最大后驗(yàn)概率(MAP)決策準(zhǔn)則。相應(yīng)的分類器便是如下定義的classify公式:
參數(shù)估計(jì):所有的模型參數(shù)都可以通過訓(xùn)練集的相關(guān)頻率來估計(jì)。常用方法是概率的最大似然估計(jì)。類的先驗(yàn)概率可以通過假設(shè)各類等概率來計(jì)算(先驗(yàn)概率 = 1 / (類的數(shù)量)),或者通過訓(xùn)練集的各類樣本出現(xiàn)的次數(shù)來估計(jì)(A類先驗(yàn)概率=(A類樣本的數(shù)量)/(樣本總數(shù)))。為了估計(jì)特征的分布參數(shù),我們要先假設(shè)訓(xùn)練集數(shù)據(jù)滿足某種分布或者非參數(shù)模型。
高斯樸素貝葉斯:如果要處理的是連續(xù)數(shù)據(jù),一種通常的假設(shè)是這些連續(xù)數(shù)值為高斯分布。例如,假設(shè)訓(xùn)練集中有一個連續(xù)屬性x。我們首先對數(shù)據(jù)根據(jù)類別分類,然后計(jì)算每個類別中的x的均值和方差。令μc表示為x在c類上的均值,令σ2c為x類在c類上的方差。在給定類中某個值的概率P(x=v|c),可以通過將v表示為均值為μc,方差為σ2c正態(tài)分布計(jì)算出來,如下:
處理連續(xù)數(shù)值問題的另一種常用的技術(shù)是通過離散化連續(xù)數(shù)值的方法。通常,當(dāng)訓(xùn)練樣本數(shù)量較少或者是精確的分布已知時,通過概率分布的方法是一種更好的選擇。在大量樣本的情形下離散化的方法表現(xiàn)更優(yōu),因?yàn)榇罅康臉颖究梢詫W(xué)習(xí)到數(shù)據(jù)的分布。由于樸素貝葉斯是一種典型的用到大量樣本的方法(越大計(jì)算量的模型可以產(chǎn)生越高的分類精確度),所以樸素貝葉斯方法都用到離散化方法,而不是概率分布估計(jì)的方法。
樣本修正:如果一個給定的類和特征值在訓(xùn)練集中沒有一起出現(xiàn)過,那么基于頻率的估計(jì)下該概率將為0。這將是一個問題。因?yàn)榕c其他概率相乘時將會把其他概率的信息統(tǒng)統(tǒng)去除。所以常常要求要對每個小類樣本的概率估計(jì)進(jìn)行修正,以保證不會出現(xiàn)有為0的概率出現(xiàn)。
盡管實(shí)際上獨(dú)立假設(shè)常常是不準(zhǔn)確的,但樸素貝葉斯分類器的若干特性讓其在實(shí)踐中能夠取得令人驚奇的效果。特別地,各類條件特征之間的解耦意味著每個特征的分布都可以獨(dú)立地被當(dāng)做一維分布來估計(jì)。這樣減輕了由于維數(shù)災(zāi)帶來的阻礙,當(dāng)樣本的特征個數(shù)增加時就不需要使樣本規(guī)模呈指數(shù)增長。然而樸素貝葉斯在大多數(shù)情況下不能對類概率做出非常準(zhǔn)確的估計(jì),但在許多應(yīng)用中這一點(diǎn)并不要求。例如,樸素貝葉斯分類器中,依據(jù)最大后驗(yàn)概率決策規(guī)則只要正確類的后驗(yàn)概率比其他類要高就可以得到正確的分類。所以不管概率估計(jì)輕度的甚至是嚴(yán)重的不精確都不影響正確的分類結(jié)果。在這種方式下,分類器可以有足夠的魯棒性去忽略樸素貝葉斯概率模型上存在的缺陷。
以上內(nèi)容主要摘自:?維基百科
以下code是根據(jù)維基百科中對性別分類的介紹實(shí)現(xiàn)的C++代碼,最終結(jié)果與維基百科結(jié)果一致:
naive_bayes_classifier.hpp:
#ifndef FBC_NN_NAIVEBAYESCLASSIFIER_HPP_
#define FBC_NN_NAIVEBAYESCLASSIFIER_HPP_#include <vector>
#include <tuple>namespace ANN {template<typename T>
struct sex_info { // height, weight, foot size, sexT height;T weight;T foot_size;int sex; // -1: unspecified, 0: female, 1: male
};template<typename T>
struct MeanVariance { // height/weight/foot_size's mean and varianceT mean_height;T mean_weight;T mean_foot_size;T variance_height;T variance_weight;T variance_foot_size;
};// Gaussian naive Bayes
template<typename T>
class NaiveBayesClassifier {
public:NaiveBayesClassifier() = default;int init(const std::vector<sex_info<T>>& info);int train(const std::string& model);int predict(const sex_info<T>& info) const;int load_model(const std::string& model) const;private:void calc_mean_variance(const std::vector<T>& data, std::tuple<T, T>& mean_variance) const;T calc_attribute_probability(T value, T mean, T variance) const;int store_model(const std::string& model) const;MeanVariance<T> male_mv, female_mv;std::vector<T> male_height, male_weight, male_foot_size;std::vector<T> female_height, female_weight, female_foot_size;T male_p = (T)0.5;T female_p = (T)0.5;int male_train_number = 0;int female_train_number = 0;
};} // namespace ANN#endif // FBC_NN_NAIVEBAYESCLASSIFIER_HPP_
naive_bayes_classifier.cpp:
#include "naive_bayes_classifier.hpp"
#include "common.hpp"
#include <math.h>
#include <iostream>
#include <algorithm>
#include <fstream>namespace ANN {template<typename T>
int NaiveBayesClassifier<T>::init(const std::vector<sex_info<T>>& info)
{int length = info.size();if (length < 2) {fprintf(stderr, "train data length should be > 1: %d\n", length);return -1;}male_train_number = 0;female_train_number = 0;for (int i = 0; i < length; ++i) {if (info[i].sex == 0) {++female_train_number;female_height.push_back(info[i].height);female_weight.push_back(info[i].weight);female_foot_size.push_back(info[i].foot_size);} else {++male_train_number;male_height.push_back(info[i].height);male_weight.push_back(info[i].weight);male_foot_size.push_back(info[i].foot_size);}}male_p = (T)male_train_number / (male_train_number + female_train_number);female_p = (T)female_train_number / (male_train_number + female_train_number);return 0;
}template<typename T>
int NaiveBayesClassifier<T>::train(const std::string& model)
{std::tuple<T, T> mean_variance;calc_mean_variance(male_height, mean_variance);male_mv.mean_height = std::get<0>(mean_variance);male_mv.variance_height = std::get<1>(mean_variance);calc_mean_variance(male_weight, mean_variance);male_mv.mean_weight = std::get<0>(mean_variance);male_mv.variance_weight = std::get<1>(mean_variance);calc_mean_variance(male_foot_size, mean_variance);male_mv.mean_foot_size = std::get<0>(mean_variance);male_mv.variance_foot_size = std::get<1>(mean_variance);calc_mean_variance(female_height, mean_variance);female_mv.mean_height = std::get<0>(mean_variance);female_mv.variance_height = std::get<1>(mean_variance);calc_mean_variance(female_weight, mean_variance);female_mv.mean_weight = std::get<0>(mean_variance);female_mv.variance_weight = std::get<1>(mean_variance);calc_mean_variance(female_foot_size, mean_variance);female_mv.mean_foot_size = std::get<0>(mean_variance);female_mv.variance_foot_size = std::get<1>(mean_variance);CHECK(store_model(model) == 0);return 0;
}template<typename T>
int NaiveBayesClassifier<T>::store_model(const std::string& model) const
{std::ofstream file;file.open(model.c_str(), std::ios::binary);if (!file.is_open()) {fprintf(stderr, "open file fail: %s\n", model.c_str());return -1;}file.write((char*)&male_p, sizeof(male_p));file.write((char*)&male_mv.mean_height, sizeof(male_mv.mean_height));file.write((char*)&male_mv.mean_weight, sizeof(male_mv.mean_weight));file.write((char*)&male_mv.mean_foot_size, sizeof(male_mv.mean_foot_size));file.write((char*)&male_mv.variance_height, sizeof(male_mv.variance_height));file.write((char*)&male_mv.variance_weight, sizeof(male_mv.variance_weight));file.write((char*)&male_mv.variance_foot_size, sizeof(male_mv.variance_foot_size));file.write((char*)&female_p, sizeof(female_p));file.write((char*)&female_mv.mean_height, sizeof(female_mv.mean_height));file.write((char*)&female_mv.mean_weight, sizeof(female_mv.mean_weight));file.write((char*)&female_mv.mean_foot_size, sizeof(female_mv.mean_foot_size));file.write((char*)&female_mv.variance_height, sizeof(female_mv.variance_height));file.write((char*)&female_mv.variance_weight, sizeof(female_mv.variance_weight));file.write((char*)&female_mv.variance_foot_size, sizeof(female_mv.variance_foot_size));file.close();return 0;
}template<typename T>
int NaiveBayesClassifier<T>::predict(const sex_info<T>& info) const
{T male_height_p = calc_attribute_probability(info.height, male_mv.mean_height, male_mv.variance_height);T male_weight_p = calc_attribute_probability(info.weight, male_mv.mean_weight, male_mv.variance_weight);T male_foot_size_p = calc_attribute_probability(info.foot_size, male_mv.mean_foot_size, male_mv.variance_foot_size);T female_height_p = calc_attribute_probability(info.height, female_mv.mean_height, female_mv.variance_height);T female_weight_p = calc_attribute_probability(info.weight, female_mv.mean_weight, female_mv.variance_weight);T female_foot_size_p = calc_attribute_probability(info.foot_size, female_mv.mean_foot_size, female_mv.variance_foot_size);T evidence = male_p * male_height_p * male_weight_p * male_foot_size_p +female_p * female_height_p * female_weight_p * female_foot_size_p;T male_posterior = male_p * male_height_p * male_weight_p * male_foot_size_p /*/ evidence*/;T female_posterior = female_p * female_height_p * female_weight_p * female_foot_size_p /*/ evidence*/;fprintf(stdout, "male posterior probability: %e, female posterior probability: %e\n",male_posterior, female_posterior);if (male_posterior > female_posterior) return 1;else return 0;
}template<typename T>
T NaiveBayesClassifier<T>::calc_attribute_probability(T value, T mean, T variance) const
{return (T)1 / std::sqrt(2 * PI * variance) * std::exp(-std::pow(value - mean, 2) / (2 * variance));
}template<typename T>
int NaiveBayesClassifier<T>::load_model(const std::string& model) const
{std::ifstream file;file.open(model.c_str(), std::ios::binary);if (!file.is_open()) {fprintf(stderr, "open file fail: %s\n", model.c_str());return -1;}file.read((char*)&male_p, sizeof(male_p) * 1);file.read((char*)&male_mv.mean_height, sizeof(male_mv.mean_height) * 1);file.read((char*)&male_mv.mean_weight, sizeof(male_mv.mean_weight) * 1);file.read((char*)&male_mv.mean_foot_size, sizeof(male_mv.mean_foot_size) * 1);file.read((char*)&male_mv.variance_height, sizeof(male_mv.variance_height) * 1);file.read((char*)&male_mv.variance_weight, sizeof(male_mv.variance_weight) * 1);file.read((char*)&male_mv.variance_foot_size, sizeof(male_mv.variance_foot_size) * 1);file.read((char*)&female_p, sizeof(female_p)* 1);file.read((char*)&female_mv.mean_height, sizeof(female_mv.mean_height) * 1);file.read((char*)&female_mv.mean_weight, sizeof(female_mv.mean_weight) * 1);file.read((char*)&female_mv.mean_foot_size, sizeof(female_mv.mean_foot_size) * 1);file.read((char*)&female_mv.variance_height, sizeof(female_mv.variance_height) * 1);file.read((char*)&female_mv.variance_weight, sizeof(female_mv.variance_weight) * 1);file.read((char*)&female_mv.variance_foot_size, sizeof(female_mv.variance_foot_size) * 1);file.close();return 0;
}template<typename T>
void NaiveBayesClassifier<T>::calc_mean_variance(const std::vector<T>& data, std::tuple<T, T>& mean_variance) const
{T sum{ 0 }, sqsum{ 0 };for (int i = 0; i < data.size(); ++i) {sum += data[i];}T mean = sum / data.size();for (int i = 0; i < data.size(); ++i) {sqsum += std::pow(data[i] - mean, 2);}// unbiased sample variancesT variance = sqsum / (data.size() - 1);std::get<0>(mean_variance) = mean;std::get<1>(mean_variance) = variance;
}template class NaiveBayesClassifier<float>;
template class NaiveBayesClassifier<double>;} // namespace ANN
funset.cpp:
#include "funset.hpp"
#include <iostream>
#include "perceptron.hpp"
#include "BP.hpp""
#include "CNN.hpp"
#include "linear_regression.hpp"
#include "naive_bayes_classifier.hpp"
#include "common.hpp"
#include <opencv2/opencv.hpp>// ================================ naive bayes classifier =====================
int test_naive_bayes_classifier_train()
{std::vector<ANN::sex_info<float>> info;info.push_back({ 6.f, 180.f, 12.f, 1 });info.push_back({5.92f, 190.f, 11.f, 1});info.push_back({5.58f, 170.f, 12.f, 1});info.push_back({5.92f, 165.f, 10.f, 1});info.push_back({ 5.f, 100.f, 6.f, 0 });info.push_back({5.5f, 150.f, 8.f, 0});info.push_back({5.42f, 130.f, 7.f, 0});info.push_back({5.75f, 150.f, 9.f, 0});ANN::NaiveBayesClassifier<float> naive_bayes;int ret = naive_bayes.init(info);if (ret != 0) {fprintf(stderr, "naive bayes classifier init fail: %d\n", ret);return -1;}const std::string model{ "E:/GitCode/NN_Test/data/naive_bayes_classifier.model" };ret = naive_bayes.train(model);if (ret != 0) {fprintf(stderr, "naive bayes classifier train fail: %d\n", ret);return -1;}return 0;
}int test_naive_bayes_classifier_predict()
{ANN::sex_info<float> info = { 6.0f, 130.f, 8.f, -1 };ANN::NaiveBayesClassifier<float> naive_bayes;const std::string model{ "E:/GitCode/NN_Test/data/naive_bayes_classifier.model" };int ret = naive_bayes.load_model(model);if (ret != 0) {fprintf(stderr, "load naive bayes classifier model fail: %d\n", ret);return -1;}ret = naive_bayes.predict(info);if (ret == 0) fprintf(stdout, "It is a female\n");else fprintf(stdout, "It is a male\n");return 0;
}
執(zhí)行結(jié)果如下:
GitHub:? https://github.com/fengbingchun/NN_Test
總結(jié)
以上是生活随笔為你收集整理的朴素贝叶斯分类器简介及C++实现(性别分类)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深度学习中的贝叶斯统计简介
- 下一篇: Windows7/10上配置OpenCV