【朴素贝叶斯】实战朴素贝叶斯_代码实现_特征选择2

【选择特征】

其实对于卡方选择来说,上一篇软文基本上已经几乎写完了——毕竟卡方值都计算出来了,接下来,选择就行了呗。选择的方法?上文不是说了么,卡方的值表示特征与类别之间的关联,选择卡方值大的特征类型不就行了么。不过,还有最后一个问题,问题在于我们有多个类别。当我们要选择m个特征的时候,每个类别选择几个?哦,如果有k各类别,那么平均分配一下,每个类别选择m/k个,各个类别加起来就是m个了。哦,那么进一步考虑,这k各类别选择的特征,之间是否有相同的?说不准哈,但很有可能真的有相同的特征。例如:词表中有“苹果”这个词,既可以代表水果类的文本,也可以代表电子类的文本。那么很有可能,我们最终选择的特征个数少于m个。那么最终会选出多少个特征呢?说不准,肯定小于等于m。这可不好,算法不可控。那么,在实际实现中,我在选择特征的时候,判断他是否在其他类别中被选择了,如果被选择了,就跳过它,继续选择下一个特征,这样就能严格保证有m个特征被选出了。

void NaiveBayes::FeaSelByChiSquare(double ** ppChiMatrix, double ** ppPProbMatrix, int iClassNum, 
										int iFeaTypeNum, int iSelFeaNum, int * pFeaSelected)
{
	// the number of feature to be selected for each class (要选择iSelFeaNum个特征,有iClassNum个类别,平均每个类别就要选择这么多特征类别)
	int iSelFeaNumAClass = iSelFeaNum / iClassNum;

	// for sort and get the top n feature type (临时的节点类,用于按照卡方值进行排序,因为是临时的,所以就写在函数里面了。不过这样写有个缺点,在调试的时候无法看到节点的值;把这个节点声明到函数外就可以了。没搞清楚为什么,可能是函数内部声明算作是临时变量,编译器不知道。而外部声明的,不一定在哪里用,所以编译器知道,所以debug的时候就......)
	class FeaChiNode
	{
	public:
		int iFeaId;
		double dChiVal;

		FeaChiNode()
		{
			iFeaId = -1;
			dChiVal = 0.0;
		};
		~FeaChiNode(){};

		// 这个内联函数,决定节点之间的比较关系,用于按照卡方排序
		inline bool operator > (const FeaChiNode & theNode)const 
		{
			if (dChiVal > theNode.dChiVal)
				return true;
			else
				return false;
		}
	};

	vector<FeaChiNode> FeaChiVec;
	FeaChiVec.resize (iFeaTypeNum);

	// iterate ppChiMatrix and fill in ppFeaSelMatrix and pFeaSelected(这个数组是个0-1数组,表示这个特征是否已经被其他类别选择了。同时,这也是返回的结果,表示哪些个特征被选择了)
	for (int i=0; i<iClassNum; i++)
	{
		// fill in FeaChiVec by ppChiMatrix[i]
		for (int j=0; j<iFeaTypeNum; j++)
		{
			FeaChiVec.at (j).iFeaId = j;
			FeaChiVec.at (j).dChiVal = ppChiMatrix[i][j];
		}
		// sort by Chi-square value
		sort (FeaChiVec.begin(), FeaChiVec.end(), greater<FeaChiNode>());

		// get the top iSelFeaNumAClass items, and set the flag arra
		vector<FeaChiNode>::iterator pFeaChi = FeaChiVec.begin();
		for (int k=0; k<iSelFeaNumAClass && pFeaChi != FeaChiVec.end(); k++, pFeaChi++)
		{
			// these conditions could be relaxed if necessary
			while (pFeaChi != FeaChiVec.end() &&		
					(
					0 != pFeaSelected[pFeaChi->iFeaId] ||			// find the feature type that never been selected
					DBL_MIN > ppPProbMatrix[i][pFeaChi->iFeaId])	// in the same time, there are sample hitting this feature type and class
					)
				pFeaChi++;

			if(pFeaChi != FeaChiVec.end())
				pFeaSelected[pFeaChi->iFeaId] = 1;
			else
				break;
		}
	}
}

关键是最后两重for循环内部的while循环,那个条件判断,嗯,你懂得。


【进一步想】

进一步想,为什么每个类别都要选择选择相同数量的特征?不一定啊。

平时我们讲特征选择,都是在想选择哪些特征,最终落到的问题都是用什么标准来进行选择,如:卡方、信息增益。然后尝试了一圈,发现效果都差不多。还有一个问题就是,选择多少个特征?这个,也可以进行实验,1w,2w,或者再少一点,5k等等。最后选一个效果最好的。其实,再进一步,就是对每个类别,选择多少个特征?这个问题似乎很少有人提及。但我觉得还是比较重要的。

我们想一下,对于某个类别,特征选择得越多(在一定数量限定条件下),这个类别的文本分类的结果就越准。那么,套用机器学习的损失函数的概念,我们也可以引入“风险”的概念,对风险越大的类别,选择的特征越多;反之,就越小。如果风险都一样,还有一种准则,就是各个类别的训练样本比例。如果对于样本比例大的类别,选择了更多个特征,那么这个类别样本的准确率提高,就更有助于整体准确率的提高。这个再升华一下,就是按照“收益率”来确定特征选择的比例。

......

这样想下去,还有很多good idea可以提出,也可以进行试验,写一些paper。哎,自从博士毕业以后,有段时间没操刀写paper了。


好,对卡方选择,就说到这里。


  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值