国产亚洲精品久久YY5099,精品国产动漫免费一区二区三区 http://www.51zclw.cn 寶寶取名 公司起名 專家起名 周易起名 姓氏起名 Sat, 19 Nov 2022 02:29:49 +0000 zh-Hans hourly 1 https://wordpress.org/?v=6.8.2 http://www.51zclw.cn/wp-content/uploads/2023/04/2023042403580774.png 算法 – 寶寶取名網(wǎng) http://www.51zclw.cn 32 32 店名測(cè)試打分免費(fèi)測(cè)試?店名測(cè)試打分免費(fèi)測(cè)試吉兇! http://www.51zclw.cn/archives/25944 Sat, 19 Nov 2022 02:29:40 +0000 http://www.51zclw.cn/?p=25944 朋友們,我來了~

今天這一章還是講測(cè)試,基于屬性的測(cè)試(Property-Based Testing)。其實(shí)我感覺叫隨機(jī)驗(yàn)證測(cè)試更通俗易懂一點(diǎn)。

我們?nèi)粘W约簻y(cè)試的時(shí)候,會(huì)有一個(gè)問題,就是我們既是裁判,也是球員。因?yàn)槲覀兊某绦蚓褪琼樦覀兊乃悸穼懙?,測(cè)試也是按照同樣的思路來測(cè)試的。所以,我們可能通常很難測(cè)出我們自己寫出來的bug。

解決這個(gè)問題的一個(gè)辦法,就是把測(cè)試交給其他人。這也是測(cè)試這個(gè)崗位存在的意義之一。但是,如果我們把測(cè)試交給了其他人,上一章提到的,對(duì)測(cè)試的思考可以幫助更好地設(shè)計(jì)程序,這個(gè)好處就不存在了。

基于屬性測(cè)試

解決這個(gè)問題的辦法就是,把其他人,換成我們的計(jì)算機(jī),讓計(jì)算機(jī)來幫我們自動(dòng)測(cè)試。

之前關(guān)于契約或者合約(Contract)那一章中,提到了要給程序制定一個(gè)合同,規(guī)定了輸入的規(guī)則、輸出的規(guī)則、以及不變量(比如給一個(gè)數(shù)組排序,進(jìn)去和出來的數(shù)組長(zhǎng)度應(yīng)該是不變的)。

那么合約和不變量在這里統(tǒng)稱為屬性(property),很抽象是不是?個(gè)人覺得它的定義并不重要,舉個(gè)例子就懂了。

比如我們要驗(yàn)證一個(gè)list的排序,我們可以驗(yàn)證這兩件事:1.輸入和輸出的list長(zhǎng)度是否相等;2.是不是list里的每一個(gè)都比它前面一個(gè)大。

python寫出來就是這樣:

from hypothesis import given
import hypothesis.strategies as some

@given(some.lists(some.Integers()))
def test_list_size_is_invariant_across_sorting(a_list):
original_length = len(a_list)
a_list.sort()
assert len(a_list) == original_length

@given(some.lists(some.text()))
def test_sorted_result_is_ordered(a_list):
a_list.sort()
for i in range(len(a_list) – 1):
assert a_list[i] <= a_list[i + 1]

更重要的是,它利用了hypothesis這個(gè)模塊,@given(some.lists(some.integers()))會(huì)讓它在運(yùn)行的時(shí)候,利用隨機(jī)的數(shù)值把同樣的方法運(yùn)行100次。它會(huì)把出現(xiàn)錯(cuò)誤的情況記錄下來。

對(duì)數(shù)器

之前在學(xué)習(xí)算法的時(shí)候,也接觸到了一個(gè)叫做對(duì)數(shù)器的概念,其實(shí)和這里的基于屬性測(cè)試,基本上是一件事情。

就是為了驗(yàn)證我們的算法A寫對(duì)了,我們先寫一個(gè)絕對(duì)正確的算法B,不管效率,只管正確。然后用同樣的數(shù)據(jù)同時(shí)跑算法A和算法B。當(dāng)然也是隨機(jī)跑很多次啦。如果兩個(gè)出現(xiàn)了不同的結(jié)果,那就說明算法A寫錯(cuò)了。

思路基本相同,只不過,相對(duì)來說,可能這個(gè)基于屬性測(cè)試更嚴(yán)謹(jǐn)一點(diǎn),比如,同樣是排序算法,我用我寫的冒泡排序算法來驗(yàn)證我的選擇排序算法,萬一,我的冒泡排序也寫錯(cuò)了呢?但是,如果我直接從根本上解析出排序(正序)就是后一個(gè)比前一個(gè)大,顯然是更不容易出問題的,其實(shí)也能更進(jìn)一步地鍛煉我們尋找根本問題的能力。

當(dāng)然啦,嚴(yán)格意義上來說,比較后一個(gè)比前一個(gè)大,這個(gè)本身也是一種算法。

實(shí)話說,要思考清楚有哪些屬性是要測(cè)試的,這件事本身就充滿了難度。如果你平時(shí)沒有這個(gè)習(xí)慣,突然讓你想,你會(huì)大腦一片空白的,就像是剛學(xué)編程那會(huì),遇到了一個(gè)需求完全不知道從哪里下手的那種感覺。坦白的說,我現(xiàn)在就是這種狀態(tài),想必這也是需要刻意練習(xí)的。

這種隨機(jī)大量測(cè)試的方式,可以幫助我們測(cè)出一些邊界值,測(cè)出一些我們想不到的情況。往往最容易出問題的地方也是在邊界值的地方。就跟開車似的,車開起來了,通常都沒什么問題,但是起步可能會(huì)熄火,停車可能會(huì)倒不進(jìn)去。

Java的相關(guān)框架

關(guān)于這個(gè)基于屬性測(cè)試的框架,我隨手搜了一搜,Python有的,沒理由咱們Java沒有,對(duì)不對(duì)?

一、找到兩個(gè)github上開源:

1.https://github.com/HypothesisWorks/hypothesis-java

2.https://github.com/quicktheories/QuickTheories

二、找到一個(gè)都已經(jīng)有自己網(wǎng)站的(雖然也有g(shù)ithub):

https://jqwik.net/

三、還有一個(gè)直接是JUnit家的JUnit-Quickcheck(感覺上這個(gè)更香一點(diǎn))

https://github.com/pholser/junit-quickcheck

我還沒來得及仔細(xì)研究,朋友們可以先自行研究起來~

另一個(gè)實(shí)例

書中其實(shí)還提到了另外一個(gè)更加實(shí)際一點(diǎn)的例子,但我個(gè)人覺得那算不上bug,它和實(shí)際的需求有關(guān)系。這邊簡(jiǎn)單提一下吧,或許你也有不同的見解。

大概就是有一個(gè)倉(cāng)庫(kù)類,它可以存放各種貨品,不同貨品有各自的數(shù)量,大概是這樣一個(gè)結(jié)構(gòu)吧:List<Map<String,Integer>>。然后我們可以查詢某個(gè)貨品是否有庫(kù)存、查詢某個(gè)貨品還剩多少庫(kù)存、可以從中取出某個(gè)數(shù)量的貨品。

代碼如下:

class Warehouse:
def __init__(self, stock):
self.stock = stock

def in_stock(self, item_name):
return (item_name in self.stock) and (self.stock[item_name] > 0)

def take_from_stock(self, item_name, quantity):
if quantity <= self.stock[item_name]:
self.stock[item_name] -= quantity
else:
raise Exception("Oversold {}".format(item_name))

def stock_count(self, item_name):
return self.stock[item_name]

倉(cāng)庫(kù)的初始化是這樣的:

wh = Warehouse({"shoes": 10, "hats": 2, "umbrellas": 0})

然后,同樣用了hypothesis來做批量測(cè)試,然后在調(diào)用take_from_stock( item_name='hats', quantity=3)這樣一組數(shù)據(jù)的時(shí)候報(bào)錯(cuò)了。

作者說,在in_stock我們不應(yīng)該只判斷庫(kù)存是不是大于0,而是要判斷它有沒有包含我們要拿取的貨品數(shù)。代碼應(yīng)該改成這樣:

def in_stock(self, item_name, quantity):
return (item_name in self.stock) and (self.stock[item_name] >= quantity)

反正我是覺得這不算個(gè)bug吧,畢竟在真正獲取貨品的時(shí)候,就報(bào)錯(cuò)了呀。要看我們對(duì)于in_stock這個(gè)方法本身的要怎么定義咯,是只需要知道它還有沒有庫(kù)存,還是需要知道它有沒有我需要的庫(kù)存。

雖然實(shí)際需求中,后者可能性更大,但是在take_from_stock方法里報(bào)錯(cuò),又有什么問題呢?(又或者,作者只是想舉個(gè)例子,是我太較真了)

尾聲

基于屬性的測(cè)試是對(duì)于單元測(cè)試的補(bǔ)充,對(duì)于單元測(cè)試的思考,可以讓我們思考代碼實(shí)現(xiàn)的其他方式?;趯傩缘臏y(cè)試,可以讓我們更加清晰,我們的方法能干什么不能干什么,同時(shí),也消除一些意外的情況。

如果,你還沒有把這兩種測(cè)試用起來,現(xiàn)在就趕緊用起來吧~

]]>
fuller是什么意思英語?fuller是什么意思! http://www.51zclw.cn/archives/23444 Thu, 20 Oct 2022 17:07:29 +0000 http://www.51zclw.cn/?p=23444 嘉賓介紹

克里斯托弗·貝澤梅克 Christoph Bezemek

奧地利格拉茨大學(xué)教授、法學(xué)院院長(zhǎng)

Professorand Dean of theFaculty of Law at theKarl-Franzens-University Graz

克里斯托弗·貝澤梅克獲得耶魯大學(xué)法學(xué)院LL.M和維也納大學(xué)哲學(xué)學(xué)位,擔(dān)任歐洲、非洲和北美多所大學(xué)的客座教授。他的研究重點(diǎn)包括憲法、法律和政治理論,并就這些主題發(fā)表了大量著作。除此之外,他還是《維也納國(guó)際憲法法雜志》的聯(lián)合主編,以及哈特的“維也納法律哲學(xué)講座”系列的聯(lián)合主編。

Prof.Bezemek completed a postgraduate degree from Yale Law School as a Master of Laws(LL.M.). He also holds a degree in philosophy from the University of Vienna.He has held numerous visiting teaching appointments at Universities in Europe, Africa, and North America. His research focuses on Constitutional Law, Legal and Political Theory and he has published extensively on those topics. He is, among other things, the Co-Editor-in-Chief of the Vienna Journal on International Constitutional Law (ICL-Journal, published with De Gruyter) and a Co-Editor of Hart’s “Vienna Lectures on Legal Philosophy”-Series.

算法與法的概念

§ 1

陌生的領(lǐng)域

我們處于一個(gè)陌生的領(lǐng)域,至少學(xué)術(shù)上如此。我們這門學(xué)科是這樣的——在這一點(diǎn)上甚至可能獨(dú)此一家——在主題問題上并沒有達(dá)成一致?!笆裁词?a href="http://www.51zclw.cn/archives/tag/%e6%b3%95%e5%be%8b" title="【查看含有[法律]標(biāo)簽的文章】" target="_blank">法律?”即使不稱之為法學(xué)的最大問題,也是重大問題之一。這個(gè)問題意義重大,卻也不值一提。對(duì)大多數(shù)律師來說,“什么是法律”這個(gè)問題與他們?nèi)粘9ぷ鞯年P(guān)系,就如同“什么是醫(yī)學(xué)”與醫(yī)生的關(guān)系一樣:那就是毫無關(guān)系。甚至,醫(yī)生定義醫(yī)學(xué)或許還要更容易一些。

所以,一般情況下即使我們不知道自己在處理什么也沒關(guān)系。但說“不知道”可能太苛刻了。從學(xué)科的角度來看,與其說我們“不知道”法律是什么,不如說我們不能就這一問題達(dá)成一致,因?yàn)槲覀儗?duì)權(quán)利的基本假設(shè)和態(tài)度在主題表現(xiàn)方面的分歧太大了。這一點(diǎn)也值得注意,但通常也不值一提。畢竟,對(duì)于這一主題,擁有根本不同的基本假設(shè)和主張的律師也可以談?wù)摾碚搯栴},而不會(huì)陷入巴比倫式的混亂。如果法律學(xué)者們不理解彼此,那并不是因?yàn)樗麄兓ハ酂o法理解。

相應(yīng)地,由于這樣一個(gè)事實(shí):盡管存在各種的差異,但我們學(xué)科中的各個(gè)思想傳統(tǒng)都有一個(gè)共同的出發(fā)點(diǎn)。只是,就目前而言,在給定的語境中,真正重要的是,與“法律是什么”相比,人們很少談?wù)摗胺ㄊ鞘裁础?。?dāng)一個(gè)人這樣做的時(shí)候,幾乎帶有一種懺悔的性質(zhì),因?yàn)樗欠▽W(xué)的重大問題之一。

§ 2

巴比倫時(shí)期

在許多情況下,法和算法之間的這種相互關(guān)系如何表現(xiàn),取決于詢問對(duì)象。如果你問數(shù)學(xué)家,那么一切以結(jié)果為導(dǎo)向的、機(jī)械的、循序漸進(jìn)的可以被理解為算法的指令——縱使再簡(jiǎn)單——也在結(jié)構(gòu)上比法律規(guī)范中包含的事實(shí)和法律后果的相互作用更為高級(jí)?;蛘邠Q一種說法——一種或許更為簡(jiǎn)單的說法:法律規(guī)范只是算法的一種應(yīng)用或子案例,或者至少有些學(xué)者認(rèn)為有些法律規(guī)范就是如此。例如,在讓-盧克·沙拜爾(Jean-Luc Chabert)編寫的《算法的歷史》(History of Algorithms)中,我們能讀到巴比倫人——回到巴比倫時(shí)——已經(jīng)使用算法來解決法律問題。

這種觀點(diǎn)并非沒有誘惑,來看看《漢謨拉比法典》第110節(jié)中的規(guī)范,根據(jù)該規(guī)范,不居住在修道院的圣役,如果經(jīng)營(yíng)或出入啤酒店喝酒,要被處以火刑。

對(duì)于有些人來說,這種觀點(diǎn)也并非沒有誘惑?;谥T如此類的例子,人們想要理解在由大前提、小前提和結(jié)論組成的三段論中作為一種“編程語言”如何適用法律。

誠(chéng)然,在這些觀點(diǎn)的背后隱藏著一種不成熟的形式主義,這種形式主義為現(xiàn)代實(shí)證主義思想家如漢斯·凱爾森(Hans Kelsen)所反對(duì)。只需將其與20世紀(jì)法律解釋中討論最多的問題作一個(gè)簡(jiǎn)單的比較,就可以發(fā)現(xiàn)如何處理圣役,比如當(dāng)她在啤酒店里喝水,當(dāng)她是被迫喝啤酒,當(dāng)她不是被迫但不是在啤酒店里而是在啤酒店前喝啤酒,或者當(dāng)她自愿在啤酒店里喝啤酒但卻是在剛從修道院逃出后才喝的,所能提出的問題不比“公園內(nèi)禁止車輛”這條規(guī)定上所能提出的問題更簡(jiǎn)單。

這并不是否定三段論在法律論證中的整體作用。但這個(gè)例子意在強(qiáng)調(diào),法律論證不僅僅像算盤算術(shù)一樣,“法律的開放結(jié)構(gòu)”也不僅僅意味著沉溺于演繹推理。在這方面,“法律的生命(一直)不只是邏輯;不只是結(jié)果導(dǎo)向的機(jī)械論的步進(jìn)指令的實(shí)施”。在古代巴比倫,就像在今天的奧地利一樣,正如霍姆斯大法官指出的,“在決定統(tǒng)治人類的規(guī)則時(shí),時(shí)下所感受到的必要性、流行的道德觀念和政治觀點(diǎn)、公共政策的已知或無意識(shí)的直覺,甚至是規(guī)則制定者與他們的同胞共享的偏見,都比三段論更為重要?!?/p>

現(xiàn)在,我們能夠這樣說:法律不是一種算法。至少不是上面所描述的那種算法。

相反,這并不意味著算法不是法律——盡管這樣說略微有些無視同一性定理的意味。至少如果我們相信勞倫斯?萊斯格(Lawrence Lessig)的話,就是如此。萊斯格在1999年發(fā)表了關(guān)于網(wǎng)絡(luò)空間監(jiān)管特性的分析,正是在這篇文章中他推導(dǎo)出并引入了被多次引用的流行語“代碼即法律”。萊斯格認(rèn)為,基于算法的代碼在虛擬現(xiàn)實(shí)中發(fā)揮了與法律監(jiān)管相同的功能。“在現(xiàn)實(shí)世界中,我們意識(shí)到法律是如何監(jiān)管的——通過憲法、制定法和其他法律法典。在網(wǎng)絡(luò)空間,我們必須理解另一種“代碼”是如何監(jiān)管的——軟件和硬件(即網(wǎng)絡(luò)空間的“代碼”)構(gòu)成了網(wǎng)絡(luò)空間,也同樣監(jiān)管著它。

這里的代碼是“法律的法典”。對(duì)此,我想說,這絕對(duì)是一種誤解。

在此,我再次引用萊斯格的一段話,讓這種誤解更清楚地突顯出來。在這段話中,萊斯格承認(rèn)算法編碼和法律規(guī)范之間存在差異,但卻將其棄之不顧,以便進(jìn)行比較。他解釋說:“霍姆斯大法官以把監(jiān)管機(jī)構(gòu)的重點(diǎn)放在了‘壞人’身上聞名。他提出了一種以‘壞人’為核心的監(jiān)管理論。他的關(guān)鍵點(diǎn)并非每個(gè)人都是‘壞人’;相反,關(guān)鍵是我們?nèi)绾尾拍茏詈玫貥?gòu)建監(jiān)管體系?!彼J(rèn)為“我的觀點(diǎn)也是如此。我建議,如果我們考慮到監(jiān)管的‘程序人’理論,就能學(xué)到一些東西——它主要關(guān)注代碼監(jiān)管。”我承認(rèn),“壞人”和“程序人”是有趣的文字游戲。不過在我看來,它也就僅此而已。

霍姆斯在對(duì)美國(guó)“法律預(yù)測(cè)說”的建立產(chǎn)生了巨大影響的《法律的道路》中說的是什么?霍姆斯在萊斯格提到過的關(guān)鍵段落中這樣評(píng)論:“如果一個(gè)人只想了解法律而不關(guān)心其他,那么就必須把法律看成一個(gè)只關(guān)心這類知識(shí)能讓他預(yù)測(cè)到后果的壞人,而不是一個(gè)在良心的模糊約束下為他的行為尋找理由的好人,不管是在法律范圍內(nèi)還是法律之外?!?/p>

那個(gè)壞人,是法學(xué)界被誤解最多的生物之一。在我看來,他一點(diǎn)都不壞,意思是:不是真的壞。即使我在這一領(lǐng)域的許多同事不這么認(rèn)為。因?yàn)樗z毫沒有道德觀念,而不是不遵守道德。在法律以客觀的意義呈現(xiàn)給他的抽象事物的具象化中,他只是想知道他能走多遠(yuǎn),或者更確切地說,理論上能走多遠(yuǎn)。壞人并不比教義學(xué)者更壞或更好,根據(jù)凱爾森(Kelson)的說法,教義學(xué)者在法律政策方面為行動(dòng)和決策提供選擇。在這方面,壞人并不比她、我或我們所有人更好或更壞:像壞人一樣,我們能夠在這些選擇的基礎(chǔ)上做決定,來培養(yǎng)克里斯托弗·穆勒斯(Christoph M?llers)分別以積極和消極的術(shù)語恰如其分地描述的“規(guī)范的可能性”——遵循法律和違反法律之間的選擇,在這里——也是凱爾森的說法——法律第一次迎來它真正的考驗(yàn)。

§ 3

程序人

但這與我們的問題以及在算法代碼調(diào)節(jié)效應(yīng)下的“程序人”有多大關(guān)系?關(guān)系非常大。但要詳細(xì)說明,我們必須面對(duì)這樣一個(gè)大問題。為什么“代碼”不是“法律”?畢竟,我們的虛擬現(xiàn)實(shí)在最激烈的意義上表現(xiàn)為一種外部強(qiáng)制秩序,可以被理解為一種行為控制的“社會(huì)技術(shù)”;然而,具有決定性區(qū)別的是,這種外部強(qiáng)制秩序并不對(duì)應(yīng)于那些受其約束的人的選擇自由。在萊斯格的理解中,調(diào)節(jié)效應(yīng)——由算法代碼決定的環(huán)境——來自于現(xiàn)實(shí)的客觀秩序,即使它是虛擬現(xiàn)實(shí),它限制了個(gè)體的行為,而不像上面描述的那樣隱藏任何“規(guī)范的可能性”。它的潛力是在事實(shí)中而不是在規(guī)范中展開的,其結(jié)果是,人不是主動(dòng)地躺在床上,而是被動(dòng)地躺在床上。如果這是一張普羅克拉斯提斯之床,它也一樣好。因此,“程序人”所受的控制效果與高速公路上的防撞護(hù)欄的控制效果或公園長(zhǎng)凳上的防流浪漢護(hù)欄的控制效果沒有區(qū)別,它們可以被理解為一種針對(duì)汽車司機(jī)或社會(huì)上不受歡迎的人的社會(huì)技術(shù)。這里所使用的外部強(qiáng)制,當(dāng)然不是凱爾森所設(shè)想的,當(dāng)他定位動(dòng)機(jī)時(shí),他所考慮的動(dòng)機(jī)是,約束守法和有效地避免規(guī)范所威脅的邪惡。是否服從它并不取決于個(gè)人。

因此,算法代碼的事實(shí)能力和創(chuàng)造世界的能力與法律秩序用來實(shí)現(xiàn)其控制主體的要求的世界內(nèi)在規(guī)范能力之間存在張力。它的強(qiáng)制性——就像任何規(guī)范秩序的強(qiáng)制性一樣——預(yù)設(shè)了自愿性:即人不想犯罪。

§ 4

元宇宙中的富勒

現(xiàn)在有人可能會(huì)反對(duì):這太過簡(jiǎn)單化了。因?yàn)檫@充其量只是對(duì)創(chuàng)造的外部范圍的恰當(dāng)描述。造物主如何對(duì)待他的創(chuàng)造物是另一回事。

讓我們簡(jiǎn)要地看看馬克·扎克伯格對(duì)“元宇宙”的設(shè)想。在我看來,這是一個(gè)可怕的反烏托邦的虛擬環(huán)境,它完全復(fù)制了真實(shí)行動(dòng)和互動(dòng)的可能性,當(dāng)然,應(yīng)該遠(yuǎn)不止于此。扎克伯格在介紹它時(shí)做了長(zhǎng)達(dá)一個(gè)多小時(shí)的浮夸演示,講述了元宇宙中可能發(fā)生的事情。但這種可能性顯然不是前面提到的規(guī)范的可能性,而是現(xiàn)實(shí)的延伸,沿著它產(chǎn)生的事實(shí)發(fā)現(xiàn)它的限制。規(guī)范性要求是在由此產(chǎn)生的現(xiàn)實(shí)中明確、要求和執(zhí)行的,這一點(diǎn)絕不能被排除在外。為什么要這樣呢?直到今天,我還可以在Instagram上發(fā)布裸照。根據(jù)使用條款,我不應(yīng)該這么做。如果我這么做了,我會(huì)受到制裁。但是,這并不構(gòu)成在任何方面都具有結(jié)構(gòu)意義的挑戰(zhàn),也不構(gòu)成宣傳法律和代碼等同的理由。

偉大的美國(guó)法學(xué)家朗·富勒(Lon Fuller)關(guān)于法律秩序功能的觀點(diǎn)也適用于此,他把法律理解為“使人的行為服從規(guī)則治理的事業(yè)”。我們已經(jīng)討論了構(gòu)成這種行為的所有先決條件和不確定因素。

照這一理解,同樣的情況也反映在一般層面上,即前面提到的自愿接受法律的強(qiáng)制?!?a href="http://www.51zclw.cn/archives/tag/%e8%a7%84%e5%88%99" title="【查看含有[規(guī)則]標(biāo)簽的文章】" target="_blank">規(guī)則”(廣義上)包含上述適用于政治共同體的規(guī)范性潛能,從形式上講,就是規(guī)范的可能性的整個(gè)視界。從實(shí)質(zhì)上講,如霍姆斯所說,即前面提到的時(shí)下所感受到的必要性、流行的道德觀念和政治觀點(diǎn)、公共政策的已知或無意識(shí)的直覺,還有規(guī)則制定者與他們的同胞共同的偏見。

富勒認(rèn)為,為了讓人類行為服從法治,它需要保證互惠性:立法者保證,正是這些規(guī)則衡量了那些受法律約束的人的行為,并與遵守或不遵守相應(yīng)的后果聯(lián)系在一起(而不是其他)。法律秩序由此構(gòu)成一個(gè)相互期待的可追究與問責(zé)的空間。能夠?qū)崿F(xiàn)這些相互期望的先決條件是遵守某些結(jié)構(gòu)原則,這些原則構(gòu)成了富勒所說的“法律的內(nèi)在道德”。這些原則包括法律規(guī)則中的必要公示和杜絕矛盾,避免溯及性立法,以及規(guī)則與執(zhí)行的對(duì)應(yīng)。

然而,最重要的是,必須要有規(guī)則:一般來說,政治共同體遵循的抽象規(guī)范可以指導(dǎo)他們的行動(dòng)。

在遵循這些結(jié)構(gòu)性原則的同時(shí),法律的內(nèi)在道德傳遞著上述意義的政治外在道德。關(guān)于這種道德是否必須是嚴(yán)格意義上的“道德”這一問題則眾說紛紜,正如畢希納(Büchner)導(dǎo)演的電影《沃伊采克》(Woyzek)中上尉所說的那般。富勒認(rèn)為這是必要的,但他的許多批評(píng)者并不這么認(rèn)為。我傾向于同意他的批評(píng)者們,并假定這里所說的像適用于《歐洲人權(quán)公約》一樣適用于古巴比倫的法律,但我補(bǔ)充這一點(diǎn)僅僅是為了澄清。

§ 5

共享的政治道德

但是這些對(duì)于我的話題來說意味著什么?

首先,如果人們要遵循富勒的理解,將法律理解為“使人類行為服從規(guī)則治理的事業(yè)”(我承認(rèn)這是一個(gè)寬泛的定義),那么就需要既可以遵守又可以無視的規(guī)則;簡(jiǎn)而言之,一個(gè)基于前面提到過的個(gè)人自主性的規(guī)范性的可能性空間。安東尼·凱西(Anthony Casey)和安東尼·恩比列特(Anthony Nbilett)宣揚(yáng)基于算法的微觀指令,用兩位作者的話來說就是:“精確定制的指令,具體規(guī)定在每一種特殊情況下允許做什么”,告知個(gè)人“在行動(dòng)之前確切地如何遵守每一項(xiàng)相關(guān)法律”,不僅會(huì)導(dǎo)致“規(guī)則和標(biāo)準(zhǔn)的消亡”,就像他們兩人坦言承認(rèn)的那樣。它們不符合上述意義上的法律,至少就它們否定個(gè)人的選擇是任何忠于法律的基本前提這一點(diǎn)來說是如此。

奧姆里·本-沙哈爾(Omri Ben-Shahar)和阿里爾·波拉特(Ariel Porat)在最近的一份出版物中以“針對(duì)不同人的不同規(guī)則”為題討論了在具體實(shí)施松散定義的目標(biāo)時(shí)的個(gè)性化法律,無論如何,這與之前制定的標(biāo)準(zhǔn)明顯存在矛盾。碎片化的法律既是一個(gè)政治共同體的催化劑,也表明了一個(gè)政治共同體不再能夠在政治道德的外部領(lǐng)域中發(fā)現(xiàn)和了解自己。

然后,當(dāng)一般立法開始侵蝕,個(gè)別立法的變形便不能停止。換句話說:如凱斯?桑斯坦(Cass Sunstein)和丹尼爾?卡尼曼(Daniel Kahneman)最近再次大力宣傳的,越來越多的法律決策轉(zhuǎn)向基于算法的系統(tǒng),就像其支持者所主張的那樣,可以輕松地(而且顯著地)減少“噪音”,即擾亂或影響我們決策的隨機(jī)因素。但代價(jià)是巨大的。我甚至不是在談?wù)撈顔栴}或黑箱現(xiàn)象,這些算法決策要么延續(xù)程序員的偏見,要么使決策不透明。通過優(yōu)化算法可以減少偏差,黑盒可以打開,決策路徑也可以設(shè)法理解。這樣的決定缺乏以上文提到過的互惠性來區(qū)分立法的基本特征。他們?nèi)狈?duì)于理由的陳述,不是在不能理解它們的意義上,而是在要求參與從法律上實(shí)現(xiàn)的政治道德的共同領(lǐng)域的意義上。做出法律決定意味著參與這種政治道德,而在這種參與中,為建立在共同體的共同政治道德基礎(chǔ)上的判斷辯護(hù),僅靠透明是無法做到的。

王 健 金惠珠

]]>
60行代碼實(shí)現(xiàn)經(jīng)典論文:0.7秒搞定泊松盤采樣,比Numpy快100倍 http://www.51zclw.cn/archives/19343 Mon, 12 Sep 2022 19:54:39 +0000 http://www.51zclw.cn/?p=19343

編輯整理自 太極圖形
量子位 | 公眾號(hào) QbitAI

隨機(jī)均勻的點(diǎn)組成的圖案,在動(dòng)植物身上已經(jīng)很常見了。

像楊梅、草莓、荔枝、紅毛丹這樣的水果,表面都有顆?;蛘呙l(fā)狀的結(jié)構(gòu),它們隨機(jī)、均勻地散布在水果表面:

類似的圖案在動(dòng)物身上也有,比如大家都愛涮的毛肚:

同樣地,在計(jì)算機(jī)模擬下,也有不少場(chǎng)景需要在空間中隨機(jī)、均勻地生成點(diǎn)。

像生成動(dòng)物毛發(fā)時(shí)的毛孔位置、多人對(duì)戰(zhàn)游戲中的玩家出生位置、生成森林時(shí)的樹木位置等等。

這些場(chǎng)景的共同特點(diǎn)是,都需要讓任何兩點(diǎn)之間的距離大于等于一個(gè)下界(這個(gè)下界是預(yù)設(shè)的,改變它就可以控制生成點(diǎn)之間的間隔)。

但如果直接使用完全隨機(jī)生成的點(diǎn),大概率會(huì)獲得一個(gè)很不均勻的分布結(jié)果,有些地方“扎堆”、有些地方稀疏:

如果用這樣的點(diǎn)來模擬毛發(fā)等位置生成,效果就很差。

所以,需要在生成點(diǎn)的過程中加入一個(gè)距離判斷,來剔除那些不合要求的點(diǎn)。

此前,用numpy生成這樣一個(gè)效果,往往需要70s左右,非常不劃算。

現(xiàn)在,太極圖形基于Taichi實(shí)現(xiàn)了一個(gè)超快算法,同樣的效果運(yùn)行在單個(gè)CPU線程上,只需要0.7s就能生成這樣的圖案,快了100倍左右。

一起來看看他們是怎么做的。

采用Bridson算法實(shí)現(xiàn)

此前,有一種常見算法dart throwing (像一個(gè)人蒙上眼睛胡亂扔飛鏢的樣子)

每次在區(qū)域內(nèi)隨機(jī)選擇一個(gè)點(diǎn),并檢查該點(diǎn)與所有已經(jīng)得到的點(diǎn)之間是否存在“沖突”。

若該點(diǎn)與某個(gè)已得到的點(diǎn)的最小距離小于指定的下界,就拋棄這個(gè)點(diǎn),否則這就是一個(gè)合格的點(diǎn),把它加入已有點(diǎn)的集合。

重復(fù)這個(gè)操作直到獲得了足夠多的點(diǎn),或者連續(xù)失敗了N次為止(N是某個(gè)設(shè)定的正整數(shù))

但這種算法效率很低

因?yàn)殡S著得到的點(diǎn)的個(gè)數(shù)增加,沖突的概率越來越大,獲得新的點(diǎn)所需的時(shí)間也越來越長(zhǎng),每次比較當(dāng)前點(diǎn)和所有已有點(diǎn)之間的距離也會(huì)降低效率。

相比之下,Bridson算法則要更加高效。

這個(gè)算法的原理來自于Robert Bridson發(fā)表于2007年的論文”Fast Poisson Disk Sampling in Arbitrary Dimensions”[1](論文非常短,只有一頁(yè)A4紙),如果再去掉標(biāo)題、引言的話,真正的算法內(nèi)容只有一小段話。

開頭這個(gè)動(dòng)圖,演示了Bridson圓盤采樣算法在一個(gè)400×400網(wǎng)格區(qū)域上的運(yùn)行效果,算法嘗試獲得100K個(gè)均勻散布的點(diǎn),實(shí)際生成了大約53.7K個(gè):

這個(gè)動(dòng)畫是使用Taichi生成的,運(yùn)行在單個(gè)CPU線程上,除去編譯的時(shí)間計(jì)算,耗時(shí)僅在0.7s多一點(diǎn),而同樣的代碼翻譯成NumPy要耗時(shí)70s左右。[2]

從上面的動(dòng)畫效果可見,Bridson算法很像包菜的生長(zhǎng)過程:我們從一個(gè)種子點(diǎn)開始,一層一層地向外添加新的點(diǎn)。

每一次我們添加的新的點(diǎn),都位于最外層的點(diǎn)的周圍,并且盡可能地包住最外層。

為了避免每次都檢查和所有已有點(diǎn)之間的距離,Taichi采用了所謂網(wǎng)格的技巧:

將整個(gè)空間劃分為網(wǎng)格,對(duì)一個(gè)需要檢查的點(diǎn),只要找到它所在的網(wǎng)格,然后檢查它和臨近網(wǎng)格中的點(diǎn)之間的最小距離即可。

只要這個(gè)距離大于指定的下界,更遠(yuǎn)處的點(diǎn)就不必再檢查了。這個(gè)技巧在圖形學(xué)和物理仿真中是非常常用的。

這個(gè)采樣過程很難并行化,因?yàn)楫?dāng)一個(gè)線程“偷偷”加入一個(gè)新的點(diǎn)的時(shí)候,會(huì)改變其它所有線程對(duì)距離的判斷。所以Taichi僅使用單個(gè)CPU線程來執(zhí)行這個(gè)算法:

ti.init(arch=ti.cpu)

上面的代碼中通過指定arch=ti.cpu來讓程序運(yùn)行在CPU上。

你可能會(huì)想,既然是單線程+CPU,那為什么不直接寫純Python呢?別著急,我們的計(jì)算部分會(huì)放在ti.kernel函數(shù)中,這種函數(shù)并不運(yùn)行在Python虛擬機(jī)中,而是會(huì)被Taichi編譯執(zhí)行,所以會(huì)比純Python的實(shí)現(xiàn)快很多倍!

在我們介紹Bridson算法的具體實(shí)現(xiàn)之前,你不妨猜猜這個(gè)Taichi程序包含多少行代碼?

安裝和導(dǎo)入Taichi

首先推薦大家使用最新的Taichi發(fā)布版本,這樣可以使用更豐富的功能,在不同平臺(tái)上的支持也更穩(wěn)定。截止本文寫作時(shí)最新版本是1.0.3:

pip install taichi==1.0.3

然后,在代碼開頭寫上:

import taichi as ti
import taichi.math as tm

這樣會(huì)導(dǎo)入Taichi以及Taichi的math模塊。math模塊除了包含常用的數(shù)學(xué)函數(shù)之外,還提供了非常方便的向量運(yùn)算。

準(zhǔn)備工作

在泊松采樣算法中,采樣點(diǎn)之間的距離有一個(gè)下界r。

我們假設(shè)整個(gè)區(qū)域是由N×N個(gè)同樣大小的方格組成的網(wǎng)格區(qū)域,使得每個(gè)小方格的對(duì)角線長(zhǎng)度正好是r,即網(wǎng)格的邊長(zhǎng)是r/√2。

于是任何小方格中至多包含一個(gè)點(diǎn)。如下圖所示:

這就是我們前面提到的網(wǎng)格化方法,即對(duì)于任何一個(gè)點(diǎn)p,設(shè)它所在的方格是D,則任何與p的距離小于等于r的點(diǎn)必然位于以D中心的、由5×5個(gè)方格組成的正方形區(qū)域中。

在檢查距離時(shí),我們只要針對(duì)這個(gè)子區(qū)域進(jìn)行計(jì)算即可。

我們用一個(gè)一維數(shù)組samples和一個(gè)N×N的二維數(shù)組grid來記錄已經(jīng)得到的采樣點(diǎn):

  1. samples保存當(dāng)前所有已經(jīng)采樣點(diǎn)的坐標(biāo),它的每個(gè)元素是一個(gè)二維坐標(biāo)(x,y)。
  2. grid[i, j]是一個(gè)整數(shù),它存儲(chǔ)的是第(i, j)個(gè)方格中采樣點(diǎn)在數(shù)組samples中的下標(biāo)。grid[i, j] = -1表示這個(gè)方格中沒有采樣點(diǎn)。

于是我們的初始設(shè)置可以這樣寫:

grid_n = 400
res = (grid_n, grid_n)
dx = 1 / res[0]
inv_dx = res[0]
radius = dx * ti.sqrt(2)
desired_samples = 100000
grid = ti.field(dtype=int, shape=res)
samples = ti.Vector.field(2, float, shape=desired_samples)

這里網(wǎng)格大小設(shè)置為400×400,它占據(jù)的平面區(qū)域是[0,1]×[0,1],所以網(wǎng)格的步長(zhǎng)是dx = 1/400。采樣的最小間隔是每個(gè)小方格對(duì)角線的長(zhǎng)度,即radius = sqrt(2)*dx。

我們把采樣點(diǎn)的目標(biāo)個(gè)數(shù)設(shè)置為desired_examples=100000,這是一個(gè)目測(cè)值,因?yàn)?00×400的網(wǎng)格包含160000個(gè)小方格,考慮到每個(gè)方格中至多只有一個(gè)點(diǎn),我們能得到的滿足距離約束的點(diǎn)的最大數(shù)目肯定少于160000。

初始時(shí)網(wǎng)格中沒有任何點(diǎn),所以需要將grid中的值都置為-1:

grid.fill(-1)

如何生成新的點(diǎn)

在加入新的隨機(jī)點(diǎn)時(shí),我們總是從已有點(diǎn)的附近隨機(jī)選擇一個(gè)位置,然后比較它和已知點(diǎn)是否滿足最小距離約束,是的話就將其加入已有點(diǎn),否則就將其拋棄然后重新選擇點(diǎn)。

這里需要注意的是:

  1. 當(dāng)一個(gè)已有點(diǎn)附近已經(jīng)被填滿時(shí),我們后面再加入新的點(diǎn)時(shí)就不必考慮它的附近了,可以用一個(gè)下標(biāo)head來記錄這一點(diǎn)。我們約定samples數(shù)組中下標(biāo)< head的點(diǎn)附近都已經(jīng)被填滿,從而不必再考慮它們,只考慮下標(biāo)>= head的點(diǎn)即可。初始時(shí)head = 0。
  2. samples是一個(gè)長(zhǎng)度為100K的數(shù)組,這不代表我們真的能取到這么多點(diǎn),但具體個(gè)數(shù)是多少無法事先確定,所以我們還需要用一個(gè)下標(biāo)tail來記錄目前已經(jīng)獲得的點(diǎn)的個(gè)數(shù)。初始時(shí)tail = 1,因?yàn)槲覀儗⑦x擇區(qū)域中心作為第一個(gè)點(diǎn)。當(dāng)然這個(gè)初始點(diǎn)的位置可以是任意的。
  3. 正如前面提到的,當(dāng)我們檢查一個(gè)點(diǎn)p是否與已有點(diǎn)滿足最小距離約束時(shí),沒有必要遍歷檢查所有的點(diǎn)。只要檢查以p所在方格為中心,由5×5個(gè)方格組成的正方形區(qū)域即可。

檢查一個(gè)點(diǎn)是否和已有點(diǎn)沖突的邏輯我們單獨(dú)寫成一個(gè)函數(shù):

@ti.func
def check_collision(p, index):
    x, y = index
    collision = False
    for i in range(max(0, x - 2), min(grid_n, x + 3)):
        for j in range(max(0, y - 2), min(grid_n, y + 3)):
            if grid[i, j] != -1:
                q = samples[grid[i, j]]
                if (q - p).norm() < radius - 1e-6:
                    collision = True
    return collision

其中p是需要檢查點(diǎn)的坐標(biāo),index=(x, y)是p所在的方格的下標(biāo)。

我們遍歷所有滿足x-2 <= i <= x+2和y-2 <= j <= y+2的下標(biāo)(i, j),檢查方格(i, j)中是否已經(jīng)有點(diǎn),即 grid[i, j]是否等于-1。有的話它與p的距離是否小于radius,然后返回對(duì)應(yīng)的判斷。

完成了準(zhǔn)備工作,我們可以開始正式的循環(huán)了:

@ti.kernel
def poisson_disk_sample(desired_samples: int) -> int:
    samples[0] = tm.vec2(0.5)
    grid[int(grid_n * 0.5), int(grid_n * 0.5)] = 0
    head, tail = 0, 1
    while head < tail and head < desired_samples:
        source_x = samples[head]
        head += 1

        for _ in range(100):
            theta = ti.random() * 2 * tm.pi
            offset = tm.vec2(tm.cos(theta), tm.sin(theta)) * (1 + ti.random()) * radius
            new_x = source_x + offset
            new_index = int(new_x * inv_dx)

            if 0 <= new_x[0] < 1 and 0 <= new_x[1] < 1:
                collision = check_collision(new_x, new_index)
                if not collision and tail < desired_samples:
                    samples[tail] = new_x
                    grid[new_index] = tail
                    tail += 1
    return tail

首先我們把區(qū)域的中心,即坐標(biāo)為(0.5,0.5)的點(diǎn)選擇為初始點(diǎn),讓它作為“種子”將隨機(jī)點(diǎn)逐漸擴(kuò)散到整個(gè)區(qū)域。

接下來的while循環(huán)是算法的主體,這個(gè)循環(huán)是串行執(zhí)行的,只占用一個(gè)線程。

我們每次找到第一個(gè)需要考慮的點(diǎn)samples[head],然后在以它為中心,半徑為[radius, 2*raidus]的圓環(huán)中隨機(jī)選擇100個(gè)點(diǎn),逐個(gè)檢查這100個(gè)點(diǎn)是否不超出[0,1]×[0,1]的區(qū)域范圍,以及是否和已有點(diǎn)沖突。

如果都滿足的話它就是一個(gè)合格的點(diǎn),我們將它的坐標(biāo)和方格下標(biāo)更新到samples和grid中,并將已有點(diǎn)的個(gè)數(shù)tail增加1。在這100個(gè)點(diǎn)都檢查完后,可能有多個(gè)點(diǎn)會(huì)被加入已有點(diǎn)的集合。

注意在半徑為[radius, 2*raidus]的圓環(huán)中采樣可以讓我們得到的點(diǎn)在滿足最小距離約束的同時(shí)距離已有點(diǎn)也不會(huì)太遠(yuǎn)。

當(dāng)這100個(gè)點(diǎn)都檢查完畢后,我們可以認(rèn)為samples[head]這個(gè)點(diǎn)的周圍已經(jīng)沒有空白區(qū)域可以放置新的點(diǎn),所以將head增加1,并重新檢查下一個(gè)samples[head] 附近的區(qū)域。

當(dāng)所有的點(diǎn)周圍的空間都已經(jīng)被填滿,即head = tail時(shí),或者我們已經(jīng)獲得了desired_samples個(gè)點(diǎn),即tail = desired_samples時(shí)循環(huán)結(jié)束。這時(shí)samples中下標(biāo)在0~tail-1范圍內(nèi)的點(diǎn)就是全部的已有點(diǎn)。

展示動(dòng)畫效果

我們可以只用幾行代碼,就把整個(gè)采樣的過程用動(dòng)畫的形式顯示出來:

num_samples = poisson_disk_sample(desired_samples)
gui = ti.GUI("Poisson Disk Sampling", res=800, background_color=0xFFFFFF)
count = 0
speed = 300
while gui.running:
    gui.circles(samples.to_numpy()[:min(count * speed, num_samples)],
                color=0x000000,
                radius=1.5)
    count += 1
    gui.show()

這里我們控制動(dòng)畫的速度為每生成300個(gè)點(diǎn)就繪制一幀。

至此我們已經(jīng)介紹完了程序的所有要點(diǎn),把各部分組合起來:

import taichi as ti
import taichi.math as tm
ti.init(arch=ti.cpu)

grid_n = 400
res = (grid_n, grid_n)
dx = 1 / res[0]
inv_dx = res[0]
radius = dx * ti.sqrt(2)
desired_samples = 100000
grid = ti.field(dtype=int, shape=res)
samples = ti.Vector.field(2, float, shape=desired_samples)

grid.fill(-1)

@ti.func
def check_collision(p, index):
    x, y = index
    collision = False
    for i in range(max(0, x - 2), min(grid_n, x + 3)):
        for j in range(max(0, y - 2), min(grid_n, y + 3)):
            if grid[i, j] != -1:
                q = samples[grid[i, j]]
                if (q - p).norm() < radius - 1e-6:
                    collision = True
    return collision

@ti.kernel
def poisson_disk_sample(desired_samples: int) -> int:
    samples[0] = tm.vec2(0.5)
    grid[int(grid_n * 0.5), int(grid_n * 0.5)] = 0
    head, tail = 0, 1
    while head < tail and head < desired_samples:
        source_x = samples[head]
        head += 1

        for _ in range(100):
            theta = ti.random() * 2 * tm.pi
            offset = tm.vec2(tm.cos(theta), tm.sin(theta)) * (1 + ti.random()) * radius
            new_x = source_x + offset
            new_index = int(new_x * inv_dx)

            if 0 <= new_x[0] < 1 and 0 <= new_x[1] < 1:
                collision = check_collision(new_x, new_index)
                if not collision and tail < desired_samples:
                    samples[tail] = new_x
                    grid[new_index] = tail
                    tail += 1
    return tail

num_samples = poisson_disk_sample(desired_samples)
gui = ti.GUI("Poisson Disk Sampling", res=800, background_color=0xFFFFFF)
count = 0
speed = 300
while gui.running:
    gui.circles(samples.to_numpy()[:min(count * speed, num_samples)],
                color=0x000000,
                radius=1.5)
    count += 1
    gui.show()

代碼總行數(shù):60

One More Thing

具體來說,這篇代碼實(shí)現(xiàn)了兩個(gè)操作

  1. 60行代碼中實(shí)現(xiàn)了一個(gè)完整的泊松采樣動(dòng)畫。
  2. 在一個(gè)400×400的網(wǎng)格中采集了53k個(gè)點(diǎn),但耗時(shí)不到1秒。

相關(guān)代碼可以在文末的原文鏈接中找到。

嚴(yán)格來說,本文實(shí)現(xiàn)的算法和Bridson論文里描述的算法有一點(diǎn)點(diǎn)不一樣的地方(更簡(jiǎn)單一些),但是效果卻差不多。

你能看出是哪里不一樣嗎?(TIP:可以關(guān)注一下原論文Step 2中“active list”的處理方式)

項(xiàng)目地址:
https://github.com/taichi-dev/poisson-sampling-homework

參考資料:
[1]Robert Bridson的原論文見Fast Poisson Disk Sampling in Arbitrary Dimensions.
[2]Poisson采樣用Taichi, Numpy, Numba實(shí)現(xiàn)的benchmark比較見GitHub

— 完 —

量子位 QbitAI · 頭條號(hào)簽約

關(guān)注我們,第一時(shí)間獲知前沿科技動(dòng)態(tài)

]]>