人工智能笔记07 监督学习基础

监督学习:回归与分类

根据样本数据的**标记(label)**特性,可将机器学习任务分为:

  1. 监督学习:样本特征x均对应的样本标记y
  2. 无监督学习:样本特征x均没有对应的样本标记y
  3. 半监督学习:样本特征x(大)部分没有对应的样本标记y
  4. 强化学习:可近似理解为具有延迟标记信息

监督学习:样本特征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,x2xn)+ϵ

ϵ \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[yx]=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=1n(yiw0w1xi1wdxid)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. w0Jw0w0^=2i=1n(yiw0^w1^xi1wd^xid)=0w1Jw1w1^=2i=1n(yiw0^w1^xi1wd^xid)=0w2Jw0w2^=2i=1n(yiw0^w1^xi1wd^xid)=0....wdJwdwd^=2i=1n(yiw0^w1^xi1wd^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[yx]=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~=(1x1xd)Tw=(w0,w1wd)T
优化目标
J ( w ) = ( y − X w ) T ( y − X w ) \mathcal{J}(w)=(y-Xw)^T(y-Xw) J(w)=(yXw)T(yXw)
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,x1T1x2T1xnT)=111x11x21xn1x12x22xn2x1dx2dxndx×(d+1)

综上有
X T ( y − X w ^ ) = 0 X^T(y-X\hat{w})=0 XT(yXw^)=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):基于树结构进行决策

  1. 树的每个内部结点对应某个特征/属性上的判定
  2. 每个分支对应上述判定的一种可能结果(该属性的某个取值)
  3. 每个叶结点对应一个预测结果

训练:通过分析训练样本,
确定内部结点所对应的特征/
属性(划分特征/属性)

预测:将测试样本从根结点开
始,沿着划分属性所构成的判
定序列下行,直到叶结点

训练决策树的基本思想:分而治之

  1. 自根结点至叶结点进行递归
  2. 为树的中间结点找到一个划分属性

训练算法的三种终止条件:
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=1ypklog2pk
计算信息熵的时候约定若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=1ypklog2pk
H(D)越小,纯度越高

信息增益以信息熵为基础,计算当前划分对信息熵造成的变化

离散属性 a a a的大小 { a 1 , a 2 … … a V } \{a^1,a^2……a^V\} {a1,a2aV}
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=1VDDvH(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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值