试答系列:“西瓜书”-周志华《机器学习》习题试答
1.1 表1.1中若只包含编号1和4两个样例,试给出相应的版本空间。
答: P5页中对版本空间的定义是这样说:可能有多个假设与训练集一致,即存在着一个与训练集一致的“假设集合”,我们称之为“版本空间”(version space)。
表一只包含 1,4样例,则为:
编号 | 色泽 | 根蒂 | 敲声 | 好瓜 |
---|---|---|---|---|
1 | 青绿 | 蜷缩 | 浊响 | 是 |
4 | 乌黑 | 稍蜷 | 沉闷 | 否 |
类似于图1.2的做法,结果应该为:
1.2 若使用最多包含k个合取式的析合范式来表达1.1西瓜分类问题的假设空间,试估算共有多少种可能的假设。
答: 如果没有析合范式,由单个合取式所构成的假设空间共包含3×4×4+1=49种可能的假设(见教材P5,这里根据表1.1,色泽属性的取值只考虑“青绿”,“乌黑”两种情况)。如果用这49个合取式彼此做并集来构成析合范式,将会产生更多的假设。因此,通过析合范式的方式可以扩大假设空间。
数据可视化
首先,为了方便讨论,可以先将数据进行可视化,随后再来分析问题。表1.1中的西瓜分类问题只有三个属性,每个属性有2~3种取值,可以将其三维可视化:
那么,合取式可以根据其中通配符数目可以分为四种类型,在图中表现出来便是:点,线,面,体。“单个合取式”等价于图中的“点,线,面,体”:
单个样本对应于图中的一个点,共有18个点,为了下文的讨论方便,将每个点进行编号:
这样,对于上面的单个合取式可以这样称呼它们:
(色泽=乌黑,根蒂=稍蜷,敲声=浊响)----点{6}
(色泽=乌黑,根蒂=稍蜷,敲声=*)---------线{6,12,18}
(色泽=*,根蒂=稍蜷,敲声=*)--------------面{5,6,11,12,17,18}
问题分析
现在,开始来考虑题目中的问题,用最多包含k个合取式的析合范式来表达的假设空间,共有多少种可能的假设?
k=1 时,假设空间即由所有的点线面体所组成,其中点-18个,线-21条,面-8面,体-1个,再加上空集,总共的假设空间数为 18+21+8+1+1=49.
-
k=2 时,考虑通过两个合取式析合的方式可以新增出哪些假设,可能有以下情况:
点+点:C218-9=144
(扣去包含了9条竖单线的情况,比如点{1}v点{2}与单线{1,2}等同)
线+点:9×(18-2)+12×(18-3)=324
面+点:2×9+6×12=90
线+线:C221-6-12×3=168
(扣去与竖向面,线-点重复的情况,比如,线{5,11,17}+线{6,12,18}与面{5,6,11,12,17,18}等同,线{5,11,17}+线{5,6}与线{5,11,17}+点{6}等同)
面+线:2×6+6×(21-5)=108
(未考虑与面-点重复的情况)
面+面:C26=15
(未考虑与体,面-线重复的情况)合计,新增假设144+324+90+168+108+15=849个。因此,最大包含2个合取式所构成的假设空间包含49+849=898种可能的假设。
对于k=3及以上的情况可以做类似分析,但是从上面k=2的情况已经看出,很麻烦,要考虑多种组合情况,还有剔除重复的情况。因此,接下来考虑编程实现。
编程计算
编程实现需要换一个思路来考虑问题,合取式就是一个假设,析合式也是假设,假设就是一个函数,这里的函数可以用正例样本点的集合来表示。从集合观点来看,可以把单个合取式看成一个集合(判定为正例的样本集合),单个合取式对应于集合:
-
基础集合
空集:{}
点:{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},{16},{17},{18}
线:{1,2},{3,4},{5,6},{7,8},{9,10},{11,12},{13,14},{15,16},{17,18},
{1,3,5},{2,4,6},{7,9,11},{8,10,12},{13,15,17},{14,16,18},
{1,7,13},{3,9,15},{5,11,17},{2,8,14},{4,10,16},{6,12,18},面:{1,3,5,7,9,11,13,15,17},{2,4,6,8,10,12,14,16,18},
{1,2,3,4,5,6},{7,8,9,10,11,12},{13,14,15,16,17,18},
{1,2,7,8,13,14},{3,4,9,10,15,16},{5,6,11,12,17,18}体:{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18}
我们将这些集合称之为“基础集合”,将“由多个合取式构成析合范式”的过程称之为“由基础集合产生出新的集合”的过程。
问题“最多包含k个合取式的析合范式来表达的假设空间共有多少种假设?”可以重述为“最多由k个基础集合做并集,可以产生出多少种不同的集合?”
首先,有几个基本事实,我们先捋一捋:
-
基本事实
(1) 假设数目,或者说集合可能数目,最多不超过 218=262144个。可以这样看,集合中对于1~18各个点,要么选入,要不选入,因此有218种可能集合。
(2) 18个单点集合{1},{2}…{18}可以合成所有可能的集合,因此,所有集合最多需要个18基础集合来合成。
(3) 全集{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18}不能产生新的集合,因为它和其他任何集合的并集还是全集。所以,基础集合可以不考虑全集。
(4) 空集也没有产生新集合的能力,基础集合也可以不考虑空集。
最多由k个基础集合做并集,可以产生出多少种不同的集合?
一种做法是,考察所有由1~k个基础集合来做并集,剔除其中重复考虑的集合,统计最终产生了多少种不同的集合。这样很麻烦,我们换一种四路。
换一种做法,考察所有可能集合(或者说“假设”),共218个,彼此互不等同,考察每一个集合最少需要由几个基础集合来生成。这样的好处在于,不必像前一种方法那样,需要考虑重复计数的情况。那么,“最多由k个基础集合做并集,可以产生出多少种不同的集合?”等同于“最少需要1~k个基础集合来产生的集合,共有多少个?”
代码实现
基于Python语言,很方便的是,Python语言中本身就有集合数据类型,不知道其他语言里有没有。
详细编程代码附后
计算结果如下:
- 程序运行结果
最少需要1个合取式来合成的假设数目:49
最少需要2个合取式来合成的假设数目:849
最少需要3个合取式来合成的假设数目:7452
最少需要4个合取式来合成的假设数目:32907
最少需要5个合取式来合成的假设数目:72351
最少需要6个合取式来合成的假设数目:84843
最少需要7个合取式来合成的假设数目:49296
最少需要8个合取式来合成的假设数目:12933
最少需要9个合取式来合成的假设数目:1464
最少需要10-18个合取式来合成的假设数目:0
于是,现在可以回答题目给出的问题了:
- 回答问题
当k=1时,假设空间包含假设数目为 49
当k=2时,假设空间包含假设数目为 49+849=898
当k=3时,假设空间包含假设数目为 898+7452=8350
当k=4时,假设空间包含假设数目为 8350+32907=41257
当k=5时,假设空间包含假设数目为 41257+72351=113608
当k=6时,假设空间包含假设数目为 113608+84843=198451
当k=7时,假设空间包含假设数目为 198451+49296=247747
当k=8时,假设空间包含假设数目为 247747+12933=260680
当k>=9时,假设空间包含假设数目为 260680+1464=262144 (218,亦即所有可能的假设数)
1.3 若数据包含噪声,假设空间中有可能不存在与所有训练样本都一致的假设。试设计一种归纳偏好用于假设选择。
答 :比如,归纳偏好于“训练误差低”以及“尽可能一般”的假设。
1.4 将式(1.1) 中性能度量的指示函数II(·)换成其他性能度量l,试证明NFL定理仍成立。
答 :设性能度量函数l为:
于是(1.2)式可变为:
最终表达式为:
当 l10+l101-l00-l00=0时, 上式与算法ζa无关,免费午餐定理成立。
1.5 试述机器学习能在互联网搜索的哪些环节起作用。
答 :比如,在浏览网页时,根据浏览记录跳出的推荐商品广告。搜索某件商品后,推荐相关商品,还提示说“80%的用户也买了这个”,“根据你浏览的某商品推荐了这个”。
附:编程代码
习题1.2(Python)
#==================================
# 定义一些函数
#==================================
# 函数 inside(set1,set2)判断两个集合是否存在包含关系
# set1,set2为两个集合
def inside(set1,set2):
return (set1|set2==set1)|(set1|set2==set2)
# 函数 combine(ListSets)返回列表中所有集合的并集
# ListSets为列表类型,列表元素为集合类型
def combine(ListSets):
cset=ListSets[0]
for subset in ListSets:
cset=cset|subset
return cset
# 函数DeleteSubset(ListSets)对一组集合进行精简,使集合数目最小化
# 首先删去其中被其余集合所包含的集合,然后删去多余的集合(删去不影响并集结果)
# 输入ListSets为列表类型,列表元素为集合类型
# 返回精简后的集合列表
def DeleteSubset(ListSets):
#删除被包含的集合
over=False
while not over:
over=True
N=len(ListSets) #当前集合列表的集合数目
for i in range(N-1,0,-1):
set1=ListSets[i]
for set2 in ListSets[:i]:
if inside(set1,set2):
over=False
ListSets.pop(i)
break
#删除多余的集合
over=False
while not over:
over=True
N=len(ListSets) #当前集合列表的集合数目
for i in range(N-1,0,-1):
if combine(ListSets)==combine(ListSets[:i]+ListSets[i+1:]):
over=False
ListSets.pop(i)
return ListSets
# 函数Findset(base,set)
# 用于在基础集合列表base中找出合并出目标集合set的最佳方式(基础集合数最少)
def Findset(base,set):
# 首先找出在base中所有set的子集
sub_base=[k for k in base[::-1] if (k|set)==set]
# 然后删除这些子集中存在包含关系的子集
return DeleteSubset(sub_base)
# 函数GenerateSet(N1,N2,k) 递归
# 用递归方法从N1-N2的自然数中挑选k个元素所组成的所有可能集合
# 返回集合列表,包含个CkN2-N1+1个集合,每个集合包含k个元素
def GenerateSet(N1,N2,k):
if k==1:
return [{i} for i in range(N1,N2+1)]
else:
sets=[]
for i in range(N1,N2+2-k):
for subset in GenerateSet(i+1,N2,k-1):
sets+=[{i}|subset]
return sets
# 函数GenerateSet1(code) 由编码产生集合
# code为整数,比如,5,对应的二进制码为000...00101
# 那么由此产生集合{1,3}
# 返回一个集合
def GenerateSet1(code):
TransCode=list('{:018b}'.format(code))
oneset=set()
for k in range(-1,-len(TransCode)-1,-1):
if TransCode[k]=='1':
oneset|={-k}
return oneset
#==================================
# 主程序
#==================================
# 基础集合
base=[{1},{2},{3},{4},{5},{6},{7},{8},{9},{10},{11},{12},{13},{14},{15},{16},{17},{18},
{1,2},{3,4},{5,6},{7,8},{9,10},{11,12},{13,14},{15,16},{17,18},
{1,3,5},{2,4,6},{7,9,11},{8,10,12},{13,15,17},{14,16,18},
{1,7,13},{3,9,15},{5,11,17},{2,8,14},{4,10,16},{6,12,18},
{1,3,5,7,9,11,13,15,17},{2,4,6,8,10,12,14,16,18},
{1,2,3,4,5,6},{7,8,9,10,11,12},{13,14,15,16,17,18},
{1,2,7,8,13,14},{3,4,9,10,15,16},{5,6,11,12,17,18}]
# 计数器,初始为零
counter=18*[0]
# 两种方式择其一来遍历所有集合:递归和编码
'''
# 递归方式产生集合
for k in range(1,18): #考虑从1-18中挑选1~17个元素组成集合的情况
Sets=GenerateSet(1,18,k)
for EachSet in Sets:
num=len(Findset(base,EachSet)) #每个集合最少需要多少个基础集合来合成
counter[num-1]+=+1
'''
# 编码方式遍历所有集合
for code in range(1,2**18-1):
EachSet=GenerateSet1(code)
num=len(Findset(base,EachSet))
counter[num-1]+=+1
counter[0]+=2 #单独考虑空集和全集的情况
for k in range(18):
print('最少需要%d个合取式来合成的假设数目:%d'%(k+1,counter[k]))