ハッシュ衝突確率計算とpythonシミュレーション

目次

1 はじめに

2.誕生日の質問

3. ハッシュ衝突問題

4. 簡単な python シミュレーション

5. ハッシュの衝突確率を別の視点から見る


1 はじめに

        ハッシュ関数は計算理論の基本的な概念ではなく、計算理論には一方向関数しかありません。いわゆる一方向関数は複雑な定義であり、厳密な定義は理論や暗号に関する本を参照する必要があります。「人間の」言語で一方向関数を説明すると、関数に入力が与えられた場合、その結果を計算するのは簡単ですが、結果が与えられた場合、一方向である入力を計算するのは困難です。関数。あらゆる種類の暗号化関数は、一方向関数の近似と見なすことができます。ハッシュ関数 (またはハッシュ関数になる) は、一方向性関数の近似と見なすこともできます。つまり、一方向関数の定義をほぼ満たしています。

        また、ハッシュ関数についてのより一般的な理解もあります。つまり、データの圧縮されたマッピングを表します。実際のハッシュ関数は、大きな範囲を小さな範囲にマッピングすることを指します。大きな範囲を小さな範囲にマッピングする目的は、多くの場合、スペースを節約し、データを簡単に保存できるようにすることです。さらに、ハッシュ関数はしばしばルックアップに適用されます。したがって、ハッシュ関数の使用を検討する前に、その制限を理解する必要があります。

        (1) ハッシュの主な原則は、大きな範囲を小さな範囲にマッピングすることです; したがって、入力する実際の値の数は、小さな範囲以下でなければなりません。そうしないと、多くの競合が発生します。

        (2) ハッシュは一方向関数に近いため、データの暗号化に使用できます。

        (3) アプリケーションごとにハッシュ関数の要件が異なります。たとえば、暗号化に使用されるハッシュ関数は主にハッシュ関数と一方向関数との間のギャップを考慮しますが、検索に使用されるハッシュ関数は主にハッシュ関数にマッピングされた衝突率を考慮します。小さな範囲。

        ハッシュ関数アプリケーションの主なオブジェクトは配列 (たとえば、文字列) であり、そのターゲットは通常 int 型です。

        一般に、ハッシュ関数は次のカテゴリに簡単に分類できます:
        1. 加算ハッシュ;
        2. ビット操作ハッシュ;
        3. 乗算ハッシュ;
        4. 除算ハッシュ;
        5. テーブル ルックアップ ハッシュ;
        6. 混合ハッシュ;
 

        この記事では、主にハッシュ確率の計算とその単純な Python シミュレーションについて説明します。

2.誕生日の質問

        数学的に言えば、ハッシュ衝突確率問題は、実際には、より一般的ないわゆる「誕生日問題」の一般化です。

        誕生日の問題: 1 年間の 365 日間の人口における誕生日の分布が一様分布に従うと仮定します (つまり、1 年間の 365 日間で毎日生まれる人の数は統計的に等しい)。k 人のパーティーで、少なくとも 2 人が同じ誕生日である確率は? さらに、少なくとも 2 人が同じ誕生日である確率が 50% を超える最小の N は?

        この質問の結果はいくぶん直感に反するものであるため、厳密な計算を行わないと、ほぼ近い答えを推測することさえ困難です。この確率の計算方法については、以下で説明します。

        計算したいのは、少なくとも 2 人の誕生日が一致しない確率ですが、これを直接計算するのは簡単ではありません。確率計算の一般的な手法として、「少なくとも 2 人の誕生日が重複している」、つまり、どの 2 人にも誕生日の重複がないという事象の補数の確率を考慮します。

        以下の説明では、誕生日の競合は、2 人の人物が同じ誕生日であることを示すために使用されます。

        人物 1 を考えると、TA が誰の誕生日とも矛盾しないことは明らかです

        2 人称を考えると、TA が 1 人称と誕生日が一致しない確率は明らかに1-\frac{1}{365} = \frac{365-1}{365}

        3人目を考えると、TAが最初の2人と誕生日が重なる確率は明らかに(1-\frac{2}{365}) = \frac{365-2}{365}

        ...

        k 番目の人を考えると、TA が前の (k-1) 人と誕生日が競合する確率は明らかに\frac{365-(k-1)}{365}

        したがって、k 人が誕生日の競合を持たない確率は次のとおりです。

                \frac{365}{365}\frac{364}{365}\cdots\frac{365-(k-1)}{365} = \prod \limits_{l=0} \limits^{k-1} \frac{l}{365}

        したがって、少なくとも 2 人がまったく同じ誕生日である確率は次のようになります。P_{衝突} = 1 - \prod \limits_{l=0} \limits^{k-1} \frac{l}{365}

3. ハッシュ衝突問題

        データを大きな空間 (入力空間として記録) から小さな空間 (ターゲット空間として記録) に圧縮してマッピングするときに、ハッシュ関数が一様分布に従うと仮定すると、ハッシュの衝突 (任意の 2 つの入力空間の数ターゲット空間内の同じデータへのデータ マッピングの確率問題) は、実際には、上記の誕生日の問題における誕生日の競合の確率問題の一般化です。ターゲット空間のサイズが誕生日問題の 365 から一般化された N に変わるだけです。

        つまり、ターゲット空間サイズが N の場合の一般化されたハッシュ衝突確率は次のようになります。

        ​​​​​​​        P_{衝突, N} = 1 - \prod \limits_{l=0} \limits^{k-1} \frac{l}{N}

        N が非常に大きい場合、上記の式の右辺の 2 番目の部分の計算が遅くなります. 幸いなことに、この式は次のように近似できます。 

                \prod \limits_{l=0} \limits^{k-1} \frac{l}{N} = 1 - \prod \limits_{l=0} \limits^{k-1} \frac{l} {N} \cong 1 - e^{-\frac{k(k-1)}{2N}}

4. 簡単な python シミュレーション

 

# -*- coding: utf-8 -*-
"""
Created on Mon Nov 21 13:44:55 2022

@author: chenxy

ref: https://preshing.com/20110504/hash-collision-probabilities/
"""
import math
import numpy as np
import matplotlib.pyplot as plt
import time
def probCollision(N,k):
    probCollision = 1.0
    for j in range(k):
        probCollision = probCollision * (N - j) / N
    return 1 - probCollision

def probCollisionApproximation(N,k):
    # return 1 - math.exp(-0.5 * k * (k - 1) / N)
    return 1 - np.exp(-0.5 * k * (k - 1) / N)

if __name__ == '__main__':
    
    tstart=time.time()   
    Pcollision = [0]
    for k in range(1,100):
        Pcollision.append(probCollision(365, k))
        print('k = {0}, Pcollision[k] = {1}'.format(k,Pcollision[k]))
    tstop=time.time()
    print('Total elapsed time = {0} seconds'.format(tstop-tstart))
    
    tstart=time.time() 
    Pcollision2 = [0]    
    for k in range(1,100):
        Pcollision2.append(probCollisionApproximation(365, k))
        print('k = {0}, Pcollision2[k] = {1}'.format(k,Pcollision2[k]))
    tstop=time.time()
    
    print('Total elapsed time = {0} seconds'.format(tstop-tstart))

    plt.plot(Pcollision)
    plt.plot(Pcollision2)

操作の結果は次のとおりです。

k
= 17、Pcollision2[k] = 0.31106113346867104
k = 18、Pcollision2[k] = 0.34241291970846444
k = 19、Pcollision2[k] = 0.37405523755741676
k = 20、Pcollision2[k ] = 0.40580512747932584
k = 21、Pcollision2[k ] = 0.4374878053458634
k = 22、Pcollision2[k] = 0.4689381107801478
k = 23、Pcollision2[k] = 0.5000017521827107 k = 24、Pcollision2[k] = 0.53053633940905 16
k =
25、Pcollision2[k] = 0.5604121995072768

。。。

 上記のシミュレーション結果から、次のことがわかります。

(1) 前項で述べた近似法の精度は非常に高く、両者の計算結果は図からほぼ一致している

(2)23人だと誕生日が競合する確率が50%を超える!これは、パーティーに 23 人がいる場合、少なくとも 2 人が同じ誕生日である確率が 50% を超えることを意味します。考えてみれば、1年は365日で、23人集まると半分以上の確率で2人が同じ誕生日になるなんてちょっと不思議じゃないですか?

        さらに、任意の N をシミュレートすることにより、任意の N について、競合確率曲線が上記の形状を持つことがわかります。これは、競合確率が正規化された数 (k/N) の関数として実際に表現できることを意味します。これは、特定の k および N とは関係ありません。

 

5. ハッシュの衝突確率を別の視点から見る

        このセクションでは、ハッシュの衝突確率を別の観点から検討します。

        サイズ N のターゲット空間が与えられ、入力空間からデータをランダムにサンプリングし、それをターゲット空間にマッピングすると、ターゲット空間を満たすにはいくつの入力データが必要ですか? 対象空間の充填率と衝突確率の関係は?

        以下は、この問題のモンテカルロ シミュレーションです。コードは以下のように表示されます:

# -*- coding: utf-8 -*-
"""
Created on Sat Nov 26 10:04:08 2022

@author: chenxy
"""


# generate random 160 bits key

import numpy as np
import random
from collections import defaultdict
import time
import matplotlib.pyplot as plt

def key160_gen() -> int:
    """
    Generate one random 160 bits key

    Returns
    -------
    int
        160 bits key.

    """
    return random.randint(0,2**160-1)

def hash_sim(cam_depth):

    hcam = np.zeros((cam_depth,))
    key_cnt       = 0
    query_ok_cnt  = 0
    collision_cnt = 0
    camfill_cnt   = 0
        
    while 1:
        key_cnt += 1
        key = key160_gen()    
        key_hash = hash(key) %(cam_depth)
        # print('key = {0}, key_hash = {1}'.format(key,key_hash))
        
        if key == hcam[key_hash]:
            query_ok_cnt += 1
        else:
            if hcam[key_hash]==0:
                camfill_cnt += 1
            else:
                collision_cnt += 1
            hcam[key_hash] = key
    
        # if key_cnt %4096 == 0:
        #     print('key_cnt = {0}, camfill_cnt = {1}'.format(key_cnt,camfill_cnt))
    
        if camfill_cnt == cam_depth:
            # print('CAM has been filled to full, with {0} key operation'.format(key_cnt))
            break        
        
    return key_cnt, collision_cnt    

rslt = []
for k in range(10,20):
    tStart = time.time()        
    cam_depth = 2**k
    key_cnt,collision_cnt = hash_sim(2**k)
    tElapsed = time.time() - tStart            
    print('cam_depth={0}, key_cnt={1}, collision_prob={2:4.3f}, tCost={3:3.2f}(sec)'.format(cam_depth,key_cnt,collision_cnt/key_cnt,tElapsed))
    
    rslt.append([cam_depth,key_cnt])

rslt = np.array(rslt)    
plt.plot(rslt[:,0],rslt[:,1])

 操作の結果は次のとおりです。

cam_depth=1024、key_cnt=6010、collision_prob=0.830、tCost=0.07(秒) cam_depth=2048、key_cnt=
16034、collision_prob=0.872、tCost=0.17(秒) cam_depth=4096、key_cnt=30434、collision_prob=0.865
、tCost= 0.30(秒)
cam_depth=8192、key_cnt=89687、collision_prob=0.909、tCost=0.87(秒) cam_depth=16384、
key_cnt=149980、collision_prob=0.891、tCost=1.15(秒)
cam_depth=32768、key_cnt=314527、collision_prob = 0.896、tCost=2.38(秒)
cam_depth=65536、key_cnt=866673、collision_prob=0.924、tCost=6.48(秒) cam_depth=131072、key_cnt=1518369、collision_prob=0.914、tCost=11.08(秒)
cam_depth=
262144 、key_cnt= 3657451、collision_prob=0.928、tCost=26.70(秒)
cam_depth=524288、key_cnt=6648966、collision_prob=0.921、tCost=48.48(秒)

         上記のシミュレーション結果は、ハッシュ テーブルが完全に満たされた状態で動作する場合、ハッシュの衝突確率は約 90%、つまり、ハッシュ テーブルへの 10 回の書き込み操作ごとに約 9 回の衝突が発生することを示しています。ハッシュテーブルに基づくアプリケーションで最も重要な問題は、ハッシュ衝突の問題をどのように解決するかです。

参照:

[1] ハッシュ衝突確率 (preshing.com)

おすすめ

転載: blog.csdn.net/chenxy_bwave/article/details/128402156