亚洲免费性爱片,普通一级国产毛片,亚洲人成无码区在线观看 http://www.builtinbookshelves.com 寶寶取名 公司起名 專家起名 周易起名 姓氏起名 Sat, 19 Nov 2022 02:29:49 +0000 zh-Hans hourly 1 https://wordpress.org/?v=6.8.2 http://www.builtinbookshelves.com/wp-content/uploads/2023/04/2023042403580774.png 算法 – 寶寶取名網(wǎng) http://www.builtinbookshelves.com 32 32 店名測試打分免費(fèi)測試?店名測試打分免費(fèi)測試吉兇! http://www.builtinbookshelves.com/archives/25944 Sat, 19 Nov 2022 02:29:40 +0000 http://www.builtinbookshelves.com/?p=25944 朋友們,我來了~

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

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

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

基于屬性測試

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

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

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

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

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這個模塊,@given(some.lists(some.integers()))會讓它在運(yùn)行的時候,利用隨機(jī)的數(shù)值把同樣的方法運(yùn)行100次。它會把出現(xiàn)錯誤的情況記錄下來。

對數(shù)器

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

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

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

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

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

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

Java的相關(guān)框架

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

一、找到兩個github上開源:

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

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

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

https://jqwik.net/

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

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

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

另一個實(shí)例

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

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

倉庫的初始化是這樣的:

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

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

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

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

反正我是覺得這不算個bug吧,畢竟在真正獲取貨品的時候,就報錯了呀。要看我們對于in_stock這個方法本身的要怎么定義咯,是只需要知道它還有沒有庫存,還是需要知道它有沒有我需要的庫存。

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

尾聲

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

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

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

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

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

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

克里斯托弗·貝澤梅克獲得耶魯大學(xué)法學(xué)院LL.M和維也納大學(xué)哲學(xué)學(xué)位,擔(dān)任歐洲、非洲和北美多所大學(xué)的客座教授。他的研究重點(diǎn)包括憲法、法律和政治理論,并就這些主題發(fā)表了大量著作。除此之外,他還是《維也納國際憲法法雜志》的聯(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)域

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

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

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

§ 2

巴比倫時期

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

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

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

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

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

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

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

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

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

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

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

§ 3

程序人

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

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

§ 4

元宇宙中的富勒

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

讓我們簡要地看看馬克·扎克伯格對“元宇宙”的設(shè)想。在我看來,這是一個可怕的反烏托邦的虛擬環(huán)境,它完全復(fù)制了真實(shí)行動和互動的可能性,當(dāng)然,應(yīng)該遠(yuǎn)不止于此。扎克伯格在介紹它時做了長達(dá)一個多小時的浮夸演示,講述了元宇宙中可能發(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)該這么做。如果我這么做了,我會受到制裁。但是,這并不構(gòu)成在任何方面都具有結(jié)構(gòu)意義的挑戰(zhàn),也不構(gòu)成宣傳法律和代碼等同的理由。

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

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

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

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

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

§ 5

共享的政治道德

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

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

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

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

王 健 金惠珠

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

編輯整理自 太極圖形
量子位 | 公眾號 QbitAI

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

但這種算法效率很低。

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

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

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

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

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

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

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

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

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

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

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

ti.init(arch=ti.cpu)

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

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

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

安裝和導(dǎo)入Taichi

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

pip install taichi==1.0.3

然后,在代碼開頭寫上:

import taichi as ti
import taichi.math as tm

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

準(zhǔn)備工作

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

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

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

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

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

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

  1. samples保存當(dāng)前所有已經(jīng)采樣點(diǎn)的坐標(biāo),它的每個元素是一個二維坐標(biāo)(x,y)。
  2. grid[i, j]是一個整數(shù),它存儲的是第(i, j)個方格中采樣點(diǎn)在數(shù)組samples中的下標(biāo)。grid[i, j] = -1表示這個方格中沒有采樣點(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)格的步長是dx = 1/400。采樣的最小間隔是每個小方格對角線的長度,即radius = sqrt(2)*dx。

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

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

grid.fill(-1)

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

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

這里需要注意的是:

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

檢查一個點(diǎn)是否和已有點(diǎn)沖突的邏輯我們單獨(dú)寫成一個函數(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,然后返回對應(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ò)散到整個區(qū)域。

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

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

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

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

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

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

展示動畫效果

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

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()

這里我們控制動畫的速度為每生成300個點(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)了兩個操作

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

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

嚴(yán)格來說,本文實(shí)現(xiàn)的算法和Bridson論文里描述的算法有一點(diǎn)點(diǎ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 · 頭條號簽約

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

]]>