文章目录
前言
人工智能的算法需要许多预备知识,但时间比较紧,所以我只会对"对最后算法实现有帮助的资料"感兴趣,试着在不完全了解的情况下将这次实验完成。
数据集
这次要学习的数据集是UCI上的Polish companies bankruptcy data Data Set
数据集就是一堆数据的集合,而这次实验就是要对这个“破产公司”数据集进行分类。
这个数据集包含了5个.arff格式的文件,分别对应五年的数据。
我借鉴《Matlab读取.arff文件》的教程,用Excel查看了里面的内容,一个文件大概有七千条公司的记录,每条记录都有65个属性。其中前64个属性是实数格式,个别可能出现’?’,应该是‘不清楚’的意思;最后一个属性是0或1,代表了这条记录的类别(我猜测是破产/不破产)。
我要做的就是,用这些记录的属性和类别去给算法学习,达到能只凭属性来预测类别的程度。
AdaBoost
机器学习实战教程(十):提升分类器性能利器-AdaBoost
网上有大把的参考资料还算庆幸,这里举了个例子。当然要短时间完全理解还是天方夜谭的。
思路
AdaBoost大致是靠调整弱分类器的权重来运作的。
弱分类器可以暂时理解为对某个属性设置一个阈值,如果达到阈值就直接归为类1,未达到就归为类0。
一开始Adaboost导入作为训练数据的记录,根据一个弱分类器来分类所有的记录。然后对比判断结果与每条记录的实际类别的差距,来纠正这个弱分类器的权重和各个记录的权重,最后就智能的学习出了由多个带权重的弱分类器合并的强分类器,同时也可以通过记录的权重来排除不必要的训练记录。
设计
- 训练模式
- 导入训练数据
- 对训练数据定权重D={k,k,k,…,k,k},暂时先不知道k为多少合适,总之全员相等。
- 加载训练数据,使用弱分类器来预测分类,最后选定误差最小的一个弱分类器开始训练
- 给分错的数据和分对的数据分别调整数据的权重
- 对比预测和实际,然后通过某种方式来设定分类器权重α。
- 将带权重的弱分类器加入到强分类器中,得出强分类器的当前指标。
- 循环迭代进行下一次训练。
难点
1.如何将.arff里的内容输入到C++程序呢?
2.k应该是多少呢?
3.如何建立初始的弱分类器?
4.某种方式是什么呢?
这些纯属代码之外的知识盲区,还是优先解决的好。
Solve 1
在前面我已经成功的用Excel查看了里面的内容,如果用记事本打开的话,数据部分其实就是用逗号分隔的记录们。
.arff文件是无法识别的,但我可以单纯的将数据部分作为新的输入文件,然后以逗号分割整个文件,得到的集合就都是数字为元素的,再以每65个为一条记录即可。
Solve 2
初始权重在AdaBoost的教程中其实已经规定,为w=1/N,作为第一次迭代时各个记录的权重。可以说非常标准了。
Solve 3
虽然AdaBoost有加强分类器的能力,但是尽量还是让弱分类器本身的误差率最小比较好。所以在AdaBoost之前,弱分类器也是要训练出来的。
因为弱分类器本身是很简单的,类似
H 1 = { 1 , X 35 > 9.8 − 1 , X 35 ≤ 9.8 H_{1}=\left\{\begin{matrix} 1,X_{35}>9.8\\ -1,X_{35}\leq 9.8 \end{matrix}\right. H1={
1,X35>9.8−1,X35≤9.8表示若记录的第35个属性>9.8,那么就取1,反之就取-1。
分类预测时,就看分类器的结果是正还是负,正为类1,负为类0。
所以误差率就是分错的记录占总记录数的比例。
因此很容易想到,只要在某个属性的数据范围内,遍历各种可能的阈值和方向(大还是小),取其中误差最小的即可。
例如:数据分布在-10~90之间,我可以以10为步长遍历,也就是从-10,0, … ,80,90中挑出一个分类结果最准确的阈值即可。
Solve 4
Solve 4是Adaboost的核心计算部分,先要从原理上了解才能写出相应的代码。
Adaboost算法原理分析和实例+代码(简明易懂)
1.选取一个误差率最小的弱分类器H,对所有数据分类,计算当前弱分类器的误差度。
误差度ε等于分错的样本的权值之和。
2.计算该弱分类器的权重
α = 1 2 l n ( 1 − ε ε ) \alpha=\frac{1}{2}ln(\frac{1-\varepsilon }{\varepsilon }) α=21ln(ε1−ε)
3.计算分对记录和分错记录的权重
当记录被分对时,该记录新权重为:
D n e w = D o l d 2 ( 1 − ε ) D_{new}=\frac{D_{old}}{2(1-\varepsilon )} Dnew=2(1−ε)Dold
当记录被分错时,该记录新权重为:
D n e w = D o l d 2 ε D_{new}=\frac{D_{old}}{2\varepsilon } Dnew=2εDold
编程
因为想以实现预期效果为目标,所以运行效率和最终结果会有点不尽人意。
输入输出环境配置
数据集是以文件的形式存在的,想到输入输出都走文件渠道
#include <iostream>
#include <string>
#include <cstring>
#include <cmath>
#include <sstream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <map>
using namespace std;
#define LOCAL
int main()
{
#ifdef LOCAL
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
#endif
return 0;
}
代码说明
1.若Visual Studio出现报错"freopen:This function…",那么就在解决方案资源管理器里,右键项目名>属性>C/C++>常规>SDL检查>“是"改成"否”。
2.这段代码会使程序以input.txt
作为标准输入流,然后输出会在output.txt
中显示
输入数据集
//训练集记录的结构体
struct Record
{
bool hasValue[64];//64个属性是否存在
double attr[64];//64个属性的值
double weight;//记录的权重
int type;//类别,0或1
};
vector<Record> recs;//训练集
//功能:遍历字符串str,将里面的','替换为空格
void replaceComma(string& str)
{
for (int i = 0; i < str.length(); i++)
{
if (str[i] == ',')
str[i] = ' ';
}
}
//功能:输入训练集文件,将训练集的数据保存在recs之中
void inputRecords()
{
Record tempRec;
string line;
while (!cin.eof())
{
//一行作为一条完整记录
getline(cin, line);
if (line == "") break;
replaceComma(line);
stringstream ss(line);//一行的字符串作为输入流
for (int i = 0; i < 64; i++)
{
//排除值为'?'的属性
string tempStr;
ss >> tempStr;
if (tempStr != "?")
{
tempRec.hasValue[i] = true;
//把string类型转化为double类型
stringstream tempSS(tempStr);
tempSS >> tempRec.attr[i];
}
else
tempRec.hasValue[i] = false;
}
ss >> tempRec.type;//记录的最后一个数字代表类别
//记录+1
recs.push_back(tempRec);
}
//给所有记录赋初始权重
double numOfRecords = recs.size();
for (vector<Record>::iterator it = recs.begin(); it