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

  • 203
    Like
  • 6
    Comment

 現在の日本の生涯未婚率によると、男性の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 記事