监督学习:回归与分类
根据样本数据的**标记(label)**特性,可将机器学习任务分为:
- 监督学习:样本特征x均有对应的样本标记y
- 无监督学习:样本特征x均没有对应的样本标记y
- 半监督学习:样本特征x(大)部分没有对应的样本标记y
- 强化学习:可近似理解为具有延迟标记信息
监督学习:样本特征x均有对应的样本标记y
当标记为离散值时:分类
例如,一张图片预测是猫、狗、还是老虎
最简单的分类:二分类,例如
,一张图片预测是不是人脸
当标记为连续值时:回归
例如,房价预测、GDP增速预测
离散–分类
连续–回归
监督学习的目标:预测(即解决“是什么”的问题)
为了达到预测这一目标,可从训练数据集D中学习特征空间X到标记空间Y的映射f
训练集
D
=
{
(
x
1
,
y
1
)
,
…
…
,
(
x
n
,
y
n
)
}
D=\{(x_1,y_1),……,(x_n,y_n)\}
D={(x1,y1),……,(xn,yn)}
对新样本特征
x
n
+
i
x_{n+i}
xn+i 进行标记预测
f
(
x
n
+
1
)
f(x_{n+1})
f(xn+1)
线性模型
以线性回归作为例子
建立回归模型
y
=
f
(
x
1
,
x
2
…
…
,
x
n
)
+
ϵ
y=f(x_1,x_2……,x_n)+\epsilon
y=f(x1,x2……,xn)+ϵ
ϵ
\epsilon
ϵ随机误差项,表示由于人们的认识以及其它客观原因的局限而没有
考虑的各种偶然因素
若考虑映射 为线性函数,线性回归(linear regression)
模型:
y
=
w
0
+
w
1
x
1
+
w
2
x
2
+
…
…
w
d
x
d
+
ϵ
E
[
y
∣
x
]
=
f
(
x
)
=
w
0
+
…
…
+
w
d
x
d
y=w_0+w_1x_1+w_2x_2+……w_dx_d+\epsilon\\\\ \mathbb{E}[y|x]=f(x)=w_0+……+w_dx_d
y=w0+w1x1+w2x2+……wdxd+ϵE[y∣x]=f(x)=w0+……+wdxd
线性模型试图学得一个通过特征的线性组合来进行预测的线性函数f
比如
f
好
瓜
=
0.2
x
色
泽
+
0.5
x
根
+
0.3
x
敲
声
+
1
f_{好瓜}=0.2x_{色泽}+0.5x_{根}+0.3x_{敲声}+1
f好瓜=0.2x色泽+0.5x根+0.3x敲声+1
为什么考虑线性模型?
简单、易于训练和测试、数学上易于优化、可解释性好
、是复杂非线性模型的基础
如何训练线性回归模型?
最小二乘(least square)法
学习参数
w
=
(
w
0
,
…
…
,
w
d
)
w = (w_0,……,w_d)
w=(w0,……,wd)
使得其在训练集上的均方误差最小
学习问题变成优化问题
w
^
(
w
0
^
,
…
…
,
w
d
^
)
=
a
r
g
m
i
n
w
0
,
…
…
w
d
J
(
w
0
,
…
…
,
w
d
)
\hat{w}(\hat{w_0},……,\hat{w_d}) = \underset{w_0,……w_d}{argmin}\quad\mathcal{J}(w_0,……,w_d)
w^(w0^,……,wd^)=w0,……wdargminJ(w0,……,wd)
上式又可以等于 离差的平方和
∑
i
=
1
n
(
y
i
−
w
0
−
w
1
x
i
1
−
…
…
−
w
d
x
i
d
)
2
\sum^n_{i=1}(y_i-w_0-w_1x_{i1} -……-w_dx_{id})^2
i=1∑n(yi−w0−w1xi1−……−wdxid)2
求解上述最优化问题,得到最小二乘估计作为学到的模型参数,
用于后续的预测
无约束优化,非负二次函数,最小值存在;根据微积分和凸优化中的求
极值原理,最优解满足方程组:
{
∂
J
∂
w
0
∣
w
0
−
w
0
^
=
−
2
∑
i
=
1
n
(
y
i
−
w
0
^
−
w
1
^
x
i
1
−
…
…
−
w
d
^
x
i
d
)
=
0
∂
J
∂
w
1
∣
w
1
−
w
1
^
=
−
2
∑
i
=
1
n
(
y
i
−
w
0
^
−
w
1
^
x
i
1
−
…
…
−
w
d
^
x
i
d
)
=
0
∂
J
∂
w
2
∣
w
0
−
w
2
^
=
−
2
∑
i
=
1
n
(
y
i
−
w
0
^
−
w
1
^
x
i
1
−
…
…
−
w
d
^
x
i
d
)
=
0
.
.
.
.
∂
J
∂
w
d
∣
w
d
−
w
d
^
=
−
2
∑
i
=
1
n
(
y
i
−
w
0
^
−
w
1
^
x
i
1
−
…
…
−
w
d
^
x
i
d
)
=
0
\left\{\begin{array}{l} \frac{\partial\mathcal{J}}{\partial{w_0}}|_{w_0-\hat{w_0}}=-2\sum^n_{i=1}(y_i-\hat{w_0}-\hat{w_1}x_{i1} -……-\hat{w_d}x_{id}) = 0\\ \frac{\partial\mathcal{J}}{\partial{w_1}}|_{w_1-\hat{w_1}}=-2\sum^n_{i=1}(y_i-\hat{w_0}-\hat{w_1}x_{i1} -……-\hat{w_d}x_{id}) = 0\\ \frac{\partial\mathcal{J}}{\partial{w_2}}|_{w_0-\hat{w_2}}=-2\sum^n_{i=1}(y_i-\hat{w_0}-\hat{w_1}x_{i1} -……-\hat{w_d}x_{id}) = 0\\....\\ \frac{\partial\mathcal{J}}{\partial{w_d}}|_{w_d-\hat{w_d}}=-2\sum^n_{i=1}(y_i-\hat{w_0}-\hat{w_1}x_{i1} -……-\hat{w_d}x_{id}) = 0\\ \end{array}\right.
⎩⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎧∂w0∂J∣w0−w0^=−2∑i=1n(yi−w0^−w1^xi1−……−wd^xid)=0∂w1∂J∣w1−w1^=−2∑i=1n(yi−w0^−w1^xi1−……−wd^xid)=0∂w2∂J∣w0−w2^=−2∑i=1n(yi−w0^−w1^xi1−……−wd^xid)=0....∂wd∂J∣wd−wd^=−2∑i=1n(yi−w0^−w1^xi1−……−wd^xid)=0
求解可以得
w
^
\hat{w}
w^
线性回归模型的矩阵表达
E
[
y
∣
x
]
=
f
(
x
)
=
w
T
x
~
\mathbb{E}[y|x]=f(x)=w^T\tilde{x}
E[y∣x]=f(x)=wTx~
x
~
=
(
1
x
1
…
…
x
d
)
T
w
=
(
w
0
,
w
1
…
…
w
d
)
T
\tilde{x}=(1\quad x_1……x_d)^T\quad w =(w_0,w_1……w_d)^T
x~=(1x1……xd)Tw=(w0,w1……wd)T
优化目标
J
(
w
)
=
(
y
−
X
w
)
T
(
y
−
X
w
)
\mathcal{J}(w)=(y-Xw)^T(y-Xw)
J(w)=(y−Xw)T(y−Xw)
X
=
(
1
,
x
1
T
;
1
,
x
2
T
…
…
,
1
,
x
n
T
)
=
(
1
x
11
x
12
…
x
1
d
1
x
21
x
22
…
x
2
d
…
…
1
x
n
1
x
n
2
…
x
n
d
)
x
×
(
d
+
1
)
X=(1,x_1^T;1,x_2^T……,1,x_n^T)=\left(\begin{array}{l} 1&x_{11}&x_{12}&…&x_{1d}\\ 1&x_{21}&x_{22}&…&x_{2d}\\ ……\\ 1&x_{n1}&x_{n2}&…&x_{nd} \end{array}\right)_{x\times (d+1)}
X=(1,x1T;1,x2T……,1,xnT)=⎝⎜⎜⎛11……1x11x21xn1x12x22xn2………x1dx2dxnd⎠⎟⎟⎞x×(d+1)
综上有
X
T
(
y
−
X
w
^
)
=
0
X^T(y-X\hat{w})=0
XT(y−Xw^)=0
X
T
X
w
^
=
X
T
y
X^TX\hat{w}=X^Ty
XTXw^=XTy
若
X
T
X
X^TX
XTX 可逆(逆矩阵:满足和原矩阵相乘为单位矩阵的矩阵)
w
^
=
(
X
T
X
)
−
1
X
T
y
\hat{w}=(X^TX)^{-1}X^Ty
w^=(XTX)−1XTy
即是线性回归的最小二乘
若 不可逆,可在优化目标中引入正则化(regularization)项。例
如,岭回归,LASSO等
线性模型的局限性:表示能力有限,难以直接拟合复杂的映射
因此,引入更加复杂的非线性模型
非线性
以决策树做二分类为例
二(元)分类任务:样本标记只可取两个值中的一者
常用的非线性模型:决策树,神经网络
使用决策树来非线性建模特征空间X到标记空间Y之间的映射
关系:
决策树(decision tree):基于树结构进行决策
- 树的每个内部结点对应某个特征/属性上的判定
- 每个分支对应上述判定的一种可能结果(该属性的某个取值)
- 每个叶结点对应一个预测结果
训练:通过分析训练样本,
确定内部结点所对应的特征/
属性(划分特征/属性)
预测:将测试样本从根结点开
始,沿着划分属性所构成的判
定序列下行,直到叶结点
训练决策树的基本思想:分而治之
- 自根结点至叶结点进行递归
- 为树的中间结点找到一个划分属性
训练算法的三种终止条件:
3. 当前结点包含的样本全属于同一类别,无需划分
4. 当前属性集为空,或者是所有样本在所有属性上取值相同,无法划分
5. 当前结点包含的样本集合为空,不能划分
决策树算法的核心:如何从属性集A中选择最优的划分属性a*
随着划分过程不断进行,希望决策树的分支结点所包含的样本尽可能属
于同一类别,即结点的 “纯度”(purity)越来越高
使决策树得到关注并成为机器学习主流技术的算法: ID3
基于信息增益,增益率,基尼指数
信息增益的说明
信息熵可以用来度量样本集合的纯度
信息增益以信息熵为基础,计算当前划分对信息熵造成的变化
H
(
D
)
=
−
∑
k
=
1
∣
y
∣
p
k
l
o
g
2
p
k
H(D)=-\sum^{|\mathcal{y}|}_{k=1}p_klog_2p_k
H(D)=−k=1∑∣y∣pklog2pk
计算信息熵的时候约定若p=0,
p
l
o
g
2
p
=
0
plog_2p=0
plog2p=0 H(D)越小,D纯度越高
思考:H(D)何时最小,何时最大,分别对应哪种情形?
若当前样本集合D中第k类样本所占的比例为pk,则D的信息熵为:
H
(
D
)
=
−
∑
k
=
1
∣
y
∣
p
k
l
o
g
2
p
k
H(D)=-\sum^{|y|}_{k=1}p_klog_2p_k
H(D)=−k=1∑∣y∣pklog2pk
H(D)越小,纯度越高
信息增益以信息熵为基础,计算当前划分对信息熵造成的变化
离散属性
a
a
a的大小
{
a
1
,
a
2
…
…
a
V
}
\{a^1,a^2……a^V\}
{a1,a2……aV}
D
v
D^v
Dv:D在a上取值等于
a
v
a^v
av的样本集合
以a对数据集D划分的信息增益定义:
G
(
D
,
a
)
=
H
(
D
)
−
∑
v
=
1
V
∣
D
v
∣
∣
D
∣
H
(
D
v
)
G(D,a)=H(D)-\sum^V_{v=1}\frac{|D^v|}{|D|}H(D^v)
G(D,a)=H(D)−v=1∑V∣D∣∣Dv∣H(Dv)
决策树样例
测评代码
#include <bits/stdc++.h>
using namespace std;
// 每个数据或是整数或是浮点数,实际使用时每个数据仅其中一个值有效
struct Val
{
int i;
double d;
Val(int _i): i(_i), d(0) {}
Val(double _d): i(0), d(_d) {}
};
bool operator != (const Val& lhs, const Val& rhs)
{
return lhs.i != rhs.i || lhs.d != rhs.d;
}
// 样本,包括每种属性及标签(1 或 -1)
struct Sample
{
vector<Val> atr;
int label;
Sample(): atr(), label(0) {}
};
typedef vector<Sample> Data;
// 决策树节点
struct Tnode
{
int label; // 为 0 说明不为叶节点;否则标识叶节点对应标签
int atrno; // 该节点的划分属性的下标
int tp; // 0 代表连续值;1代表离散值
double par; // 若为连续值, 为划分点的值;若为离散值, 则无效
vector<Tnode> son; // 该节点的所有子节点
Tnode(): label(0), atrno(-1), tp(-1), par(0), son() {}
};
string readLine(istream& fin)
{
string s;
// 读入数据并输入到 stringstream
if (fin.eof()) {
return s;
}
getline(fin, s);
for (int i = 0; i < s.length(); ++i) {
if (s[i] == ',') s[i] = ' ';
}
return s;
}
void readData(Data& data_all, vector<int>& type_all)
{
string s;
stringstream ss;
// 读入数据类型
s = readLine(cin);
ss << s;
while (true) {
int x;
ss >> x;
if (ss.fail()) break;
type_all.push_back(x);
}
// 读入数据
bool flag = true;
while (flag) {
string s;
stringstream ss;
s = readLine(cin);
ss << s;
Sample sp;
for (int t : type_all) {
if (t == 0) {
double x;
ss >> x;
if (ss.fail()) {
flag = false;
break;
}
sp.atr.push_back(x);
}
else {
int x;
ss >> x;
if (ss.fail()) {
flag = false;
break;
}
sp.atr.push_back(x);
}
}
if (!flag) break;
// 读取 label
int x;
ss >> x;
if (ss.fail()) break;
sp.label = x;
data_all.push_back(sp);
}
}
// 若数据集中所有样本有相同 label,返回该 label 值;否则返回 0
int getLabel(const Data& data_set)
{
int label = data_set[0].label;
for (int i = 1; i < data_set.size(); ++i) {
if (data_set[i].label != label) {
return 0;
}
}
return label;
}
// 判断数据集中所有样本是否在 atr_set 中的每个属性上取值都相同
bool sameAtr(const Data& data_set, const vector<int>& atr_set)
{
Sample sp = data_set[0];
for (int i = 1; i < data_set.size(); ++i) {
for (int atrno : atr_set) {
if (sp.atr[atrno] != data_set[i].atr[atrno]) {
return false;
}
}
}
return true;
}
// 计算数据集中样本数最多的类。若相同,随机取一个
int getMostLabel(const Data& data_set)
{
map<int, int> mp;
mp[-1] = 0;
mp[1] = 0;
for (auto sp : data_set) {
mp[sp.label]++;
}
if (mp[-1] > mp[1]) return -1;
if (mp[-1] < mp[1]) return 1;
return rand() % 2 == 0 ? -1 : 1;
}
// 将数据集按某一属性分割,若为连续值则以 par 为分界点分割
vector<Data> splitData(const Data& data_set, int atrno, double par, const vector<int>& type_all)
{
int tp = type_all[atrno];
vector<Data> datas(2);
if (tp == 0) {
for (auto e : data_set) {
if (e.atr[atrno].d < par) {
datas[0].push_back(e);
}
else {
datas[1].push_back(e);
}
}
}
else {
for (auto e : data_set) {
datas[e.atr[atrno].i].push_back(e);
}
}
return datas;
}
/**
* @brief 对决策树中一个节点的划分方案作出评估
*
* @param label_group 当前待评估划分方案的标签。label_group[i][j] 为该划分方案第 i 组中的第 j 个标签。保证标签仅有 -1 和 1 两种取值。
* @return 对划分的评估,数值越大代表该划分越优秀。
*/
double calcScore(const vector<vector<int> >& label_group);
// 你的代码将被嵌入此处
// 将 datas 中的 label 提取出来,调用 calcScore 获取得分
double getScore(const vector<Data>& datas)
{
vector<vector<int> > label_group;
for (const Data& dt : datas) {
vector<int> labels;
for (auto sp : dt) {
labels.push_back(sp.label);
}
label_group.push_back(labels);
}
return calcScore(label_group);
}
// 获取某一划分的分数及划分点(若为连续变量)
pair<double, double> getScoreAndPar(const Data& data_set, int atrno, const vector<int>& type_all)
{
if (type_all[atrno] == 0) {
vector<double> points;
for (auto dt : data_set) {
points.push_back(dt.atr[atrno].d);
}
sort(points.begin(), points.end());
points.push_back(points.back() + 1);
vector<pair<double, double> > rets;
for (int i = 0; i < points.size() - 1; ++i) {
if (points[i] == points[i + 1]) continue;
double par = (points[i] + points[i + 1]) / 2;
vector<Data> datas = splitData(data_set, atrno, par, type_all);
rets.push_back({getScore(datas), par});
}
assert(!rets.empty());
sort(rets.begin(), rets.end(), greater<pair<double, double> >());
while (rets.size() > 1 && rets.back().first != rets.front().first) {
rets.pop_back();
}
int no = rand() % rets.size();
return rets[no];
}
else {
vector<Data> datas = splitData(data_set, atrno, 0, type_all);
return {getScore(datas), 0};
}
}
// 构建决策树
Tnode buildTree(const Data& data_set, const vector<int>& atr_set, const vector<int>& type_all)
{
Tnode node;
// 处理样本类别全部相同的情况
node.label = getLabel(data_set);
if (node.label) {
// printf("*1*\n");
return node;
}
// 处理属性集为空或所有样本在属性集上取值全部相同的情况
if (atr_set.empty() || sameAtr(data_set, atr_set)) {
// printf("*2*\n");
node.label = getMostLabel(data_set);
return node;
}
// 选择最优属性. 若有多个, 随机选择其中一个
vector<pair<pair<double, double>, int> > v;
for (auto atrno : atr_set) {
v.push_back({getScoreAndPar(data_set, atrno, type_all), atrno});
}
sort(v.begin(), v.end(), greater<pair<pair<double, double>, int> >());
while (v.size() > 1 && v.back().first.first != v.front().first.first) {
v.pop_back();
}
int no = rand() % v.size();
node.atrno = v[no].second;
node.tp = type_all[node.atrno];
node.par = v[no].first.second;
// 生成分支
vector<Data> datas = splitData(data_set, node.atrno, node.par, type_all);
vector<int> new_atr_set;
if (node.tp == 0) {
new_atr_set = atr_set;
}
else {
for (auto atrno : atr_set) {
if (atrno != node.atrno) new_atr_set.push_back(atrno);
}
}
for (int i = 0; i < 2; ++i) {
if (datas[i].empty()) {
Tnode nd;
nd.label = getMostLabel(data_set);
node.son.push_back(nd);
}
else {
node.son.push_back(buildTree(datas[i], new_atr_set, type_all));
}
}
return node;
}
void print(const Tnode& now, int tab = 0)
{
putchar('~');
for (int i = 0; i < tab; ++i) {
putchar('|');
}
printf("(%d, %d, %d, %f)\n", now.label, now.atrno, now.tp, now.par);
if (!now.label) {
for (const Tnode& p : now.son) {
print(p, tab + 1);
}
}
}
// 预测一个数据的值
int fit(const Tnode& root, const Sample& sample_in)
{
if (root.label != 0) return root.label;
if (root.tp == 1) return fit(root.son[sample_in.atr[root.atrno].i], sample_in);
if (sample_in.atr[root.atrno].d < root.par) return fit(root.son[0], sample_in);
return fit(root.son[1], sample_in);
}
// 返回一个vector,内容为对每个输入数据的预测
vector<int> predict(const Tnode& root, const Data& data_set)
{
vector<int> ret;
for (auto e : data_set) {
ret.push_back(fit(root, e));
}
return ret;
}
double trainAndTest(const Data& data_train, const Data& data_test, const vector<int>& type_all)
{
vector<int> atr_set;
for (int i = 0; i < type_all.size(); ++i)
{
atr_set.push_back(i);
}
Tnode root = buildTree(data_train, atr_set, type_all);
vector<int> pred = predict(root, data_test);
int total_num = data_test.size();
int correct = 0;
for (int i = 0; i < total_num; ++i)
{
if (pred[i] == data_test[i].label)
++correct;
}
return double(correct) / total_num;
}
double crossValidate(const Data& data_all, const vector<int>& type_all, int folder_num)
{
vector<Data> datas(2);
for (auto dt : data_all) {
if (dt.label == -1) datas[0].push_back(dt);
else datas[1].push_back(dt);
}
for (Data& data : datas) {
random_shuffle(data.begin(), data.end());
}
vector<Data> data_folder;
for (int i = 0; i < folder_num; ++i) {
Data folder;
for (const Data& data : datas) {
int l = i * data.size() / folder_num;
int r = (i + 1) * data.size() / folder_num;
for (int i = l; i < r; ++i) {
folder.push_back(data[i]);
}
}
data_folder.push_back(folder);
}
double precision = 0;
for (int i = 0; i < folder_num; ++i) {
Data data_train, data_test;
for (int j = 0; j < folder_num; ++j) {
if (i == j) {
data_test.insert(data_test.end(), data_folder[j].begin(), data_folder[j].end());
}
else {
data_train.insert(data_train.end(), data_folder[j].begin(), data_folder[j].end());
}
}
precision += trainAndTest(data_train, data_test, type_all) / folder_num;
}
return precision;
}
int main()
{
Data data_all;
vector<int> type_all;
srand(19260817);
readData(data_all, type_all);
double precion = crossValidate(data_all, type_all, 10);
// 输出密码,防止偷鸡行为
printf("%.2f\n", precion * 100);
return 0;
}
信息增益
经过测试确实也只有66.16%概率正确
#include<vector>
#include<cmath>
using namespace std;
double calcScore(const vector<vector<int> >& label_group)
{
double EntD = 0;
vector<double>Earray;
vector<int>Enum;
int all_pos = 0, all_neg = 0;
int leni = label_group.size();
for(int i = 0; i < leni; i++)
{
int part_pos = 0, part_neg = 0;
int lenj = label_group[i].size();
for(int j = 0; j < lenj; j++)
{
int temp = label_group[i][j];
if(temp == 1)
{
part_pos += 1;
all_pos += 1;
}
else
{
part_neg += 1;
all_neg += 1;
}
}
double EntTemp;
int SUM = part_neg + part_pos;
if(!part_neg || !part_pos)//存在一个是0
EntTemp = 0;
else
{
double fac1 = (double)part_neg / SUM;
double fac2 = (double)part_pos / SUM;
EntTemp = -(fac1*log2(fac1) + fac2*log2(fac2));
}
Earray.push_back(EntTemp);
Enum.push_back(SUM);
}
double fac1 = (double)all_neg / (all_neg+all_pos);
double fac2 = (double)all_pos / (all_neg+all_pos);
double res = -(fac1*log2(fac1) + fac2*log2(fac2));
for(int i = 0; i < Earray.size(); i++)
res -= Earray[i] * ((double)Enum[i] / (all_neg + all_pos));
return res;
}