0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Amazon Entity Resolutionで日本語住所の表記揺れに対するマッチングを試してみた

Last updated at Posted at 2025-12-04

Amazon Entity Resolutionで日本語住所の表記揺れに対するマッチングを試してみた

本記事は、私が会社のテックブログに寄稿した内容を、Qiita向けに再投稿するものです。

1. はじめに

最近「Customer 360」や「データ統合」といったワードをよく耳にします。
顧客データを複数のシステムで管理していると、同じ人が別々のIDで登録されてしまい、実は同一人物(企業)だったというケースがよくあります。

この課題を解決するための技術が Entity Resolution (ER) です。
AWSには、これを簡単に試せる Amazon Entity Resolution というサービスが提供されています。

この記事では、

  • Amazon Entity Resolutionとは何か
  • どんなことができるのか
  • 実際に触ってみて得られた知見
    をまとめます。

2. Entity Resolutionとは

一言で言うと、
似ているレコードを同じ人物としてまとめる技術 です。

例えば、同じ人物がシステムによって異なる表記で登録されているケースはよくあります。
システムAには「田中太郎」、システムBには「TANAKA TARO」と登録されている場合などです。
また、システムが変わればデータベースのカラム名も変わることがあり、
システムAでは「first_name」と「last_name」、システムBでは「full_name」といった違いもあります。

システムA(顧客マスタ)

customer_id first_name last_name address
A001 太郎 田中 東京都港区芝公園1-1-1
A002 花子 山田 東京都新宿区西新宿2-8-1
A003 一郎 佐藤 大阪府大阪市北区梅田3-1-1

システムB(会員マスタ)

member_id full_name addr
B1001 TANAKA TARO 東京都港区芝公園1丁目1番1号
B1002 YAMADA HANAKO 東京都新宿区西新宿二丁目8-1
B1003 SATO ICHIRO 大阪市北区梅田3-1-1

Entity Resolutionでは、これらを「同じ人」として扱えます。

また、日本語住所でも表記揺れはよくあります。

  • 「東京都港区芝公園1-1-1」
  • 「東京都港区芝公園1丁目1番1号」
  • 「東京都港区芝公園1-1-1」

人が見れば同じ場所ですが、システム的には異なる文字列として扱われます。
Entity Resolutionはこうした表記の違いを吸収して、同じ住所だと判断してくれます。

3. やってみたこと

1. ユーザ情報のマッチング

AWSから公式に提供されているリポジトリを使用して、Entity Resolutionを使ったユーザ情報のマッチングを試してみました。
実際の開発現場では、ユーザ情報に日本の住所が含まれることがよくあります。日本の住所は表記揺れが多いため、そのマッチング精度がどれほどかを検証しました。

テスト用ダミーデータ準備

とある企業のシステムA(顧客マスタ)とシステムB(会員マスタ)に登録されているユーザデータを模したテスト用ダミーデータを「住所なしVer」と「住所ありVer」で作成しました。
レコード数はそれぞれ1000件ずつで、600件/1000件は両方のテーブルに同一人物のデータを入れて、マッチングの精度を検証できるようにしました。
今回の検証では、この600件のうち何件を正しくマッチングできるかを確認します。
また、2つのテーブル構造も異なるように作成し、データには意図的に表記揺れを加えています。
特に日本の住所は表記揺れが大きいため、以下のようなパターンについてもテストデータに含めました。

  • 番地の「-」と「丁目」「番」「号」の有無
  • 全角と半角の違い
  • 大字の有無
  • 都道府県がある場合とない場合(例:福岡市中央区天神と福岡県福岡市中央区天神)
  • 漢数字の表記方法の違い(例:福岡市中央区高雄一−二三ー三五と福岡市中央区高雄一−二十三ー三十五)

それぞれのテーブルの構造は以下の通りです。(テスト用ダミーデータは機械的に作成しているため、住所が本来存在しないはずの住所になっているのはご了承ください)


システムA(顧客マスタ)

customer_id_a gold_person_id fullname fullname_kana fullname_romaji nickname email phone birthdate gender signup_date address
A000001 G000001 中島 徹 ナカジマ トオル Nakajima Toru トオ nakajima.toru@example.jp +81 90-2551-1404 1997-12-24 2021-11-02 福岡県名古屋市栄9丁目20番 11号
A000002 G000002 西村 彩 ニシムラ アヤ Nishimura Aya nishimura.aya@example.com 9011185809 1991-03-25 2024-05-05 山形県春日井市九段七 丁目三番一六号

システムB(会員マスタ)

customer_id_b gold_person_id family_name given_name family_name_kana given_name_kana fullname_romaji nickname email phone birthdate gender signup_date address
B000001 G000001 中島 ナカジマ トオル Nakajima Toru nakajima.toru@example.net 090-2551-1404 1997-12-24 男性 2021-11-02 福岡県名古屋市栄9丁目2011
B000002 G000002 西村 ニシムラ アヤ Nishimura Aya ニシムラアヤ nishimura.aya@example.net 090 1118 5809 1991/03/25 女性 2024-05-05 山形県春日井市九段七丁目三番一六号

スキーママッピングの定義

次にスキーママッピングを定義します。
スキーママッピングとは、各フィールドがどのような意味を持つかを指定するものです。
以下に住所ありVerのCDKでの定義例を示します。

// システムA(顧客マスタ)
    this.mainAddrSchema = new CfnSchemaMapping(this, 'MainBrandCustomerAddrSchema' + SCHEMA_VERSION, {
        schemaName: `${stackName}MainBrandCustomerAddr${SCHEMA_VERSION}`,
        mappedInputFields: [
            { fieldName: 'customer_id_a',    type: 'UNIQUE_ID',     matchKey: 'customer_id' },
            { fieldName: 'email',            type: 'EMAIL_ADDRESS', matchKey: 'email' },
            { fieldName: 'fullname',         type: 'NAME',          matchKey: 'fullname' },
            { fieldName: 'fullname_kana',    type: 'NAME',          matchKey: 'fullname_kana' },
            { fieldName: 'fullname_romaji',  type: 'NAME',          matchKey: 'fullname_romaji' },
            { fieldName: 'phone',            type: 'PHONE_NUMBER',  matchKey: 'phone' },
            { fieldName: 'birthdate',        type: 'DATE',          matchKey: 'birthdate' },
            { fieldName: 'gender',           type: 'STRING',        matchKey: 'gender' },
            { fieldName: 'address',          type: 'STRING',        matchKey: 'address' },
            { fieldName: "gold_person_id", type: "STRING" },
        ],
        });

    // システムB(会員マスタ)
    this.subAddrSchema = new CfnSchemaMapping(this, 'SubBrandCustomerAddrSchema' + SCHEMA_VERSION, {
      schemaName: `${stackName}SubBrandCustomerAddr${SCHEMA_VERSION}`,
      mappedInputFields: [
        { fieldName: 'customer_id_b',    type: 'UNIQUE_ID',     matchKey: 'customer_id' },
        { fieldName: 'email',            type: 'EMAIL_ADDRESS', matchKey: 'email' },
        { fieldName: 'family_name',      type: 'NAME', subType: 'LAST_NAME',  matchKey: 'fullname' },
        { fieldName: 'given_name',       type: 'NAME', subType: 'FIRST_NAME', matchKey: 'fullname' },
        { fieldName: 'family_name_kana', type: 'NAME',          matchKey: 'fullname_kana' },
        { fieldName: 'given_name_kana',  type: 'NAME',          matchKey: 'fullname_kana' },
        { fieldName: 'fullname_romaji',  type: 'NAME',          matchKey: 'fullname_romaji' },
        { fieldName: 'phone',            type: 'PHONE_NUMBER',  matchKey: 'phone' },
        { fieldName: 'birthdate',        type: 'DATE',          matchKey: 'birthdate' },
        { fieldName: 'gender',           type: 'STRING',        matchKey: 'gender' },
        { fieldName: 'address',          type: 'STRING',        matchKey: 'address' },
        { fieldName: "gold_person_id", type: "STRING" },
      ],
    });

Amazon Entity Resolutionでは、matchKeyとして指定されたフィールドを使ってマッチングを行います。

上記の例でいくと、

  • システムA(顧客マスタ)のcustomer_id_aとシステムB(会員マスタ)のcustomer_id_b、
  • システムA(顧客マスタ)のemailとシステムB(会員マスタ)のemail

などをマッチング対象として扱い、
これらのフィールドが似ているレコードを同じ人物としてマッチングします。

            { fieldName: 'customer_id_a',    type: 'UNIQUE_ID',     matchKey: 'customer_id' },
            { fieldName: 'customer_id_b',    type: 'UNIQUE_ID',     matchKey: 'customer_id' },

そして下のようにoutputを定義することで、マッチング結果に含めるフィールドを指定できます。


      outputSourceConfig: [
        {
          outputS3Path: `s3://${dataStorage.dataBucket.bucketName}/${this.outputPrefixNoAddr}`,
          output: [
            { name: 'customer_id_a' },
            { name: 'customer_id_b' },
            { name: 'fullname' },
            { name: 'family_name' },
            { name: 'given_name' },
            { name: 'email' },
            { name: 'phone' },
            { name: 'birthdate' },
            { name: 'gender' },
            { name: 'gold_person_id' },
          ],
        },
      ],

gold_person_idは同一人物には同じidを振っています。
そのため、出力のみ利用し、マッチングキーには含めていません。

マッチングアルゴリズムの選定

Amazon Entity Resolutionでは、マッチングアルゴリズムとして以下の2つが選べます。

  • ルールベースマッチング(RULE_BASED)
  • 機械学習マッチング(ML_MATCHING)

今回は住所の表記揺れなどに対応するために、機械学習マッチングを選びました。
設定は以下のように行いました。

    resolutionTechniques: { resolutionType: 'ML_MATCHING'},

精度検証・評価

上記のような設定でマッチングを実行しました。

以下に住所ありVerのマッチング結果の一部を示します。(一部マスキングしています)

InputSourceARN ConfidenceLevel customer_id_a customer_id_b fullname family_name given_name email phone birthdate gender address gold_person_id RecordId MatchID
arn:aws:glue:xxxxxxxx::table/demo-db/customers_b_addr 0.9990855 B000181 結衣 mori.yui@example.net 090-XXXX-YYYY 1997/12/25 女性 大分県川崎市本町2丁目16番6号 G000181 B000181 0385410ecf5941d4b2e51d5547a7584200004558345748482
arn:aws:glue:xxxxxxxx::table/demo-db/customers_a_addr 0.9990855 A000181 森 結衣 moriyui@example.co.jp 090-XXXX-YYYY 1997-12-25 大分県川崎市本町2丁目16-6 G000181 A000181 0385410ecf5941d4b2e51d5547a7584200004558345748482
arn:aws:glue:xxxxxxxx::table/demo-db/customers_a_addr 0.99917805 A000309 橋本 恵 hashimoto.megumi@example.jp 090-XXXX-YYYY 1993-7-26 長野県津島市水道橋9-13番1号 G000309 A000309 05a60b95502844458b840d8cb822ea3e00004558345748483
arn:aws:glue:xxxxxxxx:table/demo-db/customers_b_addr 0.99917805 B000309 橋本 hashimoto.megumi@example.co.jp 090-XXXX-YYYY 1993-07-26 女性 長野県津島市水道橋9丁目13-1 G000309 B000309 05a60b95502844458b840d8cb822ea3e00004558345748483

ConfidenceLevelはマッチングの確信度を示す値で、0から1の範囲で表され、1に近いほど、マッチングが正しいと確信していることを示します。
上に示したレコードは、どれも0.99以上の高い確信度でマッチングされていることがわかります。

MatchIDはマッチングされたレコードのペアを識別するためのIDで、この4つのレコード(2ペア)はそれぞれ同じMatchIDを持つため、同じ人物としてマッチングされたことがわかります。

マッチングの精度はRecall,Precision,F1-scoreで評価しました。
一言で簡単に説明すると、

  • Recall: 高いほど拾い漏れが少ない
  • Precision: 高いほどマッチングミスが少ない
  • F1-score: 高いほど全体的に精度が高い
    です。
    それぞれの詳細な説明についてはAccuracy Recall Precision F1スコアによる評価を参照してください。

住所なしVer/住所ありVerの2パターンのそれぞれの結果は以下です。

住所なしVer

Recall Precision F1
1.0 1.0 1.0

住所ありVer

Recall Precision F1
1.0 1.0 1.0

両方ともに非常に高い精度が出ました。
今回の検証では日本の住所の表記揺れによって、どれくらいマッチング精度が変わるかを確認したかったのですが、
住所以外のカラムのマッチング精度が非常に高かったため、住所の有無による差は出ませんでした。
そこで、住所のカラム以外についても表記揺れを大きくなるようにテストデータを作成し直し、住所の有無によりどれくらいマッチングの精度に差が出るのかを確認しました。
結果は以下の通りです。

住所なしVer

Recall Precision F1 TP_pairs FP_pairs FN_pairs
0.8617 0.9961 0.924 517 2 83

住所ありVer

Recall Precision F1 TP_pairs FP_pairs FN_pairs
0.8050 1 0.892 483 0 117

住所カラムを追加することで、日本語の住所特有の表記揺れによりマッチング精度が下がることが確認できました。
数値について考察してみると、
Recallが下がった理由としては、同じ人物であるにもかかわらず、日本語住所の表記揺れがあるため、別人物として扱われてしまったケースが増えたためと考えられます。
Precisionが上がった理由としては、住所カラムを追加することで、完全に別の人物と判断されるケースが増えたためと考えられます。


2. 日本語住所に特化したデータマッチング

ユーザ情報のマッチングでは、住所以外にも多くの特徴量を使ってマッチングを行うため、住所の表記揺れの影響は小さくなります。
しかし、顧客企業のマスタデータなどを統合したいケースでは、住所以外の特徴量が少なく住所の影響が大きい場合も考えられます。
そこで、次に会社名と住所のみを使ったマッチングを試してみました。

テスト用ダミーデータ準備

先ほどと同じ要領で、会社名と住所のみを持つテスト用ダミーデータを作成しました。
レコード数はそれぞれ1000件ずつで、600件/1000件は両方のテーブルに同一企業のデータを入れて、マッチングの精度を検証できるようにしました。
具体的なテーブル構造は以下の通りです。

システムA(企業マスタ)

company_id company_name company_address
A000001 カミングリッジ244 大阪府大阪市北区梅田2-27-8
A000002 グロースデザイン98 東京都千代田区栄1-27-32

システムB(取引先マスタ)

company_id company_name company_address
B000001 カミングリッジ244 大阪府大阪市北区大字梅田2-27-8
B000002 グロースデザイン98(株) 東京都東京都千代田区大字栄1-27-32

今回は以下のようなパターンでテストデータを作成しています。


パターン1: 会社名と住所の揺れがある場合

会社名には以下のような表記揺れを入れています。

  • 「グロースデザイン98」
  • 「グロースデザイン98(株)」

住所には以下のようなパターンで表記揺れを入れています。
  • 「大字」の有無

    • 福岡市中央区大字天神
    • 福岡市中央区天神
  • 都道府県の有無

    • 福岡市中央区天神
    • 福岡県福岡市中央区天神
  • 漢数字の表記方法の違い

    • 福岡市中央区高雄一−二三ー三五
    • 福岡市中央区高雄一−二十三ー三十五
  • 記号の全角/半角、文字違い

    • 福岡市中央区天神1−2‐3
    • 福岡市中央区天神1ー2ー3

パターン2: 会社名の表記揺れのみ
こちらはパターン1のテストデータに対して、住所の表記揺れをなくしたパターンです。

精度検証・評価

実験1: パターン1のデータを使用してマッチングを試す

まずはパターン1の会社名と住所の両方に表記揺れがある場合のマッチングを試しました。
結果は以下の通りです。

Recall Precision F1 TP_pairs FP_pairs FN_pairs
0.187 0.622 0.287 112 68 488

前回のユーザ情報のマッチングと比べると、非常に精度が低いことがわかります。
原因としては明白で以下のような理由が考えられます。

  • 会社名と住所の2つのカラムしかないため、マッチングに使える情報が少ない
  • 住所の表記揺れが非常に多いため、同じ住所であると認識されないケースが多い

また実際のマッチング結果を確認したところ、以下のようなケースが多く見られました。

  • 住所が完全に異なるにも関わらず会社名が似ているもしくは同じであるため、誤ってマッチングされてしまったケース
  • 同じ会社で、住所も同じだが、会社名の表記揺れにより、マッチングされなかったケース

感想として、ほとんどが会社名だけで判断されている印象で、どれだけ住所が異なっていたとしても会社名が同じであればマッチングされてしまうように感じました。

この原因として考えられるのがスキーママッピングの設定です。

        this.companyAddrSchema = new CfnSchemaMapping(this, 'CompanyAddrSchema' + SCHEMA_VERSION, {
            schemaName: `${stackName}CompanyAddr${SCHEMA_VERSION}`,
            mappedInputFields: [
                { fieldName: 'company_id',      type: 'UNIQUE_ID',     matchKey: 'company_id' },
                { fieldName: 'company_name',    type: 'NAME',          matchKey: 'company_name' },
                { fieldName: 'company_address', type: 'STRING',        matchKey: 'company_address' },
            ],
        });

私は上記のような設定を行いましたが、住所をSTRING型で定義しているため、正規化が十分に行われていない可能性があります。Amazon Entity Resolutionでは、住所をADDRESS型で定義することも可能なため、ADDRESS型で定義した場合にどれくらい精度が変わるのかを試してみる価値はあると思いました。

実験2: 住所のtypeをADDRESS型に変更する

次に住所のtypeをSTRING型からADDRESS型に変更してマッチングを試しました。

            mappedInputFields: [
                { fieldName: 'company_id',      type: 'UNIQUE_ID',     matchKey: 'company_id' },
                { fieldName: 'company_name',    type: 'NAME',          matchKey: 'company_name' },
                { fieldName: 'company_address', type: 'ADDRESS',       matchKey: 'company_address' },
            ],

結果は以下の通りです。

Recall Precision F1 TP_pairs FP_pairs FN_pairs
0.295 0.77 0.427 177 53 423

実験1と比較すると、RecallもPrecisionも向上しました。
Entity Resolutionの内部がどのように動いているかはブラックボックスのため、正確な理由はわかりませんが、
しかし、公式ドキュメントに「ADDRESS型は住所の正規化に役立つ」と記載されていることから、ADDRESS型による定義によって住所の表記揺れが正規化され、精度向上につながったと考えられます。
逆に言えば、今まで使用していたSTRING型では住所の正規化が不十分であったか、あるいは住所情報がマッチング要素として重要視されず、主に会社名だけで判断されていた可能性も考えられます。

マッチングが失敗した箇所も何が原因で失敗しているのかを確認しました。

カテゴリ 総ペア数 FN数 Recall率
大字あり/なし 150 116 0.227
都道府県あり/なし 134 108 0.194
漢数字の違い 205 169 0.176
記号揺れ(ハイフン類) 550 376 0.316

最もRecall率が低いのは「漢数字の違い」でした。また、大字の有無、都道府県の有無もRecall率が低いことがわかります。
これらの結果から、ADDRESS型で定義した場合としても、住所の正規化を完全に行うことはできず、住所の表記揺れによってマッチングが失敗するケースが多いことがわかります。

<実験3: 住所の正規化を行う

次にパターン2の会社名の表記揺れのみを持つテストデータを使ってマッチングを試しました。
実際の開発現場では住所揺れを完全に無くすことはなかなか難しいですが、生成AIの力を借りて、住所の完全な正規化(システムAの住所とシステムBの住所を一致させる)を行い、マッチングを試しました。
結果は以下の通りです。

Recall Precision F1 TP_pairs FP_pairs FN_pairs
0.692 0.777 0.732 415 119 185

実用可能なラインに乗ってきた印象です。
ただConfidenceLevelを確認したところ、ほとんどが0.6程度でマッチングされていました。これは、ユーザ情報の統合の際に記録した0.99以上という高いConfidenceLevelと比較すると、特徴量が少ない場合のマッチングには、まだまだ課題が多いと感じました。

まとめ

今回初めてAmazon Entity Resolutionを触ってみましたが、非常に簡単にデータマッチングが試せることがわかりました。
当初、住所の表記揺れによる精度を懸念していましたが、他のカラムが一致すれば、想定以上に高い精度でマッチングが可能であることも確認できました。
日本語の住所揺れは非常に複雑なので、実際に高いマッチング精度を出せるかは実データで試す必要がありますが、PoCや検証には十分使えるサービスだと感じました。
また、今回は試しませんでしたが、データの正規化やルールベースファジーマッチングなどを使うことで、さらに精度を上げられる可能性もあると思いますので興味のある方はぜひ試してみてください。

なお、本記事の検証結果はあくまでサンプルデータを用いた一例であり、すべてのケースで同様の結果を保証するものではありません。特にML系のマッチングはデータの特性や前処理方法に強く依存するため、実際のユースケースに適用する際には、必ずご自身のデータで検証することをおすすめします。

参考

0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?