سیستم توصیه گر قیمت با پایتون — راهنمای کاربردی

۲۳۵ بازدید
آخرین به‌روزرسانی: ۲۹ مرداد ۱۴۰۲
زمان مطالعه: ۵ دقیقه
سیستم توصیه گر قیمت با پایتون — راهنمای کاربردی

Mercari، بزرگترین برنامه کاربردی فروشگاهی مبتنی بر جامعه در ژاپن است. کاربران این سیستم از یک مشکل عمیق رنج می‌بردند. مساله موجود از این قرار بود که فروشندگان وقتی جنسی را برای فروش قرار می‌دادند نمی‌دانستند چگونه قیمت‌گذاری کنند. بنابراین، سازندگان فروشگاه مایل بودند به فروشندگان داخل فروشگاه برای محصولات گوناگون پیشنهاد قیمت بدهند، اما این کار به این دلیل که فروشندگان این امکان را داشتند که هر نوع محصول و یا بسته‌های محصولاتی را عرضه کنند کار سختی محسوب می‌شد. در این پروژه یادگیری ماشین، آموزش ساخت یک مدل یادگیری ماشین ارائه خواهد شد که به طور خودکار قیمت مناسبی را برای محصولات پیشنهاد می‌دهد. در راستای ساخت سیستم توصیه گر قیمت با پایتون، اطلاعات زیر فراهم شده‌اند:

  • train_id: شناسه () فهرست‌ها
  • name: عنوان لیست‌ها
  • item_condition_id : شرایطی که محصول بر اساس آن توسط فروشنده عرضه شده
  • category_name: دسته فهرست‌ها
  • brand_name: نام برند
  • price: قیمتی که یک محصول برای آن به فروش می‌رسد. این متغیر هدفی است که مدل یادگیری ماشین معرفی شده در این مطلب آن را پیش‌بینی خواهد کرد.
  • shipping: عدد ۱ اگر هزینه حمل و نقل توسط فروشنده و ۰ اگر این مبلغ توسط فروشنده پرداخت شده باشد.
  • item_description: توصیف کامل محصول

شایان ذکر است که پیش‌تر، آموزش «ساخت سیستم توصیه گر (Recommender System) فیلم با پایتون — راهنمای جامع و ساده» نیز در وبلاگ فرادرس منتشر شده است.

تحلیل داده اکتشافی

مجموعه داده مورد استفاده در اینجا را می‌توان از وب‌سایت «کَگِل» (Kaggle) (+) دانلود کرد. برای اعتبارسنجی نتایج، صرفا نیاز به train.tsv است. کار با قطعه کد زیر آغاز می‌شود.

1import gc
2import time
3import numpy as np
4import pandas as pd
5import matplotlib.pyplot as plt
6import seaborn as sns
7from scipy.sparse import csr_matrix, hstack
8from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
9from sklearn.preprocessing import LabelBinarizer
10from sklearn.model_selection import train_test_split, cross_val_score
11from sklearn.metrics import mean_squared_error
12import lightgbm as lgb
13df = pd.read_csv('train.tsv', sep = '\t')

به طور تصادفی داده‌ها به مجموعه‌های «آموزش» (train) و «آزمون» (test) تقسیم می‌شوند. از مجموعه داده آموزش فقط برای «تحلیل داده اکتشافی» (Exploratory Data Analysis | EDA) استفاده می‌شود.

1msk = np.random.rand(len(df)) < 0.8
2train = df[msk]
3test = df[~msk]
4train.shape, test.shape
((1185866, 8), (296669, 8))
برای مشاهده تصویر در اندازه واقعی، روی آن کلیک کنید.
1train.head()

سیستم توصیه‌گر قیمت

قیمت

1train.price.describe()

مدل یادگیری ماشین پیشنهاد دهنده قیمت

قیمت محصولات دارای «چولگی» (Skewness) به چپ است و در واقع حجم انبوهی از محصولات بین ۱۰ تا ۲۰ قیمت‌گذاری شده‌اند. این در حالی است که گران‌ترین محصول در سال ۲۰۰۹ قیمت‌گذاری شده، بنابراین، اکنون تبدیل سوابق روی قیمت‌ها انجام خواهد شد.

1plt.subplot(1, 2, 1)
2(train['price']).plot.hist(bins=50, figsize=(12, 6), edgecolor = 'white', range = [0, 250])
3plt.xlabel('price', fontsize=12)
4plt.title('Price Distribution', fontsize=12)
5plt.subplot(1, 2, 2)
6np.log(train['price']+1).plot.hist(bins=50, figsize=(12,6), edgecolor='white')
7plt.xlabel('log(price+1)', fontsize=12)
8plt.title('Price Distribution', fontsize=12)

مدل یادگیرنده توصیه‌گر قیمت

حمل و نقل

قیمت بیش از ٪۵۵ محصولات توسط خریداران پرداخت می‌شود.

1train['shipping'].value_counts() / len(train)

مدل یادگیری ماشین پیشنهاد دهنده قیمت

حمل و نقل چطور با قیمت ارتباط پیدا می‌کند؟

1shipping_fee_by_buyer = train.loc[df['shipping'] == 0, 'price']
2shipping_fee_by_seller = train.loc[df['shipping'] == 1, 'price']
3fig, ax = plt.subplots(figsize=(18,8))
4ax.hist(shipping_fee_by_seller, color='#8CB4E1', alpha=1.0, bins=50, range = [0, 100],
5       label='Price when Seller pays Shipping')
6ax.hist(shipping_fee_by_buyer, color='#007D00', alpha=0.7, bins=50, range = [0, 100],
7       label='Price when Buyer pays Shipping')
8plt.xlabel('price', fontsize=12)
9plt.ylabel('frequency', fontsize=12)
10plt.title('Price Distribution by Shipping Type', fontsize=15)
11plt.tick_params(labelsize=12)
12plt.legend()
13plt.show()
برای مشاهده تصویر در اندازه واقعی، روی آن کلیک کنید.
1print('The average price is {}'.format(round(shipping_fee_by_seller.mean(), 2)), 'if seller pays shipping');
2print('The average price is {}'.format(round(shipping_fee_by_buyer.mean(), 2)), 'if buyer pays shipping')
  • در صورتی که فروشنده هزینه حمل و نقل را پرداخت کند، قیمت میانگین برابر با ۲۲.۵۸ است.
  • در صورتی که خریدار هزینه حمل و نقل را پرداخت کند، قیمت میانگین برابر با ۳۰.۱۱ است.

پس از تبدیل سوابق، مجددا مقایسه قیمت انجام خواهد شد.

1fig, ax = plt.subplots(figsize=(18,8))
2ax.hist(np.log(shipping_fee_by_seller+1), color='#8CB4E1', alpha=1.0, bins=50,
3       label='Price when Seller pays Shipping')
4ax.hist(np.log(shipping_fee_by_buyer+1), color='#007D00', alpha=0.7, bins=50,
5       label='Price when Buyer pays Shipping')
6plt.xlabel('log(price+1)', fontsize=12)
7plt.ylabel('frequency', fontsize=12)
8plt.title('Price Distribution by Shipping Type', fontsize=15)
9plt.tick_params(labelsize=12)
10plt.legend()
11plt.show()
برای مشاهده تصویر در اندازه واقعی، روی آن کلیک کنید.

واضح است که قیمت میانگین هنگامی که خریدار هزینه حمل‌و‌نقل را پرداخت می‌کند بالاتر است.

نام دسته

1print('There are', train['category_name'].nunique(), 'unique values in category name column')

۱۲۶۵ مقدار یکتا در ستون نام دسته وجود دارد.

۱۰ نام دسته متداول:

1train['category_name'].value_counts()[:10]

سیستم پیشنهاد دهنده قیمت

شرایط محصول در مقایسه با قیمت

1sns.boxplot(x = 'item_condition_id', y = np.log(train['price']+1), data = train, palette = sns.color_palette('RdBu',5))

پیش‌بینی قیمت با پایتون

به نظر می‌رسد که قیمت میانگین بین هر یک از ID‌های شرایط متفاوت است. پس از تحلیل داده اکتشافی بالا، از همه این ویژگی برای ساخت مدل استفاده خواهد شد.

LightGBM

LightGBM یک «چارچوب گرادیان تقویتی» (Gradient Boosting Framework) با بهره‌گیری از پروژه DMTK مایکروسافت است که از الگوریتم‌های یادگیری مبتنی بر درخت استفاده می‌کند.

این الگوریتم با هدف توزیع شده و موثر بودن، همراه با مزایای زیر، طراحی شده است.

  • سرعت آموزش و کارایی بالاتر
  • استفاده کمتر از حافظه
  • صحت بهتر
  • یادگیری موازی و با پشتیبانی از GPU
  • دارای توانایی مدیریت داده‌های بزرگ

بنابراین، در ادامه از این چارچوب استفاده خواهد شد.

تنظیمات کلی مدل سیستم توصیه گر قیمت

1NUM_BRANDS = 4000
2NUM_CATEGORIES = 1000
3NAME_MIN_DF = 10
4MAX_FEATURES_ITEM_DESCRIPTION = 50000

«مقادیر ناموجود» (missing values) در ستون‌ها وجود دارد و بنابراین نیاز به اصلاح آن‌ها است.

1print('There are %d items that do not have a category name.' %train['category_name'].isnull().sum())

۵۰۳۸ محصول وجود دارد که دارای اسم دسته (category name) نیستند.

1print('There are %d items that do not have a brand name.' %train['brand_name'].isnull().sum())

۵۰۶۳۷۰ محصول وجود دارد که نام بِرند ندارند.

1print('There are %d items that do not have a description.' %train['item_description'].isnull().sum())

۳ محصول وجود دارد که توضیحات ندارند.

تابع کمک‌کننده برای LightGBM:

1def handle_missing_inplace(dataset): 
2    dataset['category_name'].fillna(value='missing', inplace=True) 
3    dataset['brand_name'].fillna(value='missing', inplace=True) 
4    dataset['item_description'].replace('No description yet,''missing', inplace=True) 
5    dataset['item_description'].fillna(value='missing', inplace=True)
6def cutting(dataset):
7    pop_brand = dataset['brand_name'].value_counts().loc[lambda x: x.index != 'missing'].index[:NUM_BRANDS]
8    dataset.loc[~dataset['brand_name'].isin(pop_brand), 'brand_name'] = 'missing'
9    pop_category = dataset['category_name'].value_counts().loc[lambda x: x.index != 'missing'].index[:NUM_CATEGORIES]
10def to_categorical(dataset):
11    dataset['category_name'] = dataset['category_name'].astype('category')
12    dataset['brand_name'] = dataset['brand_name'].astype('category')
13    dataset['item_condition_id'] = dataset['item_condition_id'].astype('category')

سطرهایی که در آن قیمت برابر با صفر است حذف می‌شوند.

1df = pd.read_csv('train.tsv', sep = '\t')
2msk = np.random.rand(len(df)) < 0.8
3train = df[msk]
4test = df[~msk]
5test_new = test.drop('price', axis=1)
6y_test = np.log1p(test["price"])
7train = train[train.price != 0].reset_index(drop=True)

داده‌های آموزش و داده‌های تست جدید ادغام می‌شوند.

1nrow_train = train.shape[0]
2y = np.log1p(train["price"])
3merge: pd.DataFrame = pd.concat([train, test_new])

آماده‌سازی برای آموزش

1handle_missing_inplace(merge)
2cutting(merge)
3to_categorical(merge)

ستون‌های نام دسته و نام بردار شمارش می‌شوند.

1cv = CountVectorizer(min_df=NAME_MIN_DF)
2X_name = cv.fit_transform(merge['name'])
3cv = CountVectorizer()
4X_category = cv.fit_transform(merge['category_name'])

ستون TF-IDF بردار item_description.

1tv = TfidfVectorizer(max_features=MAX_FEATURES_ITEM_DESCRIPTION, ngram_range=(1, 3), stop_words='english')
2X_description = tv.fit_transform(merge['item_description'])

ستون دودویی برچسب‌های brand_name.

1lb = LabelBinarizer(sparse_output=True)
2X_brand = lb.fit_transform(merge['brand_name'])

ساخت «متغیرهای مجازی» برای ستون‌های item_condition_id و shipping.

1X_dummies = csr_matrix(pd.get_dummies(merge[['item_condition_id', 'shipping']], sparse=True).values)

ساخت ماتریس خلوت ادغام شده.

1sparse_merge = hstack((X_dummies, X_description, X_brand, X_category, X_name)).tocsr()

حذف ویژگی‌ها با فرکانس سَنَد => ۱.

1mask = np.array(np.clip(sparse_merge.getnnz(axis=0) - 1, 0, 1), dtype=bool)
2sparse_merge = sparse_merge[:, mask]

جداسازی داده‌های آموزش و آزمون از sparse merge.

1X = sparse_merge[:nrow_train]
2X_test = sparse_merge[nrow_train:]

ساخت مجموعه داده برای lightgbm.

1train_X = lgb.Dataset(X, label=y)

تعیین پارامترها به عنوان dict.

1params = {
2        'learning_rate': 0.75,
3        'application': 'regression',
4        'max_depth': 3,
5        'num_leaves': 100,
6        'verbosity': -1,
7        'metric': 'RMSE',
8    }
  • استفاده از «رگرسیون» (regression) به دلیل آنکه مساله موجود مساله رگرسیون است.
  • استفاده از «RMSE» به عنوان سنجه به دلیل اینکه مساله موجود رگرسیون است.
  • “num_leaves”=100 به دلیل اینکه داده‌های موجود نسبتا بزرگ هستند.
  • استفاده از «“num_leaves”=100» برای اجتناب از «بیش برازش» (Overfitting)
  • استفاده از max_depth برای اجتناب از بیش برازش
  • استفاده از «verbosity» برای کنترل سطح verbosity در LightGBM (داریم ۰>: Fatal)
  • «learning_rate» تاثیر هر درخت در خروجی نهایی را تعیین می‌کند.

آغاز آموزش

آموزش دادن یک مدل نیازمند یک لیست پارامتر و مجموعه داده است و مدتی نیز به طول می‌انجامد.

1gbm = lgb.train(params, train_set=train_X, num_boost_round=3200, verbose_eval=100)

پیش‌بینی

1y_pred = gbm.predict(X_test, num_iteration=gbm.best_iteration)

ارزیابی

1from sklearn.metrics import mean_squared_error
2print('The rmse of prediction is:', mean_squared_error(y_test, y_pred) ** 0.5)

rmse پیش‌بینی برابر است با: 0.46164222941613137

کد منبع کامل این پروژه در گیت‌هاب (+) موجود است.

اگر مطلب بالا برای شما مفید بوده، آموزش‌های زیر نیز به شما پیشنهاد می‌شود:

^^

بر اساس رای ۲ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
towardsdatascience
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *