ساختن برنامه‌های توصیه «نمای ۳۶۰ درجه از مشتری» با BigQuery Graph

۱. مقدمه

در این codelab، یاد خواهید گرفت که چگونه از BigQuery Graph برای ساختن «نمای ۳۶۰ درجه از مشتری» و موتور توصیه برای Cymbal Pets، شرکت خرده‌فروشی فرضی، استفاده کنید. از قدرت SQL برای ایجاد، پُرسمان، و تجزیه‌وتحلیل داده‌های گراف مستقیماً در BigQuery استفاده خواهید کرد و آن را با جستجوی بُرداری برای توصیه‌های پیشرفته محصول ترکیب خواهید کرد.

BigQuery Graph به شما امکان می‌دهد روابط بین موجودیت‌های داده خود (مانند مشتریان، محصولات، و سفارش‌ها) را به‌عنوان یک گراف مدل‌سازی کنید، که پاسخ دادن به پرسش‌های پیچیده درباره رفتار مشتری و تمایلات محصول را آسان می‌کند.

نمودار مورد استفاده

آنچه انجام خواهید داد

  • ایجاد کردن مجموعه‌داده و طرح‌واره BigQuery برای گراف Cymbal Pets
  • بار کردن داده‌های نمونه (مشتریان، محصولات، سفارش‌ها، فروشگاه‌ها) از Cloud Storage
  • یک «گراف خصوصیت» در BigQuery ایجاد کن که این نهادها را به هم متصل کند
  • دیداری‌سازی سابقه خرید مشتری بااستفاده از پُرسمان‌های گراف
  • ساختن سیستم توصیه محصول بااستفاده از جستجوی برداری
  • بهبود توصیه‌ها بااستفاده از روابط نمودار «باهم خریداری‌شده» و شباهت جاکارد

آنچه نیاز خواهید داشت

  • مرورگر وب مانند Chrome
  • پروژه Google Cloud با صورت‌حساب فعال

این codelab برای توسعه‌دهندگان در همه سطوح، ازجمله مبتدیان، مناسب است.

۲. قبل‌از شروع

ایجاد «پروژه Google Cloud»

  1. در کنسول Google Cloud، پروژه Google Cloud را انتخاب یا ایجاد کنید.
  2. مطمئن شوید که صورت‌حساب برای پروژه Cloud شما فعال است.

شروع Cloud Shell

  1. روی فعال‌سازی Cloud Shell در بالای کنسول Google Cloud کلیک کنید.
  2. درستی‌سنجی اصالت‌سنجی:
gcloud auth list
  1. پروژه‌تان را تأیید کنید:
gcloud config get project
  1. درصورت نیاز آن را تنظیم کنید:
export PROJECT_ID=<YOUR_PROJECT_ID>
gcloud config set project $PROJECT_ID

فعال کردن میاناهای برنامه‌سازی کاربردی

این فرمان را برای فعال کردن میانای برنامه‌سازی کاربردی BigQuery موردنیاز اجرا کنید:

gcloud services enable bigquery.googleapis.com

۳. تعریف طرح‌واره

ابتدا باید یک مجموعه داده برای ذخیره جدول‌های مرتبط با گراف خود ایجاد کنید و طرح‌واره را برای گره‌ها و لبه‌های خود تعریف کنید.

  1. برای این codelab، فرمان‌های SQL را اجرا خواهیم کرد. می‌توانید این فرمان‌ها را در BigQuery Studio > ویرایشگر SQL اجرا کنید، یا از فرمان bq query در Cloud Shell استفاده کنید. پُرسمان SQL جدیدفرض می‌کنیم که از ویرایشگر BigQuery SQL استفاده می‌کنید تا تجربه بهتری با عبارت‌های ایجاد چندخطی داشته باشید.
  2. مجموعه داده cymbal_pets_demo را ایجاد کنید:
CREATE SCHEMA IF NOT EXISTS cymbal_pets_demo;
  1. جدول‌های order_items،‏ products،‏ orders،‏ stores،‏ customers، و co_related_products_for_angelica را ایجاد کنید. این جدول‌ها به‌عنوان داده‌های منبع برای نمودار ما عمل خواهند کرد.
CREATE TABLE IF NOT EXISTS cymbal_pets_demo.order_items
(
  order_id INT64,
  product_id INT64,
  order_item_id INT64,
  quantity INT64,
  price FLOAT64,
  PRIMARY KEY (order_id, product_id, order_item_id) NOT ENFORCED
)
CLUSTER BY order_item_id;

CREATE TABLE IF NOT EXISTS cymbal_pets_demo.products
(
  product_id INT64,
  product_name STRING,
  brand STRING,
  category STRING,
  subcategory INT64,
  animal_type INT64,
  search_keywords INT64,
  price FLOAT64,
  description STRING,
  inventory_level INT64,
  supplier_id INT64,
  average_rating FLOAT64,
  uri STRING,
  embedding ARRAY<FLOAT64>,
  PRIMARY KEY (product_id) NOT ENFORCED
)
CLUSTER BY product_id;

CREATE TABLE IF NOT EXISTS cymbal_pets_demo.orders
(
  customer_id INT64,
  order_id INT64,
  shipping_address_city STRING,
  store_id INT64,
  order_date STRING,
  order_type STRING,
  payment_method STRING,
  PRIMARY KEY (order_id) NOT ENFORCED
);

CREATE TABLE IF NOT EXISTS cymbal_pets_demo.stores
(
  store_id INT64,
  store_name STRING,
  address_state STRING,
  address_city STRING,
  latitude FLOAT64,
  longitude FLOAT64,
  opening_hours STRUCT<Monday STRING, Tuesday STRING, Wednesday STRING, Thursday STRING, Friday STRING, Saturday STRING, Sunday STRING>,
  manager_id INT64,
  PRIMARY KEY (store_id) NOT ENFORCED
)
CLUSTER BY store_id;

CREATE TABLE IF NOT EXISTS cymbal_pets_demo.customers
(
  customer_id INT64,
  first_name STRING,
  last_name STRING,
  email STRING,
  gender STRING,
  address_city STRING,
  address_state STRING,
  loyalty_member BOOL,
  PRIMARY KEY (customer_id) NOT ENFORCED
)
CLUSTER BY customer_id;

CREATE TABLE IF NOT EXISTS cymbal_pets_demo.co_related_products_for_angelica
(
  angelica_product_id INT64,
  other_product_id INT64,
  co_purchase_count INT64,
  jaccard_similarity FLOAT64
);

اکنون ساختار داده‌های گراف را تعریف کرده‌اید.

۴. بار کردن داده‌ها

اکنون، جدول‌ها را با داده‌های نمونه از Cloud Storage پر کنید.

عبارت‌های LOAD DATA زیر را در ویرایشگر BigQuery SQL اجرا کنید:

LOAD DATA INTO `cymbal_pets_demo.customers`
FROM FILES (
    format = 'AVRO',
    uris = ['gs://sample-data-and-media/cymbal-pets/tables/customers/*.avro'],
    enable_logical_types = true
);

LOAD DATA INTO `cymbal_pets_demo.order_items`
FROM FILES (
    format = 'AVRO',
    uris = ['gs://sample-data-and-media/cymbal-pets/tables/order_items/*.avro'],
    enable_logical_types = true
);

LOAD DATA INTO `cymbal_pets_demo.orders`
FROM FILES (
    format = 'AVRO',
    uris = ['gs://sample-data-and-media/cymbal-pets/tables/orders/*.avro'],
    enable_logical_types = true
);

-- 1. Create a new partitioned and clustered table from your current data
CREATE TABLE cymbal_pets_demo.orders_temp
PARTITION BY order_date
CLUSTER BY order_id
AS
SELECT
  customer_id,
  order_id,
  shipping_address_city,
  store_id,
  -- Parse the string to a DATE type. Adjust format string ('%Y-%m-%d') if necessary.
  PARSE_DATE('%Y-%m-%d', order_date) AS order_date,
  order_type,
  payment_method
FROM
  cymbal_pets_demo.orders;

-- 2. Drop the original, non-partitioned table
DROP TABLE cymbal_pets_demo.orders;

-- 3. Rename the temporary table to the original table name
ALTER TABLE cymbal_pets_demo.orders_temp RENAME TO orders;

LOAD DATA INTO `cymbal_pets_demo.products`
FROM FILES (
    format = 'AVRO',
    uris = ['gs://sample-data-and-media/cymbal-pets/tables/products/*.avro'],
    enable_logical_types = true
);

LOAD DATA INTO `cymbal_pets_demo.stores`
FROM FILES (
    format = 'AVRO',
    uris = ['gs://sample-data-and-media/cymbal-pets/tables/stores/*.avro'],
    enable_logical_types = true
);

باید تأییدیه‌ای ببینید که نشان می‌دهد ردیف‌ها در هر جدول بار شده‌اند.

۵. ایجاد نمودار دارایی

با بار شدن داده‌ها، اکنون می‌توانید نمودار دارایی را تعریف کنید. این کار به BigQuery می‌گوید که کدام جدول‌ها نشان‌دهنده گره‌ها (نهادهایی مثل «مشتریان»، «محصولات») و کدام جدول‌ها نشان‌دهنده یال‌ها (روابطی مثل «بازدیدشده»، «ثبت‌شده»، «دارد») هستند.

طرح‌واره گراف

عبارت DDL زیر را اجرا کنید:

CREATE OR REPLACE PROPERTY GRAPH cymbal_pets_demo.PetsOrderGraph
NODE TABLES (
  cymbal_pets_demo.customers KEY(customer_id) LABEL Customer,
  cymbal_pets_demo.products KEY(product_id) LABEL Products,
  cymbal_pets_demo.stores KEY(store_id) LABEL Stores,
  cymbal_pets_demo.orders KEY(order_id) LABEL Orders
)
EDGE TABLES (
  cymbal_pets_demo.orders as customer_to_store_edge
    KEY (order_id)
    SOURCE KEY (customer_id) references customers(customer_id)
    DESTINATION KEY (store_id) references stores(store_id)
    LABEL Visited
    PROPERTIES ALL COLUMNS,

  cymbal_pets_demo.order_items
    KEY (order_item_id)
    SOURCE KEY (order_id) references orders(order_id)
    DESTINATION KEY (product_id) references products(product_id)
    LABEL Has
    PROPERTIES ALL COLUMNS,

  cymbal_pets_demo.orders as customer_to_orders_edge
    KEY (order_id)
    SOURCE KEY (customer_id) references customers(customer_id)
    DESTINATION KEY (order_id) references orders(order_id)
    LABEL Placed
    PROPERTIES ALL COLUMNS,

  cymbal_pets_demo.co_related_products_for_angelica
    KEY (angelica_product_id)
    SOURCE KEY (angelica_product_id) references products(product_id)
    DESTINATION KEY (other_product_id) references products(product_id)
    LABEL BoughtTogether
    PROPERTIES ALL COLUMNS
);

این کار گراف PetsOrderGraph را ایجاد می‌کند که به ما امکان می‌دهد بااستفاده از عملگر GRAPH_TABLE پیمایش گراف را انجام دهیم.

‫۶. تجسم سابقه خرید همه مشتریان

برای بخش‌های دیداری‌سازی و توصیه این codelab، از دیداری‌سازی نمودار بومی در BigQuery Studio استفاده خواهیم کرد. این کار به ما امکان می‌دهد نتایج نمودار را به‌راحتی تجسم کنیم.

یا می‌توانید در BigQuery Graph Notebook بااستفاده از IPython Magics تجسم کنید. با افزودن فرمان جادویی %%bigquery با تابع TO_JSON، می‌توانید نتایج را همان‌طور که در بخش‌های زیر نشان داده شده است تجسم کنید.

فرض کنید Cymbal Pets می‌خواهد تجسمی ۳۶۰ درجه از همه مشتریان و خریدهایی که در یک بازه زمانی خاص انجام داده‌اند به‌دست آورد.

در برگه جدید BigQuery Studio، موارد زیر را اجرا کنید:

GRAPH cymbal_pets_demo.PetsOrderGraph
  # finds the customer node and then finds all
  # the Orders nodes that are connected to that customer through the
  # Placed relationship
  MATCH (customer:Customer)-[placed:Placed]->(ordr:Orders)-[has:Has]->(product:Products)
  # filters the Orders nodes to only include those where the
  # order_date is within the last 3 months.
  WHERE ordr.order_date >= date('2024-11-27')
  # # This line finds all the Products nodes that are connected to the
  # # filtered Orders nodes through the Has relationship.
  MATCH p=(customer:Customer)-[placed:Placed]->(ordr:Orders)-[has:Has]->(product:Products)
  LIMIT 40
  RETURN 
    TO_JSON(p) as paths

برای تجسم نتایج، در قاب نتایج «پُرسمان»، روی «نمودار» کلیک کنید.

سابقه خرید همه مشتریان

‫۷. Visualize Purchase History of Angelica

فرض کنید Cymbal Pets می‌خواهد اطلاعات عمیقی درباره مشتری‌ای به‌نام Angelica Russell به‌دست آورد. آن‌ها می‌خواهند محصولاتی را که آنجلیکا در ۳ ماه گذشته خریده است و فروشگاه‌هایی را که این مشتری بازدید کرده است تجزیه‌وتحلیل کنند.

GRAPH cymbal_pets_demo.PetsOrderGraph
  # finds the customer node with the name "Angelica Russell" and then finds all
  # the Orders nodes that are connected to that customer through the
  # Placed relationship and all the Products nodes that are connected to the
  # filtered Orders nodes through the Has relationship.
   MATCH p=(customer:Customer {first_name: 'Angelica', last_name: 'Russell'})-[placed:Placed]->(ordr:Orders)-[has:Has]->(product:Products)
  # filters the Orders nodes to only include those where the
  # order_date is within the last 3 months.
  WHERE ordr.order_date >= date('2024-11-27')
  # finds the Stores nodes where Angelica placed order from
  MATCH p2=(customer)-[visited:Visited]->(store:Stores)
  RETURN
    TO_JSON(p) as path, TO_JSON(p2) as path2

سابقه خرید Angelica

‫۸. توصیه محصول بااستفاده از جستجوی برداری

‫Cymbal Pets می‌خواهد براساس خریدهای اخیر آنجلیکا، محصولاتی را به او توصیه کند. می‌توانیم از جستجوی برداری برای پیدا کردن محصولاتی با جاسازی‌های مشابه خریدهای قبلی او استفاده کنیم.

اسکریپت SQL زیر را در سلول Colab جدیدی اجرا کنید. این اسکریپت:

  1. محصولاتی را که آنجلیکا اخیراً خریده است شناسایی می‌کند.
  2. از VECTOR_SEARCH برای پیدا کردن ۴ محصول مشابه برتر از جدول products استفاده می‌کند.

توجه: این مرحله فرض می‌کند که قبلاً AI.GENERATE_EMBEDDINGS را برای ایجاد ستون جاسازی‌ها در جدول محصولات اجرا کرده‌اید.

DECLARE products_bought_by_angelica ARRAY<INT64>;

-- 1. Get IDs of products bought by Angelica
SET products_bought_by_angelica = (
  SELECT ARRAY_AGG(product_id) FROM
   GRAPH_TABLE(
    cymbal_pets_demo.PetsOrderGraph
      MATCH (c:Customer {first_name: 'Angelica', last_name: 'Russell'})-[placed:Placed]->(o:Orders)
      WHERE o.order_date >= date('2024-11-27')
      MATCH (o)-[has_edge:Has]->(p:Products)
      RETURN DISTINCT p.product_id as product_id
  ));

-- 2. Find similar products using vector search
SELECT 
  query.product_name as AngelicaBought, 
  base.product_name as RecommendedProducts, 
  base.category
FROM
  VECTOR_SEARCH(
    TABLE cymbal_pets_demo.products,
    'embedding',
    (SELECT * FROM cymbal_pets_demo.products
     WHERE product_id IN UNNEST(products_bought_by_angelica)),
    'embedding',
    top_k => 4)
WHERE query.product_name <> base.product_name;

باید فهرستی از محصولات توصیه‌شده را ببینید که ازنظر معنایی مشابه آنچه آنجلیکا خریده است باشد.

نتایج جستجوی برداری

‫۹. توصیه بااستفاده از «باهم خریداری شده» و شباهت جاکارد

یک روش قدرتمند دیگر برای توصیه «فیلتر مشارکتی» است — توصیه محصولاتی که اغلب توسط کاربران دیگر باهم خریداری می‌شوند.

می‌توانیم این محصولات را با پیمایش گراف از مشتری به محصولات خریداری‌شده‌اش، سپس به مشتریان دیگری که آن محصولات را خریده‌اند، و درنهایت به محصولات دیگری که آن مشتریان خریده‌اند پیدا کنیم.

غلبه بر سوگیری محبوبیت با شباهت جاکارد

اگرچه تعداد خام خریدهای مشترک مفید است، اما ممکن است نسبت‌به محصولات پرطرفدار سوگیری داشته باشد. محصولی بسیار محبوب ممکن است به‌طور تصادفی با چیزهای زیادی خریداری شود.

شباهت جاکارد با نرمال‌سازی تعداد خریدهای مشترک، توصیه‌ها را یک گام فراتر می‌برد. این معیار شباهت بین دو مجموعه را اندازه‌گیری می‌کند (در این مورد، مجموعه‌های سفارش‌های حاوی هر محصول).

فرمول «شباهت جاکارد» عبارت است از:

مکان:

  • اشتراک A و B عبارت است از تعداد سفارش‌هایی که هم محصول A و هم محصول B را دربرمی‌گیرد (تعداد خرید هم‌زمان).
  • A تعداد کل سفارش‌های حاوی محصول A است.
  • B تعداد کل سفارش‌های حاوی محصول B است.

در مثال زیر، مجموعه A = {b,c,e,f,g}، مجموعه B = {a,d,b,g}، اشتراک آن‌ها A⋂B = {b,g}، اجتماع آن‌ها A⋃B = {a,b,c,d,e,f,g}، بنابراین شباهت جاکارد بین A و B برابر است با 2 / 7 = 0.285714

تولید و رتبه‌بندی مجدد نامزدها

در سیستم‌های توصیه‌گر دنیای واقعی که روی مجموعه‌داده‌های عظیم کار می‌کنند، اغلب محاسبه امتیازهای شباهت پیچیده (مانند جاکارد) برای همه جفت‌های ممکن محصولات غیرعملی است. درعوض، الگوی رایج استفاده از رویکرد دومرحله‌ای است:

  1. تولید نامزد: از یک سنجه ساده و سریع (مانند تعداد خرید مشترک خام) برای فیلتر کردن فضای جستجو و یافتن تعداد قابل‌مدیری از نامزدها (مثلاً ۱۰ نامزد برتر) استفاده کنید.
  2. رتبه‌بندی مجدد: اعمال سنجه‌ای دقیق‌تر اما ازنظر محاسباتی سنگین‌تر (مانند «شباهت جاکارد») برای رتبه‌بندی آن مجموعه کوچک از نامزدها و انتخاب توصیه‌های برتر نهایی.

در این codelab، این الگو را دنبال خواهیم کرد:

  • مرحله ۱: اجرای پُرسمان برای یافتن ۱۰ محصول برتر خریداری‌شده باهم برای هر محصول، براساس تعداد خام خرید باهم، و ذخیره آن‌ها در جدول.
  • مرحله ۲: از پُرسمان نمودار برای بازیابی این نامزدها استفاده کنید، آن‌ها را براساس شباهت جاکارد رتبه‌بندی کنید، و ۳ نامزد برتر را برگردانید.

[!هشدار] نقطه ضعف: با فیلتر کردن براساس تعداد خام در مرحله ۱، ممکن است «یادآوری» خریدهای مشترک بسیار خاص اما با فراوانی کم را ازدست بدهیم. اگر محصولی بسیار شبیه به محصول دیگری باشد اما هر دو به‌ندرت خریداری شوند، ممکن است در بین ۱۰ نامزد برتر قرار نگیرد و ازدست برود.

پرسمان زیر را اجرا کنید تا هم تعداد خرید مشترک خام و هم شباهت جاکارد را محاسبه کنید و ۱۰ نامزد برتر را براساس تعداد خام ذخیره کنید:

CREATE OR REPLACE TABLE cymbal_pets_demo.co_related_products_for_angelica AS
-- Calculate the total number of orders for each product
WITH ProductOrderCounts AS (
    SELECT product_id, COUNT(DISTINCT order_id) as total_count
    FROM cymbal_pets_demo.order_items
    GROUP BY product_id
),
-- Calculate the intersection of each product pairs
CoPurchases AS (
    SELECT
        angelicaProduct.product_id AS angelica_product_id,
        otherProduct.product_id AS other_product_id,
        count(DISTINCT otherOrder.order_id) AS co_purchase_count
    FROM
        GRAPH_TABLE (cymbal_pets_demo.PetsOrderGraph
          MATCH (angelica:Customer {first_name: 'Angelica', last_name: 'Russell'})-[:Placed]->(o:Orders)-[:Has]->(angelicaProduct:Products)
          WHERE o.order_date >= date('2024-11-27')
          WITH angelica, angelicaProduct
          MATCH (otherCustomer:Customer)-[:Placed]->(otherOrder:Orders)-[:Has]->(angelicaProduct)
          WHERE otherCustomer <> angelica
          WITH angelicaProduct, otherOrder
          MATCH (otherOrder)-[:HAS]->(otherProduct:Products)
          WHERE angelicaProduct <> otherProduct
          RETURN angelicaProduct, otherProduct, otherOrder
        )
    GROUP BY
        angelicaProduct.product_id, otherProduct.product_id
)
SELECT * FROM (
    SELECT
        cp.angelica_product_id,
        cp.other_product_id,
        cp.co_purchase_count,
        -- The Jaccard calculation, which is the intersection of A and B divided by (A + B - intersection)
        SAFE_DIVIDE(cp.co_purchase_count, (poc1.total_count + poc2.total_count - cp.co_purchase_count)) AS jaccard_similarity,
        ROW_NUMBER() OVER (PARTITION BY cp.angelica_product_id ORDER BY cp.co_purchase_count DESC) AS rn
    FROM CoPurchases cp
    JOIN ProductOrderCounts poc1 ON cp.angelica_product_id = poc1.product_id
    JOIN ProductOrderCounts poc2 ON cp.other_product_id = poc2.product_id
)
WHERE rn <= 10;

منطق توصیه

این پُرسمان را اجرا کنید تا ۳ محصول برتر برای هریک از خریدهای Angelica را که مستقیماً ازطریق لبه BoughtTogether متصل شده‌اند توصیه کند و هم تعداد خرید مشترک و هم شباهت جاکارد را نشان دهد:

SELECT * FROM GRAPH_TABLE(
  cymbal_pets_demo.PetsOrderGraph
  MATCH (customer:Customer {first_name: 'Angelica', last_name: 'Russell'})-[placed:Placed]->(ordr:Orders)
  WHERE ordr.order_date >= date('2024-11-27')
  MATCH (ordr)-[has:Has]->(product:Products)
  MATCH (product)-[bought_together:BoughtTogether]->(recommended_product:Products)
  RETURN 
    product.product_name AS OriginalProduct,
    recommended_product.product_name AS Recommended,
    bought_together.co_purchase_count AS Strength,
    bought_together.jaccard_similarity AS JaccardSimilarity
)
-- Rank product recommendations by Jaccard Similarity
QUALIFY ROW_NUMBER() OVER (PARTITION BY OriginalProduct ORDER BY JaccardSimilarity DESC) <= 3
ORDER BY OriginalProduct;

این پُرسمان از «مشتری» -> «سفارش» -> «محصول» -> (خریداری‌شده باهم) -> «محصول توصیه‌شده» عبور می‌کند، توصیه‌هایی را براساس رفتار خرید جمعی به شما نشان می‌دهد، و امتیازهای شباهت آن‌ها را بازیابی می‌کند.

خریداری‌شده باهم

‫۱۰. مرتب‌سازی

برای جلوگیری از کسر هزینه‌های جاری از حساب Google Cloud، منابع ایجادشده درطول این codelab را حذف کنید.

مجموعه داده و همه جدول‌ها را حذف کنید:

DROP SCHEMA IF EXISTS cymbal_pets_demo CASCADE;

اگر پروژه جدیدی برای این codelab ایجاد کرده‌اید، می‌توانید پروژه را نیز حذف کنید:

gcloud projects delete $PROJECT_ID

‫۱۱. تبریک می‌گوییم

تبریک! بااستفاده از BigQuery Graph، «نمای ۳۶۰ درجه از مشتری» و موتور توصیه را باموفقیت ساختید.

آنچه آموخته‌اید

  • نحوه ایجاد کردن گراف خصوصیت در BigQuery.
  • نحوه بار کردن داده‌ها در گره‌ها و لبه‌های نمودار.
  • نحوه پُرسمان الگوهای نمودار بااستفاده از GRAPH_TABLE و MATCH.
  • نحوه ترکیب کردن پُرسمان‌های نمودار با جستجوی برداری برای توصیه‌های ترکیبی.

مراحل بعدی