机器学习(周志华西瓜书) 参考答案 总目录
机器学习(周志华) 参考答案 第一章 绪论
表1.1包含4个样例,3种属性,假设空间中有 3∗4∗4+1=49 3 ∗ 4 ∗ 4 + 1 = 49 种假设。在不考虑沉余的情况下,最多包含k个合取式来表达假设空间,显然k的最大值是49,每次从中选出k个来组成析合式,共 ΣCk49=249 Σ C 49 k = 2 49 种可能。但是其中包含了很多沉余的情况(至少存在一个合取式被剩余的析合式完全包含<空集除外>)。
如果考虑沉余的情况
在这里忽略空集,一个原因是并不是太明白空集是否应该加入析合式,另外就算需要加入,求出了前面48种假设的组合,可以很容易求出加入空集后的组合数(每种可能都可以加上空集,再加上1种空集单独的情况)。
48种假设中:
具体假设: 2∗3∗3=18 2 ∗ 3 ∗ 3 = 18 种
一个属性泛化假设: 2∗3+3∗3+2∗3=21 2 ∗ 3 + 3 ∗ 3 + 2 ∗ 3 = 21 种
两个属性泛化假设: 2+3+3=8 2 + 3 + 3 = 8 种
三属性泛化: 1 1 种
当k= 1 1 时,任选一种假设都可以作为一种没有沉余的假设,共 48 48 种。
k的最大值是 18 18 ,当k等于 18 18 时,就是 18 18 种具体属性假设的析取式,共 1 1 种。
当k取中间值时,就不好分析了。
一种可行的算法:
由于属性泛化后,一个泛化的假设可以对应多个具体假设。
把所有假设按三属性泛化,二属性泛化,一属性泛化,具体属性排序(这样可以保证排在后面的假设不会包含前面的任何一个假设,所以省略了一些包含判断),进行循环枚举,按顺序遍历所有假设组合 248 2 48 种可能(当然绝大部分都提前结束了,不会是那么夸张的量级,虽然也不低):
由于一共有 18 18 种具体假设,可以用一个 32 32 位整型(变量为hypos_cur)的后 18 18 位来表示每一个具体假设。用1表示具体假设没被包含,用 0 0 表示具体假设已经被析合式包含。初始的析合式为空,可以设初试值为 0X3FFFF 0 X 3 F F F F 。每个假设也对应一个 32 32 位整型(假设变量为hypo_const),代表着它所对应了哪些具体假设,如果它包含了某种具体假设,则该位为 1 1 。
#include
#include
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 ss;
int hypos_cur;
vector 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 */