17

わずかに異なる命名規則を持つ 100 万を超える名前の 2 つのリストがあります。ここでの目標は、類似したレコードを 95% の信頼度のロジックで一致させることです。

Python の FuzzyWuzzy モジュールなど、利用できるライブラリがあることを知りました。

ただし、処理に関しては、1 つのリスト内のすべての文字列を他のリストと比較するには、あまりにも多くのリソースを消費するようです。

この問題に対する他のより効率的な方法はありますか?

アップデート:

そこで、バケット関数を作成し、空白や記号を削除して値を小文字に変換するなどの単純な正規化を適用しました...

for n in list(dftest['YM'].unique()):
    n = str(n)
    frame = dftest['Name'][dftest['YM'] == n]
    print len(frame)
    print n
    for names in tqdm(frame):
            closest = process.extractOne(names,frame)

pythons pandas を使用することで、年ごとにグループ化された小さなバケットにデータが読み込まれ、FuzzyWuzzy モジュールをprocess.extractOne使用して最適な一致が得られます。

結果はまだ少し残念です。テスト中、上記のコードは、わずか 5,000 個の名前を含むテスト データ フレームで使用され、ほぼ 1 時間かかります。

テスト データは で分割されます。

  • 名前
  • 年 生年月日

そして、YM が同じバケットにあるバケットごとに比較しています。

問題は、私が使用している FuzzyWuzzy モジュールが原因でしょうか? どんな助けにも感謝します。

4

3 に答える 3

18

この問題を O(n^2) から時間の複雑さを軽減するために、いくつかのレベルの最適化が可能です。

  • 前処理: 最初のパスでリストを並べ替え、各文字列の出力マップを作成します。マップのキーは正規化された文字列にすることができます。正規化には次のものが含まれる場合があります。

    • 小文字変換、
    • 空白なし、特殊文字の削除、
    • 可能であれば、Unicode を同等の ASCII コードに変換し、unicodedata.normalizeまたはunidecodeモジュールを使用します)

    これにより"Andrew H Smith"、、、同じキーが生成"andrew h. smith"され、数百万の名前のセットが、一意/類似のグループ化された名前のより小さなセットに削減されます。"ándréw h. smith""andrewhsmith"

このユーティリティ メソッドを使用して、文字列を正規化できます (ただし、Unicode 部分は含まれません)。

def process_str_for_similarity_cmp(input_str, normalized=False, ignore_list=[]):
    """ Processes string for similarity comparisons , cleans special characters and extra whitespaces
        if normalized is True and removes the substrings which are in ignore_list)
    Args:
        input_str (str) : input string to be processed
        normalized (bool) : if True , method removes special characters and extra whitespace from string,
                            and converts to lowercase
        ignore_list (list) : the substrings which need to be removed from the input string
    Returns:
       str : returns processed string
    """
    for ignore_str in ignore_list:
        input_str = re.sub(r'{0}'.format(ignore_str), "", input_str, flags=re.IGNORECASE)

    if normalized is True:
        input_str = input_str.strip().lower()
        #clean special chars and extra whitespace
        input_str = re.sub("\W", "", input_str).strip()

    return input_str
  • 正規化されたキーが同じであれば、同様の文字列はすでに同じバケットにあります。

  • さらに比較するには、名前ではなくキーのみを比較する必要があります。たとえば andrewhsmithandrewhsmeeth。この名前の類似性には、上記で行った正規化された比較とは別に、あいまいな文字列の一致が必要になるためです。

  • バケット化: 5 文字のキーと 9 文字のキーを比較して、それが 95% 一致しているかどうかを確認する必要がありますか? いいえ、ありません。したがって、一致する文字列のバケットを作成できます。たとえば、5 文字の名前は 4 ~ 6 文字の名前と一致し、6 文字の名前は 5 ~ 7 文字の名前と一致します。文字キーの n+1、n-1 文字の制限は、ほとんどの実際の一致に適したバケットです。

  • 開始一致: 名前のほとんどのバリエーションは、正規化された形式で同じ最初の文字を持ちます (例: Andrew H Smithándréw h. smithAndrew H. Smeeth生成キーandrewhsmithandrewhsmith、およびandrewhsmeethそれぞれ。通常、最初の文字に違いはないため、 で始まるキーのマッチングaを他のキーと実行できます。これは で始まりa、長さのバケット内に収まります. これにより、マッチング時間が大幅に短縮されます.最初の文字で名前のバリエーションが存在することはめったにないため、キーandrewhsmithをにマッチングする必要はありません.bndrewhsmith

次に、このメソッド(または FuzzyWuzzy モジュール)の行で何かを使用して、文字列の類似性のパーセンテージを見つけることができます。速度と結果の品質を最適化するために、 jaro_winklerまたは difflib のいずれかを除外できます。

def find_string_similarity(first_str, second_str, normalized=False, ignore_list=[]):
    """ Calculates matching ratio between two strings
    Args:
        first_str (str) : First String
        second_str (str) : Second String
        normalized (bool) : if True ,method removes special characters and extra whitespace
                            from strings then calculates matching ratio
        ignore_list (list) : list has some characters which has to be substituted with "" in string
    Returns:
       Float Value : Returns a matching ratio between 1.0 ( most matching ) and 0.0 ( not matching )
                    using difflib's SequenceMatcher and and jellyfish's jaro_winkler algorithms with
                    equal weightage to each
    Examples:
        >>> find_string_similarity("hello world","Hello,World!",normalized=True)
        1.0
        >>> find_string_similarity("entrepreneurship","entreprenaurship")
        0.95625
        >>> find_string_similarity("Taj-Mahal","The Taj Mahal",normalized= True,ignore_list=["the","of"])
        1.0
    """
    first_str = process_str_for_similarity_cmp(first_str, normalized=normalized, ignore_list=ignore_list)
    second_str = process_str_for_similarity_cmp(second_str, normalized=normalized, ignore_list=ignore_list)
    match_ratio = (difflib.SequenceMatcher(None, first_str, second_str).ratio() + jellyfish.jaro_winkler(unicode(first_str), unicode(second_str)))/2.0
    return match_ratio
于 2016-08-16T10:26:27.140 に答える
4

O(n^2) の実行を避けるために、文字列をインデックス化または正規化する必要があります。基本的に、各文字列を正規形にマッピングし、すべての単語が対応する正規形にリンクされた逆辞書を作成する必要があります。

「世界」と「言葉」の通常形は同じであると考えてみましょう。したがって、最初に次のような反転辞書を作成しNormalized -> [word1, word2, word3],ます。

"world" <-> Normalized('world')
"word"  <-> Normalized('wrd')

to:

Normalized('world') -> ["world", "word"]

これで、Normalized dict 内の複数の値を持つすべての項目 (リスト) が一致した単語になります。

正規化アルゴリズムはデータ、つまり単語に依存します。多くのうちの1つを考えてみましょう:

  • サウデックス
  • メタフォン
  • ダブルメタフォン
  • NYSIIS
  • カベルフォン
  • ケルンの表音
  • MRAコーデックス
于 2016-08-16T08:04:12.690 に答える