214
150

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

いい結婚相手を見つける最適な方法を検証してみた

Last updated at Posted at 2017-06-25

 現在の日本の生涯未婚率によると、男性の4人に1人、女性の7人に1人は50歳まで一度も結婚したことがなく、そうした人たちの割合は今後も増えていくそうです(出典: ハフィントンポスト)。原因は様々あるようですが、やはり**「適当な相手にめぐり合わない」**という理由は上位に来るようです。

 ですが、適当な相手とは、一体全体どういう相手なのでしょうか?

 年収、容姿、性格、家、などなど人によって様々相手に求める条件があるものですが、「人の出会いは一期一会」ともいうように、いい相手とめぐり合えたとしても「もしかしたら今後もっといい人と会えるかも……」などとうじうじしているうちに、機会を逃すことも多いかもしれません(涙

 この問題は、結婚相手を探しているA君がいるとすると、

  1. A君は、これから結婚相手の候補となるN人と女性と出会う
  2. 候補となる相手は、1人ずつ次々に現れる
  3. 候補となる相手は、それぞれ違うスコアを持つ
  4. A君は、なるべく高いスコアの人と結婚したい
  5. 候補となる相手は、A君がYesと言えば結婚できる。だが、残りの候補相手とはもう会えない
  6. 候補となる相手は、A君がNoと言ってしまうと去り、もう会うことはできない

 というルールのゲームに簡略化できます。この問題は、Secretary Problemと呼ばれ、数学的に最適な戦略が存在します。それは、

  1. 最初の37%の人にはそれがどんなスコアであれNoという
  2. それ以降の人には、その人のスコアが今までで1番だったらその人に決める

 という実に簡単なものです。37%というのは、厳密には 1/e に対応します。この戦略により、候補相手の人数Nによらず、高スコアの相手と結婚できる確率が高まるそうです。

 ……と聞いても、「ホントかよー」って正直思ってしまいます。どこかで閾値を設定しないといけないのはわかるのですが、37%という半端な数値ではなく、50%とか、もっと待って80%とかの方がいい相手が見つかるような気もします。

 今回は、簡単なシミュレーションを使って、この戦略を検証したいと思います。

戦略の実装

 Python3を使って計算していきます。まずは、必要なライブラリを入れます。


# import libraries
import random
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

 メインとなる関数は、候補となる人数と、閾値を作るまでに出会う人数をインプットとして、初めて閾値を超えた相手を選ぶようにします。その相手のランクとスコアを、アウトプットします。候補相手のスコアは、0から100のランダムな整数値を取るとします。


# function to find a partner based on a given decision-time
def getmeplease(rest, dt, fig):
    ## INPUT: rest ... integer representing the number of the fixed future encounters
    #         dt ... integer representing the decision time to set the threshold
    #         fig ... 1 if you want to plot the data
    # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    
    # score ranging from 0 to 100
    scoremax = 100
    #random.seed(1220)
    
    # sequence of scores 
    distribution = random.sample(range(scoremax), k=rest)
    
    # visualize distribution
    if fig==1:
        # visualize distribution of score
        plt.close('all')
        plt.bar(range(1,rest+1), distribution, width=1)
        plt.xlabel('sequence')
        plt.ylabel('score')
        plt.xlim((0.5,rest+0.5))
        plt.ylim((0,scoremax))
        plt.xticks(range(1, rest+1))

    # remember the highest score among the 100 x dt %
    if dt > 0:
        threshold = max(distribution[:dt])
    else:
        threshold = 0
    
    # pick up the first one whose score exceeds the threshold
    partner_id = 1
    partner_score = distribution[0]
    t = dt
    for t in range(dt+1, rest):
        if partner_score < threshold:
            partner_score = distribution[t]
        else:
            partner_id = t
            break
    else:
        partner_score = distribution[-1]
        partner_id = rest
        
    # get the actual ranking of the partner
    array = np.array(distribution)
    temp = array.argsort()
    ranks = np.empty(len(array), int)
    ranks[temp] = np.arange(len(array))
    partner_rank = rest - ranks[partner_id-1]
            
    # visualize all
    if fig==1:
        plt.plot([decisiontime+0.5,decisiontime+0.5],[0,scoremax],'--k')
        plt.bar(partner_id,partner_score, color='g', width=1)   
    
    return [partner_id, partner_score, partner_rank]

 例えば、候補人数を10人、閾値を作るまでに出会う人の数をその約37%の4人だとすると、この関数 getmeplease(10,4,1)は以下のようなグラフを描きます。

exp1.png

 ……10人のうち、8番目に現れた最もスコアの高い人と結婚できましたー((((oノ´3`)ノ すごーい(白い目

シミュレーション

 出会う人数Nを5, 10, 20, 100と4パターン用意します。同様に、閾値を作るまでに出会う人数を、全体の10%, 37%, 50%, 80%、と変えたときの4パターンの戦略を用意し、比べたいと思います。

 以下の関数では、それぞれの戦略("optimal": 37%、"early": 10%、"late": 80%、"half": 50%)を10000回シミュレーションし、選ばれた相手のランクとスコアのヒストグラムを作り、中央値を求めます。


# parameters
repeat = 10000
rest = [5,10,20,100]
opt_dt = [int(round(x/np.exp(1))) for x in rest]
tooearly_dt = [int(round(x*0.1)) for x in rest]
toolate_dt = [int(round(x*0.8)) for x in rest]
half_dt = [int(round(x*0.5)) for x in rest]

# initialization
opt_rank = np.zeros(repeat*len(rest))
early_rank = np.zeros(repeat*len(rest))
late_rank = np.zeros(repeat*len(rest))
half_rank = np.zeros(repeat*len(rest))
opt_score = np.zeros(repeat*len(rest))
early_score = np.zeros(repeat*len(rest))
late_score = np.zeros(repeat*len(rest))
half_score = np.zeros(repeat*len(rest))

# loop to find the actual rank and score of a partner
k = 0
for r in range(len(rest)):
    for i in range(repeat):    
        # optimal model
        partner_opt = getmeplease(rest[r], opt_dt[r], 0)
        opt_score[k] = partner_opt[1]
        opt_rank[k] = partner_opt[2]
        
        # too-early model
        partner_early = getmeplease(rest[r], tooearly_dt[r], 0)
        early_score[k] = partner_early[1]
        early_rank[k] = partner_early[2]
        
        # too-late model
        partner_late = getmeplease(rest[r], toolate_dt[r], 0)
        late_score[k] = partner_late[1]
        late_rank[k] = partner_late[2]
        
        # half-time model
        partner_half = getmeplease(rest[r], half_dt[r], 0)
        half_score[k] = partner_half[1]
        half_rank[k] = partner_half[2]
        
        k += 1
        
# visualize distributions of ranks of chosen partners
plt.close('all')
begin = 0
for i in range(len(rest)):
    plt.figure(i+1)
    
    plt.subplot(2,2,1)
    plt.hist(opt_rank[begin:begin+repeat],color='blue')
    med = np.median(opt_rank[begin:begin+repeat])
    plt.plot([med, med],[0, repeat*0.8],'-r')
    plt.title('optimal: %i' %int(med))
    
    plt.subplot(2,2,2)
    plt.hist(early_rank[begin:begin+repeat],color='blue')
    med = np.median(early_rank[begin:begin+repeat])
    plt.plot([med, med],[0, repeat*0.8],'-r')
    plt.title('early: %i' %int(med))
    
    plt.subplot(2,2,3)
    plt.hist(late_rank[begin:begin+repeat],color='blue')
    med = np.median(late_rank[begin:begin+repeat])
    plt.plot([med, med],[0, repeat*0.8],'-r')
    plt.title('late: %i' %int(med))
    
    plt.subplot(2,2,4)
    plt.hist(half_rank[begin:begin+repeat],color='blue')
    med = np.median(half_rank[begin:begin+repeat])
    plt.plot([med, med],[0, repeat*0.8],'-r')
    plt.title('half: %i' %int(med))
        
    fig = plt.gcf()
    fig.canvas.set_window_title('rest' + ' ' + str(rest[i]))
    
    begin += repeat
    
    plt.savefig(figpath + 'rank_' + str(rest[i]))

begin = 0
for i in range(len(rest)):
    plt.figure(i+10)
    
    plt.subplot(2,2,1)
    plt.hist(opt_score[begin:begin+repeat],color='green')
    med = np.median(opt_score[begin:begin+repeat])
    plt.plot([med, med],[0, repeat*0.8],'-r')
    plt.title('optimal: %i' %int(med))
    
    plt.subplot(2,2,2)
    plt.hist(early_score[begin:begin+repeat],color='green')
    med = np.median(early_score[begin:begin+repeat])
    plt.plot([med, med],[0, repeat*0.8],'-r')
    plt.title('early: %i' %int(med))
    
    plt.subplot(2,2,3)
    plt.hist(late_score[begin:begin+repeat],color='green')
    med = np.median(late_score[begin:begin+repeat])
    plt.plot([med, med],[0, repeat*0.8],'-r')
    plt.title('late: %i' %int(med))
    
    plt.subplot(2,2,4)
    plt.hist(half_score[begin:begin+repeat],color='green')
    med = np.median(half_score[begin:begin+repeat])
    plt.plot([med, med],[0, repeat*0.8],'-r')
    plt.title('half: %i' %int(med))
        
    fig = plt.gcf()
    fig.canvas.set_window_title('rest' + ' ' + str(rest[i]))

    begin += repeat
    
    plt.savefig(figpath + 'score_' + str(rest[i]))
    

 

N = 20の場合

 20人と会う場合、スコアとランクの分布は以下のようになりました。

 ランク
rank_20.png

 Optimalな戦略(37%)を採用した場合、ランクの中央値は3になりました。「最初の37%にはNo、その後一番スコアの高い人にYes」という戦略で臨めば、高確率で自分が出会うことが可能な人のうち、3番目以内の人と結婚できることになります。

 ちなみに、Early (10%)、Half (50%)での中央値はそれぞれ6、4ですので、決して悪くはない(?)ですね。Late (80%)ですと、中央値が9ですので、まぁ、ちょっと失敗する確率は高くなります。。。

 スコアはどうでしょうか。 
score_20.png

 Optimalな戦略(37%)を採用した場合、スコアの中央値は89になりました。他の戦略では、Early (10%)が78、Late (80%)が59、Half (80%)が86ですので、やはり、閾値を1/eに決めたときのパフォーマンスは高いですね。よりいい人に決まりやすくなるようです。最適な戦略は必ずしも最適な解を導きませんが、最適な解に近づく可能性を上げることはできます。

 おまけとして、候補人数が5人、10人、100人の場合も載せておきます。

N = 5

ランク
rank_5.png

スコア
score_5.png

N = 10

ランク
rank_10.png

スコア
score_10.png

N = 100

ランク
rank_100.png

スコア
score_100.png

 明らかな傾向として、候補人数が増えるにつれ特に"late"のパフォーマンスは悪くなっていますね。候補人数が少ないと、戦略によるパフォーマンスの違いこそ少ないですが、スコアの値は低くなりがちです。どの場合でも、全体の37%の人と最初に会って閾値を決める戦略が、高ランク高スコアの相手に決まる可能性が高いようです。

結論

  • 候補人数が多いほど、よりよい相手と巡り合える傾向がある
  • ただし、「もっとよく考えてから……」とぐだぐだしていると、機会を損失するだけでなく、最終的に決まる人のスコアも悪くなる
  • だからといって早すぎてもよくない
  • 今後出会う人数を決め、その1/eの人と会って閾値を決め、その後初めて閾値を超えた人に決めるのが最適な戦略の模様

注意 ##

  • 現実には、こちらがYesと言っても相手にNoと言われる場合が多い
  • 休みの日に家でコード書いているような人は そもそも候補がいないことも多い
  • 最適な戦略を採用したからといって、最適な解が得られるわけではない

 ……がんばりましょう!

参考

ハフィントンポスト記事
TED講演*ハンナ・フライ~愛を語る数学~
The Washington Post 記事

214
150
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
214
150

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?