本題解答參考:
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
進一步地,我們給出假設空間並消除亢餘的步驟,如下:
- 取48種假設中的k元組合。
- 對此k個假設進行析取運算,即按位相或。
- 將所得的結果存入一個集合A,若集合A爲空集,則直接添加進去,若A非空,則後續的結果需先與集合A中所有的元進行比較,若有相同的,則捨去該結果,這一步便實現了亢餘的剔除。
- 遍歷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花了我將近我四個小時,本程序存在很大的改進空間,剩餘結果歡迎廣大網友自己下載代碼嘗試