BigQuery 그래프를 사용한 사기 감지

1. 소개

마케팅 헤더 - 핀테크 사기

허위 행위에는 연결된 항목의 숨겨진 네트워크가 포함되는 경우가 많습니다. 예를 들어 동일한 이메일 주소, 전화번호 또는 실제 주소를 공유하는 여러 계정이 있습니다. 기존의 관계형 데이터베이스는 이러한 복잡한 다중 홉 관계를 효율적으로 쿼리하는 데 어려움을 겪을 수 있습니다.

BigQuery Graph 를 사용하면 그래프 데이터베이스를 사용하여 이러한 네트워크를 대규모로 분석할 수 있습니다. 기존 BigQuery 테이블 위에 속성 그래프를 정의하고 Graph Query Language (GQL)를 사용하여 데이터에서 패턴을 찾을 수 있습니다.

사기 감지를 위한 그래프 네트워크의 일반적인 애플리케이션은 사기 네트워크와 연결된 배송 주소가 있는 주문을 중지하거나 에 속한 결제를 중지하는 것입니다 .

이 Codelab에서는 BigQuery Graph를 사용하여 사기 감지 솔루션을 빌드합니다. Cloud Storage에서 데이터를 로드하고, 속성 그래프를 만들고, 그래프 쿼리를 사용하여 의심스러운 연결을 식별합니다.

학습할 내용

  • BigQuery 데이터 세트를 만들고 데이터를 로드하는 방법을 알아봅니다.
  • DDL을 사용하여 속성 그래프를 정의하는 방법을 알아봅니다.
  • GQL을 사용하여 그래프를 쿼리하는 방법을 알아봅니다.
  • 그래프 분석을 사용하여 사기를 감지하는 방법을 알아봅니다.

필요한 항목

  • 결제가 사용 설정된 Google Cloud 프로젝트.
  • BigQuery 노트북 환경 (BigQuery Studio 또는 Colab Enterprise).

비용

이 실습에서는 청구 가능한 Google Cloud 리소스를 사용합니다. 완료 후 리소스를 삭제한다고 가정할 때 예상 비용은 5달러 미만입니다.

2. 시작하기 전에

Google Cloud 프로젝트 선택 또는 만들기

  1. Google Cloud 콘솔의 프로젝트 선택기 페이지에서 Google Cloud 프로젝트를 선택하거나 만듭니다.
  2. Google Cloud 프로젝트에 결제가 사용 설정되어 있는지 확인합니다. 결제가 사용 설정되어 있는지 확인하는 방법을 알아봅니다.

환경 선택

이 실습을 실행하려면 노트북 환경이 필요합니다. BigQuery Studio 또는 Colab Enterprise 를 사용할 수 있습니다.

  1. Google Cloud 콘솔에서 BigQuery 페이지로 이동합니다.
  2. Python 노트북을 사용하여 그래프 쿼리를 실행합니다.

Cloud Shell 시작

  1. Google Cloud 콘솔 상단에서 Cloud Shell 활성화 를 클릭합니다.
  2. 인증 확인:
gcloud auth list
  1. 프로젝트 확인:
gcloud config get project
  1. 필요한 경우 설정합니다.
export PROJECT_ID=<YOUR_PROJECT_ID>
gcloud config set project $PROJECT_ID

API 사용 설정

이 명령어를 실행하여 필요한 BigQuery API를 사용 설정합니다.

gcloud services enable bigquery.googleapis.com \
  bigqueryreservation.googleapis.com 

BigQuery 예약 및 할당 만들기

GQL 쿼리를 실행하려면 Enterprise 또는 Enterprise Plus 버전을 사용하는 예약이 있어야 합니다. Cloud Shell에서 자동 확장을 사용하여 Enterprise 버전 예약을 만듭니다.

# 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 \
  fraud-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=fraud-reservation \
  --job_type=QUERY \
  --assignee_type=PROJECT \
  --assignee_id=${GCP_PROJECT}

3. 데이터 로드

이 단계에서는 BigQuery 데이터 세트를 만들고 Cloud Storage에서 샘플 데이터를 로드합니다.

샘플 데이터는 시뮬레이션된 소매 환경을 나타내는 여러 CSV 파일로 구성됩니다.

  • customers.csv: 고객 계정 정보
  • emails.csv: 이메일 주소
  • phones.csv: 전화번호
  • addresses.csv: 실제 주소
  • customer_emails.csv, customer_phones.csv, customer_addresses.csv: 테이블 연결
  • orders.csv: 사기 플래그를 포함한 주문 내역

데이터세트 만들기

테이블을 보관할 fraud_demo라는 데이터 세트를 만듭니다.

  1. 이 Codelab에서는 SQL 명령어를 실행합니다. BigQuery Studio > SQL 편집기에서 이러한 명령어를 실행하거나 Cloud Shell에서 bq query 명령어를 사용할 수 있습니다. 새 SQL 쿼리여러 줄의 create 문을 더 효과적으로 사용하기 위해 BigQuery SQL 편집기 를 사용한다고 가정합니다.
CREATE SCHEMA IF NOT EXISTS `fraud_demo` OPTIONS(location="US");

테이블 로드

다음 SQL 문을 실행하여 Cloud Storage에서 데이터 세트로 데이터를 로드합니다.

LOAD DATA OVERWRITE `fraud_demo.customers`
FROM FILES (
  format = 'CSV',
  uris = ['gs://sample-data-and-media/fraud-demo-data/customers.csv'],
  skip_leading_rows = 1
);

LOAD DATA OVERWRITE `fraud_demo.emails`
FROM FILES (
  format = 'CSV',
  uris = ['gs://sample-data-and-media/fraud-demo-data/emails.csv'],
  skip_leading_rows = 1
);

LOAD DATA OVERWRITE `fraud_demo.phones`
FROM FILES (
  format = 'CSV',
  uris = ['gs://sample-data-and-media/fraud-demo-data/phones.csv'],
  skip_leading_rows = 1
);

LOAD DATA OVERWRITE `fraud_demo.addresses`
FROM FILES (
  format = 'CSV',
  uris = ['gs://sample-data-and-media/fraud-demo-data/addresses.csv'],
  skip_leading_rows = 1
);

LOAD DATA OVERWRITE `fraud_demo.customer_emails`
FROM FILES (
  format = 'CSV',
  uris = ['gs://sample-data-and-media/fraud-demo-data/customer_emails.csv'],
  skip_leading_rows = 1
);

LOAD DATA OVERWRITE `fraud_demo.customer_phones`
FROM FILES (
  format = 'CSV',
  uris = ['gs://sample-data-and-media/fraud-demo-data/customer_phones.csv'],
  skip_leading_rows = 1
);

LOAD DATA OVERWRITE `fraud_demo.customer_addresses`
FROM FILES (
  format = 'CSV',
  uris = ['gs://sample-data-and-media/fraud-demo-data/customer_addresses.csv'],
  skip_leading_rows = 1
);

LOAD DATA OVERWRITE `fraud_demo.orders`
FROM FILES (
  format = 'CSV',
  uris = ['gs://sample-data-and-media/fraud-demo-data/orders.csv'],
  skip_leading_rows = 1
);

4. 속성 그래프 만들기

이제 데이터가 로드되었으므로 속성 그래프를 정의할 수 있습니다. 속성 그래프는 노드 (항목)와 에지 (관계)로 구성됩니다.

이 실습에서 노드는 다음과 같습니다.

  • 고객: 계정 소유자를 나타냅니다.
  • 전화: 전화번호를 나타냅니다.
  • 이메일: 이메일 주소를 나타냅니다.
  • 주소: 실제 주소를 나타냅니다.

에지는 다음과 같습니다.

  • OwnsPhone: 고객을 전화에 연결합니다.
  • OwnsEmail: 고객을 이메일에 연결합니다.
  • LinkedToAddress: 고객을 주소에 연결합니다.

그래프 만들기

다음 DDL 문을 실행하여 fraud_demo 데이터 세트에 FraudDemo라는 그래프를 만듭니다.

CREATE OR REPLACE PROPERTY GRAPH fraud_demo.FraudDemo
  NODE TABLES(
    fraud_demo.customers
      KEY(account_id)
      LABEL Customer PROPERTIES(
        account_id,
        name),

    fraud_demo.emails
      KEY(email)
      LABEL Email PROPERTIES(
        email,
        email_type),

    fraud_demo.phones
      KEY(phone_number)
      LABEL Phone PROPERTIES(
        phone_number,
        phone_type),

    fraud_demo.addresses
      KEY(address)
      LABEL Address PROPERTIES(
        address,
        address_type)
  )
  EDGE TABLES(
    fraud_demo.customer_emails
      KEY(account_id, email)
      SOURCE KEY(account_id) REFERENCES customers(account_id)
      DESTINATION KEY(email) REFERENCES emails(email)
      LABEL OwnsEmail PROPERTIES(
        account_id,
        email,
        last_updated_ts),

    fraud_demo.customer_phones
      KEY(account_id, phone_number)
      SOURCE KEY(account_id) REFERENCES customers(account_id)
      DESTINATION KEY(phone_number) REFERENCES phones(phone_number)
      LABEL OwnsPhone PROPERTIES(
        account_id,
        phone_number,
        last_updated_ts),

    fraud_demo.customer_addresses
      KEY(account_id, address)
      SOURCE KEY(account_id) REFERENCES customers(account_id)
      DESTINATION KEY(address) REFERENCES addresses(address)
      LABEL LinkedToAddress PROPERTIES(
        account_id,
        address,
        last_updated_ts)
  );

5. 네트워크 분석 (2홉)

BigQuery Studio에서 새 노트북 을 엽니다.

새 노트북 만들기

이 Codelab의 시각화 및 추천 부분에서는 BigQuery Studio의 Google Colab 노트북을 사용합니다. 이렇게 하면 그래프 결과를 쉽게 시각화할 수 있습니다.

다음 코드를 코드 셀에 붙여넣습니다.

!pip install bigquery-magics==0.12.1

BigQuery Graph 노트북은 IPython 매직으로 구현됩니다. TO_JSON 함수와 함께 %%bigquery 매직 명령어를 추가하면 다음 섹션에 표시된 대로 결과를 시각화할 수 있습니다. 이 단계에서는 그래프 쿼리를 실행하여 계정 간의 간단한 연결을 찾습니다. 이 쿼리는 시작 노드에서 2홉 이동하여 관련 노드 (예: 고객 -> 이메일 -> 고객)를 찾기 때문에 '2홉' 쿼리입니다.

먼저 Nicole Wade 의 계정을 조사합니다. 2홉을 통해 그녀와 관련된 계정을 찾으려고 합니다.

2홉 쿼리 실행

노트북에서 다음 쿼리를 실행합니다.

%%bigquery --graph
GRAPH fraud_demo.FraudDemo
MATCH 
 p=(a:Customer) 
  ( -[e:OwnsEmail|OwnsPhone|LinkedToAddress WHERE e.last_updated_ts < '2025-07-30']- (n) ){2}
WHERE a.account_id IN ("d2f1f992-d116-41b3-955b-6c76a3352657")
  -- Verify the final node in the hop array is a Customer
  AND 'Customer' IN UNNEST(LABELS(n[OFFSET(1)]))

RETURN TO_JSON(p) AS paths

각 결과의 의미

이 쿼리는 다음과 같은 작업을 합니다.

  1. account_id 'd2f1f992-d116-41b3-955b-6c76a3352657'(Nicole Wade)로 Customer 노드에서 시작합니다.
  2. 연결 노드 (Phone, Email, 또는 Address)로 이어지는 OwnsEmail, OwnsPhone, 또는 LinkedToAddress 에지를 따릅니다.
  3. 해당 연결 노드에서 다른 Customer 노드로 이어지는 에지를 따릅니다.
  4. 타임스탬프 (last_updated_ts)를 기준으로 에지를 필터링하여 특정 시점의 네트워크 상태를 확인합니다.

Zachary CordovaBrenda Brown 이 동일한 주소를 통해 Nicole에 연결되어 있음을 확인할 수 있습니다.

6. 네트워크 분석 (4홉)

이 단계에서는 더 복잡한 관계를 찾도록 쿼리를 확장합니다. 4홉 연결을 찾습니다. 이렇게 하면 여러 중간 항목을 통해 연결된 계정 (예: 고객 A -> 이메일 -> 고객 B -> 전화 -> 고객 C)을 찾을 수 있습니다.

또한 이 네트워크가 시간이 지남에 따라 어떻게 변화하는지 관찰합니다.

'이전' 상태

먼저 2025년 7월 30일 에 존재했던 네트워크를 살펴보겠습니다.

다음 쿼리를 실행합니다.

%%bigquery --graph
GRAPH fraud_demo.FraudDemo
MATCH p= ANY SHORTEST (a:Customer) 
  ( -[e:OwnsEmail|OwnsPhone|LinkedToAddress WHERE e.last_updated_ts < '2025-07-30']- (n) ){3, 5}(reachable_a:Customer)
WHERE a.account_id IN ("d2f1f992-d116-41b3-955b-6c76a3352657")
  -- Ensure the final node in the dynamic chain is actually a Customer
  AND 'Customer' IN UNNEST(LABELS(n[OFFSET(ARRAY_LENGTH(n) - 1)]))
RETURN 
  TO_JSON(p) AS paths,                   -- Array of all traversed edges
  ARRAY_LENGTH(e) AS hop_count

'이후' 상태

이제 2주 후 네트워크가 어떻게 보이는지 살펴보겠습니다. 동일한 쿼리를 실행하지만 날짜 제한은 없습니다.

다음 쿼리를 실행합니다.

%%bigquery --graph
GRAPH fraud_demo.FraudDemo
MATCH p= ANY SHORTEST (a:Customer) 
  ( -[e:OwnsEmail|OwnsPhone|LinkedToAddress]- (n) ){3, 5}(reachable_a:Customer)
WHERE a.account_id IN ("d2f1f992-d116-41b3-955b-6c76a3352657")
  -- Ensure the final node in the dynamic chain is actually a Customer
  AND 'Customer' IN UNNEST(LABELS(n[OFFSET(ARRAY_LENGTH(n) - 1)]))

RETURN 
  TO_JSON(p) AS paths,                   -- Array of all traversed edges
  ARRAY_LENGTH(e) AS hop_count

각 결과의 의미

날짜 필터를 삭제하면 전체 데이터 세트에 대해 쿼리합니다. 네트워크가 크게 확장되었음을 확인할 수 있습니다. 이제 Nicole Wade 는 훨씬 더 크고 연결성이 높은 그룹에 속해 있습니다. 연결된 네트워크의 이러한 빠른 확장은 시간이 지남에 따라 리소스를 공유하는 사기 조직과 같은 잠재적 허위 행위를 나타내는 강력한 지표입니다.

7. 사기 보고서 생성

이 단계에서는 그래프 분석을 기존 비즈니스 데이터 (주문)와 결합하여 포괄적인 사기 보고서를 생성합니다. 위험에 처한 계정과 잠재적 사기 주문을 식별합니다.

이 쿼리는 더 복잡합니다. GRAPH_TABLE을 사용하여 표준 SQL 내에서 그래프 쿼리를 실행하고 이전 단계에서 관찰한 '이전' 상태와 '이후' 상태 간의 네트워크 크기 변화 (diff)를 계산합니다.

사기 보고서 쿼리 실행

노트북에서 다음 쿼리를 실행합니다.

%%bigquery --graph
WITH num_orders AS (
  SELECT account_id, COUNT(1) AS num_order
  FROM fraud_demo.orders
  WHERE order_time > '2025-07-30'
  GROUP BY account_id
),

orders AS (
  SELECT account_id, order_id, fraud, order_total
  FROM fraud_demo.orders
  WHERE order_time > '2025-07-30'
),

-- Use Quantified Path Patterns to find connections up to 4 hops away
latest_connect AS (
  SELECT 
    account_id, 
    ARRAY_LENGTH(ARRAY_AGG(DISTINCT connected_id)) AS size
  FROM GRAPH_TABLE(
    fraud_demo.FraudDemo
    MATCH (a:Customer)-[:OwnsEmail|OwnsPhone|LinkedToAddress]-{4}(connected:Customer)
    RETURN a.account_id AS account_id, connected.account_id AS connected_id
  )
  GROUP BY account_id
),

prev_connect AS (
  SELECT 
    account_id, 
    ARRAY_LENGTH(ARRAY_AGG(DISTINCT connected_id)) AS size
  FROM GRAPH_TABLE(
    fraud_demo.FraudDemo
    -- Apply the timestamp filter to EVERY edge in the 4-hop chain
    MATCH (a:Customer)
          (-[e:OwnsEmail|OwnsPhone|LinkedToAddress WHERE e.last_updated_ts < '2025-07-30']-(n)){4}
    WHERE 'Customer' IN UNNEST(LABELS(n[OFFSET(3)]))
    RETURN a.account_id AS account_id, n[OFFSET(3)].account_id AS connected_id
  )
  GROUP BY account_id
),

edge_changes AS (
  SELECT account_id, MAX(last_updated_ts) AS max_last_updated_ts
  FROM fraud_demo.customer_addresses
  GROUP BY account_id
)

SELECT
    la.account_id,
    o.order_id,
    la.size AS latest_size,
    COALESCE(pa.size, 0) AS previous_size,
    la.size - COALESCE(pa.size, 0) AS diff,
    nos.num_order,
    o.fraud AS reported_as_fraud,
    o.order_total,

    CASE
      WHEN (la.size - COALESCE(pa.size, 0)) > 10 AND nos.num_order IS NULL THEN "CUSTOMER AT RISK"
      WHEN (la.size - COALESCE(pa.size, 0)) > 10 AND nos.num_order IS NOT NULL AND o.fraud THEN "CONFIRMED FRAUD ORDER"
      WHEN (la.size - COALESCE(pa.size, 0)) > 10 AND nos.num_order IS NOT NULL AND NOT o.fraud THEN "POTENTIAL FRAUD ORDER"
      ELSE ""
    END AS notes
FROM latest_connect la
LEFT JOIN prev_connect pa ON la.account_id = pa.account_id
LEFT JOIN num_orders nos ON la.account_id = nos.account_id
LEFT JOIN orders o ON la.account_id = o.account_id
INNER JOIN edge_changes ec ON la.account_id = ec.account_id
WHERE nos.num_order > 1 OR (la.size - COALESCE(pa.size, 0)) > 10
ORDER BY diff DESC

각 결과의 의미

이 보고서에는 다음이 표시됩니다.

  • account_id: 분석 중인 계정의 ID입니다.
  • order_id: 최근 주문 ID입니다.
  • latest_size: 오늘 연결된 네트워크의 크기입니다.
  • previous_size: 2주 전 네트워크의 크기입니다.
  • diff: 네트워크 크기의 증가입니다.
  • num_order: 최근 주문 수입니다.
  • reported_as_fraud: 주문이 사기로 신고되었는지 여부입니다.
  • order_total: 주문의 총액입니다.
  • notes: 네트워크 증가 및 주문 내역을 기반으로 계산된 위험 상태입니다.

diff 값이 크고 주문 총액이 높은 계정이 표시되며, 이러한 계정은 추가 조사의 주요 대상입니다. '위험에 처한 고객' 및 '잠재적 사기 주문' 메모는 이러한 계정의 우선순위를 지정하는 데 도움이 됩니다.

8. 대규모 감지

이 최종 분석 단계에서는 네트워크를 더 큰 규모로 시각화합니다. 단일 계정으로 시작하는 대신 의심스러운 계정 집합 간의 연결을 쿼리합니다.

이렇게 하면 여러 독립적인 조사가 실제로 더 큰 사기 조직의 일부인지 확인할 수 있습니다.

확장된 쿼리 실행

노트북에서 다음 쿼리를 실행합니다.

%%bigquery --graph
GRAPH fraud_demo.FraudDemo
MATCH
p= ANY SHORTEST (a:Customer) 
  ( -[e:OwnsEmail|OwnsPhone|LinkedToAddress]- (n) ){3, 5}
(reachable_a:Customer)
-- these IDs are from the previous results
WHERE a.account_id in (   "845f2b14-cd10-4750-9f28-fe542c4a731b"
  , "3ff59684-fbf9-40d7-8c41-285ade5002e6"
  , "8887c17b-e6fb-4b3b-8c62-cb721aafd028"
  , "03e777e5-6fb4-445d-b48c-cf42b7620874"
  , "81629832-eb1d-4a0e-86da-81a198604898"
  , "845f2b14-cd10-4750-9f28-fe542c4a731b",
   "89e9a8fe-ffc4-44eb-8693-a711a3534849"
 )
 LIMIT 400
RETURN TO_JSON(p) as paths

각 결과의 의미

이 쿼리는 지정된 의심스러운 계정이 어떻게 겹치고 리소스를 공유하는지 보여주는 복잡한 그래프를 반환합니다. 이제 대규모 사기 감지를 살펴보고, 조정된 대응이 필요할 수 있는 활동 클러스터를 식별합니다.

9. 삭제

이 Codelab에서 사용한 리소스 비용이 Google Cloud 계정에 청구되지 않도록 하려면 데이터 세트와 속성 그래프를 삭제해야 합니다.

다음 SQL 문을 실행하여 환경을 정리합니다.

DROP PROPERTY GRAPH IF EXISTS fraud_demo.FraudDemo;
DROP SCHEMA IF EXISTS fraud_demo CASCADE;

10. 축하합니다

축하합니다 BigQuery Graph를 사용하여 사기 감지 솔루션을 빌드했습니다.

지금까지 학습한 내용은 다음과 같습니다.

  • Cloud Storage에서 BigQuery로 데이터를 로드합니다.
  • DDL을 사용하여 속성 그래프를 정의합니다.
  • GQL을 사용하여 그래프를 쿼리하여 간단하고 복잡한 관계를 찾습니다.
  • 그래프 분석을 비즈니스 데이터와 결합하여 위험을 식별합니다.
  • 대규모로 네트워크를 시각화합니다.

기타 자료