You are currently viewing کدگذاری متغیرهای طبقه بندی شده: یک فرو رفتن عمیق در کدگذاری هدف |  توسط خوان خوزه مونوز

کدگذاری متغیرهای طبقه بندی شده: یک فرو رفتن عمیق در کدگذاری هدف | توسط خوان خوزه مونوز


داده ها به شکل ها و اشکال مختلفی می آیند. یکی از این شکل ها و فرم ها به داده های طبقه ای معروف است.

خوان خوزه مونوز
به سوی علم داده

این مشکل ایجاد می کند زیرا اکثر الگوریتم های یادگیری ماشین فقط از داده های عددی به عنوان ورودی استفاده می کنند. با این حال، به لطف توابع ساده و به خوبی تعریف شده که آنها را به مقادیر عددی تبدیل می کند، معمولاً کار با داده های طبقه بندی چالشی نیست. اگر هر دوره علوم داده ای را گذرانده اید، با استراتژی تک کدگذاری داغ برای ویژگی های طبقه بندی آشنا خواهید شد. این استراتژی زمانی عالی است که ویژگی های شما دارای دسته بندی های محدودی باشد. با این حال، هنگام کار با ویژگی‌های کاردینالیتی بالا (ویژگی‌های چند دسته‌ای) با مشکلاتی مواجه خواهید شد.

در اینجا نحوه استفاده از رمزگذاری هدف برای تبدیل ویژگی های دسته بندی به مقادیر عددی آمده است.

عکس از Sonika Agarwal در Unsplash

در ابتدای هر دوره علم داده، با کدنویسی داغ به عنوان یک استراتژی کلیدی برای مقابله با مقادیر طبقه‌بندی آشنا می‌شوید.و درست است، زیرا این استراتژی روی ویژگی‌های کم کاردینالیتی (ویژگی‌هایی با دسته‌های محدود) بسیار خوب کار می‌کند.

به طور خلاصه، یک کدگذاری داغ هر دسته را به یک بردار باینری تبدیل می کند، که در آن دسته مربوط به عنوان “درست” یا “1” و همه دسته های دیگر به عنوان “نادرست” یا “0” علامت گذاری شده اند.

import pandas as pd

# Sample categorical data
data = {'Category': ['Red', 'Green', 'Blue', 'Red', 'Green']}

# Create a DataFrame
df = pd.DataFrame(data)

# Perform one-hot encoding
one_hot_encoded = pd.get_dummies(df['Category'])

# Display the result
print(one_hot_encoded)

یک راه داغ برای کدنویسی – ما می‌توانیم این را با انداختن یک ستون بهبود دهیم، زیرا اگر آبی و سبز را بدانیم، می‌توانیم مقدار قرمز را محاسبه کنیم. تصویر توسط نویسنده

اگرچه این برای ویژگی هایی با دسته بندی های محدود عالی عمل می کند (کمتر از 10-20 دسته)با افزایش تعداد دسته‌ها، بردارهای تک کد طولانی‌تر و پراکنده‌تر می‌شوند و به طور بالقوه منجر به افزایش استفاده از حافظه و پیچیدگی محاسباتی می‌شوند، اجازه دهید به یک مثال نگاه کنیم.

کد زیر از اعتبارنامه کارمندان آمازون ارسال شده در kaggle استفاده می کند: https://www.kaggle.com/datasets/lucamassaron/amazon-employee-access-challenge

داده ها شامل هشت ستون از ویژگی های طبقه بندی شده است که ویژگی های منبع، نقش و گروه کاری مورد نیاز کارمند در آمازون را نشان می دهد.

data.info()
اطلاعات ستون. تصویر توسط نویسنده
# Display the number of unique values in each column
unique_values_per_column = data.nunique()

print("Number of unique values in each column:")
print(unique_values_per_column)

هشت عملکرد دارای کاردینالیته بالایی هستند. تصویر توسط نویسنده

استفاده از کدگذاری یک‌طرفه می‌تواند در مجموعه داده‌هایی مانند این چالش‌برانگیز باشد زیرا تعداد زیادی دسته‌بندی مجزا برای هر ویژگی وجود دارد.

#Initial data memory usage
memory_usage = data.memory_usage(deep=True)
total_memory_usage = memory_usage.sum()
print(f"\nTotal memory usage of the DataFrame: {total_memory_usage / (1024 ** 2):.2f} MB")
مجموعه داده اولیه 11.24 مگابایت است. تصویر توسط نویسنده
#one-hot encoding categorical features
data_encoded = pd.get_dummies(data,
columns=data.select_dtypes(include="object").columns,
drop_first=True)

data_encoded.shape

پس از کدگذاری داغ، مجموعه داده دارای 15618 ستون است. تصویر توسط نویسنده
مجموعه داده‌های به‌دست‌آمده بسیار پراکنده است، به این معنی که حاوی صفر و یک‌های زیادی است. تصویر بر اساس نویسنده
# Memory usage for the one-hot encoded dataset
memory_usage = data_encoded.memory_usage(deep=True)
total_memory_usage = memory_usage.sum()
print(f"\nTotal memory usage of the DataFrame: {total_memory_usage / (1024 ** 2):.2f} MB")
استفاده از حافظه مجموعه داده به دلیل افزایش تعداد ستون ها به 488.08 مگابایت افزایش یافت. تصویر توسط نویسنده

همانطور که می بینید، رمزگذاری تک شات راه حل مناسبی برای مقابله با ویژگی های دسته بندی با کاردینالیته بالا نیست، زیرا اندازه مجموعه داده را به شدت افزایش می دهد.

در موارد با کاردینالیته بالا، رمزگذاری هدف گزینه بهتری است.

کدگذاری هدف، یک ویژگی طبقه‌بندی را بدون افزودن ستون‌های اضافی به یک ویژگی عددی تبدیل می‌کند و از تبدیل مجموعه داده‌ها به مجموعه داده‌های بزرگ‌تر و پراکنده‌تر جلوگیری می‌کند.

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

برای مشکلات رگرسیون، مقدار مورد انتظار صرفاً میانگین آن دسته است.

برای مسائل طبقه بندی، مقدار مورد انتظار احتمال مشروط داده شده به آن دسته است.

در هر دو مورد، ما می توانیم به سادگی با استفاده از تابع ‘group_by’ در پانداها به نتایج دست یابیم.

#Example of how to calculate the expected value for Target encoding of a Binary outcome
expected_values = data.groupby('ROLE_TITLE')['ACTION'].value_counts(normalize=True).unstack()
expected_values
جدول به دست آمده احتمال هر نتیجه “ACTION” را با شناسه منحصر به فرد “Role_title” نشان می دهد. تصویر توسط نویسنده

جدول به دست آمده احتمال هر یک را نشان می دهدعمل” نتیجه منحصر به فرد “ROLE_TITLE“شناسه. تنها کاری که باید انجام دهید این است که جایگزین “ROLE_TITLEid با مقادیر احتمالی که “ACTION” در مجموعه داده های اصلی 1 بوده است. (یعنی به جای دسته 117879 مجموعه داده 0.889331 را نشان می دهد)

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

برای جلوگیری از این خطاها، باید ترانسفورماتور کدگذاری هدف را پایدارتر کنید.

برای قوی تر کردن کدگذاری هدف، می توانید یک کلاس ترانسفورماتور سفارشی ایجاد کنید و آن را با scikit-learn ادغام کنید تا بتوان از آن در هر خط لوله مدلی استفاده کرد.

توجه: کد زیر از “کتاب کاگل” گرفته شده است و می توانید آن را در Kaggle پیدا کنید: https://www.kaggle.com/code/lucamassaron/meta-features-and-target-encoding

import numpy as np
import pandas as pd

from sklearn.base import BaseEstimator, TransformerMixin

class TargetEncode(BaseEstimator, TransformerMixin):

def __init__(self, categories="auto", k=1, f=1,
noise_level=0, random_state=None):
if type(categories)==str and categories!='auto':
self.categories = [categories]
else:
self.categories = categories
self.k = k
self.f = f
self.noise_level = noise_level
self.encodings = dict()
self.prior = None
self.random_state = random_state

def add_noise(self, series, noise_level):
return series * (1 + noise_level *
np.random.randn(len(series)))

def fit(self, X, y=None):
if type(self.categories)=='auto':
self.categories = np.where(X.dtypes == type(object()))[0]

temp = X.loc[:, self.categories].copy()
temp['target'] = y
self.prior = np.mean(y)
for variable in self.categories:
avg = (temp.groupby(by=variable)['target']
.agg(['mean', 'count']))
# Compute smoothing
smoothing = (1 / (1 + np.exp(-(avg['count'] - self.k) /
self.f)))
# The bigger the count the less full_avg is accounted
self.encodings[variable] = dict(self.prior * (1 -
smoothing) + avg['mean'] * smoothing)

return self

def transform(self, X):
Xt = X.copy()
for variable in self.categories:
Xt[variable].replace(self.encodings[variable],
inplace=True)
unknown_value = {value:self.prior for value in
X[variable].unique()
if value not in
self.encodings[variable].keys()}
if len(unknown_value) > 0:
Xt[variable].replace(unknown_value, inplace=True)
Xt[variable] = Xt[variable].astype(float)
if self.noise_level > 0:
if self.random_state is not None:
np.random.seed(self.random_state)
Xt[variable] = self.add_noise(Xt[variable],
self.noise_level)
return Xt

def fit_transform(self, X, y=None):
self.fit(X, y)
return self.transform(X)

ممکن است در ابتدا دلهره آور به نظر برسد، اما بیایید هر قطعه کد را تجزیه کنیم تا بفهمیم چگونه یک رمزگذار Target قوی ایجاد کنیم.

تعریف کلاس

class TargetEncode(BaseEstimator, TransformerMixin):

این اولین مرحله تضمین می‌کند که می‌توانید از این کلاس ترانسفورماتور در خطوط لوله یادگیری scikit برای پیش‌پردازش داده‌ها، مهندسی ویژگی‌ها و گردش‌های کاری یادگیری ماشین استفاده کنید. با به ارث بردن از کلاس‌های scikit-learn به این امر دست می‌یابد BaseEstimator و TransformerMixin.

وراثت اجازه می دهد TargetEncode کلاس برای استفاده مجدد یا نادیده گرفتن روش ها و ویژگی های تعریف شده در کلاس های پایه، در این مورد، BaseEstimator و TransformerMixin

BaseEstimator کلاس پایه برای همه برآوردگرهای یادگیری scikit است. تخمین‌گرها اشیایی هستند که با روش‌های «مطابق» برای آموزش روی داده‌ها و روش «پیش‌بینی» برای پیش‌بینی‌سازی هستند.

TransformerMixin یک کلاس mixin برای ترانسفورماتورها در scikit-learn است، روش های اضافی مانند “fit_transform” را ارائه می دهد که fit و transform را در یک مرحله ترکیب می کند.

ارث از BaseEstimator و TransformerMixin, به TargetEncode اجازه می دهد تا این روش ها را پیاده سازی کند و آن را با API scikit-learn سازگار می کند.

سازنده را تعریف کنید

def __init__(self, categories="auto", k=1, f=1, 
noise_level=0, random_state=None):
if type(categories)==str and categories!='auto':
self.categories = [categories]
else:
self.categories = categories
self.k = k
self.f = f
self.noise_level = noise_level
self.encodings = dict()
self.prior = None
self.random_state = random_state

این مرحله دوم سازنده برای را تعریف می کند “TargetEncode” و متغیرهای نمونه را با مقادیر پیش فرض یا تعیین شده توسط کاربر مقداردهی اولیه می کند.

دسته بندی هاپارامتر ” مشخص می کند که کدام ستون ها در داده های ورودی باید به عنوان متغیرهای طبقه بندی برای رمزگذاری هدف در نظر گرفته شوند. پیش‌فرض روی «خودکار» تنظیم می‌شود تا به‌طور خودکار ستون‌های دسته‌بندی را در طول فرآیند برازش شناسایی کند.

پارامترهای k، f و noise_level اثر هموارسازی را در طول رمزگذاری هدف و سطح نویز اضافه شده در طول تبدیل را کنترل می‌کنند.

اضافه کردن نویز

این مرحله بعدی برای جلوگیری از نصب بیش از حد بسیار مهم است.

def add_noise(self, series, noise_level):
return series * (1 + noise_level *
np.random.randn(len(series)))

اضافه کردن نویزروش ” نویز تصادفی را برای معرفی تنوع و جلوگیری از تنظیم مجدد در مرحله تبدیل اضافه می کند.

“np.random.randn(len(series))” آرایه ای از اعداد تصادفی را از یک توزیع نرمال استاندارد (میانگین = 0، انحراف استاندارد = 1) تولید می کند.

ضرب این آرایه در “نویز_سطح” sنویز تصادفی را بر اساس سطح نویز مشخص شده کاهش می دهد.”

این مرحله به استحکام و تعمیم پذیری فرآیند رمزگذاری هدف کمک می کند.

رمزگذار هدف را سوار کنید

این قطعه کد رمزگذار هدف را بر روی داده های ارائه شده با محاسبه رمزگذاری های هدف برای ستون های طبقه بندی شده و ذخیره آنها برای استفاده بعدی در طول تبدیل آموزش می دهد.

def fit(self, X, y=None):
if type(self.categories)=='auto':
self.categories = np.where(X.dtypes == type(object()))[0]

temp = X.loc[:, self.categories].copy()
temp['target'] = y
self.prior = np.mean(y)
for variable in self.categories:
avg = (temp.groupby(by=variable)['target']
.agg(['mean', 'count']))
# Compute smoothing
smoothing = (1 / (1 + np.exp(-(avg['count'] - self.k) /
self.f)))
# The bigger the count the less full_avg is accounted
self.encodings[variable] = dict(self.prior * (1 -
smoothing) + avg['mean'] * smoothing)

اصطلاح هموارسازی به جلوگیری از برازش بیش از حد کمک می‌کند، به‌ویژه زمانی که با دسته‌هایی با نمونه‌های کوچک سروکار داریم.

این روش از قرارداد scikit-learn برای روش‌های برازش در ترانسفورماتور پیروی می‌کند.

با بازرسی و شناسایی ستون‌های طبقه‌بندی و ایجاد یک DataFrame موقت که فقط شامل ستون‌های دسته‌بندی انتخاب شده از ورودی X و متغیر هدف y است، شروع می‌شود.

مقدار میانگین قبلی متغیر هدف محاسبه و در ویژگی قبلی ذخیره می شود. این مقدار میانگین کلی متغیر هدف را در کل مجموعه داده نشان می دهد.

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

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

رمزگذاری های محاسبه شده برای هر دسته در متغیر فعلی در فرهنگ لغت رمزگذاری ذخیره می شود. این واژگان بعداً در مرحله تبدیل استفاده خواهد شد.

تبدیل داده ها

این قطعه کد، مقادیر مقوله‌ای اصلی را با مقادیر کد شده هدف مربوطه که در به طور مستقل.

def transform(self, X):
Xt = X.copy()
for variable in self.categories:
Xt[variable].replace(self.encodings[variable],
inplace=True)
unknown_value = {value:self.prior for value in
X[variable].unique()
if value not in
self.encodings[variable].keys()}
if len(unknown_value) > 0:
Xt[variable].replace(unknown_value, inplace=True)
Xt[variable] = Xt[variable].astype(float)
if self.noise_level > 0:
if self.random_state is not None:
np.random.seed(self.random_state)
Xt[variable] = self.add_noise(Xt[variable],
self.noise_level)
return Xt

این مرحله دارای یک بررسی استحکام اضافی است تا اطمینان حاصل شود که رمزگذار هدف می‌تواند دسته‌های جدید یا دیده نشده را مدیریت کند. برای این دسته های جدید یا ناشناخته، آنها را با مقدار میانگین متغیر هدف جایگزین می کند در متغیر prior_mean ذخیره می شود.

اگر به مقاومت بیشتری در برابر برازش نیاز دارید، می توانید a را تنظیم کنید سطح_نویز بزرگتر از 0 برای اضافه کردن نویز تصادفی به مقادیر کد شده.

را تناسب_تغییر این روش کارکرد برازش و تبدیل داده را با برازش ترانسفورماتور به داده های آموزشی و سپس تبدیل آن بر اساس رمزگذاری های محاسبه شده ترکیب می کند.

حالا که فهمیدید کد چگونه کار می کند، بیایید آن را در عمل ببینیم.

#Instantiate TargetEncode class
te = TargetEncode(categories="ROLE_TITLE")
te.fit(data, data['ACTION'])
te.transform(data[['ROLE_TITLE']])
خروجی با عنوان نقش کدگذاری شده هدف. تصویر توسط نویسنده

رمزگذار هدف جایگزین هر “ROLE_TITLE” شناسه با احتمال برای هر دسته. حالا بیایید همین کار را برای همه عملکردها انجام دهیم و پس از استفاده از Target Encoding میزان مصرف حافظه را بررسی کنیم.

y = data['ACTION']
features = data.drop('ACTION',axis=1)

te = TargetEncode(categories=features.columns)
te.fit(features,y)
te_data = te.transform(features)

te_data.head()

خروجی، توابع کدگذاری شده هدف. تصویر توسط نویسنده
memory_usage = te_data.memory_usage(deep=True)
total_memory_usage = memory_usage.sum()
print(f"\nTotal memory usage of the DataFrame: {total_memory_usage / (1024 ** 2):.2f} MB")
مجموعه داده به دست آمده تنها از 2.25 مگابایت در مقایسه با 488.08 مگابایت از رمزگذار تک شات استفاده می کند. تصویر توسط نویسنده

رمزگذاری هدف با موفقیت داده های دسته بندی را بدون ایجاد ستون های اضافی یا افزایش استفاده از حافظه به داده های عددی تبدیل می کند.

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

در نسخه 1.3 scikit-learn، در حدود ژوئن 2023، کلاس Target Encoder را به API خود معرفی کردند. در اینجا نحوه استفاده از کدگذاری هدفمند با Scikit Learn آورده شده است

from sklearn.preprocessing import TargetEncoder

#Splitting the data
y = data['ACTION']
features = data.drop('ACTION',axis=1)

#Specify the target type
te = TargetEncoder(smooth="auto",target_type="binary")
X_trans = te.fit_transform(features, y)

#Creating a Dataframe
features_encoded = pd.DataFrame(X_trans, columns = features.columns)

خروجی از تبدیل رمزگذار هدف sklearn. تصویر توسط نویسنده

توجه داشته باشید که به دلیل پارامتر صافی و تصادفی بودن سطح نویز، نتایج کمی متفاوت از کلاس رمزگذار هدف دستی دریافت می کنیم.

همانطور که می بینید، sklearn انجام تبدیل های کدگذاری هدفمند را آسان می کند. با این حال، برای درک و توضیح نتیجه، ابتدا مهم است که بفهمیم تبدیل زیر کاپوت چگونه عمل می کند.

اگرچه رمزگذاری هدف یک روش رمزگذاری قدرتمند است، اما مهم است که الزامات و ویژگی های خاص مجموعه داده خود را در نظر بگیرید و روش رمزگذاری را انتخاب کنید که به بهترین وجه نیازهای شما و الزامات الگوریتم یادگیری ماشینی را که قصد استفاده از آن را دارید برآورده کند.

[1] Banachewicz، K. & Massaron، L. (2022). کتاب Kaggle: تجزیه و تحلیل داده ها و یادگیری ماشین برای علم داده های رقابتی. بسته>

[2] ماسارون، ال. (2022، ژانویه). چالش دسترسی کارکنان آمازون بازیابی شده در 1 فوریه 2024، از https://www.kaggle.com/datasets/lucamassaron/amazon-employee-access-challenge

[3] Massaron, L. متا ویژگی ها و کدگذاری هدف. بازیابی شده در 1 فوریه 2024، از https://www.kaggle.com/luca-massaron/meta-features-and-target-encoding

[4] Scikit- Learn.sklearn.preprocessing.TargetEncoder. در scikit-learn: یادگیری ماشین در پایتون (نسخه 1.3). بازیابی شده در 1 فوریه 2024، از https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.TargetEncoder.html



Source link