《機器學習》周志華 習題1.2的解答

本題解答參考:

1.CSDN博主「是你的小魚」:

https://blog.csdn.net/yuzeyuan12/article/details/83113461

2.CSDN博主「四去六進」:

https://blog.csdn.net/yuzeyuan12/article/details/83113461

 

  • 題目理解:

題目:

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

 

在剛看到此題時,首先我想到的是,利用與解1.1類似的方法,將各個假設進行編碼,並定義各編碼之間的合取與析取運算,如將色澤屬性分別編碼:青綠:01 烏黑:10 (通配)*:11。如此,則假設:(色澤=青綠)∧(根蒂=稍蜷)∧(敲聲=沉悶)(色澤=青綠)∧(根蒂=稍蜷)∧(敲聲=沉悶) 編碼爲:01010100。

基於此,定義假設之間的析取運算即是將析取的兩個編碼按位相與,便可得結果。

但此種做法存在極大不便,首先,析取的結果所形成的集合無法與假設空間形成滿射,例如假設色澤=青綠∧(根蒂=稍蜷)∧(敲聲=沉悶)與假設色澤=青綠∧(根蒂=硬挺)∧(敲聲=濁響) 相析取,即01010100與01100001按位相或,所得結果爲01110101,該結果編碼沒有對應的鍵值,導致最終計算的失敗。其次,該編碼方式不具備良好的處理亢餘的能力,因此不可行。

之後我閱讀博主[四去六進][2]關於本題的解答,同樣對用棧的方式遍歷組合的方法表示深刻的懷疑,原因博主[是你的小魚][1]在其文章中解釋的十分清楚,在此我作一些簡單的解讀。

 

題目要求從48種假設裏面取k個假設進行析取,然後列出所有可能的組合用來表示假設空間,如果不考慮亢餘情況,此題很簡單,假設空間的大小即是C48k

但問題在於,由於48種假設中存在通配的情況,故而不可避免的要出現亢餘,這樣的話,我們就需要將這些亢餘從假設空間剔除出去。

剔除的方法便在於理解亢餘的本質,48中假設中,只有18種假設不包含通配情況,它們之間的析取不會產生亢餘,我們將其稱爲葉子假設。需要注意的是,任何一種包含通配的假設,都可以表示爲葉子假設的析取。

故而我們可以按位將18種葉子假設進行編碼,當前位爲1意味着對應的葉子假設被包含,如(青綠,蜷縮,濁響)編碼爲100000000000000000,同時由於通配表示葉子假設的析取,即包含通配的假設中有多位爲1,例如(*,*,清脆)編碼爲000111000000111000

進一步地,我們給出假設空間並消除亢餘的步驟,如下:

  1. 取48種假設中的k元組合。
  2. 對此k個假設進行析取運算,即按位相或。
  3. 將所得的結果存入一個集合A,若集合A爲空集,則直接添加進去,若A非空,則後續的結果需先與集合A中所有的元進行比較,若有相同的,則捨去該結果,這一步便實現了亢餘的剔除。
  4. 遍歷C48k的所有組合,得最終的假設空間A,A中元素的數目變爲假設空間的大小。

圖示:

 

  • 代碼實現:

博主「是你的小魚」提供了它的代碼,根據網友反饋跑不起來,故在這裏我給出我的代碼。

代碼採用python3.7實現,加載itertools進行迭代運算用於組合數的處理,中間運用了運算符重載,將假設的析取用類的“+”運算表示。

# -*- coding: utf-8 -*-
"""
Created on Sat Sep 21 14:36:27 2019

@author: Administrator
"""
import itertools 
import time


#將數存儲爲數組
def method(value):
    #divmod()是內置函數,返回整商和餘數組成的元組
    result = []
    for i in range(len(value)):
        result.append(eval(value[i]))
    return result

#用類表示編碼,並定義重載的運算符 + 爲析取
class WMcode:
    val  = ''
    len  = 0
    def __init__(self, strv):
        self.val = method(strv)
    def __add__(self,other):             #析取運算
        strv = [] 
        for i in range(len(self.val)):
            addr = self.val[i]+other.val[i]
            if addr >= 2:
                addr = 1
            strv.append(str(addr))
        return WMcode(strv)
    def __or__(self,other):              #合取運算,本題未用到
        strv = [] 
        for i in range(len(self.val)):
            addr = self.val[i]*other.val[i]
            strv.append(str(addr))
        return WMcode(strv)
    
#對48中假設進行編碼
nodes = [WMcode('100000000000000000'),WMcode('010000000000000000'),WMcode('001000000000000000'),
         WMcode('000100000000000000'),WMcode('000010000000000000'),WMcode('000001000000000000'),
         WMcode('000000100000000000'),WMcode('000000010000000000'),WMcode('000000001000000000'),
         WMcode('000000000100000000'),WMcode('000000000010000000'),WMcode('000000000001000000'),
         WMcode('000000000000100000'),WMcode('000000000000010000'),WMcode('000000000000001000'),
         WMcode('000000000000000100'),WMcode('000000000000000010'),WMcode('000000000000000001'),
         WMcode('100000000100000000'),WMcode('010000000010000000'),WMcode('001000000001000000'),
         WMcode('000100000000100000'),WMcode('000010000000010000'),WMcode('000001000000001000'),
         WMcode('000000100000000100'),WMcode('000000010000000010'),WMcode('000000001000000001'),
         WMcode('111000000000000000'),WMcode('000111000000000000'),WMcode('000000111000000000'),
         WMcode('000000000111000000'),WMcode('000000000000111000'),WMcode('000000000000000111'),
         WMcode('100100100000000000'),WMcode('010010010000000000'),WMcode('001001001000000000'),
         WMcode('000000000100100100'),WMcode('000000000010010010'),WMcode('000000000001001001'),
         WMcode('111000000111000000'),WMcode('000111000000111000'),WMcode('000000111000000111'),
         WMcode('100100100100100100'),WMcode('010010010010010010'),WMcode('001001001001001001'),
         WMcode('111111111000000000'),WMcode('000000000111111111'),WMcode('111111111111111111')]
#           (青綠,蜷縮,濁響) (青綠,稍蜷,濁響) (青綠,硬挺,濁響)
#           (青綠,蜷縮,清脆) (青綠,稍蜷,清脆) (青綠,硬挺,清脆)
#           (青綠,蜷縮,沉悶) (青綠,稍蜷,沉悶) (青綠,硬挺,沉悶)
#           (烏黑,蜷縮,濁響) (烏黑,稍蜷,濁響) (烏黑,硬挺,濁響)
#           (烏黑,蜷縮,清脆) (烏黑,稍蜷,清脆) (烏黑,硬挺,清脆)
#           (烏黑,蜷縮,沉悶) (烏黑,稍蜷,沉悶) (烏黑,硬挺,沉悶)
#           (*,蜷縮,濁響) (*,稍蜷,濁響) (*,硬挺,濁響)
#           (*,蜷縮,清脆) (*,稍蜷,清脆) (*,硬挺,清脆)
#           (*,蜷縮,沉悶) (*,稍蜷,沉悶) (*,硬挺,沉悶)
#           (青綠,*,濁響) (青綠,*,清脆) (青綠,*,沉悶)
#           (烏黑,*,濁響) (烏黑,*,清脆) (烏黑,*,沉悶)
#           (青綠,蜷縮,*) (青綠,稍蜷,*) (青綠,硬挺,*)
#           (烏黑,蜷縮,*) (烏黑,稍蜷,*) (烏黑,硬挺,*)
#           (*,*,濁響) (*,*,清脆) (*,*,沉悶)
#           (*,蜷縮,*) (*,稍蜷,*) (*,硬挺,*)
#           (青綠,*,*) (烏黑,*,*) (*,*,*)

#存儲最終結果的集合A
A = []
#合取式數量k,在此進行改動即可
k = 3
#是否捨去當前假設標籤abandon
abandon = 0
time_start=time.time()               #開始計時
comb = list(itertools.combinations(nodes,k))
for i in range(len(comb)):
    WMadd = WMcode('000000000000000000')
    for j in range(k):
        WMadd = WMadd + comb[i][j]
    for allval in A:                       #若A中已經存在當前假設,則捨去,因爲這是亢餘
        if WMadd.val == allval.val:
            print("symposis abandoned")
            abandon = 1
            break
    if abandon == 1:
        abandon = 0
        continue
    else:
        A.append(WMadd)
        print(WMadd.val, "is added")
    print("Present combination calculated")
time_end=time.time()                 #結束計時
print("Work is done")
print(len(A))
print('totally cost',time_end-time_start)

· 運行結果:

k=1時:

k=2時:

k=3時:

k=4時:

k = 5時

..........

可見,k=5花了我將近我四個小時,本程序存在很大的改進空間,剩餘結果歡迎廣大網友自己下載代碼嘗試

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章