1. 程式人生 > >機器學習(周志華) 參考答案 第一章 緒論 1.2

機器學習(周志華) 參考答案 第一章 緒論 1.2

機器學習(周志華) 參考答案 第一章 緒論 1.2

機器學習(周志華西瓜書) 參考答案 總目錄

機器學習(周志華) 參考答案 第一章 緒論

2.與使用單個合取式來進行假設表示相比,使用“析合正規化”將使得假設空間具有更強的表示能力。若使用最多包含k個合取式的析合正規化來表達1.1的西瓜分類問題的假設空間,試估算有多少種可能的假設。

表1.1包含4個樣例,3種屬性,假設空間中有344+1=49種假設。在不考慮沉餘的情況下,最多包含k個合取式來表達假設空間,顯然k的最大值是49,每次從中選出k個來組成析合式,共ΣC49k=249 種可能。但是其中包含了很多沉餘的情況(至少存在一個合取式被剩餘的析合式完全包含<空集除外>)。

如果考慮沉餘的情況
在這裡忽略空集,一個原因是並不是太明白空集是否應該加入析合式,另外就算需要加入,求出了前面48種假設的組合,可以很容易求出加入空集後的組合數(每種可能都可以加上空集,再加上1種空集單獨的情況)。
48種假設中:
具體假設:233=18
一個屬性泛化假設:23+33+23=21
兩個屬性泛化假設:2+3+3=8
三屬性泛化:1
當k=1時,任選一種假設都可以作為一種沒有沉餘的假設,共48種。
k的最大值是18,當k等於18時,就是18種具體屬性假設的析取式,共

1種。
當k取中間值時,就不好分析了。
一種可行的演算法:
由於屬性泛化後,一個泛化的假設可以對應多個具體假設。
把所有假設按三屬性泛化,二屬性泛化,一屬性泛化,具體屬性排序(這樣可以保證排在後面的假設不會包含前面的任何一個假設,所以省略了一些包含判斷),進行迴圈列舉,按順序遍歷所有假設組合248種可能(當然絕大部分都提前結束了,不會是那麼誇張的量級,雖然也不低):

  • 使用棧來實現非遞迴,如果當前假設還有沒被析合式所包含的具體假設,則認為可以入棧,並當前棧大小的長度計數加1,並繼續掃描。
  • 如果當前掃描已經到了最後一個假設,或者所有具體假設已經被全部包含,則退棧。
  • 迴圈結束條件:當最後一個假設作為第一個壓入棧的元素時,認為已經遍歷結束。

由於一共有18種具體假設,可以用一個32位整型(變數為hypos_cur)的後18位來表示每一個具體假設。用1表示具體假設沒被包含,用0表示具體假設已經被析合式包含。初始的析合式為空,可以設初試值為0X3FFFF。每個假設也對應一個32位整型(假設變數為hypo_const),代表著它所對應了哪些具體假設,如果它包含了某種具體假設,則該位為1

  • 判斷析合式是否包含了全部的具體假設:hypos_cur=0
  • 判斷該假設是否已經被析合正規化包含:用hypo_const與hypos_cur做與運算(結果用hypo_tmp表示),如果為0表示已經被包含(判斷該假設是否包含了當前的析合式:用hypo_const與hypos_cur做或運算,如果為0X3FFFFF,則認為該假設包含了當前析合式,但由於前面對所有假設做了排序,不可能出現這種情況,所以可以省略該判斷)。
  • 當某個假設加入析合正規化後(入棧)用hypos_cur與hypo_tmp做異或運算,來更改析合式所包含的具體假設。
  • 出棧時再次用hypos_cur與hypo_tmp做異或,回到加入該假設前的情況。
  • 因為是指數級遍歷的演算法,所以很慢,我的3代i7筆記本大概算了3分鐘。
#include <vector>
#include <stack>
using namespace std;

//按泛化程度排序,保證排在後面的假設不會不會包含前面的任何一個假設
static const char list[] = {
    0,0,0,
    0,0,1,0,0,2,0,0,3,0,1,0,0,2,0,0,3,0,1,0,0,2,0,0,
    0,1,1,0,1,2,0,1,3,0,2,1,0,2,2,0,2,3,0,3,1,0,3,2,0,3,3,
    1,0,1,1,0,2,1,0,3,2,0,1,2,0,2,2,0,3,
    1,1,0,1,2,0,1,3,0,2,1,0,2,2,0,2,3,0,
    1,1,1,1,1,2,1,1,3,1,2,1,1,2,2,1,2,3,1,3,1,1,3,2,1,3,3,
    2,1,1,2,1,2,2,1,3,2,2,1,2,2,2,2,2,3,2,3,1,2,3,2,2,3,3
};

//用來派生的抽象類
class hypos {
public:
    virtual int insert(int cur) = 0;
};

//單個的假設類
/*
hypo_const  假設對應的具體假設集合
*/
class hypo :public hypos {
public:
    hypo(int a, int b, int c) {
        hypo_const = 0;
        vector<char>  p[3];
        if (a == 0) {
            p[0].push_back(1);
            p[0].push_back(2);
        }
        else
            p[0].push_back(a);
        if (b == 0) {
            p[1].push_back(1);
            p[1].push_back(2);
            p[1].push_back(3);
        }
        else
            p[1].push_back(b);
        if (c == 0) {
            p[2].push_back(1);
            p[2].push_back(2);
            p[2].push_back(3);
        }
        else
            p[2].push_back(c);
        for (unsigned int i = 0;i < p[0].size();i++)
            for (unsigned int j = 0;j < p[1].size();j++)
                for (unsigned int k = 0;k < p[2].size();k++)
                    hypo_const |= (1 << (p[0][i] * 9 + p[1][j] * 3 + p[2][k] - 13));
    }

    //判斷是否要加入到析合式 如果還有具體假設沒被包含,則加入
    int insert(int cur) {
        return (hypo_const & cur);
    };

private:
    int hypo_const;
};

//用於壓入棧的派生類 用來實現非遞迴
/*
hypo_tmp    記錄這個假設入棧時,帶入了哪些具體假設,出棧時要還原
ptr         記錄入棧時的位置
*/
class hypo_ss :public hypos {
public:
    hypo_ss(int _ptr,int tmp){
        hypo_tmp = tmp;
        ptr = _ptr;
    }
    int insert(int cur) {
        return 0;
    };
    int hypo_tmp;
    int ptr;
};

//用來迴圈遍歷的類
/*
sum     各個長度的析合式各有多少種可能
ss      用來實現非遞迴的棧
hypos_cur   當前沒被包含的具體假設 初始值為0X3FFFF
hyposs  48個假設集合
*/
class Traversal :public hypos {
public:
    Traversal() {
        hypos_cur = 0x3ffff;
        for(int i=0;i<48;i++)
            hyposs.push_back(hypo(list[3*i], list[3*i+1], list[3*i+2]));
    }

    //迴圈順序遍歷的主體
    //cur  初試的位置 設為0
    int insert(int cur) {
        //當前指向的位置
        int ptr = cur;
        while (1) {
            //退出條件 當最後一個假設作為第一個入棧的元素 表示遍歷完成
            if (ptr > 47 && !ss.size()) break;
            //回退條件  掃描到最後或者所有具體假設都被包含 
            if (hypos_cur == 0 || ptr>47) {
                hypo_ss hypo_tmp = ss.top();
                hypos_cur ^= hypo_tmp.hypo_tmp;
                ptr = hypo_tmp.ptr + 1;
                ss.pop();
                continue;
            }

            //入棧條件  如果該假設還有未被包含的具體假設 則入棧,並當前棧大小的計數加1
            if (int tmp =hyposs[ptr].insert(hypos_cur)) {
                hypos_cur ^= tmp;
                ss.push(hypo_ss(ptr, tmp));
                if (sum.size() < ss.size())
                    sum.push_back(0);
                sum[ss.size() - 1]++;
            }
            ptr++;
        }
        return 1;
    };
    //輸出各個長度的可能數
    void print() {
        for (unsigned int i = 0;i < sum.size();i++)
            printf("length %d : %d\n", i + 1, sum[i]);
    }
private:
    vector<int> sum;
    stack<hypo_ss> ss;
    int hypos_cur;
    vector<hypo> hyposs;
};

int main()
{
    Traversal traversal;
    traversal.insert(0);
    traversal.print();
    system("pause");
    return 0;
}

/*
最終輸出:
length 1 : 48
length 2 : 931
length 3 : 10332
length 4 : 72358
length 5 : 342057
length 6 : 1141603
length 7 : 2773332
length 8 : 4971915
length 9 : 6543060
length 10 : 6175660
length 11 : 4003914
length 12 : 1676233
length 13 : 422676
length 14 : 61884
length 15 : 5346
length 16 : 435
length 17 : 27
length 18 : 1
*/