레이크하우스와 AI를 사용하여 고객 의견에서 수요 예측

이 튜토리얼에서는 레이크하우스에서 구조화되지 않은 자유 형식 고객 의견을 구조화된 판매 데이터와 함께 분석하여 신제품 출시를 평가하는 방법을 보여줍니다.

Midnight Swirl이라는 새로운 맛을 출시할 계획인 가상의 냉동 요구르트 회사인 Froyo의 데이터 과학자라고 가정해 보겠습니다. 글로벌 출시를 결정하기 전에 비즈니스에서 다음 세 가지 질문에 답해야 합니다.

  • 새로운 맛에 적용되는 알레르기 유발 물질과 고객 피드백 우려사항은 무엇인가요?
  • 회사는 향후 12개월 동안 기존 맛에 대해 얼마나 많은 수요를 예상해야 할까요?
  • 고객 감정과 재구매 의도가 예측된 수요와 어떻게 비교되나요?

이 튜토리얼은 BigQuery 및 기본 SQL에 익숙한 데이터 분석가, 데이터 과학자, 데이터 엔지니어를 대상으로 합니다. Cloud Shell과 Google Cloud 콘솔을 사용하여 모든 단계를 완료합니다.

목표

  • Lakehouse 런타임 카탈로그에서 카탈로그를 만든 다음 Lakehouse에 제품, 판매, 고객 피드백 데이터를 저장하는 Apache Iceberg 테이블을 만듭니다.
  • BigQuery AI 함수 AI.CLASSIFY 및 AI.IF를 사용하여 Iceberg 테이블에 저장된 비정형 텍스트에서 감정, 주제, 구매 의도를 추출합니다.
  • BigQuery Studio의 대화형 분석을 사용하여 AI.FORECAST로 12개월 수요를 예측하고 예측을 제품 및 피드백 통계와 결합합니다.

비용

이 문서에서는 비용이 청구될 수 있는 Google Cloud구성요소를 사용합니다.

프로젝트 사용량을 기준으로 예상 비용을 산출하려면 가격 계산기를 사용하세요.

Google Cloud 신규 사용자는 무료 체험판을 사용할 수 있습니다.

이 문서에 설명된 태스크를 완료했으면 만든 리소스를 삭제하여 청구가 계속되는 것을 방지할 수 있습니다. 자세한 내용은 삭제를 참조하세요.

시작하기 전에

  1. Google Cloud 콘솔의 프로젝트 선택기 페이지에서 Google Cloud 프로젝트를 선택하거나 만듭니다.

    프로젝트 선택기로 이동

  2. Google Cloud 프로젝트에 결제가 사용 설정되어 있는지 확인합니다.

필요한 역할

이 튜토리얼의 작업을 완료하는 데 필요한 권한을 얻으려면 관리자에게 프로젝트에 대한 다음 IAM 역할을 부여해 달라고 요청하세요.

역할 부여에 대한 자세한 내용은 프로젝트, 폴더, 조직에 대한 액세스 관리를 참조하세요.

커스텀 역할이나 다른 사전 정의된 역할을 통해 필요한 권한을 얻을 수도 있습니다.

개발 환경 준비

Cloud Shell을 활성화하고, 리소스의 환경 변수를 설정하고, 필요한 API를 사용 설정합니다.

  1. Google Cloud 콘솔에서 Cloud Shell을 활성화합니다. 메시지가 표시되면 승인을 클릭합니다.

    Cloud Shell 활성화

  2. 이 튜토리얼 전체에서 재사용할 수 있도록 Cloud Shell에서 프로젝트 ID, 리전, 버킷 이름, 카탈로그 ID, 네임스페이스의 환경 변수를 설정합니다.

    export PROJECT_ID=$(gcloud config get-value project)
    export REGION="us-central1"
    export BUCKET_NAME="${PROJECT_ID}-froyo-lakehouse"
    export CATALOG_ID="froyo_catalog"
    export NAMESPACE_ID="froyo_lakehouse"
  3. 프로젝트에 필요한 API를 사용 설정합니다.

    • BigQuery API(bigquery.googleapis.com)
    • 레이크하우스 런타임 카탈로그에 대한 액세스를 제공하는 BigLake API (biglake.googleapis.com)
    • Cloud Storage API(storage.googleapis.com)
    • Agent Platform API (aiplatform.googleapis.com)
    • Conversational Analytics API (geminidataanalytics.googleapis.com)
    • Google Cloud를 위한 Gemini API (cloudaicompanion.googleapis.com)

    Cloud Shell에서 다음 API를 사용 설정합니다.

    gcloud services enable \
        bigquery.googleapis.com \
        biglake.googleapis.com \
        storage.googleapis.com \
        aiplatform.googleapis.com \
        geminidataanalytics.googleapis.com \
        cloudaicompanion.googleapis.com \
        --project=${PROJECT_ID}

Cloud Storage 버킷 만들기

Cloud Shell에서 Iceberg 테이블 파일을 저장할 Cloud Storage 버킷을 만듭니다.

gcloud storage buckets create gs://${BUCKET_NAME} \
    --project=${PROJECT_ID} \
    --location=${REGION}

Lakehouse 런타임 카탈로그에서 카탈로그 만들기

Iceberg REST 카탈로그 엔드포인트를 사용하여 카탈로그가 자동 프로비저닝된 서비스 계정을 사용하여 버킷에 액세스하고 BigQuery 연결을 만들지 않아도 되도록 사용자 인증 정보 제공 모드에서 카탈로그를 만듭니다.

  1. Cloud Shell에서 사용자 인증 정보 제공 모드로 다중 버킷 카탈로그를 만듭니다.

    gcloud biglake iceberg catalogs create ${CATALOG_ID} \
        --project=${PROJECT_ID} \
        --catalog-type=biglake \
        --default-location=gs://${BUCKET_NAME} \
        --primary-location=${REGION} \
        --credential-mode=vended-credentials
  2. 카탈로그의 자동 프로비저닝된 서비스 계정에 버킷에 대한 스토리지 객체 사용자 (roles/storage.objectUser) 역할을 부여합니다.

    CATALOG_SA=$(gcloud biglake iceberg catalogs describe ${CATALOG_ID} \
        --project=${PROJECT_ID} \
        --format='value(biglake-service-account)')
    
    gcloud storage buckets add-iam-policy-binding gs://${BUCKET_NAME} \
        --member="serviceAccount:${CATALOG_SA}" \
        --role="roles/storage.objectUser"
  3. 카탈로그에서 테이블의 네임스페이스를 만듭니다.

    gcloud biglake iceberg namespaces create ${NAMESPACE_ID} \
        --project=${PROJECT_ID} \
        --catalog=${CATALOG_ID}

Iceberg 테이블 만들기

카탈로그에 Iceberg 테이블 3개를 만들고 샘플 데이터를 로드합니다.

  • products: 새로운 Midnight Swirl 맛과 이 맛의 기반이 되는 기존 맛을 포함한 제품 목록을 저장합니다.
  • sales_history: 기존 맛의 월별 단위 판매량을 리전별로 24개월 동안 저장합니다.
  • customer_feedback: 의견 기록을 저장합니다. 각 행은 구조화된 메타데이터 (feedback_id, submitted_date, flavor_name, channel)를 비구조화된 자유 텍스트를 저장하는 comment STRING 열과 페어링합니다.

BigQuery에서는 4부분으로 구성된 P.C.N.T. 이름 지정 구조(Project.Catalog.Namespace.Table)를 사용하여 Lakehouse 테이블을 참조합니다.

  1. Cloud Shell에서 products, sales_history, customer_feedback 테이블을 만들고 샘플 데이터로 채웁니다. 스크립트는 각 명령문을 순서대로 실행하며 완료하는 데 약 1분이 걸립니다.

    bq query --project_id=${PROJECT_ID} --location=${REGION} --use_legacy_sql=false << EOF
    -- Product list.
    CREATE TABLE \`${PROJECT_ID}.${CATALOG_ID}.${NAMESPACE_ID}.products\` (
      product_name STRING,
      base_flavor STRING,
      status STRING,
      allergens STRING,
      target_regions STRING);
    
    INSERT INTO \`${PROJECT_ID}.${CATALOG_ID}.${NAMESPACE_ID}.products\` VALUES
      ('Midnight Swirl', 'Classic Chocolate', 'Planned launch',
       'Milk, Soy', 'North America, Europe'),
      ('Classic Chocolate', 'Classic Chocolate', 'In market',
       'Milk, Soy', 'North America, Europe, Asia Pacific'),
      ('Vanilla Bean', 'Vanilla Bean', 'In market',
       'Milk', 'North America, Europe, Asia Pacific'),
      ('Strawberry Swirl', 'Strawberry Swirl', 'In market',
       'Milk', 'North America, Europe, Asia Pacific');
    
    -- Monthly sales history for the flavors that are in market.
    CREATE TABLE \`${PROJECT_ID}.${CATALOG_ID}.${NAMESPACE_ID}.sales_history\` (
      sale_month DATE,
      flavor_name STRING,
      region STRING,
      units_sold INT64);
    
    INSERT INTO \`${PROJECT_ID}.${CATALOG_ID}.${NAMESPACE_ID}.sales_history\`
    WITH
      months AS (
        SELECT sale_month
        FROM UNNEST(GENERATE_DATE_ARRAY(
          '2024-01-01', '2025-12-01', INTERVAL 1 MONTH)) AS sale_month
      ),
      flavors AS (
        SELECT * FROM UNNEST([
          STRUCT('Classic Chocolate' AS flavor_name, 1200 AS base_units),
          ('Vanilla Bean', 1000),
          ('Strawberry Swirl', 800)])
      ),
      regions AS (
        SELECT * FROM UNNEST([
          STRUCT('North America' AS region, 1.0 AS region_factor),
          ('Europe', 0.8),
          ('Asia Pacific', 0.6)])
      )
    SELECT
      m.sale_month,
      f.flavor_name,
      r.region,
      CAST(
        f.base_units * r.region_factor
        -- Two percent month-over-month growth.
        * (1 + 0.02 * DATE_DIFF(m.sale_month, DATE '2024-01-01', MONTH))
        -- Seasonal peak in July.
        * (1 + 0.25 * SIN(2 * ACOS(-1)
            * (EXTRACT(MONTH FROM m.sale_month) - 4) / 12))
        AS INT64) AS units_sold
    FROM months AS m
    CROSS JOIN flavors AS f
    CROSS JOIN regions AS r;
    
    -- Customer feedback records with unstructured text in the comment column.
    CREATE TABLE \`${PROJECT_ID}.${CATALOG_ID}.${NAMESPACE_ID}.customer_feedback\` (
      feedback_id INT64,
      submitted_date DATE,
      flavor_name STRING,
      channel STRING,
      comment STRING);
    
    INSERT INTO \`${PROJECT_ID}.${CATALOG_ID}.${NAMESPACE_ID}.customer_feedback\` VALUES
      (1, '2025-11-03', 'Midnight Swirl', 'Taste panel',
       "Rich, deep chocolate with a hint of salt. Honestly better than the classic chocolate. I'd order this every week."),
      (2, '2025-11-03', 'Midnight Swirl', 'Taste panel',
       "Loved the dark chocolate swirl, but it was a bit too bitter for my kids."),
      (3, '2025-11-04', 'Midnight Swirl', 'Taste panel',
       "Best froyo I've tried this year. Smooth texture and not too sweet."),
      (4, '2025-11-04', 'Midnight Swirl', 'Taste panel',
       "Great taste. I'd definitely buy it again if it's priced like the other flavors."),
      (5, '2025-11-05', 'Midnight Swirl', 'Taste panel',
       "Does this contain soy? I have a soy allergy so I skipped it. Please label it clearly."),
      (6, '2025-11-05', 'Midnight Swirl', 'Taste panel',
       "Tastes like a premium dessert. The sea salt really makes it."),
      (7, '2025-06-12', 'Classic Chocolate', 'App review',
       "Solid chocolate flavor, my go-to order."),
      (8, '2025-07-02', 'Classic Chocolate', 'App review',
       "It's fine, but a little too sweet compared to other brands."),
      (9, '2025-08-19', 'Classic Chocolate', 'Support email',
       "Consistently good. I wish the cups were bigger for the price."),
      (10, '2025-09-07', 'Classic Chocolate', 'Support email',
       "My chocolate froyo was icy this time and the texture was off."),
      (11, '2025-10-21', 'Classic Chocolate', 'App review',
       "Classic for a reason. Always creamy. I'll keep ordering it."),
      (12, '2025-11-05', 'Classic Chocolate', 'Taste panel',
       "Good but forgettable next to the new dark chocolate sample."),
      (13, '2025-05-14', 'Vanilla Bean', 'App review',
       "Real vanilla flavor, you can see the specks. Love it."),
      (14, '2025-06-30', 'Vanilla Bean', 'App review',
       "Pretty plain. I only order it as a base for toppings."),
      (15, '2025-08-02', 'Vanilla Bean', 'App review',
       "Creamy and simple, perfect for my toddler."),
      (16, '2025-09-15', 'Vanilla Bean', 'Support email',
       "The store was out of vanilla bean two weekends in a row."),
      (17, '2025-06-08', 'Strawberry Swirl', 'App review',
       "Tastes like fresh strawberries. Great in the summer."),
      (18, '2025-07-26', 'Strawberry Swirl', 'App review',
       "Too artificial tasting. Not a fan."),
      (19, '2025-08-11', 'Strawberry Swirl', 'Support email',
       "My favorite flavor. Please never discontinue it."),
      (20, '2025-10-03', 'Strawberry Swirl', 'App review',
       "The price went up, but it's still worth it.");
    EOF
  2. 카탈로그에 products, sales_history, customer_feedback 테이블이 나열되어 있는지 확인합니다.

    gcloud biglake iceberg tables list \
        --project=${PROJECT_ID} \
        --catalog=${CATALOG_ID} \
        --namespace=${NAMESPACE_ID}

AI 함수로 피드백 분석

customer_feedback 테이블의 comment 열에는 직접 집계하거나 조인할 수 없는 구조화되지 않은 텍스트가 포함되어 있습니다. BigQuery AI 함수를 사용하여 각 댓글에서 구조화된 값을 추출하고 새 feedback_insights 테이블을 만듭니다.

  • AI.CLASSIFY는 사용자가 정의한 카테고리 목록에서 각 댓글에 감정 (positive, neutral 또는 negative)과 기본 주제 (예: taste, texture, price 또는 allergens)를 할당합니다.
  • AI.IF는 각 댓글에 대한 자연어 조건을 평가하고 BOOL 값을 반환합니다. 이 경우 값은 고객이 제품을 다시 구매할 의향이 있는지를 나타냅니다.

이러한 관리 AI 함수는 사용자 인증 정보를 사용하여 Gemini를 호출하므로 원격 모델이나 연결을 만들 필요가 없습니다.

  1. Cloud Shell에서 feedback_insights 테이블을 만들고 채웁니다.

    bq query --project_id=${PROJECT_ID} --location=${REGION} --use_legacy_sql=false << EOF
    CREATE TABLE \`${PROJECT_ID}.${CATALOG_ID}.${NAMESPACE_ID}.feedback_insights\` (
      feedback_id INT64,
      flavor_name STRING,
      channel STRING,
      comment STRING,
      sentiment STRING,
      topic STRING,
      would_buy_again BOOL);
    
    INSERT INTO \`${PROJECT_ID}.${CATALOG_ID}.${NAMESPACE_ID}.feedback_insights\`
    SELECT
      feedback_id,
      flavor_name,
      channel,
      comment,
      AI.CLASSIFY(
        comment,
        categories => ['positive', 'neutral', 'negative']) AS sentiment,
      AI.CLASSIFY(
        comment,
        categories => ['taste', 'texture', 'price', 'allergens',
                       'availability', 'other']) AS topic,
      AI.IF(
        ('This customer says or implies that they would buy the product ',
         'again. Comment: ', comment)) AS would_buy_again
    FROM \`${PROJECT_ID}.${CATALOG_ID}.${NAMESPACE_ID}.customer_feedback\`;
    EOF
  2. 각 버전의 의견 측정항목을 요약합니다.

    bq query --project_id=${PROJECT_ID} --location=${REGION} --use_legacy_sql=false << EOF
    SELECT
      flavor_name,
      COUNT(*) AS comments,
      COUNTIF(sentiment = 'positive') AS positive,
      COUNTIF(would_buy_again) AS would_buy_again,
      ROUND(COUNTIF(sentiment = 'positive') / COUNT(*), 2) AS positive_share
    FROM \`${PROJECT_ID}.${CATALOG_ID}.${NAMESPACE_ID}.feedback_insights\`
    GROUP BY flavor_name
    ORDER BY positive_share DESC;
    EOF

    출력은 다음과 비슷합니다.

    +-------------------+----------+----------+-----------------+----------------+
    |    flavor_name    | comments | positive | would_buy_again | positive_share |
    +-------------------+----------+----------+-----------------+----------------+
    | Strawberry Swirl  |        4 |        3 |               2 |           0.75 |
    | Midnight Swirl    |        6 |        4 |               3 |           0.67 |
    | Vanilla Bean      |        4 |        2 |               2 |            0.5 |
    | Classic Chocolate |        6 |        2 |               3 |           0.33 |
    +-------------------+----------+----------+-----------------+----------------+
    

대화형 분석으로 수요 예측

BigQuery Studio의 대화형 분석을 사용하면 자연어를 사용하여 Iceberg 테이블을 분석할 수 있습니다. Lakehouse 런타임 카탈로그에서 테이블 스키마를 가져오고, BigQuery SQL (AI.FORECAST과 같은 AI 함수 포함)을 생성하고 실행하며, 테이블과 차트를 채팅에 직접 반환합니다.

표를 활용해 대화 시작

  1. Google Cloud 콘솔에서 BigQuery 에이전트 페이지로 이동합니다.

    에이전트로 이동

  2. BigQuery Studio 편집기 창에서 에이전트 탭을 클릭하여 대화 창을 연 다음 새 채팅을 클릭합니다.

  3. 데이터와 채팅 창에서 지식 소스 탭을 클릭합니다. 소스 검색 필드에서 다음 값을 사용하여 Iceberg 테이블 3개를 검색하고 선택합니다. PROJECT_ID를 프로젝트 ID로 바꿉니다.

    • PROJECT_ID.froyo_catalog.froyo_lakehouse.products
    • PROJECT_ID.froyo_catalog.froyo_lakehouse.feedback_insights
    • PROJECT_ID.froyo_catalog.froyo_lakehouse.sales_history
  4. Chat을 클릭합니다.

질문을 통해 수요를 예측하고 출시 평가하기

일련의 자연어 질문을 통해 Midnight Swirl 출시와 관련된 세 가지 비즈니스 질문에 답변합니다.

  1. 질문하기 필드에 다음 프롬프트를 입력하여 Midnight Swirl의 제품 세부정보와 고객 의견을 확인한 다음 send_spark 보내기를 클릭합니다.

    Using products and feedback_insights, what are Midnight Swirl's allergens,
    base flavor, positive sentiment share, would_buy_again count, and customer
    concerns from negative or neutral comments?
    

    에이전트는 두 테이블을 모두 쿼리하고 Midnight Swirl이 Classic Chocolate을 기반으로 하며 우유와 콩이 포함되어 있다고 보고합니다. 또한 긍정적 감정 점유율이 0.67로, 6명의 고객 중 3명이 다시 구매할 의향이 있다고 보고하며, 콩 알레르기 유발 물질 라벨링과 다크 초콜릿의 쓴맛에 대한 우려를 강조합니다.

  2. 다음 프롬프트를 입력하여 기존 맛의 12개월 수요를 예측한 다음 send_spark 보내기를 클릭합니다.

    Forecast monthly sales by flavor for the next 12 months, plotting only the
    trend lines without confidence intervals.
    

    대화형 분석에서는 별도의 모델을 학습하지 않고 BigQuery ML의 기본 제공 TimesFM 모델을 사용하는 AI.FORECAST로 쿼리를 실행하고 각 맛에 대한 12개월 예측 차트를 표시합니다.

  3. 다음 프롬프트를 입력하여 수요 예측과 고객 의견을 결합하고 출시 추천을 받은 후 send_spark 보내기를 클릭합니다.

    Join the total 12-month forecasted units_sold for each flavor with its
    positive sentiment share, would_buy_again count, and top complaint topics
    from feedback_insights, and recommend next steps for the Midnight Swirl
    launch.
    
  4. 선택사항: 생성된 SQL 쿼리를 검사하거나 에이전트의 추론 단계를 보려면 생각 보기를 펼칩니다.

여기에서 대화에 후속 질문을 하거나 (예: 지역별 예측 분류) 세부정보 창에서 에이전트 만들기를 클릭하여 재사용 가능한 데이터 에이전트를 저장하고 팀과 공유할 수 있습니다.

삭제

이 튜토리얼에서 사용된 리소스 비용이 Google Cloud 계정에 청구되지 않도록 하려면 리소스가 포함된 프로젝트를 삭제하거나 프로젝트는 유지하되 개별 리소스를 삭제하세요.

프로젝트 삭제

  1. Google Cloud 콘솔에서 리소스 관리 페이지로 이동합니다.

    리소스 관리로 이동

  2. 프로젝트 목록에서 삭제할 프로젝트를 선택하고 삭제를 클릭합니다.
  3. 대화상자에서 프로젝트 ID를 입력한 후 종료를 클릭하여 프로젝트를 삭제합니다.

개별 리소스 삭제

프로젝트를 유지하려면 만든 개별 리소스를 삭제합니다.

  1. 대화를 삭제하려면 BigQuery Studio 편집기 창에서 에이전트 탭을 선택합니다. 확장해야 할 수도 있는 왼쪽 탐색 패널에서 최근 채팅 아래에 있는 대화를 찾은 다음 more_vert 작업 보기 > 삭제를 클릭합니다.

  2. Cloud Shell에서 네 개의 테이블, 네임스페이스, 카탈로그, Cloud Storage 버킷을 삭제합니다.

    for TABLE in products sales_history customer_feedback feedback_insights; do
      gcloud biglake iceberg tables delete ${TABLE} \
          --project=${PROJECT_ID} \
          --catalog=${CATALOG_ID} \
          --namespace=${NAMESPACE_ID} \
          --quiet
    done
    
    gcloud biglake iceberg namespaces delete ${NAMESPACE_ID} \
        --project=${PROJECT_ID} \
        --catalog=${CATALOG_ID} \
        --quiet
    
    gcloud biglake iceberg catalogs delete ${CATALOG_ID} \
        --project=${PROJECT_ID} \
        --quiet
    
    gcloud storage rm -r gs://${BUCKET_NAME}

다음 단계

Lakehouse 데이터를 사용하는 다양한 방법을 알아보려면 다음 리소스를 참고하세요.