本文主要包含谈谈自己对随机森林(Random Forest)的一点理解以及代码注释~,randomforest等服务器相关知识,网友希望可以进行参考
谈谈自己对随机森林(Random Forest)的一点理解以及代码注释~,randomforest
之前因为做过随机森林方面的项目,对随机森林有过研究,但理论这块还不是很深入,代码倒是看了不少,这里写下这篇博客,说说对随机森林的一些理解,以及附上了一份代码注释。
1. 随机森林
随机森林属于非传统式的机器学习算法,由多颗决策树组成,每棵决策树处理的是一个训练样本子集。训练阶段,通过决策树的节点分裂来筛选特征,层层对样本进行细分,直至将每个训练样本子集分类正确,测试阶段,直接基于训练出的特征进行样本分类,所以测试速度较快(但训练速度较慢)。属于“傻瓜式”的策略(这点和adaboost很像很像),以下部分是标准随机森林训练阶段的大致流程。
1. 假如有N个样本,则有放回的随机选择N个样本(每次随机选择一个样本,然后返回继续选择)。这选择好了的N个样本用来训练一个决策树,作为决策树根节点处的样本。2. 当每个样本有M个属性时,在决策树的每个节点需要分裂时,随机从这M个属性中选取出m个属性,满足条件m << M。然后从这m个属性中采用某种策略(比如说信息增益)来选择1个属性作为该节点的分裂属性。
3. 决策树形成过程中每个节点都要按照步骤2来分裂,一直到不能够再分裂为止(就是收敛)。所谓不能再分裂,就是全部到达叶子节点,如果下一次该节点选出来的那一个属性是刚刚其父节点分裂时用过的属性,则该节点就是叶子节点。
4. 按照步骤1~3建立大量的决策树,便构成了随机森林。
从上面的步骤可以看出,随机森林的随机性体现在每颗数的训练样本是随机的,树中每个节点的分类属性也是随机选择的。有了这2个随机的保证,随机森林就不会产生过拟合的现象了。
值得注意的是,随机森林的基本思想都一样,但是在细节上却有所不同,尤其是在节点分裂所采用规则,叶子节点确定那里,有着很多种变种,也就具有不同的效果。说一个我自己遇到的,在确定叶子节点那块,它不是采用属性和父节点一致来确定,而是属性为空来确定,因为它在每一个节点选择一个属性之后,都会将该属性从候选属性集中删除,这样就会造成叶子节点候选属性集为空的情况。
给出一份某位朋友在论坛中提问的代码,关于文本分类的,刚好符合我上面描述的情况,我新添加了注释,与朋友们分享,里面肯定有很多问题,请不吝赐教啊!!!
2. 代码
</pre><pre name="code" class="cpp">#include <iostream>
#include <fstream>
#include <sstream>
#include "random_forest.h"
using namespace std;
vector<decision_tree*> alltrees; // 森林(决策树集合)
vector<TupleData> trainAll,train,test; // 样本集
vector<int> attributes; // 属性集(元素为属性序号)
int trainAllNum = 0;
int testAllNum = 0;
int MaxAttr; // 属性总数
int *ArrtNum; // 属性个数集(元素为属性最大值)
unsigned int F;
int tree_num = 100; // 决策树个数
const int leafattrnum = -1; // 叶子节点的属性序号
int TP = 0,
FN = 0,
FP = 0,
TN = 0,
TestP = 0,
TestN = 0;
// 读入数据
void init(char * trainname, char * testname)
{
trainAllNum = readData(trainAll, trainname);
testAllNum = readData(test, testname);
calculate_attributes();
double temp = (double)trainAllNum;
temp = log(temp)/log(2.0);
F = (unsigned int)floor(temp+0.5)+1;
if(F>MaxAttr) F = MaxAttr;
}
// 初始化训练样本子集
void sub_init()
{
// 选取决策树的训练样本集合
RandomSelectData(trainAll, train);
// 计算样本属性个数
calculate_ArrtNum();
}
// 读数据
int readData(vector<TupleData> &data, const char* fileName)
{
ifstream fin;
fin.open(fileName);
string line;
int datanum=0;
// 每行数据作为一个样本
while(getline(fin,line))
{
TupleData d;
istringstream stream(line);
string str;
// 设置每个样本的标签和内容
while(stream>>str)
{
if(str.find('+')==0)
{
d.label='+';
}
else if(str.find('-')==0)
{
d.label='-';
}
else
{
int j=stringtoint(str);
d.A.push_back(j);
}
}
data.push_back(d);
datanum++;
}
fin.close();
return datanum;
}
// 生成根节点的训练样本子集
void RandomSelectData(vector<TupleData> &data, vector<TupleData> &subdata)
{
int index;
subdata.clear();
int d = 0;
while (d < trainAllNum)
{
index = rand() % trainAllNum;
subdata.push_back(data.at(index));
d++;
}
}
// 计算属性序列
void calculate_attributes()
{
// 每个样本必须具有相同的属性个数
TupleData d = trainAll.at(0);
MaxAttr = d.A.size();
attributes.clear();
// 建立属性集合attributes,元素为属性序号
for (int i = 0; i < MaxAttr; i++)
{
attributes.push_back(i);
}
// 初始化属性最大值序列,元素为属性最大值
ArrtNum = new int[MaxAttr];
}
// 字符串转化为int
int stringtoint(string s)
{
int sum=0;
for(int i=0; s[i]!='\0';i++)
{
int j=int(s[i])-48;
sum=sum*10+j;
}
return sum;
}
// 计算ArrtNum元素值
void calculate_ArrtNum()
{
for(int i = 0; i < MaxAttr; i++) ArrtNum[i] = 0;
// ArrtNum元素值为属性最大值
for (vector<TupleData>::const_iterator it = train.begin(); it != train.end(); it++)
{
int i = 0;
for (vector<int>::const_iterator intt=(*it).A.begin(); intt!=(*it).A.end();intt++)
{
int valuemax=(*intt)+1;
if(valuemax>ArrtNum[i]) ArrtNum[i]=valuemax;
i++;
}
}
}
// 计算熵
double Entropy(double p, double s)
{
double n = s - p;
double result = 0;
if (n != 0)
result += - double(n) / s * log(double(n) / s) / log(2.0);
if (p != 0)
result += double(-p) / s * log(double(p) / s) / log(2.0);
return result;
}
// 训练一棵决策树
int creat_classifier(decision_tree *&p, const vector<TupleData> &samples, vector<int> &attributes)
{
if (p == NULL)
p = new decision_tree();
// 根据样本真实类别,输出叶子节点类别
if (Allthesame(samples, '+'))
{
p->node.label = '+';
p->node.attrNum = leafattrnum;
p->childs.clear();
return 1;
}
if (Allthesame(samples, '-'))
{
p->node.label = '-';
p->node.attrNum = leafattrnum;
p->childs.clear();
return 1;
}
// 如果属性序列为空,当前节点就为叶子节点
if (attributes.size() == 0)
{
p->node.label = Majorityclass(samples);
p->node.attrNum = leafattrnum;
p->childs.clear();
return 1;
}
// 计算当前节点的最优属性
p->node.attrNum = BestGainArrt(samples, attributes);
// 中间节点无标签
p->node.label = ' ';
// 计算子节点候选属性集合,候选集合元素越来越少
vector<int> newAttributes;
for (vector<int>::iterator it = attributes.begin(); it != attributes.end(); it++)
if ((*it) != p->node.attrNum)
newAttributes.push_back((*it));
// 初始化样本子集,建立maxvalue个样本子集,也就说明该节点有maxvalue个子节点
// 为什么不建立一个阈值,进行二分类?
int maxvalue = ArrtNum[p->node.attrNum];
vector<TupleData>* subSamples = new vector<TupleData>[maxvalue];
for (int i = 0; i < maxvalue; i++)
subSamples[i].clear();
// 将样本集合分为样本子集
for (vector<TupleData>::const_iterator it = samples.begin(); it != samples.end(); it++)
{
// 对样

