前段時間看到趙玉平老師講的關於相親選擇的問題,感覺比較有趣,希望通過概率模擬驗證一下該方法的有效性。
原鏈接如下,感興趣可先了解原講解:管理學博士是怎麼硬核相親的,過程太真實了,最後居然選到這麼好的“對象”!強烈建議大家學學…… %相親 https://v.douyin.com/RWQQnC4/
1.問題重述
1.1原問題描述
各位同學是我的媒人,每位同學爲趙老師介紹一個男朋友,寫下一個數字代表該男生的質量。每次見一個人,單方向不可重複。如決定要則結束,如決定不要則繼續。
總結方法:先選4位同學(共12人)進行詢問,記住其中的最大值,然後再從其他同學中詢問,當得到大於前面所述最大值,則決定要。
1.2數學模型
現將原問題抽象爲以下數學過程
- 隨機生成n個數字;
- 將n個數字依次輸入模型中,當決定選擇其中某一個數字時(記爲第k個,k<n)過程停止;
- 目標是使選出的數字儘可能大,儘可能在n個數字中排在前列。
策略是:
- 記n個數字的前a(a<n,a/n=r)箇中的最大值爲m;
- 從第a+1個數字開始,若小於等於第a個數則捨棄,若大於第a個數字則選擇;
- 若直到最後一個數仍未出現大於第a個數的,則選擇最後一個數。
該過程中存在這樣一個問題:即這n個隨機數服從什麼分佈。先假設其服從0到100之間的均分分佈。
2.過程實現
設n=100,a=33,模擬如下。
import numpy as np class choose: def __init__(self,nums): # nums代表提供的數字集 self.nums = nums self.n=len(nums) def get_max(self,a): # 從前a個數中選出一個最大的,記爲max_num self.a = a self.max_num = max(self.nums[0:a]) def decide(self): self.flag = 0 for i in range(self.a,self.n): # 如果遇到更大的數則選取 if self.nums[i]>self.max_num: self.choice = self.nums[i] self.flag = i # 如全程未遇到更大的數則只能選擇最後一個 if self.flag == 0: self.choice = self.nums[self.n-1] def show(self): print('共有%d個數字,其中的最大值是%f,選擇的數字是%f,排第%d位' % (self.n, max(self.nums), self.choice, sum(c.nums>c.choice)+1)) if self.flag == 0: print('在後續數字中未找到更大的,故最終在所有數字中選擇了最後一個') else: print('選擇了第%d個數字' % (self.flag+1)) n=100 a=33 nums=np.random.uniform(0, 100, size=n) c=choose(nums) c.get_max(a) c.decide() c.show()
模擬幾次,結果如下:
共有100個數字,其中的最大值是96.647844,選擇的數字是96.647844,排第1位 選擇了第64個數字
共有100個數字,其中的最大值是99.571219,選擇的數字是65.277241,排第31位 在後續數字中未找到更大的,故最終在所有數字中選擇了最後一個
共有100個數字,其中的最大值是97.931539,選擇的數字是41.759912,排第50位 在後續數字中未找到更大的,故最終在所有數字中選擇了最後一個
共有100個數字,其中的最大值是99.566610,選擇的數字是99.566610,排第1位 選擇了第73個數字
共有100個數字,其中的最大值是98.271705,選擇的數字是98.271705,排第1位 選擇了第82個數字
可以發現,在5次模擬中有3次選到了全局最大,效果不錯,但也有兩次在後續數字中未找到更大的。
直接進行100000次模擬,觀察情況:
choices = [] rank = [] fail = 0 for i in range(0,100000): if i%1000==0: print(i) nums=np.random.uniform(0, 100, size=n) c=choose(nums) c.get_max(a) c.decide() choices.append(c.choice) rank.append(sum(c.nums>c.choice)+1) if c.flag == 0: fail += 1
觀察選出的數字的分佈:
import matplotlib.pyplot as plt plt.hist(choices,100)
可以看到,大部分落在90~100之間,落在99~100之間的概率爲0.28,效果較好,平均值np.mean(choices)=82.01201051783117。
觀察選出的數字在原100個數字中的位次的分佈:
plt.hist(rank,100)
可以看到,大部分位次落在0~10之間,選到最大值(排第1位)的概率爲0.37。平均值np.mean(rank)=18.16273,平均相對位次0.18。
3.敏感性分析
當參數發生變化時,該方法是否仍能有較好的效果。
3.1小樣本
現實情況中我們的精力有限,往往沒有那麼多的選擇機會,那麼,當n減少時(a/n=r近似不變),會發生什麼。
n=50,a=17。進行100000次模擬,平均位次10.10,平均相對位次0.20。
n=10,a=3。進行100000次模擬,平均位次3.03,平均相對位次0.30。
n=5,a=2。進行100000次模擬,平均位次2.2,平均相對位次0.44。
隨着n的減小,能夠取得的平均相對位次的變化情況如下圖。
可見隨着n的減小,該方法的效果變差,但由於樣本少,該現象情有可原。
3.2策略值r
保持n=100不變,觀察r的變化對結果的影響,對r從0.1到0.9取9個值,每個值模擬10000次。
n=100 rs=np.linspace(0.1, 0.9, 9) m_choices=[] m_rank=[] m_fail=[] for r in rs: print(r) a=int(round(n*r)) choices = [] rank = [] fail = 0 for i in range(0,10000): nums=np.random.uniform(0, 100, size=n) c=choose(nums) c.get_max(a) c.decide() choices.append(c.choice) rank.append(sum(c.nums>c.choice)+1) if c.flag == 0: fail += 1 m_choices.append(np.mean(choices)) m_rank.append(np.mean(rank)) m_fail.append(fail)
r對選擇結果的影響:
plt.plot(rs,m_choices)
plt.plot(rs,m_fail)
當r較大時,很容易在後續數字中找不到更大的,從而獲得較差的最終選擇。
n=10,觀察r的變化對結果的影響,對r從0.1到0.9取9個值,每個值模擬10000次。
plt.plot(rs,m_choices)
plt.plot(rs,m_fail)
當n較小時,雖然隨着r的增大,在後續數字中找不到更大的數字的概率也是近乎線性增大,但是如果a太小則選不到較大的閾值,因此r在0.1到0.9之間存在最優解。
這啓示我們,當基數n較大時,r的取值可適當減小,以便獲得更優的結果。
4.進一步討論
可以看到,對於該方法,當在後續數字中找不到更大的數字時,只能被迫選擇最後一個數字,導致結果較差。一個後續改進的思路是,當在一段時間內(第a個數字後的一些數字中)未發現更好的時,應適當降低要求。
對於不同的n,存在不同r的最優取值,n越大,r的最優取值越小。
如果原數據服從其他分佈規律,如卡方分佈,F分佈等,則結果可能有不同的表現,可能需要根據不同的分佈採取不同的策略。
實際上,迴歸相親問題,現實情況遠比該數學問題複雜的多,首先每個人有自己的特點,不能被很好地量化打分,即使打分也是各方面表現有不同的分數,其次,n是不可控的,且人的思維都是不斷髮展變化的,挑選者不可能一成不變地遵循一個死板的策略,被挑選者也不一定在被挑選後即接受。最後,且行且珍惜。