การระบุตัวตนลูกค้าด้วย BigQuery Graph

1. บทนำ

ใน Codelab นี้ คุณจะได้สร้างเครื่องมือการแก้ปัญหาข้อมูลระบุตัวตนของลูกค้า (การจับคู่เอนทิตี) แบบโมดูลาร์ตั้งแต่ต้นทางถึงปลายทางภายใน Google Cloud BigQuery โดยตรง คุณจะใช้ Google Cloud Shell เพื่อการทำให้ใช้งานได้โครงสร้างพื้นฐานร่วมกับโปรแกรมแก้ไข SQL ของ BigQuery Studio เพื่อการทำความสะอาดข้อมูล การให้คะแนนผู้สมัคร การสร้างกราฟพร็อพเพอร์ตี้ และการข้ามเส้นทาง GQL (Graph Query Language) ของ ISO

การแก้ปัญหาข้อมูลระบุตัวตนเป็นความสามารถพื้นฐานสำหรับ Customer 360 ขององค์กร การตรวจหาการฉ้อโกง และการรวมข้อมูลหลายระบบ เนื่องจากมีแนวทางที่ถูกต้องหลายอย่างในการระบุตัวตนโดยขึ้นอยู่กับความสมบูรณ์ของข้อมูลและความต้องการทางธุรกิจ ขั้นตอนทั้งหมดในโค้ดแล็บนี้จึงเป็นแบบแยกส่วนและไม่บังคับ ไปป์ไลน์นี้ออกแบบมาเพื่อแสดงเทคนิคต่างๆ ในอุตสาหกรรมที่ใช้กันทั่วไปและมีคุณภาพระดับโปรดักชัน ซึ่งรวมถึงการแปลงที่อยู่ UDF จากระยะไกลให้เป็นรูปแบบมาตรฐาน, การบล็อกตามการออกเสียงแบบ Soundex, การค้นหาเวกเตอร์เชิงความหมาย (AI.EMBED), การให้คะแนนฟีเจอร์แบบไฮบริด และการจัดกลุ่มกราฟพร็อพเพอร์ตี้ GQL เพื่อให้คุณเลือกใช้รูปแบบที่เหมาะกับสถาปัตยกรรมของคุณได้

ควรปรับแต่งวิธีการจับคู่และเกณฑ์การให้คะแนนตามความต้องการขององค์กรในการจับคู่แบบดีเทอร์มินิสติกเทียบกับการจับคู่แบบน่าจะเป็น ซึ่งกำหนดโดยกรณีการใช้งานเป้าหมาย ตัวอย่างเช่น การปฏิบัติตามข้อกำหนดอย่างเคร่งครัด การเรียกเก็บเงิน หรือการปฏิบัติการทางการเงินมักจะใช้กฎที่กำหนดได้อย่างแม่นยำสูง (เช่น การจับคู่ SSN หรือหมายเลขประจำตัวผู้เสียภาษีที่ตรงกันทุกประการ) เพื่อป้องกันการเชื่อมโยงที่ไม่ถูกต้อง ในขณะที่การปรับเปลี่ยนการตลาดตามโปรไฟล์ของผู้ใช้ ข้อมูลวิเคราะห์ และเครื่องมือแนะนำมักจะใช้การจับคู่แบบคร่าวๆ ที่เป็นไปได้และความคล้ายคลึงของเวกเตอร์เชิงความหมายเพื่อเพิ่มการเรียกคืนสูงสุดและค้นพบการเชื่อมต่อที่ละเอียดอ่อน

สถาปัตยกรรมของเครื่องมือการแก้ปัญหาข้อมูลระบุตัวตนของลูกค้าใน BigQuery

สิ่งที่คุณต้องทำ

  • นำเข้าชุดข้อมูลเปรียบเทียบ FEBRL3: โหลดระเบียนลูกค้าสังเคราะห์และคู่ที่ตรงกันของข้อมูลที่ได้จากการสังเกตการณ์ลงใน BigQuery
  • ติดตั้งใช้งาน UDF ระยะไกลสำหรับการตรวจสอบที่อยู่: ติดตั้งใช้งาน Cloud Function ของ Python และลงทะเบียนฟังก์ชันระยะไกลของ BigQuery เพื่อจัดรูปแบบที่อยู่
  • ประมวลผลข้อมูลโปรไฟล์และการเข้ารหัสแบบสัทอักษรล่วงหน้า: เรียกใช้การล้างข้อมูล SQL, เรียกใช้ UDF ที่อยู่ และคำนวณSOUNDEXคีย์แบบสัทอักษรและระยะทางแก้ไข Levenshtein:
    • การเข้ารหัสตามการออกเสียงแบบ Soundex: อัลกอริทึมการออกเสียงสำหรับการจัดทำดัชนีชื่อตามเสียงที่ออกในภาษาอังกฤษ โดยจะแปลงชื่อเป็นรหัส 4 อักขระ (อักษรตัวแรกตามด้วยตัวเลข 3 หลัก) ซึ่งแสดงถึงกลุ่มเสียงพยัญชนะ (เช่น ทั้ง "John" และ "Jon" จะแมปกับ J500 ในขณะที่ "Smith" และ "Smyth" จะแมปกับ S530) เพื่อให้สัญญาณการจับคู่ตามหลักสัทศาสตร์สำหรับการให้คะแนนฟีเจอร์และการบล็อกเดลต้าแบบเพิ่มทีละรายการแบบเรียลไทม์
    • ระยะทางเลเวนชไตน์ (EDIT_DISTANCE): เมตริกสตริงที่วัดจำนวนการแก้ไขอักขระเดียวขั้นต่ำ (การแทรก การลบ หรือการแทนที่) ที่จำเป็นในการเปลี่ยนสตริงหนึ่งเป็นอีกสตริงหนึ่ง ซึ่งช่วยให้การจับคู่ชื่อและที่อยู่แบบคร่าวๆ แม่นยำ
  • สร้างการฝังโปรไฟล์เชิงความหมายและการค้นหาเวกเตอร์: สร้างการฝังข้อความใน SQL โดยตรงโดยใช้ AI.EMBED (text-embedding-005) และค้นหาเพื่อนบ้านที่ใกล้ที่สุด K อันดับแรกโดยใช้ VECTOR_SEARCH เพื่อใช้เป็นเลเยอร์การสร้างผู้สมัครรับเลือกแบบย่อยเชิงเส้น
  • การให้คะแนนคู่ผู้สมัครและฟีเจอร์การผสานรวมแบบไฮบริดที่ Edge: ใช้ประโยชน์จากคู่ผู้สมัครในการค้นหาเวกเตอร์เพื่อขจัดความซับซ้อนของการรวมข้าม O(N²) คำนวณคะแนนความคล้ายที่ถ่วงน้ำหนักแบบหลายฟีเจอร์ (SSN, ระยะทางแก้ไข Levenshtein, DOB, Jaccard ของที่อยู่) และผสานรวม Edge ลงในตารางผู้สมัครแบบรวม
  • การสร้างกราฟพร็อพเพอร์ตี้และการข้ามเส้นทาง GQL ของ ISO: สร้าง PROPERTY GRAPH ใน BigQuery, เรียกใช้การค้นหาเส้นทาง GQL ของ ISO {1, 2} (GRAPH_TABLE) เพื่อแก้ปัญหาคลัสเตอร์ลูกค้าที่เชื่อมต่อกัน, คำนวณเมตริกการประเมินแต่ละรายการ และทำการจัดกลุ่มครัวเรือนแบบคร่าวๆ โดยใช้การถ่วงน้ำหนักกราฟ Adamic-Adar
  • ความละเอียดที่เพิ่มขึ้นและความเสถียรที่คงที่: ประมวลผลการนำเข้าแบบเป็นชุดรายวันด้วยการจับคู่ส่วนต่างที่เพิ่มขึ้น
  • การรวมกลุ่มและการคงที่ของคลัสเตอร์ (การทับซ้อน 1-ε): บังคับใช้ความคงที่ของคลัสเตอร์อย่างต่อเนื่องในการเรียกใช้ไปป์ไลน์โดยใช้การรับประกันเกณฑ์การทับซ้อน (1-ε)

สิ่งที่คุณต้องมี

  • เว็บเบราว์เซอร์ เช่น Chrome
  • โปรเจ็กต์ Google Cloud ที่เปิดใช้การเรียกเก็บเงิน

Codelab นี้ออกแบบมาสำหรับวิศวกรข้อมูล นักพัฒนาฐานข้อมูล และผู้ปฏิบัติงานด้าน AI/ML ทุกระดับ รวมถึงผู้เริ่มต้น

ระยะเวลาโดยประมาณ: 45 นาที
ค่าใช้จ่ายโดยประมาณ: น้อยกว่า $2.00 USD (ใช้ Cloud Functions แบบจ่ายเมื่อใช้และ BigQuery สำหรับการประมวลผลคำค้นหา)

2. ก่อนเริ่มต้น

สร้างโปรเจ็กต์ Google Cloud

  1. ในคอนโซล Google Cloud ให้เลือกหรือสร้างโปรเจ็กต์ Google Cloud ในหน้าตัวเลือกโปรเจ็กต์
  2. ตรวจสอบว่าได้เปิดใช้การเรียกเก็บเงินสำหรับโปรเจ็กต์ที่อยู่ในระบบคลาวด์แล้ว ดูวิธีตรวจสอบว่าได้เปิดใช้การเรียกเก็บเงินในโปรเจ็กต์แล้วหรือไม่

เริ่มต้น Cloud Shell

Cloud Shell คือสภาพแวดล้อมบรรทัดคำสั่งที่ทำงานใน Google Cloud ซึ่งโหลดเครื่องมือที่จำเป็นไว้ล่วงหน้า

  1. คลิกเปิดใช้งาน Cloud Shell ที่ด้านบนของคอนโซล Google Cloud
  2. ยืนยันการตรวจสอบสิทธิ์
gcloud auth list
  1. กำหนดค่าตัวแปรสภาพแวดล้อมใน Cloud Shell โดยทำดังนี้
export GCP_PROJECT=$(gcloud config get-value project)
export REGION="us-central1"
export DATASET_ID="identity_resolution"

เปิดใช้ API ที่จำเป็น

เรียกใช้คำสั่งต่อไปนี้ใน Cloud Shell โดยใช้บัญชีผู้ใช้เพื่อเปิดใช้บริการ Google Cloud ที่จำเป็นทั้งหมด

gcloud services enable \
  addressvalidation.googleapis.com \
  cloudbuild.googleapis.com \
  cloudfunctions.googleapis.com \
  cloudresourcemanager.googleapis.com \
  artifactregistry.googleapis.com \
  aiplatform.googleapis.com \
  run.googleapis.com \
  bigqueryconnection.googleapis.com \
  bigqueryreservation.googleapis.com \
  bigquery.googleapis.com

หากต้องการให้การดำเนินการ API และการเข้าถึงข้อมูลรับรองเริ่มต้นของแอปพลิเคชัน (ADC) เป็นไปอย่างราบรื่น ให้สร้างบัญชีบริการของ Lab โดยเฉพาะและเปิดใช้gcloudการแอบอ้างเป็นผู้ใช้รายอื่น:

# 1. Create a Service Account for the lab (if it does not already exist)
gcloud iam service-accounts create identity-res-sa \
  --display-name="Identity Resolution Service Account" 2>/dev/null || true

# Wait 5 seconds for IAM propagation
sleep 5

# Extract Project Number for default build and compute service accounts
export PROJECT_NUMBER=$(gcloud projects describe ${GCP_PROJECT} --format="value(projectNumber)")

# 2. Grant specific required least-privilege roles to the lab Service Account
for role in roles/bigquery.admin \
            roles/bigquery.resourceAdmin \
            roles/run.admin \
            roles/cloudfunctions.admin \
            roles/resourcemanager.projectIamAdmin \
            roles/cloudbuild.builds.editor \
            roles/cloudbuild.builds.builder \
            roles/artifactregistry.repoAdmin \
            roles/artifactregistry.writer \
            roles/storage.admin \
            roles/logging.logWriter \
            roles/iam.serviceAccountUser \
            roles/aiplatform.user; do
  gcloud projects add-iam-policy-binding ${GCP_PROJECT} \
    --member="serviceAccount:identity-res-sa@${GCP_PROJECT}.iam.gserviceaccount.com" \
    --role="${role}" --quiet
done

# 3. Grant required build & storage permissions to default Compute Engine & Cloud Build service accounts (required for 2nd-gen Cloud Functions container builds)
for role in roles/cloudbuild.builds.builder \
            roles/logging.logWriter \
            roles/artifactregistry.writer \
            roles/storage.objectAdmin; do
  gcloud projects add-iam-policy-binding ${GCP_PROJECT} \
    --member="serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \
    --role="${role}" --quiet || true
  gcloud projects add-iam-policy-binding ${GCP_PROJECT} \
    --member="serviceAccount:${PROJECT_NUMBER}@cloudbuild.gserviceaccount.com" \
    --role="${role}" --quiet || true
done

# 4. Grant Service Account Token Creator role to your user account
export SA_EMAIL="identity-res-sa@${GCP_PROJECT}.iam.gserviceaccount.com"

gcloud iam service-accounts add-iam-policy-binding \
  "${SA_EMAIL}" \
  --member="user:$(gcloud config get-value account)" \
  --role="roles/iam.serviceAccountTokenCreator" --quiet

# 5. Enable Service Account impersonation for gcloud
gcloud config set auth/impersonate_service_account "${SA_EMAIL}"

# 6. Wait for IAM role assignments and impersonation caches to propagate
echo "Waiting 90 seconds for IAM policies and impersonation caches to propagate..."
sleep 90

สร้างชุดข้อมูล BigQuery

สร้างชุดข้อมูล BigQuery เพื่อจัดเก็บโหนดลูกค้า ขอบ โมเดลกราฟ และมุมมองการประเมิน

bq mk --location=US --dataset ${GCP_PROJECT}:${DATASET_ID}

คุณควรเห็นเอาต์พุตที่คล้ายกับตัวอย่างต่อไปนี้

Dataset 'your-project-id:identity_resolution' successfully created.

หากต้องการให้มั่นใจว่ามีความจุในการประมวลผลเฉพาะสำหรับการค้นหาดัชนีเวกเตอร์ การรวมกราฟ และการเรียกใช้ฟังก์ชันระยะไกลโดยไม่ถูกจำกัดด้วยขีดจำกัดของ CPU แบบออนดีมานด์หรือโควต้าที่ใช้ร่วมกัน ให้สร้างการจอง Enterprise Edition ด้วยการปรับขนาดอัตโนมัติใน Cloud Shell โดยทำดังนี้

# 1. Create a BigQuery Enterprise reservation with 0 baseline slots and 100 max autoscaling slots
bq mk --reservation \
  --project_id=${GCP_PROJECT} \
  --location=US \
  --edition=ENTERPRISE \
  --slots=0 \
  --autoscale_max_slots=100 \
  --ignore_idle_slots=true \
  identity-res-reservation

# 2. Assign your Cloud project to the newly created reservation for query execution
bq mk --reservation_assignment \
  --project_id=${GCP_PROJECT} \
  --location=US \
  --reservation_id=identity-res-reservation \
  --job_type=QUERY \
  --assignee_type=PROJECT \
  --assignee_id=${GCP_PROJECT}

3. นำเข้าชุดข้อมูลโหนดลูกค้า FEBRL3

ก่อนที่จะติดตั้งใช้งานฟังก์ชันระยะไกลของการตรวจสอบความถูกต้องของที่อยู่และทำการระบุตัวตน คุณจะต้องโหลดชุดข้อมูลเปรียบเทียบการระบุเอนทิตี FEBRL3 สังเคราะห์ (ซึ่งมีระเบียนลูกค้า 5,000 รายการที่มีคลัสเตอร์ที่ซ้ำกันหลายรายการ โดยมีการซ้ำกันสูงสุด 5 รายการต่อลูกค้า) โดยใช้ไลบรารี recordlinkage ของ Python และเขียนโหนดลูกค้าดิบ (customer_nodes) และลิงก์การจับคู่ความจริงพื้นฐาน (ground_truth_links) ไปยัง BigQuery โดยใช้ BigQuery DataFrames (bigframes)

เรียกใช้คำสั่งต่อไปนี้ใน Cloud Shell เพื่อติดตั้งส่วนที่ใช้อ้างอิงและเรียกใช้สคริปต์การส่งผ่านข้อมูล

# 1. Install recordlinkage dataset library & bigframes (if outside Cloud Shell, activate your virtual environment first)
pip install recordlinkage bigframes --quiet

# 2. Write and execute the FEBRL3 dataset ingestion script
cat << 'EOF' > ingest_febrl.py
import os
import pandas as pd
import bigframes.pandas as bpd
from recordlinkage.datasets import load_febrl3

GCP_PROJECT = os.environ.get("GCP_PROJECT", "your-project-id")
DATASET_ID = "identity_resolution"

table_raw_id = f"{GCP_PROJECT}.{DATASET_ID}.customer_nodes"
table_gt_id = f"{GCP_PROJECT}.{DATASET_ID}.ground_truth_links"

print("Loading FEBRL3 benchmark dataset...")
df_nodes, true_links = load_febrl3(return_links=True)
df_nodes = df_nodes.reset_index()
df_nodes['dataset_source'] = 'febrl3'

for col in df_nodes.columns:
    if df_nodes[col].dtype == 'object':
        df_nodes[col] = df_nodes[col].fillna('')

print("Ingesting raw customer nodes into BigQuery via BigQuery DataFrames...")
bf_nodes = bpd.read_pandas(df_nodes)
bf_nodes.to_gbq(table_raw_id, if_exists="replace")

print("Ingesting ground truth links into BigQuery via BigQuery DataFrames...")
df_gt = pd.DataFrame(list(true_links), columns=["source_id", "target_id"])
bf_gt = bpd.read_pandas(df_gt)
bf_gt.to_gbq(table_gt_id, if_exists="replace")

print(f"Raw customer nodes ingested into `{table_raw_id}` ({len(df_nodes):,} rows).")
print(f"Ground truth links ingested into `{table_gt_id}` ({len(df_gt):,} pairs).")
EOF

python3 ingest_febrl.py

ในคอนโซล Google Cloud ให้ไปที่ BigQuery Studio เปิดแท็บการค้นหา SQL ใหม่ (+) แล้วเรียกใช้การค้นหาด้านล่างเพื่อตรวจสอบตารางโหนดลูกค้าที่นำเข้า

SELECT rec_id, given_name, surname, street_number, address_1, address_2, suburb, postcode, state, date_of_birth, soc_sec_id, dataset_source
FROM `identity_resolution.customer_nodes`
LIMIT 5;

คุณควรเห็นเอาต์พุตที่คล้ายกับตัวอย่างต่อไปนี้

rec_id

given_name

แซ่

street_number

address_1

address_2

ชานเมือง

รหัสไปรษณีย์

รัฐ

date_of_birth

soc_sec_id

dataset_source

rec-10-org

brent

wood

11

girdlestone circuit

kingston tower

clifton springs

4152

nsw

19340706

1075870

febrl3

rec-10-dup-0

brnt

woode

11

girdelstone circut

cliffton springs

4152

nsw

19340706

1075870

febrl3

rec-10-dup-1

bernt

wood

11

girdlestone cir

kingston twr

clifton spngs

4152

19340760

1075870

febrl3

rec-10-dup-2

brent

wod

15

girdlestone crt

clifton springs

4152

nsw

19340706

febrl3

rec-25-org

mccarthy

henry

13

beasley street

crystal brook farm

ingleburn

6164

vic

19770913

1347524

febrl3

สังเกตว่าชุดข้อมูลการเปรียบเทียบจะแสดงข้อมูลที่ไม่ถูกต้องที่สมจริงในคลัสเตอร์ที่ซ้ำกันอย่างไร

  • การสะกดและคำที่ออกเสียงคล้ายกัน: brent กับ brnt / bernt, wood กับ woode / wod และ clifton กับ cliffton
  • การย่อและพิมพ์ที่อยู่ผิด: girdlestone circuit เทียบกับ girdelstone circut / girdlestone cir / girdlestone crt และหมายเลขบ้าน 11 เทียบกับข้อผิดพลาดของ OCR 15
  • การสลับอักขระและค่าที่ขาดหายไป: การสลับวันเกิด (19340706 กับ 19340760) รัฐที่ขาดหายไป ( ) และรหัสประกันสังคมที่ขาดหายไป ( )

ในขั้นตอนถัดไป คุณจะใช้SOUNDEXการเข้ารหัสแบบสัทอักษร, UDF การแปลงที่อยู่ให้เป็นรูปแบบมาตรฐาน, ระยะทางแก้ไข Levenshtein และAI.EMBEDการค้นหาเวกเตอร์เพื่อเชื่อมโยงความคลาดเคลื่อนเหล่านี้และลิงก์โปรไฟล์ที่ซ้ำกันอย่างถูกต้อง

4. ติดตั้งใช้งาน UDF ฟังก์ชันระยะไกลของการตรวจสอบที่อยู่

การแปลงที่อยู่เป็นรูปแบบมาตรฐานจะแปลงชื่อถนน ขอบเขตชานเมือง และรหัสไปรษณีย์เป็นรูปแบบมาตรฐานก่อนทำการจับคู่ Google Maps Address Validation API เป็นบริการที่ยอมรับที่อยู่ ระบุคอมโพเนนต์ของที่อยู่ และตรวจสอบความถูกต้องของคอมโพเนนต์เหล่านั้น ในขั้นตอนนี้ คุณจะทําการติดตั้งใช้งาน Python Cloud Function ใน Cloud Shell ซึ่งจะแสดง UDF การตรวจสอบและการแปลงที่อยู่ให้เป็นรูปแบบมาตรฐานไปยัง BigQuery

เขียนไฟล์ต้นฉบับของ Cloud Function

เรียกใช้คำสั่งต่อไปนี้ใน Cloud Shell เพื่อสร้างไดเรกทอรีต้นทางของ Cloud Functions และเขียน main.py และ requirements.txt

mkdir -p cloud_function_address_validation && cd cloud_function_address_validation

cat << 'EOF' > main.py
import os
import json
import logging
import requests
from functools import lru_cache
from concurrent.futures import ThreadPoolExecutor

import functions_framework
import google.auth
from google.auth.transport.requests import AuthorizedSession
from requests.adapters import HTTPAdapter
from urllib3.util import Retry

ADDRESS_VALIDATION_URL = "https://addressvalidation.googleapis.com/v1:validateAddress"
ENABLE_ADDRESS_VALIDATION_API = os.environ.get("ENABLE_ADDRESS_VALIDATION_API", "false").lower() == "true"

# ==========================================
# GLOBAL INITIALIZATION (Runs once per Cold Start)
# ==========================================

credentials, _ = google.auth.default(scopes=["https://www.googleapis.com/auth/cloud-platform"])
session = AuthorizedSession(credentials)

retries = Retry(
    total=4, 
    backoff_factor=0.5, 
    status_forcelist=[429, 500, 502, 503, 504],
    allowed_methods=["POST"]
)
adapter = HTTPAdapter(max_retries=retries, pool_connections=100, pool_maxsize=100)
session.mount("https://", adapter)

executor = ThreadPoolExecutor(max_workers=50)


@lru_cache(maxsize=10000)
def call_validation_api(address_text: str) -> str:
    payload = {
        "address": {
            "regionCode": "AU",
            "addressLines": [address_text]
        }
    }
    
    response = session.post(ADDRESS_VALIDATION_URL, json=payload, timeout=10)
    response.raise_for_status()
        
    res_data = response.json()
    result = res_data.get('result', {})
    address_obj = result.get('address', {})
    verdict = result.get('verdict', {})

    formatted = address_obj.get('formattedAddress', address_text).lower()
    has_unconfirmed = verdict.get('hasUnconfirmedComponents', True)
    address_complete = verdict.get('addressComplete', False)
    granularity = verdict.get('validationGranularity', 'UNCONFIRMED')
    actions = verdict.get('possibleNextActions', [])
    next_action = str(actions[0]) if actions else "NONE"

    is_valid = bool(address_complete and not has_unconfirmed)

    return {
        "formatted_address": formatted,
        "address_is_valid": is_valid,
        "validation_granularity": granularity,
        "possible_next_action": next_action
    }


def process_single_call(call):
    call = call or []
    padded = (call + [""] * 6)[:6]
    
    cleaned_parts = [str(p).strip() if p is not None else "" for p in padded]
    street_num, addr_1, addr_2, suburb, state, postcode = cleaned_parts
    
    address_parts = [p for p in cleaned_parts if p]
    address_text = " ".join(address_parts)

    if not address_text:
        return {
            "formatted_address": "",
            "address_is_valid": False,
            "validation_granularity": "EMPTY",
            "possible_next_action": "NONE"
        }

    if not ENABLE_ADDRESS_VALIDATION_API:
        normalized = (
            address_text.lower()
            .replace("street", "st")
            .replace("road", "rd")
            .replace("place", "pl")
            .replace("avenue", "ave")
            .replace("circuit", "cct")
        )
        return {
            "formatted_address": normalized,
            "address_is_valid": bool(len(address_parts) >= 3),
            "validation_granularity": "PREMISE" if postcode and suburb else "SUBURB",
            "possible_next_action": "NONE"
        }

    try:
        return call_validation_api(address_text)
    except Exception as e:
        logging.error(f"Address Validation API Error for '{address_text}': {str(e)}")
        return {
            "formatted_address": address_text.lower(),
            "address_is_valid": False,
            "validation_granularity": "UNCONFIRMED",
            "possible_next_action": "NONE"
        }


@functions_framework.http
def validate_address_udf(request):
    request_json = request.get_json(silent=True) or {}
    calls = request_json.get('calls', [])

    if not calls:
        return {'replies': []}

    try:
        # executor.map inherently preserves array input order (Strictly required by BigQuery)
        replies = list(executor.map(process_single_call, calls))
        return {'replies': replies}
    except Exception as e:
        logging.error(f"Batch execution failed: {e}")
        return {'errorMessage': str(e)}, 400
EOF

cat << 'EOF' > requirements.txt
functions-framework==3.*
requests==2.*
google-auth==2.*
urllib3==2.*
EOF

ติดตั้งใช้งาน Cloud Function และกำหนดค่าสิทธิ์ IAM

เรียกใช้คำสั่งเหล่านี้ใน Cloud Shell เพื่อทำให้ Cloud Function รุ่นที่ 2 ใช้งานได้และกำหนดค่าการเชื่อมต่อทรัพยากร Cloud ของ BigQuery

# 1. Deploy 2nd-Gen Cloud Function
gcloud functions deploy validate_address_udf \
  --gen2 \
  --runtime=python311 \
  --region=${REGION} \
  --source=. \
  --entry-point=validate_address_udf \
  --trigger-http \
  --no-allow-unauthenticated \
  --memory=512Mi \
  --cpu=1 \
  --concurrency=80 \
  --quiet

# 2. Extract Function Endpoint URI
export FUNCTION_URL=$(gcloud functions describe validate_address_udf --region=${REGION} --gen2 --format="value(serviceConfig.uri)")

# 3. Create BigQuery Cloud Resource Connection
bq mk --connection --location=US --project_id=${GCP_PROJECT} --connection_type=CLOUD_RESOURCE address_val_conn || true

# 4. Extract Connection Service Account Email
export BQ_SA_EMAIL=$(bq show --format=prettyjson --connection US.address_val_conn | grep -o '"serviceAccountId": "[^"]*"' | cut -d'"' -f4)

# 5. Bind Cloud Run Invoker and Vertex AI User IAM Roles to BigQuery Connection Service Account
gcloud run services add-iam-policy-binding validate-address-udf \
  --region=${REGION} \
  --member="serviceAccount:${BQ_SA_EMAIL}" \
  --role="roles/run.invoker" --quiet

gcloud projects add-iam-policy-binding ${GCP_PROJECT} \
  --member="serviceAccount:${BQ_SA_EMAIL}" \
  --role="roles/aiplatform.user" --quiet

# 6. Wait for connection IAM policy propagation
echo "Waiting 60 seconds for BigQuery connection IAM policy to propagate..."
sleep 60

คุณควรเห็นเอาต์พุตที่ระบุว่าการติดตั้งใช้งาน Cloud Functions เสร็จสมบูรณ์แล้ว และใช้การเชื่อมโยง IAM ได้สำเร็จ

ลงทะเบียนฟังก์ชันการทำให้ที่อยู่ระยะไกลเป็นมาตรฐาน

ตอนนี้คุณจะลงทะเบียน DDL ของฟังก์ชันระยะไกลของ BigQuery (validate_address_udf) ที่เชื่อมต่อแถวของตาราง BigQuery กับปลายทาง Cloud Functions ที่คุณได้ติดตั้งใช้งาน (${FUNCTION_URL})

เรียกใช้คำสั่งต่อไปนี้ใน Cloud Shell เพื่อดึงข้อมูล URL ของ Cloud Function ที่คุณได้ติดตั้งใช้งานและลงทะเบียนฟังก์ชันระยะไกลโดยอัตโนมัติ

# 1. Retrieve deployed Cloud Function URL
export FUNCTION_URL=$(gcloud functions describe validate_address_udf --region=${REGION:-us-central1} --gen2 --format="value(serviceConfig.uri)")

# 2. Register Remote Function DDL in BigQuery
bq query --use_legacy_sql=false \
"CREATE OR REPLACE FUNCTION \`${GCP_PROJECT}.${DATASET_ID}.validate_address_udf\`(
  street_number STRING,
  address_1 STRING,
  address_2 STRING,
  suburb STRING,
  state STRING,
  postcode STRING
) RETURNS JSON
REMOTE WITH CONNECTION \`us.address_val_conn\`
OPTIONS (
  endpoint = '${FUNCTION_URL}',
  max_batching_rows = 100
);"

5. ประมวลผลข้อมูลโปรไฟล์และการเข้ารหัสสัทอักษรล่วงหน้า

ในขั้นตอนนี้ คุณจะเรียกใช้การค้นหาการประมวลผลล่วงหน้าของ BigQuery SQL ในcustomer_nodesตารางที่ส่งผ่านข้อมูล

เรียกใช้การล้างข้อมูลและการค้นหาฟีเจอร์สัทอักษร

ในเครื่องมือแก้ไข SQL ของ BigQuery Studio ให้เรียกใช้การค้นหาด้านล่างเพื่อสร้าง customer_nodes_cleaned การค้นหานี้

  1. การเรียกใช้ validate_address_udf เพื่อรับที่อยู่ที่ปรับให้เป็นมาตรฐานและผลการตรวจสอบ
  2. สร้างSOUNDEXการเข้ารหัสสัทอักษรสำหรับ given_name และ surname เพื่อจัดการการสะกดที่แตกต่างกัน
  3. สร้างprofile_textฟิลด์ที่มีโครงสร้าง
CREATE OR REPLACE TABLE `identity_resolution.customer_nodes_cleaned` AS
WITH raw_data AS (
  SELECT 
    rec_id, dataset_source,
    TRIM(LOWER(given_name)) AS given_name_clean,
    TRIM(LOWER(surname)) AS surname_clean,
    `identity_resolution.validate_address_udf`(street_number, address_1, address_2, suburb, state, postcode) AS addr_json,
    TRIM(suburb) AS suburb, TRIM(state) AS state, TRIM(postcode) AS postcode,
    TRIM(date_of_birth) AS date_of_birth, TRIM(soc_sec_id) AS soc_sec_id
  FROM `identity_resolution.customer_nodes`
)
SELECT
  rec_id, dataset_source,
  given_name_clean AS given_name,
  SOUNDEX(given_name_clean) AS given_name_soundex,
  surname_clean AS surname,
  SOUNDEX(surname_clean) AS surname_soundex,
  CONCAT(given_name_clean, ' ', surname_clean) AS full_name,
  STRING(addr_json.formatted_address) AS formatted_address,
  BOOL(addr_json.address_is_valid) AS address_is_valid,
  STRING(addr_json.validation_granularity) AS validation_granularity,
  STRING(addr_json.possible_next_action) AS possible_next_action,
  suburb, state, postcode, date_of_birth, soc_sec_id,
  CONCAT('Name: ', CONCAT(given_name_clean, ' ', surname_clean), '; Address: ', STRING(addr_json.formatted_address), '; DOB: ', date_of_birth, '; SSN: ', soc_sec_id) AS profile_text
FROM raw_data;

เรียกใช้การค้นหาตารางโหนดที่ล้างแล้ว

SELECT rec_id, given_name, given_name_soundex, surname, surname_soundex, formatted_address 
FROM `identity_resolution.customer_nodes_cleaned`
LIMIT 5;

คุณควรเห็นเอาต์พุตที่คล้ายกับตัวอย่างต่อไปนี้

rec_id

given_name

given_name_soundex

แซ่

surname_soundex

formatted_address

rec-001-A

John

J500

Smith

S530

12 high st richmond vic 3121

rec-001-B

Jon

J500

Smith

S530

12 high st richmond vic 3121

rec-002-A

Elizabeth

E421

Taylor

T460

45 park rd suite 4 south yarra vic 3141

6. สร้างการฝังโปรไฟล์เชิงความหมายและ Vector Search

นอกจากการทำให้ที่อยู่เป็นรูปแบบเดียวกัน คีย์การออกเสียง Soundex และระยะทางแก้ไข Levenshtein แล้ว BigQuery ยังรองรับฟังก์ชันการฝัง Generative AI ในตัวผ่าน AI.EMBED

เมื่อใช้ AI.EMBED BigQuery จะสร้างการฝังข้อความใน SQL โดยตรงโดยใช้โมเดลพื้นฐาน (เช่น text-embedding-005) โดยไม่ต้องใช้ DDL ดัชนีเวกเตอร์ด้วยตนเอง

เรียกใช้การค้นหาด้านล่างในเครื่องมือแก้ไข SQL ของ BigQuery Studio

-- 1. Generate Customer Profile Embeddings using AI.EMBED (offloaded to Vertex AI)
CREATE OR REPLACE TABLE `identity_resolution.customer_embeddings` AS
SELECT 
  rec_id,
  dataset_source,
  profile_text,
  AI.EMBED(profile_text, connection_id => 'us.address_val_conn', endpoint => 'text-embedding-005').result AS text_embedding
FROM `identity_resolution.customer_nodes_cleaned`;

-- 2. Execute VECTOR_SEARCH for Top-K Nearest Neighbors Candidate Generation
CREATE OR REPLACE TABLE `identity_resolution.vector_candidate_edges` AS
SELECT 
  query.rec_id AS source_id,
  base.rec_id AS target_id,
  distance AS vector_distance
FROM VECTOR_SEARCH(
  TABLE `identity_resolution.customer_embeddings`,
  'text_embedding',
  TABLE `identity_resolution.customer_embeddings`,
  top_k => 5,
  distance_type => 'COSINE'
)
WHERE query.rec_id < base.rec_id AND distance <= 0.20;

7. การให้คะแนนคู่ตัวเลือกและการผสานฟีเจอร์ไฮบริดเอดจ์

การประเมินคู่บันทึกลูกค้าที่เป็นไปได้ทั้งหมด (การเติบโตแบบกำลังสอง O(N²)) จะกลายเป็นข้อจำกัดด้านการคำนวณเมื่อชุดข้อมูลมีขนาดใหญ่ขึ้น ในเครื่องมือฐานข้อมูลเชิงสัมพันธ์ เช่น BigQuery การพยายามใช้การบล็อกตามกฎโดยใช้ORเงื่อนไขการรวมที่ซับซ้อนในหลายคอลัมน์ (เช่น การรวมใน a.soc_sec_id = b.soc_sec_id OR a.given_name_soundex = b.given_name_soundex OR ...) จะทําให้เครื่องมือเพิ่มประสิทธิภาพการค้นหาไม่สามารถใช้การรวมแฮชที่ปรับขนาดได้หรือการรวมแบบเรียงลําดับและผสานในคีย์การรวมที่เท่ากันรายการเดียว แต่เครื่องมือจะกลับไปใช้การคูณไขว้ O(N²) และกรองแต่ละคู่ ซึ่งจะล้มเหลวเมื่อมีขนาดใหญ่

ในขั้นตอนนี้ คุณจะใช้คู่ผู้สมัครที่สร้างโดยตารางการค้นหาเวกเตอร์ (vector_candidate_edges) และรวมเข้ากับ customer_nodes_cleaned ผ่านการรวมแบบเท่ากันที่รวดเร็วและมีการจัดทำดัชนี (ON c.source_id = a.rec_id และ ON c.target_id = b.rec_id) จากนั้น คุณจะคำนวณคะแนนการจับคู่แบบถ่วงน้ำหนักโดยรวมสิ่งต่อไปนี้

  • คะแนนการจับคู่ SSN (น้ำหนัก: 0.30)
  • ความคล้ายคลึงของการแก้ไขนามสกุลโดยใช้ระยะทางเลเวนชไตน์ EDIT_DISTANCE (น้ำหนัก: 0.20)
  • ความคล้ายคลึงของการแก้ไขชื่อ (น้ำหนัก: 0.20)
  • คะแนนการจับคู่วันเกิด (น้ำหนัก: 0.15)
  • ความคล้ายคลึงของโทเค็นที่อยู่แบบ Jaccard (น้ำหนัก: 0.15) ใน SPLIT(LOWER(formatted_address), ' ')

คำนวณขอบตัวเลือกและคะแนนความคล้ายคลึงแบบถ่วงน้ำหนัก

เรียกใช้การค้นหาต่อไปนี้ในเครื่องมือแก้ไข SQL ของ BigQuery Studio เพื่อป้อนข้อมูล matched_edges

CREATE OR REPLACE TABLE `identity_resolution.matched_edges` AS
WITH candidate_pairs AS (
  SELECT 
    c.source_id, c.target_id,
    a.given_name AS a_given_name, b.given_name AS b_given_name,
    a.surname AS a_surname, b.surname AS b_surname,
    a.given_name_soundex AS a_gn_snd, b.given_name_soundex AS b_gn_snd,
    a.surname_soundex AS a_sn_snd, b.surname_soundex AS b_sn_snd,
    a.date_of_birth AS a_dob, b.date_of_birth AS b_dob,
    a.soc_sec_id AS a_ssn, b.soc_sec_id AS b_ssn,
    SPLIT(LOWER(a.formatted_address), ' ') AS a_tokens,
    SPLIT(LOWER(b.formatted_address), ' ') AS b_tokens
  FROM `identity_resolution.vector_candidate_edges` c
  JOIN `identity_resolution.customer_nodes_cleaned` a ON c.source_id = a.rec_id
  JOIN `identity_resolution.customer_nodes_cleaned` b ON c.target_id = b.rec_id
),
scored_pairs AS (
  SELECT
    source_id, target_id,
    CASE WHEN a_ssn = b_ssn AND a_ssn != '' THEN 1.0 ELSE 0.0 END AS ssn_match,
    CASE WHEN a_dob = b_dob THEN 1.0 ELSE 0.0 END AS dob_match,
    CASE WHEN a_gn_snd = b_gn_snd THEN 1.0 ELSE 0.0 END AS given_name_soundex_match,
    CASE WHEN a_sn_snd = b_sn_snd THEN 1.0 ELSE 0.0 END AS surname_soundex_match,
    GREATEST(
      (1.0 - (EDIT_DISTANCE(a_given_name, b_given_name) / GREATEST(LENGTH(a_given_name), LENGTH(b_given_name), 1))),
      (1.0 - (EDIT_DISTANCE(a_given_name, b_surname) / GREATEST(LENGTH(a_given_name), LENGTH(b_surname), 1)))
    ) AS given_name_edit_sim,
    GREATEST(
      (1.0 - (EDIT_DISTANCE(a_surname, b_surname) / GREATEST(LENGTH(a_surname), LENGTH(b_surname), 1))),
      (1.0 - (EDIT_DISTANCE(a_surname, b_given_name) / GREATEST(LENGTH(a_surname), LENGTH(b_given_name), 1)))
    ) AS surname_edit_sim,
    (
      (SELECT COUNT(DISTINCT t) FROM UNNEST(a_tokens) t JOIN UNNEST(b_tokens) t2 ON t = t2)
      /
      GREATEST(1.0, (SELECT COUNT(DISTINCT t) FROM UNNEST(ARRAY_CONCAT(a_tokens, b_tokens)) t))
    ) AS address_jaccard_sim
  FROM candidate_pairs
)
SELECT
  source_id, target_id,
  ROUND((0.30 * ssn_match) + (0.20 * surname_edit_sim) + (0.20 * given_name_edit_sim) + (0.15 * dob_match) + (0.15 * address_jaccard_sim), 4) AS match_score
FROM scored_pairs
WHERE ((0.30 * ssn_match) + (0.20 * surname_edit_sim) + (0.20 * given_name_edit_sim) + (0.15 * dob_match) + (0.15 * address_jaccard_sim)) >= 0.55;

ตรวจสอบการจับคู่ขอบที่เป็นไปได้

SELECT source_id, target_id, match_score 
FROM `identity_resolution.matched_edges`
ORDER BY match_score DESC;

คุณควรเห็นเอาต์พุตที่คล้ายกับตัวอย่างต่อไปนี้

source_id

target_id

match_score

rec-001-A

rec-001-B

0.9400

rec-002-A

rec-002-B

0.9100

rec-003-A

rec-003-B

0.7300

ผสานขอบของกฎที่อิงตามกฎและเวกเตอร์เข้ากับตารางแบบรวม

รวมขอบผู้สมัครจาก Fuzzy Matching ตามกฎและการค้นหาเวกเตอร์เชิงความหมายไว้ในตาราง final_matched_edges เดียวที่กรองข้อมูลที่ซ้ำกันออกแล้ว

CREATE OR REPLACE TABLE `identity_resolution.final_matched_edges` AS
SELECT 
  source_id, 
  target_id, 
  MAX(edge_weight) AS edge_weight,
  IF(COUNT(DISTINCT edge_type) > 1, 'HYBRID', MAX(edge_type)) AS edge_type
FROM (
  SELECT source_id, target_id, match_score AS edge_weight, 'RULE_BASED' AS edge_type
  FROM `identity_resolution.matched_edges`
  UNION ALL
  SELECT source_id, target_id, ROUND(1.0 - vector_distance, 4) AS edge_weight, 'VECTOR_SEARCH' AS edge_type
  FROM `identity_resolution.vector_candidate_edges`
  WHERE vector_distance <= 0.05
)
GROUP BY source_id, target_id;

8. การสร้างกราฟพร็อพเพอร์ตี้และการข้ามเส้นทาง GQL ของ ISO

BigQuery รองรับ ISO GQL (Graph Query Language) โดยกำเนิดผ่านกราฟพร็อพเพอร์ตี้ กราฟเชิงคุณสมบัติจะสร้างมุมมองกราฟเชิงตรรกะเหนือตาราง BigQuery เชิงสัมพันธ์โดยไม่ต้องทำซ้ำข้อมูล

ในขั้นตอนนี้ คุณจะสร้างกราฟพร็อพเพอร์ตี้ customer_identity_graph โดยใช้ตารางขอบผู้สมัครที่รวมเป็นหนึ่ง (final_matched_edges) และเชื่อมต่อกราฟการค้นหาในโปรไฟล์ลูกค้าโดยใช้การสำรวจเส้นทาง {1, 2} k-hop

การเชื่อมต่อแบบทรานซิทีฟและการข้าม K-Hop

สร้าง DDL ของกราฟพร็อพเพอร์ตี้ BigQuery

เรียกใช้คำสั่ง DDL ต่อไปนี้ในโปรแกรมแก้ไข SQL ของ BigQuery Studio

CREATE OR REPLACE PROPERTY GRAPH `identity_resolution.customer_identity_graph`
NODE TABLES (
  `identity_resolution.customer_nodes_cleaned` AS `Customer`
  KEY (rec_id)
)
EDGE TABLES (
  `identity_resolution.final_matched_edges`
  KEY (source_id, target_id)
  SOURCE KEY (source_id) REFERENCES `Customer`(rec_id)
  DESTINATION KEY (target_id) REFERENCES `Customer`(rec_id)
  LABEL MATCHED_TO
);

แสดงภาพคลัสเตอร์กราฟ K-Hop

เรียกใช้คําค้นหาด้านล่างเพื่อแสดงภาพคลัสเตอร์ลูกค้าที่ตรงกันในการข้ามความสัมพันธ์ 1-2 ครั้ง

GRAPH `identity_resolution.customer_identity_graph`
MATCH p = (c1:Customer)-[e:MATCHED_TO]->{1, 2}(c2:Customer)
RETURN TO_JSON(p) AS graph_cluster_path
LIMIT 10;

การแสดงภาพคลัสเตอร์กราฟ K-Hop

แก้ไขคลัสเตอร์ลูกค้า Canonical

เรียกใช้การค้นหาต่อไปนี้เพื่อแก้ไขคลัสเตอร์เอนทิตีเป็น resolved_customers

CREATE OR REPLACE TABLE `identity_resolution.resolved_customers` AS
WITH graph_paths AS (
  SELECT 
    source_node_id, target_node_id
  FROM GRAPH_TABLE(
    `identity_resolution.customer_identity_graph`
    MATCH (c1:Customer)-[e:MATCHED_TO]->{1, 2}(c2:Customer)
    COLUMNS (c1.rec_id AS source_node_id, c2.rec_id AS target_node_id)
  )
),
all_connections AS (
  SELECT source_node_id AS node_id, target_node_id AS connected_id FROM graph_paths
  UNION DISTINCT
  SELECT target_node_id AS node_id, source_node_id AS connected_id FROM graph_paths
  UNION DISTINCT
  SELECT rec_id AS node_id, rec_id AS connected_id FROM `identity_resolution.customer_nodes_cleaned`
),
clusters AS (
  SELECT 
    node_id,
    MIN(connected_id) AS canonical_customer_id
  FROM all_connections
  GROUP BY node_id
)
SELECT 
  canonical_customer_id,
  ARRAY_AGG(node_id) AS customer_records,
  COUNT(node_id) AS record_count
FROM clusters
GROUP BY canonical_customer_id;

ค้นหาตารางคลัสเตอร์ที่แก้ไขแล้ว

SELECT canonical_customer_id, record_count, customer_records 
FROM `identity_resolution.resolved_customers`
ORDER BY record_count DESC;

คุณควรเห็นเอาต์พุตที่คล้ายกับตัวอย่างต่อไปนี้

canonical_customer_id

record_count

customer_records

rec-001-A

2

['rec-001-A', 'rec-001-B']

rec-002-A

2

['rec-002-A', 'rec-002-B']

rec-003-A

2

['rec-003-A', 'rec-003-B']

สร้างมุมมองเมตริกการประเมิน

หากต้องการคำนวณความแม่นยำ ความอ่อนไหว และคะแนน F1 เทียบกับตาราง ground_truth_links ให้เรียกใช้คำสั่งต่อไปนี้

CREATE OR REPLACE VIEW `identity_resolution.evaluation_metrics` AS
WITH predictions AS (
  SELECT 
    LEAST(source_id, target_id) AS source_id, 
    GREATEST(source_id, target_id) AS target_id 
  FROM `identity_resolution.final_matched_edges` 
  WHERE edge_weight >= 0.55
),
ground_truth AS (
  SELECT 
    LEAST(source_id, target_id) AS source_id, 
    GREATEST(source_id, target_id) AS target_id 
  FROM `identity_resolution.ground_truth_links`
),
stats AS (
  SELECT
    COUNT(g.source_id) AS total_ground_truth,
    COUNT(p.source_id) AS total_predictions,
    COUNTIF(p.source_id IS NOT NULL AND g.source_id IS NOT NULL) AS true_positives,
    COUNTIF(p.source_id IS NOT NULL AND g.source_id IS NULL) AS false_positives,
    COUNTIF(p.source_id IS NULL AND g.source_id IS NOT NULL) AS false_negatives
  FROM ground_truth g
  FULL OUTER JOIN predictions p ON g.source_id = p.source_id AND g.target_id = p.target_id
)
SELECT
  total_ground_truth, total_predictions, true_positives, false_positives, false_negatives,
  ROUND(true_positives / NULLIF(true_positives + false_positives, 0), 4) AS precision,
  ROUND(true_positives / NULLIF(true_positives + false_negatives, 0), 4) AS recall,
  ROUND(2 * true_positives / NULLIF((2 * true_positives) + false_positives + false_negatives, 0), 4) AS f1_score
FROM stats;

ค้นหาข้อมูลในมุมมองเมตริกการประเมิน

SELECT * FROM `identity_resolution.evaluation_metrics`;

คุณควรเห็นเอาต์พุตที่คล้ายกับตัวอย่างต่อไปนี้

total_ground_truth

total_predictions

true_positives

false_positives

false_negatives

ความแม่นยำ

การเรียกคืน

f1_score

6538

5620

5608

13

930

0.9977

0.8579

0.9225

แก้ไขคลัสเตอร์ครัวเรือนผ่านการให้น้ำหนักกราฟ Adamic-Adar

แม้ว่าการแก้ปัญหาข้อมูลระบุตัวบุคคลจะแก้ปัญหาบันทึกที่เป็นของบุคคลเดียวกัน แต่สถาปัตยกรรม Customer 360 ขององค์กรมักต้องมีการจัดกลุ่มเอนทิตีครัวเรือนระดับสูงกว่า ซึ่งจะจัดกลุ่มบุคคลที่อาศัยอยู่ร่วมกันและใช้ที่อยู่เดียวกัน

เนื่องจากเกณฑ์เปรียบเทียบสังเคราะห์ (เช่น FEBRL3) ประเมินข้อมูลจากการสังเกตการณ์โดยตรงระดับบุคคล ระบบจึงทำการแก้ปัญหาในระดับครัวเรือนเป็นขั้นตอนดาวน์สตรีม หากไม่มีประวัติการย้ายที่ที่มีการประทับเวลา บุคคลที่ลิงก์กับที่อยู่หลายแห่งอาจทำให้เกิดการผสานมากเกินไปหรือการกระจายคลัสเตอร์ เราจึงใช้การกำหนดน้ำหนักกราฟ Adamic-Adar เพื่อสร้างการเป็นสมาชิกในครัวเรือนแบบยืดหยุ่น

เรียกใช้การค้นหาด้านล่างในเครื่องมือแก้ไข SQL ของ BigQuery Studio เพื่อป้อนข้อมูล household_clusters โดยใช้การถ่วงน้ำหนักกราฟ Adamic-Adar

CREATE OR REPLACE TABLE `identity_resolution.household_clusters` AS
WITH customer_addresses AS (
  SELECT DISTINCT
    r.canonical_customer_id,
    c.formatted_address
  FROM `identity_resolution.resolved_customers` r,
  UNNEST(r.customer_records) AS rec_id
  JOIN `identity_resolution.customer_nodes_cleaned` c ON rec_id = c.rec_id
  WHERE c.formatted_address IS NOT NULL AND c.formatted_address != ''
),
-- Adamic-Adar Exclusivity Weighting: 1.0 / LN(GREATEST(degree, 2))
address_degrees AS (
  SELECT 
    formatted_address,
    COUNT(DISTINCT canonical_customer_id) AS address_degree,
    1.0 / LN(GREATEST(COUNT(DISTINCT canonical_customer_id), 2)) AS address_exclusivity_weight
  FROM customer_addresses
  GROUP BY formatted_address
),
customer_household_affinity AS (
  SELECT 
    ca.canonical_customer_id,
    ca.formatted_address AS household_address,
    ad.address_degree,
    ad.address_exclusivity_weight,
    ad.address_exclusivity_weight * COUNT(DISTINCT ca2.canonical_customer_id) AS raw_household_affinity
  FROM customer_addresses ca
  JOIN address_degrees ad ON ca.formatted_address = ad.formatted_address
  LEFT JOIN customer_addresses ca2 
    ON ca.formatted_address = ca2.formatted_address 
   AND ca.canonical_customer_id != ca2.canonical_customer_id
  GROUP BY ca.canonical_customer_id, ca.formatted_address, ad.address_degree, ad.address_exclusivity_weight
),
ranked_households AS (
  SELECT 
    canonical_customer_id,
    household_address,
    address_degree AS total_residents,
    ROUND(
      COALESCE(SAFE_DIVIDE(raw_household_affinity, SUM(raw_household_affinity) OVER(PARTITION BY canonical_customer_id)), 1.0),
      4
    ) AS household_membership_weight,
    ROW_NUMBER() OVER(PARTITION BY canonical_customer_id ORDER BY raw_household_affinity DESC) AS household_rank
  FROM customer_household_affinity
)
SELECT 
  CONCAT('hh-', ABS(FARM_FINGERPRINT(household_address))) AS canonical_household_id,
  canonical_customer_id,
  household_address,
  total_residents,
  household_membership_weight,
  household_rank
FROM ranked_households;

ค้นหาตารางคลัสเตอร์ครัวเรือนที่แก้ไขแล้ว

SELECT canonical_household_id, canonical_customer_id, household_address, total_residents, household_membership_weight, household_rank
FROM `identity_resolution.household_clusters`
ORDER BY total_residents DESC;

แสดงภาพลำดับชั้นของข้อมูลระบุตัวตนแบบครบวงจรผ่าน GQL

หากต้องการติดตามลําดับชั้นของข้อมูลระบุตัวตนแบบ 3 ระดับทั้งหมดด้วยภาพ โดยเชื่อมต่อลูกค้าดิบที่ไม่ได้จัดกลุ่มกับเอนทิตีลูกค้าที่ได้รับการแก้ไข และต่อไปยังเอนทิตีครัวเรือนที่ได้รับการแก้ไข ให้เรียกใช้การค้นหา DDL และ ISO GQL ต่อไปนี้ใน BigQuery Studio

-- 1. Create Household Node Table
CREATE OR REPLACE TABLE `identity_resolution.household_nodes` AS
SELECT DISTINCT 
  canonical_household_id, 
  household_address, 
  total_residents
FROM `identity_resolution.household_clusters`;

-- 2. Create Unresolved Record to Resolved Entity Edge Table
CREATE OR REPLACE TABLE `identity_resolution.customer_entity_edges` AS
SELECT DISTINCT
  rec_id,
  canonical_customer_id
FROM `identity_resolution.resolved_customers`,
UNNEST(customer_records) AS rec_id;

-- 3. Create Primary Household Edge Table (Highest Weighted Household Rank = 1)
CREATE OR REPLACE TABLE `identity_resolution.primary_household_edges` AS
SELECT 
  canonical_customer_id,
  canonical_household_id,
  household_membership_weight,
  household_rank
FROM `identity_resolution.household_clusters`
WHERE household_rank = 1;

-- 4. Update Unified Property Graph DDL
CREATE OR REPLACE PROPERTY GRAPH `identity_resolution.customer_identity_graph`
NODE TABLES (
  `identity_resolution.customer_nodes_cleaned` AS `RawCustomer`
    KEY (rec_id),
  `identity_resolution.resolved_customers` AS `ResolvedCustomer`
    KEY (canonical_customer_id),
  `identity_resolution.household_nodes` AS `ResolvedHousehold`
    KEY (canonical_household_id)
)
EDGE TABLES (
  `identity_resolution.final_matched_edges`
    KEY (source_id, target_id)
    SOURCE KEY (source_id) REFERENCES `RawCustomer`(rec_id)
    DESTINATION KEY (target_id) REFERENCES `RawCustomer`(rec_id)
    LABEL MATCHED_TO,
  `identity_resolution.customer_entity_edges`
    KEY (rec_id, canonical_customer_id)
    SOURCE KEY (rec_id) REFERENCES `RawCustomer`(rec_id)
    DESTINATION KEY (canonical_customer_id) REFERENCES `ResolvedCustomer`(canonical_customer_id)
    LABEL RESOLVED_TO,
  `identity_resolution.primary_household_edges`
    KEY (canonical_customer_id, canonical_household_id)
    SOURCE KEY (canonical_customer_id) REFERENCES `ResolvedCustomer`(canonical_customer_id)
    DESTINATION KEY (canonical_household_id) REFERENCES `ResolvedHousehold`(canonical_household_id)
    LABEL BELONGS_TO_HOUSEHOLD
);

-- 5. Execute 3-Tier GQL Query for Multi-Resident Household Visualization
GRAPH `identity_resolution.customer_identity_graph`
MATCH p = (raw:RawCustomer)-[e1:RESOLVED_TO]->(c:ResolvedCustomer)-[e2:BELONGS_TO_HOUSEHOLD]->(h:ResolvedHousehold)
WHERE h.total_residents > 1
RETURN TO_JSON(p) AS multi_resident_household_hierarchy_path
LIMIT 15;

การเรียกใช้การค้นหา GQL นี้ใน BigQuery Studio จะแสดง Canvas การแสดงข้อมูลเป็นกราฟแบบอินเทอร์แอกทีฟ 3 ระดับ ซึ่งแสดงระเบียนโปรไฟล์ลูกค้าดิบ (RawCustomer) ที่ได้รับการแก้ไขเป็นเอนทิตี Canonical แต่ละรายการ (ResolvedCustomer) ซึ่งลิงก์กับเอนทิตีครัวเรือนที่มีผู้พำนักอาศัยร่วมกันหลายราย (ResolvedHousehold)

การแสดงภาพคลัสเตอร์กราฟครัวเรือนที่มีผู้พำนักอาศัยหลายคน

9. ความละเอียดที่เพิ่มขึ้นและความเสถียรที่คงที่

ในแอปพลิเคชันระดับองค์กรในโลกจริง ระบบจะได้รับระเบียนลูกค้าใหม่ๆ อย่างต่อเนื่องผ่านการนำเข้าแบบเป็นชุดรายวันหรือแบบเรียลไทม์ เครื่องมือจับคู่ส่วนเพิ่มของเดลต้าจะเปรียบเทียบระเบียนใหม่ที่เข้ามากับคลัสเตอร์พื้นฐานที่แก้ไขแล้วที่มีอยู่ (resolved_customers) แทนที่จะเรียกใช้การแก้ไขกราฟแบบเต็มอีกครั้งในชุดข้อมูลย้อนหลังทั้งหมด

โดยเครื่องมือจะใช้การค้นหาเวกเตอร์ (

VECTOR_SEARCH

) เป็นรูปแบบหนึ่งของการจัดกลุ่มแบบไดนามิก เมื่อถือว่าแต่ละระเบียนขาเข้าเป็นจุดค้นหา VECTOR_SEARCH จะดึงชุดของเพื่อนบ้านที่ใกล้ที่สุด K อันดับแรกจากดัชนีการฝังเส้นฐานในอดีต หากระเบียนขาเข้าตรงกับโปรไฟล์ลูกค้าเดิมที่มีอยู่ซึ่งอยู่เหนือเกณฑ์ความคล้ายคลึง ระบบจะผสานระเบียนดังกล่าวเข้ากับคลัสเตอร์นั้นแบบไดนามิก และรับค่าพื้นฐาน canonical_customer_id (MATCHED_TO_EXISTING_CLUSTER) หากไม่พบค่าพื้นฐานที่ใกล้ที่สุดซึ่งอยู่เหนือเกณฑ์ ระบบจะสร้าง UUID ของเอนทิตีใหม่ (NEW_CUSTOMER_ENTITY)

ส่งผ่านข้อมูลบันทึกการรับข้อมูลแบบกลุ่มที่เพิ่มขึ้นตัวอย่าง

วางและเรียกใช้ DDL ต่อไปนี้ในตัวแก้ไข SQL ของ BigQuery Studio เพื่อสร้าง incremental_daily_intake

CREATE OR REPLACE TABLE `identity_resolution.incremental_daily_intake` AS
SELECT * FROM UNNEST([
  STRUCT(
    'rec-9999-new-1' AS rec_id, 'erin' AS given_name, 'donaldson' AS surname, 
    'E650' AS given_name_soundex, 'D543' AS surname_soundex, 
    '19810427' AS date_of_birth, '2955815' AS soc_sec_id, 
    '13 hawkesbury crescent aralee lewiston 7018' AS formatted_address, '7018' AS postcode
  ),
  STRUCT(
    'rec-9999-new-2' AS rec_id, 'hollie' AS given_name, 'lillie-hinrichs' AS surname, 
    'H400' AS given_name_soundex, 'L446' AS surname_soundex, 
    '19251130' AS date_of_birth, '4920253' AS soc_sec_id, 
    '27 hemmings crescent kilvinton village banyo 4030' AS formatted_address, '4030' AS postcode
  ),
  STRUCT(
    'rec-9999-new-3' AS rec_id, 'sarah' AS given_name, 'ryan' AS surname, 
    'S600' AS given_name_soundex, 'R500' AS surname_soundex, 
    '20010101' AS date_of_birth, '999999999' AS soc_sec_id, 
    '500 market st melbourne vic 3000' AS formatted_address, '3000' AS postcode
  ),
  STRUCT(
    'rec-9999-new-4' AS rec_id, 'zzyzx' AS given_name, 'qx-vonderland' AS surname, 
    'Z220' AS given_name_soundex, 'Q215' AS surname_soundex, 
    '19991231' AS date_of_birth, '999887766' AS soc_sec_id, 
    '9999 zulu orbit station moon-base alpha 9999' AS formatted_address, '9999' AS postcode
  )
]);

เรียกใช้การค้นหาการจับคู่เดลต้าแบบเพิ่ม

เรียกใช้การค้นหาต่อไปนี้ในตัวแก้ไข SQL ของ BigQuery เพื่อทำการจับคู่ส่วนต่างกับชุดข้อมูลพื้นฐานที่แก้ไขแล้ว

CREATE OR REPLACE TABLE `identity_resolution.incremental_resolved_customers` AS
WITH historical_resolved_base AS (
  SELECT c.rec_id, c.given_name, c.surname, c.given_name_soundex, c.surname_soundex, c.date_of_birth, c.soc_sec_id, c.formatted_address, c.postcode, r.canonical_customer_id
  FROM `identity_resolution.customer_nodes_cleaned` c
  JOIN (
    SELECT canonical_customer_id, node_id
    FROM `identity_resolution.resolved_customers`, UNNEST(customer_records) AS node_id
  ) r ON c.rec_id = r.node_id
),
incremental_intake AS (
  SELECT 
    rec_id AS new_rec_id, given_name, surname, given_name_soundex, surname_soundex, date_of_birth, soc_sec_id, formatted_address, postcode,
    CONCAT('Name: ', CONCAT(given_name, ' ', surname), '; Address: ', formatted_address, '; DOB: ', date_of_birth, '; SSN: ', soc_sec_id) AS profile_text
  FROM `identity_resolution.incremental_daily_intake`
),
rule_delta_matches AS (
  SELECT 
    i.new_rec_id,
    h.canonical_customer_id AS matched_canonical_id,
    h.rec_id AS matched_baseline_rec_id,
    (
      0.30 * (CASE WHEN i.soc_sec_id = h.soc_sec_id AND i.soc_sec_id != '' THEN 1.0 ELSE 0.0 END) +
      0.20 * (1.0 - (EDIT_DISTANCE(i.surname, h.surname) / GREATEST(LENGTH(i.surname), LENGTH(h.surname), 1))) +
      0.20 * (1.0 - (EDIT_DISTANCE(i.given_name, h.given_name) / GREATEST(LENGTH(i.given_name), LENGTH(h.given_name), 1))) +
      0.15 * (CASE WHEN i.date_of_birth = h.date_of_birth THEN 1.0 ELSE 0.0 END) +
      0.15 * (1.0 - (EDIT_DISTANCE(i.formatted_address, h.formatted_address) / GREATEST(LENGTH(i.formatted_address), LENGTH(h.formatted_address), 1)))
    ) AS match_score
  FROM incremental_intake i
  JOIN historical_resolved_base h
    ON (i.soc_sec_id = h.soc_sec_id AND i.soc_sec_id != '')
    OR (i.date_of_birth = h.date_of_birth AND i.given_name_soundex = h.given_name_soundex)
),
vector_intake_embeddings AS (
  SELECT new_rec_id, AI.EMBED(profile_text, connection_id => 'us.address_val_conn', endpoint => 'text-embedding-005').result AS text_embedding
  FROM incremental_intake
),
vector_delta_matches AS (
  SELECT 
    v.query.new_rec_id,
    h.canonical_customer_id AS matched_canonical_id,
    h.rec_id AS matched_baseline_rec_id,
    ROUND(1.0 - v.distance, 4) AS match_score
  FROM VECTOR_SEARCH(
    TABLE `identity_resolution.customer_embeddings`,
    'text_embedding',
    TABLE vector_intake_embeddings,
    top_k => 3,
    distance_type => 'COSINE'
  ) v
  JOIN historical_resolved_base h ON v.base.rec_id = h.rec_id
  WHERE v.distance <= 0.20
),
combined_delta AS (
  SELECT 
    new_rec_id, matched_canonical_id, matched_baseline_rec_id, match_score,
    'RULE_BASED' AS match_strategy
  FROM rule_delta_matches WHERE match_score >= 0.55
  
  UNION ALL
  
  SELECT 
    new_rec_id, matched_canonical_id, matched_baseline_rec_id, match_score,
    'VECTOR_SEARCH' AS match_strategy
  FROM vector_delta_matches
),
aggregated_delta AS (
  SELECT 
    new_rec_id, matched_canonical_id, matched_baseline_rec_id,
    MAX(match_score) AS match_score,
    CASE 
      WHEN COUNT(DISTINCT match_strategy) > 1 THEN 'BOTH'
      ELSE MAX(match_strategy)
    END AS match_strategy
  FROM combined_delta
  GROUP BY new_rec_id, matched_canonical_id, matched_baseline_rec_id
),
best_matches AS (
  SELECT 
    new_rec_id, matched_canonical_id, matched_baseline_rec_id, match_score, match_strategy,
    ROW_NUMBER() OVER(PARTITION BY new_rec_id ORDER BY match_score DESC) AS rank
  FROM aggregated_delta
)
SELECT 
  i.new_rec_id AS record_id,
  i.given_name, i.surname,
  COALESCE(b.matched_canonical_id, GENERATE_UUID()) AS persistent_canonical_customer_id,
  h.canonical_household_id AS assigned_household_id,
  CASE WHEN b.matched_canonical_id IS NOT NULL THEN 'MATCHED_TO_EXISTING_CLUSTER' ELSE 'NEW_CUSTOMER_ENTITY' END AS assignment_type,
  b.matched_baseline_rec_id,
  b.match_score,
  COALESCE(b.match_strategy, 'NONE') AS match_strategy
FROM incremental_intake i
LEFT JOIN best_matches b ON i.new_rec_id = b.new_rec_id AND b.rank = 1
LEFT JOIN `identity_resolution.primary_household_edges` h ON b.matched_canonical_id = h.canonical_customer_id;

ค้นหาผลลัพธ์ความละเอียดที่เพิ่มขึ้น

SELECT record_id, persistent_canonical_customer_id, assigned_household_id, assignment_type, match_score, match_strategy 
FROM `identity_resolution.incremental_resolved_customers`;

คุณควรเห็นเอาต์พุตที่คล้ายกับตัวอย่างต่อไปนี้

record_id

persistent_canonical_customer_id

assigned_household_id

assignment_type

match_score

match_strategy

rec-9999-new-1

rec-529-dup-0

hh-8745613133408211212

MATCHED_TO_EXISTING_CLUSTER

1.0000

BOTH

rec-9999-new-2

rec-875-dup-0

hh-4802217335228845918

MATCHED_TO_EXISTING_CLUSTER

1.0000

BOTH

rec-9999-new-3

rec-359-dup-0

hh-8891673998566910207

MATCHED_TO_EXISTING_CLUSTER

0.8739

VECTOR_SEARCH

rec-9999-new-4

197e02f9-7175-4484-9c32-b7edbc731c9e

NULL

NEW_CUSTOMER_ENTITY

NULL

NONE

10. การรวมคลัสเตอร์และการคงที่ของคลัสเตอร์ (การทับซ้อน 1-ε)

ในระบบขององค์กรที่ใช้งานจริง ทีมมักจะจัดกลุ่มกราฟทั้งหมดใหม่เป็นประจำ (เช่น ทุกสัปดาห์หรือทุกเดือน) เพื่อรวมขอบและแหล่งข้อมูลใหม่ๆ เมื่อเกิดความสัมพันธ์ใหม่ การจัดกลุ่มกราฟทั้งหมดอีกครั้งอาจทำให้ตัวระบุคลัสเตอร์เปลี่ยนหรือพลิกไปมาโดยพลการในการดำเนินการไปป์ไลน์

ความเสถียรของคลัสเตอร์จะประเมินการทับซ้อนของโหนดระหว่างคลัสเตอร์การเรียกใช้ปัจจุบัน (t) กับคลัสเตอร์การเรียกใช้ก่อนหน้า (t-1) โดยใช้เกณฑ์การทับซ้อน (1 - ε) (โดยที่ ε = 0.30 ซึ่งต้องมีการทับซ้อนของโหนดอย่างน้อย 70%) เพื่อรักษารหัสลูกค้าแบบถาวรสำหรับระบบ CRM, CDP และการเรียกเก็บเงินที่ดาวน์สตรีม

หากคลัสเตอร์ที่เพิ่งคำนวณใหม่ใน Run (t) แชร์ระเบียนสมาชิกอย่างน้อย 70% กับคลัสเตอร์จาก Run (t-1) คลัสเตอร์นั้นจะรับช่วงรหัสลูกค้าแบบถาวรในอดีต (STABLE_EVOLUTION) คลัสเตอร์ใหม่เอี่ยมจะได้รับ UUID ที่สร้างขึ้นใหม่ (NEW_CLUSTER_CREATED)

เรียกใช้การค้นหาการทับซ้อนและความเสถียรของคลัสเตอร์

เรียกใช้การค้นหาต่อไปนี้ในเครื่องมือแก้ไข SQL ของ BigQuery Studio เพื่อป้อนข้อมูล stable_resolved_customers

CREATE OR REPLACE TABLE `identity_resolution.stable_resolved_customers` AS
WITH previous_run_clusters AS (
  SELECT 
    canonical_customer_id AS previous_persistent_id,
    node_id
  FROM `identity_resolution.resolved_customers`, UNNEST(customer_records) AS node_id
),
current_run_clusters AS (
  SELECT 
    canonical_customer_id AS new_cluster_id,
    node_id,
    COUNT(*) OVER (PARTITION BY canonical_customer_id) AS current_cluster_size
  FROM `identity_resolution.resolved_customers`, UNNEST(customer_records) AS node_id
  UNION ALL
  SELECT 
    persistent_canonical_customer_id AS new_cluster_id,
    record_id AS node_id,
    COUNT(*) OVER (PARTITION BY persistent_canonical_customer_id) AS current_cluster_size
  FROM `identity_resolution.incremental_resolved_customers`
),
cluster_intersections AS (
  SELECT 
    c.new_cluster_id,
    p.previous_persistent_id,
    c.current_cluster_size,
    COUNT(c.node_id) AS shared_node_count,
    COUNT(c.node_id) / c.current_cluster_size AS overlap_fraction
  FROM current_run_clusters c
  JOIN previous_run_clusters p ON c.node_id = p.node_id
  GROUP BY c.new_cluster_id, p.previous_persistent_id, c.current_cluster_size
),
best_matching_previous_cluster AS (
  SELECT 
    new_cluster_id,
    previous_persistent_id,
    shared_node_count,
    overlap_fraction,
    ROW_NUMBER() OVER (PARTITION BY new_cluster_id ORDER BY overlap_fraction DESC) AS rank
  FROM cluster_intersections
  WHERE overlap_fraction >= 0.70
)
SELECT 
  c.new_cluster_id AS raw_cluster_id,
  COALESCE(b.previous_persistent_id, GENERATE_UUID()) AS persistent_canonical_customer_id,
  ARRAY_AGG(c.node_id) AS customer_records,
  COUNT(c.node_id) AS record_count,
  COALESCE(MAX(b.shared_node_count), 0) AS shared_node_count,
  COALESCE(MAX(b.overlap_fraction), 0.0) AS overlap_fraction,
  CASE 
    WHEN b.previous_persistent_id IS NOT NULL THEN 'STABLE_EVOLUTION'
    ELSE 'NEW_CLUSTER_CREATED'
  END AS cluster_status
FROM current_run_clusters c
LEFT JOIN best_matching_previous_cluster b 
  ON c.new_cluster_id = b.new_cluster_id AND b.rank = 1
GROUP BY c.new_cluster_id, b.previous_persistent_id;

ดูตัวอย่างที่กรองแล้วสำหรับระเบียนที่เพิ่มขึ้น

เรียกใช้การค้นหานี้เพื่อยืนยันสถานะความเสถียรของคลัสเตอร์สำหรับบันทึกการนำเข้าข้อมูลรายวัน

SELECT 
  s.persistent_canonical_customer_id,
  node_id AS record_id,
  h.canonical_household_id AS assigned_household_id,
  s.cluster_status
FROM `identity_resolution.stable_resolved_customers` s, UNNEST(s.customer_records) AS node_id
LEFT JOIN `identity_resolution.primary_household_edges` h 
  ON s.persistent_canonical_customer_id = h.canonical_customer_id
WHERE node_id IN ('rec-9999-new-1', 'rec-9999-new-2', 'rec-9999-new-3', 'rec-9999-new-4')
ORDER BY record_id;

คุณควรเห็นเอาต์พุตที่คล้ายกับตัวอย่างต่อไปนี้

persistent_canonical_customer_id

record_id

assigned_household_id

cluster_status

rec-529-dup-0

rec-9999-new-1

hh-8745613133408211212

STABLE_EVOLUTION

rec-875-dup-0

rec-9999-new-2

hh-4802217335228845918

STABLE_EVOLUTION

rec-359-dup-0

rec-9999-new-3

hh-8891673998566910207

STABLE_EVOLUTION

589f8102-1204-4530-8910-bc10294810a4

rec-9999-new-4

NULL

NEW_CLUSTER_CREATED

11. ล้างข้อมูล

หากต้องการหลีกเลี่ยงการเรียกเก็บเงินอย่างต่อเนื่องในบัญชี Google Cloud ให้ล้างข้อมูลทรัพยากรที่ติดตั้งใช้งานและชุดข้อมูล BigQuery

ใน Cloud Shell ให้เรียกใช้คำสั่งต่อไปนี้

# 1. Delete BigQuery Dataset
bq rm -r -f -d ${GCP_PROJECT}:${DATASET_ID}

# 2. Delete Cloud Function (2nd-Gen)
gcloud functions delete validate_address_udf --region=${REGION} --gen2 --quiet

# 3. Delete BigQuery Cloud Connection
bq rm -f --connection US.address_val_conn

หากสร้างโปรเจ็กต์ที่อยู่ในระบบคลาวด์ Google เฉพาะสำหรับ Lab นี้ คุณสามารถลบโปรเจ็กต์ได้โดยทำดังนี้

gcloud projects delete ${GCP_PROJECT}

12. ขอแสดงความยินดี

ยินดีด้วย คุณสร้างเครื่องมือการแก้ปัญหาข้อมูลระบุตัวตนของลูกค้าแบบครบวงจรภายใน Google Cloud BigQuery ได้สำเร็จโดยใช้กราฟพร็อพเพอร์ตี้ BigQuery, การค้นหา GQL ของ ISO, การจับคู่ความคล้ายคลึงแบบไฮบริด, การจับคู่ส่วนต่างแบบเพิ่มทีละรายการ และการรับประกันความเสถียรของคลัสเตอร์แบบถาวร

สิ่งที่คุณได้เรียนรู้

  • วิธีทำให้ Cloud Function รุ่นที่ 2 ใช้งานได้และเปิดเผยเป็นฟังก์ชันระยะไกลของ BigQuery
  • วิธีประมวลผลข้อมูลประชากรของลูกค้าล่วงหน้าโดยใช้SOUNDEXการเข้ารหัสแบบสัทอักษรและการตรวจสอบที่อยู่
  • วิธีดำเนินการบล็อกผู้สมัครและคำนวณคะแนนความคล้ายคลึงแบบไฮบริดโดยใช้ระยะทางเลเวนสไตน์ (EDIT_DISTANCE) และความคล้ายคลึงของโทเค็น Jaccard
  • วิธีสร้างกราฟพร็อพเพอร์ตี้ BigQuery (CREATE PROPERTY GRAPH) บนตารางโหนดและขอบ
  • วิธีค้นหาเส้นทางกราฟโดยใช้ GQL (GRAPH_TABLE) ของ ISO กับตัวระบุปริมาณ {1, 2} k-hop
  • วิธีแก้ไขคลัสเตอร์ลูกค้าที่เป็น Canonical และประเมินประสิทธิภาพของโมเดลเทียบกับเมตริกข้อมูลที่ระบุว่าถูกต้องโดยเจ้าหน้าที่
  • วิธีดำเนินการจับคู่เดลต้าที่เพิ่มขึ้นสำหรับการนำเข้าแบบเป็นกลุ่มรายวันโดยไม่ต้องประมวลผลชุดข้อมูลทั้งหมดซ้ำ
  • วิธีใช้การรับประกันเกณฑ์การทับซ้อน (1 - ε) เพื่อรักษาเสถียรภาพของคลัสเตอร์อย่างต่อเนื่องในการเรียกใช้ไปป์ไลน์

ขั้นตอนถัดไป

เอกสารอ้างอิง