مسئله تانک آلمانی – آشنایی و پیاده سازی در پایتون

۱۳۲۶ بازدید
آخرین به‌روزرسانی: ۲۷ تیر ۱۴۰۲
زمان مطالعه: ۲۲ دقیقه
مسئله تانک آلمانی – آشنایی و پیاده سازی در پایتون

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

فهرست مطالب این نوشته

تاریخچه مسئله تانک آلمانی

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

تانک آلمانی

آن‌ها از برترین ماموران اطلاعاتی به این منظور کمک می‌گرفتند اما در کنار این راه‌حل، از کارشناسان آمار (Statistician) نیز کمک خواستند. هر دو دسته با توجه به اطلاعاتی که به دست آورده بودند، پیش‌بینی‌هایی برای تعداد تانک‌های تولید شده در هر ماه ارائه کردند که به شرح زیر است:

تاریخپیش‌بینی مامورین اطلاعاتپیش‌بینی کارشناسان آمار
ژوئن ۱۹۴۰۱۰۰۰۱۶۹
ژوئن ۱۹۴۱۱۵۵۰۲۴۴
آگوست ۱۹۴۲۱۵۵۰۳۲۷

همانطور که مشاهده می‌کنیم، به صورت میانگین کارشناسان آمار مقادیر ۸۰٪ پایین‌تر را پیش‌بینی کرده‌اند که اختلاف بسیار بزرگی است. پس از اتمام جنگ و بررسی اسناد مربوط به تولید، اعداد واقعی مربوط به تولید زمان‌های گفته شده به شکل زیر آمده است:

تاریخپیش‌بینی مامورین اطلاعاتپیش‌بینی کارشناسان آمارمقادیر واقعی
ژوئن ۱۹۴۰۱۰۰۰۱۶۹۱۲۲
ژوئن ۱۹۴۱۱۵۵۰۲۴۴۲۷۱
آگوست ۱۹۴۲۱۵۵۰۳۲۷۳۴۲

رابطه قدر مطلق درصد خطا (Absolute Percentage Error یا APE) به شکل زیر است:

$$ APE = 100 \times \left | \frac { y - P }{ y } \right | $$

اگر از این رابطه برای محاسبه میانگین قدر مطلق درصد خطا استفاده کنیم، برای ماموران اطلاعات عدد ۵۱۴/۹% و برای کارشناسان آمار عدد ۱۷/۶% به دست خواهد آمد. به این ترتیب مشاهده می‌کنیم که چه مقدار ماموران اطلاعات متفقین دچار خطا شده بوده‌اند و همان اندازه کارشناسان آمار دقت خوبی از خود نشان داده‌اند.

کارشناسان آمار چگونه توانستد تا این حد پیش‌بینی دقیقی داشته باشند؟

کارشناسان آمار، برخلاف ماموران اطلاعات که به دنبال نشت اطلاعات و جاسوسی از کارخانجات آلمانی بودند، از اطلاعات موجود در صحنه نبرد استفاده کردند. آن‌ها متوجه شده بودند که شرکت‌های سازنده تانک، بر روی قطعات تانک‌ها یک شماره سریال (Serial Number) می‌نویسند که این اعداد به ترتیب (مانند ۴۰۳، ۴۰۴، ۴۰۵، …) هستند. نکته مهمی که آلمانی‌ها به آن توجه نکرده بودند، مرتب بودن این اعداد بود.

شماره سریال تانک های آلمانی

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

$$ N \cong m + \frac { m } { k } - 1 $$

در این رابطه، متغیر N تعداد واقعی تانک‌ها، m بزرگ‌ترین شماره سریال یافته شده و k تعداد شماره سریال‌های یافت شده است. برای مثال اگر شماره سریال‌های زیر یافته شده باشد:

$$ 5, \; 19, \; 29, \; 31 \; 55 \; 60 $$

مقدار m برابر با بزرگ‌ترین عدد یافت شده یعنی ۶۰ خواهد بود و مقدار k برابر با ۶ خواهد بود. در این شرایط رابطه فوق به صورت زیر پیش‌بینی انجام می‌دهد:

$$
N \cong m + \frac { m } { k } - 1 = 60 + \frac { 60 } { 6 } - 1 = 60 + 10 - 1 = 69
$$

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

رابطه گفته شده چگونه کار می‌کند؟

فرض می‌کنیم که شماره سریال‌های یافت شده به شکل زیر است:

$$ x _ 1 , x _ 2 , \; ... \; , x _ k $$

اگر این اعداد مرتب شده باشند، ابتدای بازه را ۱ و انتهای آن را N در نظر بگیریم، اختلاف هر شماره سریال از شماره سریال قبلی به شکل زیر خواهد بود:

$$
1 \to x _ 1 \to x _ 2\to \;...\;\to x _ 3\to N
$$

$$
\begin{aligned}
& d_1=x_1-1 \\
& d_2=x_2-x_1-1 \\
& d_3=x_3-x_2-1 \\
& \vdots \\
& d_k=x_k-x_{k-1}-1
\end{aligned}
$$

توجه داشته باشید که اختلاف بین ۱ (کمترین مقدار قابل مشاهده) و $$ x _ 1 $$ را نیز به عنوان اولین فاصله در نظر می‌گیریم. حال می‌توانیم امید ریاضی (Expected Value) d را که بازه بین هر دو داده متوالی است را محاسبه کنیم:

$$
\begin{aligned}
\mathrm{E}[d]=\mathrm{E}\left[d_i \mid\right. & 1 \leq i \leq k] \\
& =\frac{\left(x_1-1\right)+\left(x_2-x_1-1\right)+\left(x_3-x_2-1\right)+\cdots+\left(x_k-x_{k-1}-1\right)}{k-1} \\
& =\frac{x_1-1+x_2-x_1-1+x_3-x_2-1+\cdots+x_k-x_{k-1}-1}{k} \\
& =\frac{x_k-k}{k}=\frac{x_k}{k}-1
\end{aligned}
$$

بنابراین، می‌توان گفت بین هر شماره سریال با شماره سریال بعدی، به صورت میانگین $$ \frac { x _ k } { k } - 1 $$ واحد فاصله وجود دارد. حال می‌توانیم امید ریاضی اختلاف N با $$ x _ k $$ را نیز محاسبه کنیم:

$$
\mathrm{E}\left[N-x_k\right]=\frac{x_k}{k}-1 \rightarrow \mathrm{E}[N]=x_k+\frac{x_k}{k}-1
$$

حال با جایگذاری مقدار m به جای $$ x _ k $$ و استفاده از N به جای امید ریاضی N خواهیم داشت:

$$
\mathrm{E}[N] \cong N \cong m+\frac{m}{k}-1=x_k+\frac{x_k}{k}-1
$$

به این ترتیب رابطه گفته شده اثبات می‌شود.

اثبات رابطه با کمک امید ریاضی M

امید ریاضی هر توزیع به شکل زیر قابل محاسبه است:

$$
\mathrm{E}[X]=x_1 \cdot p_1+x_2 \cdot p_2+\cdots+x_k \cdot p_k=\sum_{i=1}^k x_i \cdot p_i
$$

به عبارتی، هر مقدار از توزیع، در احتمال وقوع خود ضرب می‌شود و در نهایت اعداد حاصل با یکدیگر جمع می‌شود. اگر جامعه آماری اولیه به شکل زیر باشد:

$$
\{ 1,\;2,\;3,\;...,\; N - 1 , \; N\}
$$

در این شرایط اگر k عدد نمونه‌برداری انجام دهیم، به تعداد $$ \left (_k ^ N \right) $$ حالت برای نمونه‌برداری وجود خواهد داشت. حال اگر بخواهیم بزرگ‌ترین نمونه انتخاب شده برابر با m باشد، باید $$ k - 1 $$ نمونه دیگر را از بازه $$ [ 1,\; m - 1 ] $$انتخاب کنیم:

$$
P_{(M=m)}=\frac{\left(\begin{array}{l}
m-1 \\
k-1
\end{array}\right)}{\left(\begin{array}{l}
N \\
k
\end{array}\right)}
$$

به این ترتیب احتمال بیشینه بودن هر مقدار m قابل محاسبه خواهد بود. حال می‌توانیم تابع توزیع احتمال را وارد رابطه امید ریاضی کنیم:

$$
\begin{array}{rl}
\mathrm{E}[M]:=\sum_{m=k}^N & m \cdot P_{(M=m)}=\sum_{m=k}^N m \cdot \frac{\left(\begin{array}{l}
m-1 \\
k-1
\end{array}\right)}{\left(\begin{array}{l}
N \\
k
\end{array}\right)} \\
& =\sum_{m=k}^N m \cdot \frac{\frac{(m-1) !}{(k-1) ! \cdot(m-k) !}}{N !}=\sum_{m=k}^N m \cdot \frac{(m-1) ! \cdot k ! \cdot(N-k) !}{N ! \cdot(k-1) ! \cdot(m-k) !} \\
& =\sum_{m=k}^N \frac{k \cdot m ! \cdot k ! \cdot(N-k) !}{N ! \cdot k ! \cdot(m-k) !}=\frac{k \cdot k ! \cdot(N-k) !}{N !} \cdot \sum_{m=k}^N\left(\begin{array}{l}
m \\
k
\end{array}\right) \\
& =\frac{k \cdot k ! \cdot(N-k) !}{N !} \cdot\left(\begin{array}{l}
N+1 \\
k+1
\end{array}\right)=\frac{k \cdot k ! \cdot(N-k) !}{N !} \cdot \frac{(N+1) !}{(k+1) ! \cdot(N-k) !} \\
& =\frac{k \cdot(N+1)}{(k+1)}
\end{array}
$$

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

$$
\begin{aligned}
& \mathrm{E}[M]=\frac{k \cdot(N+1)}{(k+1)} \\
& \rightarrow(k+1) \cdot \mathrm{E}[M]=k \cdot(N+1) \\
& \rightarrow N+1=\frac{(k+1) \cdot \mathrm{E}[M]}{k} \\
& \rightarrow N=\frac{k \cdot \mathrm{E}[M]+\mathrm{E}[M]}{k}-1=\mathrm{E}[M]+\frac{\mathrm{E}[M]}{k}-1
\end{aligned}
$$

با توجه به اینکه مقدار مشاهده از توزیع M برای ما برابر با m است، آن را به عنوان بهترین حدس در رابطه قرار می‌دهیم:

$$
\to N = m + \frac { m }{ k } - 1
$$

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

پیاده‌سازی مسئله تانک آلمانی در پایتون

تمامی کدهای نوشته شده برای این مطلب در محیط برنامه‌نویسی پایتون، از طریق این لینک در دسترس است.

ساخت مجموعه داده برای مسئله تانک آلمانی

در ابتدا، برای بررسی دقت مدل‌های (Model) یادگیری ماشین (Machine Learning) و روش آماری آورده شده، به یک مجموعه داده (Dataset) نیاز داریم که به اندازه کافی بزرگ باشد. به این منظور یک مجموعه داده سنتز خواهیم کرد.

در ابتدا کتابخانه‌های مورد نیاز پایتون را فراخوانی می‌کنیم:

1import numpy as np
2import pandas as pd
3import seaborn as sb
4import sklearn.metrics as met
5import matplotlib.pyplot as plt
6import sklearn.linear_model as lm
7import sklearn.preprocessing as pp

این موارد به ترتیب برای عملیات زیر استفاده خواهند شد:

  1. کتابخانه Numpy  برای کار با داده، ماتریس و محاسبات آماری استفاده خواهد شد.
  2. کتابخانه Pandas   برای تولید و ذخیره‌سازی مجموعه داده استفاده خواهد شد.
  3. کتابخانه Seaborn   برای رسم نمودار همبستگی بین ویژگی‌های مجموعه داده استفاده خواهد شد.
  4. بخش metrics   مربوط به کتابخانه Scikit-learn   برای محاسبه معیارهای ارزیابی رگرسیون (Regression Metrics) کاربرد خواهد داشت. برای آشنایی با معیارهای ارزیابی رگرسیون در پایتون، می‌توانید به مطلب «بررسی معیارهای ارزیابی رگرسیون در پایتون – پیاده سازی + کدها» مراجعه نمایید.
  5. کتابخانه Matplotlib   برای رسم نمودارهای تحلیل داده و رگرسیون کاربرد خواهد داشت.
  6. بخش linear_model   مربوط به کتابخانه Scikit-learn  برای ایجاد و آموزش مدل رگرسیون خطی (Linear Regression) کاربرد خواهد داشت.
  7. بخش preprocessing  مربوط به کتابخانه Scikit-learn   برای اصلاح مقیاس (Rescaling) مجموعه داده استفاده خواهد شد.

تنظیمات مربوط به Random State و Style کدها برای مسئله تانک آلمانی

قبل از شروع کدنویسی، دو سطر زیر را وارد می‌کنیم:

1np.random.seed(seed=0)
2plt.style.use(style='ggplot')

در نتیجه این دو سطر کد، اعداد تصادفی تولید شده و در نتیجه‌ی آن خروجی‌های کد قابل بازتولید (Reproducible) خواهند بود و نمودارهای Matplotlib  با فرمت ggplot   رسم خواهند شد.

تولید مجموعه داده برای مسئله تانک آلمانی

به منظور تولید مجموعه داده مربوط با مسئله گفته شده، تابعی به اسم CreateDataset  ایجاد می‌کنیم:

1def CreateDataset(nD:int,
2                  MinN:int,
3                  MaxN:int,
4                  MinK:int,
5                  MaxK:int) -> pd.DataFrame:

این تابع در ورودی موارد زیر را دریافت خواهد کرد:

  1. تعداد داده مورد نظر ( nD  ): این عدد تعیین خواهد کرد که مجموعه داده دارای چند نمونه (Sample) باشد.
  2. کمترین مقدار N که با ورودی MinN  شناخته می‌شود.
  3. بیشترین مقدار N که با ورودی MaxN  شناخته می‌شود.
  4. کمترین مقدار k که با ورودی MinK  شناخته می‌شود.
  5. بیشترین مقدار k که با ورودی MaxK  شناخته می‌شود.

در خروجی تابع نیز یک دیتافریم (Dataframe) دریافت خواهیم کرد. به عبارت دیگر این تابع سناریوهای مختلف مربوط به شرایط جنگ جهانی دوم را شبیه‌سازی می‌کند و اعداد موجود را ذخیره می‌کند:

  1. به تعداد N عدد تانک با شماره سری 1 تا N تولید می‌کند.
  2. به تعداد k عدد تانک با شماره سری‌های تصادفی $$ x _ 1 , x _ 2 , \; ... \; , x _ k $$ به دست متفقین می‌افتد.
  3. معیارهای آماری توزیع x محاسبه و به همراه N و k به یک دیتافریم اضافه می‌شود.
  4. این شبیه‌سازی به تعداد nD  بار تکرار می‌شود.
  5. مقدار N به صورت تصادفی از بازه $$ \left [ MinN,\;MaxN \right] $$ انتخاب می‌شود.
  6. مقدار k به صورت تصادفی از بازه $$ \left [ MinN,\;MaxN \right] $$ انتخاب می‌شود.

به این ترتیب به جای 3 عدد داده مربوط به زمان جنگ جهانی، به تعداد نامحدودی نمونه داده می‌توانیم تولید کنیم و روی آن‌ها آزمایش انجام دهیم. در ابتدای تابع، ویژگی‌های مورد نظر یا به عبارتی ستون‌های دیتافریم را تعریف می‌کنیم:

1def CreateDataset(nD:int,
2                  MinN:int,
3                  MaxN:int,
4                  MinK:int,
5                  MaxK:int) -> pd.DataFrame:
6    Columns = ['Min', 'Max', 'Range',
7               'M', 'S', 'CV', 'M + S', 'M + 2S',
8               'Q1', 'Q2', 'Q3', 'IQR',
9               'K', 'N']

این ستون‌ها به شکل زیر یک معیار آماری را نشان می‌دهند:

توضیحاسم ستون
کمترین (Minimum) شماره سریال یافت‌شده Min
بیشترین (Maximum) شماره سریال یافت‌شده Max
دامنه (Range) بین بیشترین و کمترین شماره سریال یافت‌شده Range
میانگین (Mean) شماره سریال‌های یافت‌شده M
انحراف معیار (Standard Deviation) شماره سریال‌های یافت‌شده S
ضریب تغییرات (Coefficient of Variation) شماره سریال‌های یافت‌شده CV
میانگین به علاوه انحراف معیار M + S
میانگین به علاوه دو برابر انحراف معیار M + 2S
چارک اول (First Quartile) شماره سریال‌های یافت شده Q1
چارک دوم (Second Quartile) یا میانه (Median) شماره سریال‌های یافت شده Q2
چارک سوم (Third Quartile) شماره سریال‌های یافت شده Q3
دامنه میان چارکی (Interquartile Range) شماره سریال‌های یافت شده IQR
تعداد تانک‌های یافت شده K
تعداد واقعی تانک‌ها N

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

علاوه بر موارد آورده شده، می‌توان نسبت این معیارها، لگاریتم آن‌ها، حاصل‌ضرب و .... را نیز آورد. این فرآیند مشابه روند استخراج ویژگی (Feature Extraction) در علم داده (Data Science) است. به عنوان داده اولیه و Index  مربوط به دیتافریم نیز از کدهای زیر استفاده می‌کنیم و دیتافریم را می‌سازیم:

1def CreateDataset(nD:int,
2                  MinN:int,
3                  MaxN:int,
4                  MinK:int,
5                  MaxK:int) -> pd.DataFrame:
6    Columns = ['Min', 'Max', 'Range',
7               'M', 'S', 'CV', 'M + S', 'M + 2S',
8               'Q1', 'Q2', 'Q3', 'IQR',
9               'K', 'N']
10    Data = np.zeros(shape=(nD, len(Columns)),
11                    dtype=np.float32)
12    Index = np.arange(start=0, stop=nD, step=1)
13    DF = pd.DataFrame(data=Data, columns=Columns, index=Index)

توجه داشته باشید که در نتیجه این کد، دیتافریم حاصل دارای ۱۴ ستون و nD  سطر خواهد بود که تمامی مقادیر برابر با ۰ خواهد بود. حال یک حلقه ایجاد می‌کنیم که به تعداد nD  بار تکرار شده و در هر بار اجرا یک سطر از دیتافریم ایجاد شده را تکمیل کند:

1def CreateDataset(nD:int,
2                  MinN:int,
3                  MaxN:int,
4                  MinK:int,
5                  MaxK:int) -> pd.DataFrame:
6    Columns = ['Min', 'Max', 'Range',
7               'M', 'S', 'CV', 'M + S', 'M + 2S',
8               'Q1', 'Q2', 'Q3', 'IQR',
9               'K', 'N']
10    Data = np.zeros(shape=(nD, len(Columns)),
11                    dtype=np.float32)
12    Index = np.arange(start=0, stop=nD, step=1)
13    DF = pd.DataFrame(data=Data, columns=Columns, index=Index)
14    for i in range(nD):

حال داخل حلقه باید مقدار N   را تعیین کنیم و سپس مقدار K   را انتخاب کنیم:

1def CreateDataset(nD:int,
2                  MinN:int,
3                  MaxN:int,
4                  MinK:int,
5                  MaxK:int) -> pd.DataFrame:
6    Columns = ['Min', 'Max', 'Range',
7               'M', 'S', 'CV', 'M + S', 'M + 2S',
8               'Q1', 'Q2', 'Q3', 'IQR',
9               'K', 'N']
10    Data = np.zeros(shape=(nD, len(Columns)),
11                    dtype=np.float32)
12    Index = np.arange(start=0, stop=nD, step=1)
13    DF = pd.DataFrame(data=Data, columns=Columns, index=Index)
14    for i in range(nD):
15        N = np.random.randint(low=MinN, high=MaxN + 1)
16        K = np.random.randint(low=MinK, high=min(MaxK + 1, N + 1))

توجه داشته باشید که اندازه جمعیت اولیه N   است، بنابراین مقدار K   نمی‌تواند بیشتر از N   باشد، به همین دلیل مقدار high  برای انتخاب K   برابر با MaxK  نبوده و برابر با min(MaxK + 1, N + 1)   است.

حال می‌دانیم که چه تعداد تانک توسط آلمانی‌ها تولید شده است و چه تعداد تانک باید توسط متفقین یافته شود. برای نمونه‌برداری تصادفی از تانک‌ها، ابتدا شماره سریال تمامی تانک‌ها را با تابع numpy.arange  ایجاد می‌کنیم و در متغیر All  ذخیره می‌کنیم، سپس به تعداد K   مورد تانک از بین آن‌ها انتخاب می‌کنیم:

1def CreateDataset(nD:int,
2                  MinN:int,
3                  MaxN:int,
4                  MinK:int,
5                  MaxK:int) -> pd.DataFrame:
6    Columns = ['Min', 'Max', 'Range',
7               'M', 'S', 'CV', 'M + S', 'M + 2S',
8               'Q1', 'Q2', 'Q3', 'IQR',
9               'K', 'N']
10    Data = np.zeros(shape=(nD, len(Columns)),
11                    dtype=np.float32)
12    Index = np.arange(start=0, stop=nD, step=1)
13    DF = pd.DataFrame(data=Data, columns=Columns, index=Index)
14    for i in range(nD):
15        N = np.random.randint(low=MinN, high=MaxN + 1)
16        K = np.random.randint(low=MinK, high=min(MaxK + 1, N + 1))
17        All = np.arange(start=1, stop=N + 1, step=1)
18        X = np.random.choice(a=All, size=K, replace=False)

توجه داشته باشید که ورودی replace  مربوط به تابع numpy.random.choice  تعیین می‌کند که آیا یک مورد می‌تواند چندین بار انتخاب شود که باید خاموش شود و به همین دلیل مقدار False  به خود گرفته است. حال معیارهای گفته شده را یک به یک محاسبه می‌کنیم تا به دیتافریم اضافه کنیم:

1def CreateDataset(nD:int,
2                  MinN:int,
3                  MaxN:int,
4                  MinK:int,
5                  MaxK:int) -> pd.DataFrame:
6    Columns = ['Min', 'Max', 'Range',
7               'M', 'S', 'CV', 'M + S', 'M + 2S',
8               'Q1', 'Q2', 'Q3', 'IQR',
9               'K', 'N']
10    Data = np.zeros(shape=(nD, len(Columns)),
11                    dtype=np.float32)
12    Index = np.arange(start=0, stop=nD, step=1)
13    DF = pd.DataFrame(data=Data, columns=Columns, index=Index)
14    for i in range(nD):
15        N = np.random.randint(low=MinN, high=MaxN + 1)
16        K = np.random.randint(low=MinK, high=min(MaxK + 1, N + 1))
17        All = np.arange(start=1, stop=N + 1, step=1)
18        X = np.random.choice(a=All, size=K, replace=False)
19        Min = X.min()
20        Max = X.max()
21        Range = Max - Min
22        M = X.mean()
23        S = X.std()
24        CV = S / M
25        MS = M + S
26        M2S = M + 2 * S
27        Q1 = np.quantile(X, q=0.25)
28        Q2 = np.quantile(X, q=0.5)
29        Q3 = np.quantile(X, q=0.75)
30        IQR = Q3 - Q1

حال می‌توانیم مقادیر محاسبه شده را به سطر i   دیتافریم اضافه کنیم و در نهایت دیتافریم حاصل را در خروجی تابع برگردانیم:

1def CreateDataset(nD:int,
2                  MinN:int,
3                  MaxN:int,
4                  MinK:int,
5                  MaxK:int) -> pd.DataFrame:
6    Columns = ['Min', 'Max', 'Range',
7               'M', 'S', 'CV', 'M + S', 'M + 2S',
8               'Q1', 'Q2', 'Q3', 'IQR',
9               'K', 'N']
10    Data = np.zeros(shape=(nD, len(Columns)),
11                    dtype=np.float32)
12    Index = np.arange(start=0, stop=nD, step=1)
13    DF = pd.DataFrame(data=Data, columns=Columns, index=Index)
14    for i in range(nD):
15        N = np.random.randint(low=MinN, high=MaxN + 1)
16        K = np.random.randint(low=MinK, high=min(MaxK + 1, N + 1))
17        All = np.arange(start=1, stop=N + 1, step=1)
18        X = np.random.choice(a=All, size=K, replace=False)
19        Min = X.min()
20        Max = X.max()
21        Range = Max - Min
22        M = X.mean()
23        S = X.std()
24        CV = S / M
25        MS = M + S
26        M2S = M + 2 * S
27        Q1 = np.quantile(X, q=0.25)
28        Q2 = np.quantile(X, q=0.5)
29        Q3 = np.quantile(X, q=0.75)
30        IQR = Q3 - Q1
31        DF.iloc[i] = [Min, Max, Range, M, S, CV, MS, M2S, Q1, Q2, Q3, IQR, K, N]
32    return DF

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

1DF = CreateDataset(2000, 100, 300, 50, 150)

به این ترتیب ۲۰۰۰ سناریوی تصادفی ایجاد خواهد شد که در هر کدام آلمانی‌ها بین ۱۰۰ تا ۳۰۰ تانک خواهند ساخت و متفقین بیت ۵۰ تا ۱۵۰ مورد از آن‌ها را خواهند یافت. توجه داشته باشید که حد بالای مقدار K   به عدد N   محدود شده است.

مصورسازی همبستگی بین ستون‌های مجموعه داده برای مسئله تانک آلمانی

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

با توجه به اینکه ممکن است روابط غیرخطی بین ستون‌ها وجود داشته باشد، از ضریب همبستگی اسپیرمن (Spearman Correlation Coefficient) استفاده خواهیم کرد. به این منظور تابع با اسم PlotCorrelation  ایجاد می‌کنیم. این تابع در ورودی دیتافریم مجموعه داده را دریافت خواهد کرد و تنها یک نمودار نشان خواهد داد:

1def PlotCorrelation(DF:pd.DataFrame) -> None:
2    C = DF.corr(method='spearman')
3    sb.heatmap(C, cmap='RdYlGn',
4               annot=True, fmt='.2f',
5               xticklabels=DF.columns,
6               yticklabels=DF.columns)
7    plt.title('Spearman Correlation Coeficient Between Dataset Columns')
8    plt.show()

این نمودار که Heat Map نام دارد، به کمک کتابخانه Seaborn   رسم می‌شود. برای استفاده از این تابع، به شکل زیر عمل می‌کنیم:

1PlotCorrelation(DF)

در خروجی این کد، نمودار زیر حاصل می‌شود:

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

می‌توانیم مشاهده کنیم که بین اعداد اغلب همبستگی شدیدی وجود دارد، به جز Min  ، CV  و K   که به دلایلی قابل توجیه است. با توجه به اینکه قصد داریم ستون N   را پیش‌بینی کنیم، هر ستون دیگری که با آن همبستگی قدرتمندی داشته باشد، می‌تواند در انجام پیش‌بینی به ما کمک کند. به این جهت، تمامی ستون‌ها به جز N ، K  ، Q1   و Min  دارای همبستگی بالای ۰/۹۷ هستند که مقدار قابل توجهی است.

رسم نمودارهای نقطه‌ای برای مسئله تانک آلمانی

نمودار دیگری که می‌تواند بین ویژگی هدف و سایر ویژگی‌های ورودی رسم شود، نمودار نقطه‌ای (Scatter Plot) است که برای نشان دادن ارتباط بین دو متغیر بسیار مفید است. به این منظور تابع زیر را استفاده خواهیم کرد:

1def ScatterPlot(DF:pd.DataFrame) -> None:
2    yColumn = 'N'
3    for xColumn in DF.columns:
4        if xColumn != yColumn:
5            plt.scatter(DF.loc[:, xColumn],
6                        DF.loc[:, yColumn],
7                        s=20, c='crimson',
8                        alpha=0.8, marker='o')
9            plt.xlabel(xColumn)
10            plt.ylabel(yColumn)
11            plt.show()

توجه داشته باشید که ستون N   همواره ستون هدف است و تمامی ستون‌های غیر از N   ستون ویژگی ورودی هستند، بنابراین yColumn   برابر با N   تعریف می‌شود و xColumn   با کمک یک حلقه مدام تغییر می‌کند و عبارت xColumn != yColumn   از برابر نبودن دو متغیر اطمینان ایجاد می‌کند. این تابع به شکل زیر استفاده می‌شود:

1ScatterPlot(DF)

در خروجی این سطر از کد، ۱۳ نمودار حاصل می‌شود که در GIF زیر پشت سر هم آمده‌اند:

برای مشاهده تصویر متحرک در ابعاد اصلی، بر روی آن کلیک کنید.

به این ترتیب مشاهده می‌کنیم که در اغلب موارد یک ارتباط تقریباً خطی بین مقادیر قابل مشاهده است. اما توجه داشته باشید که برای Min  نمودار زیر را داریم:

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

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

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

در این نمودار هیچگونه همبستگی مشاهده نمی‌شود، اما می‌توان گفت مقادیر CV  حول خط $$ x = 0.57 $$ پراکنده شده‌اند و با دور شدن از این خط چگالی (Density) داده‌ها نیز کاهش پیدا می‌کند. برای K   نیز نمودار زیر حاصل می‌شود:

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

مشاهده می‌کنیم که برای بازه $$ x \leq 100 $$ مقادیر N   به صورت یکنواخت پخش شده‌اند، درحالیکه که برای بازه $$ x \gt 100 $$ این شرایط حاکم نیست و حد پایین N   رفته‌رفته افزایش می‌یابد. با توجه به اینکه اندازه جمعیت اولیه N  است و در بیشترین حالت می‌توانیم N   نمونه از جمعیت اولیه انتخاب کنیم، وجود این خط قابل توجیه است. در مورد سایر موارد نیز می‌توانید تک‌تک نمودارها بررسی کنید و متوجه روابط بین هر ویژگی با ویژگی هدف شوید.

پیاده‌سازی فرمول یافته شده توسط کارشناسان آمار و ارزیابی آن برای مسئله تانک آلمانی

به این منظور ابتدا از دیتافریم ایجاد شده، ستون هدف ( N  ) و مقادیر Max   و K   را به شکل آرایه (Array) استخراج می‌کنیم:

1Y = DF.loc[:, 'N'].to_numpy()
2M = DF.loc[:, 'Max'].to_numpy()
3K = DF.loc[:, 'K'].to_numpy()

حال می‌توانیم خروجی فرمول به دست آمده را نیز محاسبه کنیم:

1Ps = M + M / K - 1

طراحی تابع گزارش رگرسیون برای مسئله تانک آلمانی

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

تابعی به اسم RegressionReport  تعریف می‌کنیم که در ورودی مقادیر واقعی (Target Values, Observed Values) و مقادیر پیش‌بینی شده (Predicted Values) را به همراه نام مجموعه داده دریافت می‌کند:

1def RegressionReport(Y:np.ndarray, P:np.ndarray, Dataset:str) -> None:

حال با استفاده از توابع موجود در sklearn.metrics   معیارهایی همچون میانگین مربعات خطا (Mean Squared Error یا MSE)، میانگین قدر مطلق خطا (Mean Absolute Error یا MAE)، میانگین قدر مطلق درصد خطا (Mean Absolute Percentage Error یا MAPE) و ضریب تعیین (Coefficient of Determination یا R2 Score) قابل محاسبه است. ۳ معیار دیگر نیز براساس MSE  و MAE  قابل محاسبه هستند:

1def RegressionReport(Y:np.ndarray, P:np.ndarray, Dataset:str) -> None:
2    MSE = met.mean_squared_error(Y, P)
3    RMSE = MSE ** 0.5
4    Range = Y.max() - Y.min()
5    NRMSE = 100 * RMSE / Range
6    MAE = met.mean_absolute_error(Y, P)
7    NMAE = 100 * MAE / Range
8    MAPE = 100 * met.mean_absolute_percentage_error(Y, P)
9    R2 = 100 * met.r2_score(Y, P)

حال باید معیارهای محاسبه شده را به شکل یک متن نمایش دهیم:

1def RegressionReport(Y:np.ndarray, P:np.ndarray, Dataset:str) -> None:
2    MSE = met.mean_squared_error(Y, P)
3    RMSE = MSE ** 0.5
4    Range = Y.max() - Y.min()
5    NRMSE = 100 * RMSE / Range
6    MAE = met.mean_absolute_error(Y, P)
7    NMAE = 100 * MAE / Range
8    MAPE = 100 * met.mean_absolute_percentage_error(Y, P)
9    R2 = 100 * met.r2_score(Y, P)
10    print(f'Regression Report For {Dataset}:')
11    print(f'MSE: {MSE:.2f}')
12    print(f'RMSE: {RMSE:.2f}')
13    print(f'NRMSE: {NRMSE:.2f} %')
14    print(f'MAE: {MAE:.2f}')
15    print(f'NMAE: {NMAE:.2f} %')
16    print(f'MAPE: {MAPE:.2f} %')
17    print(f'R2: {R2:.2f} %')

به این ترتیب این تابع کامل می‌شود و می‌توانیم آن را به شکل زیر استفاده کنیم:

1RegressionReport(Y, Ps, 'Statistic Formula')

در این شرایط یک گزارش از کیفیت پیش‌بینی انجام شده به شکل زیر در خروجی نوشته خواهد شد:

1Regression Report For Statistic Formula:
2MSE: 3.76
3RMSE: 1.94
4NRMSE: 0.97 %
5MAE: 1.22
6NMAE: 0.61 %
7MAPE: 0.59 %
8R2: 99.88 %

به این ترتیب مشاهده می‌کنیم که ضریب تعیین به عدد ۹۹/۸۸% رسیده است که عدد بسیار مناسبی است. معیار MAPE  نیز عدد ۰/۵۹% را نشان می‌دهد که خطایی کمتر از ۱% بوده و بسیار مناسب است.

طراحی تابع رسم نمودار رگرسیون برای مسئله تانک آلمانی

اگر قصد داشته باشیم که برای رگرسیون انجام شده یک نمودار نیز رسم کنیم، می‌توانیم یک تابع به شکل زیر ایجاد کنیم:

1def RegressionPlot(Y:np.ndarray, P:np.ndarray, Dataset:str) -> None:
2    a = min(Y.min(), P.min())
3    b = max(Y.max(), P.max())
4    ab = np.array([a, b])
5    plt.scatter(Y, P, s=20, c='teal', marker='o', label='Data')
6    plt.plot(ab, ab, ls='-', lw=1, c='k', label='Y = X')
7    plt.plot(ab, 0.9 * ab, ls='--', lw=0.9, c='r', label='Y = 0.8 * X')
8    plt.plot(ab, 1.1 * ab, ls='--', lw=0.9, c='r', label='Y = 1.1 * X')
9    plt.title(f'Regression Plot For {Dataset}')
10    plt.xlabel('Target Values')
11    plt.ylabel('Predicted Values')
12    plt.legend()
13    plt.show()

این تابع نیز مشابه قبل در ورودی مقادیر واقعی، مقادیر پیش‌بینی شده و نام مجموعه داده را دریافت می‌کنم. بر روی نمودار حاصل با استفاده از تابع matplotlib.pyplot.scatter   هر داده به شکل یک نقطه نمایش داده خواهد شد. ۳ خط راهنما نیز رسم خواهند شد تا بازه مربوط به خطای $$ [ -10\%,\; +10\%] $$ را نمایش دهد. از این تابع به شکل زیر استفاده می‌کنیم:

1RegressionPlot(Y, Ps, 'Statistic Formula')

و در خروجی نمودار زیر حاصل می‌شود:

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

به این ترتیب مشاهده می‌کنیم که اکثر داده‌ها در فاصله بسیار نزدیکی از خط $$ y = x $$ قرار گرفته‌اند که مطلوب است. در کل مجموعه داده نیز هیچ داده‌ای خارج از دو خط قرمز وجود ندارد، بنابراین بیشترین قدر مطلق درصد خطا کمتر از ۱۰% است. به این ترتیب به عنوان یک نتیجه‌گیری می‌توان گفت که فرمول پیشنهاد شده توسط کارشناسان آمار از دقت بسیار بالایی برخوردار است.

انجام پیش‌بینی با استفاده از مدل‌های یادگیری ماشین برای مسئله تانک آلمانی

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

به منظور آموزش یک مدل رگرسیون، به ویژگی‌های ورودی مجموعه داده (X) و ویژگی‌های هدف مجموعه داده (Y) نیاز داریم. در این مسئله ستون N   دیتافریم ویژگی هدف و سایر ستون‌ها ویژگی‌های ورودی هستند. بنابراین به شکل زیر می‌توانیم این ویژگی‌ها را جدا کنیم:

1X0 = DF.drop(labels=['N'], axis=1, inplace=False).to_numpy()
2Y0 = DF.loc[:, ['N']].to_numpy()

اصلاح مقیاس مجموعه داده برای مسئله تانک آلمانی

با توجه به اینکه این ویژگی‌ها دارای مقیاس (Scale) و بازه (Range) متفاوتی هستند، باید آن‌ها را اصلاح مقیاس (Rescaling) کنیم. بنابراین ویژگی‌های قبل از اصلاح مقیاس را X0   و Y0   نام‌گذاری می‌کنیم. در مرحله بعد باید ویژگی‌ها اصلاح مقیاس شوند، به همین دلیل باید ابتدا به دو مجموعه داده آموزش (Train Dataset) و مجموعه داده آزمایش (Test Dataset) تقسیم شود. به شکل زیر ابتدا تعداد داده‌ها محاسبه سپس 80% داده‌ها برای Train انتخاب می‌شود:

1nD = X0.shape[0]
2nDtr = round(0.8 * nD)
3
4trX0 = X0[:nDtr]
5teX0 = X0[nDtr:]
6trY0 = Y0[:nDtr]
7teY0 = Y0[nDtr:]

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

sklearn.model_selection.train_test_split

حال با استفاده از sklearn.preprocessing.StandardScaler   ویژگی‌ها را اصلاح مقیاس می‌کنیم.

توجه داشته باشید که چون یک مسئله رگرسیون داریم، باید ویژگی هدف نیز به این شکل اصلاح مقیاس شود:

1xScaler = pp.StandardScaler()
2trX = xScaler.fit_transform(trX0)
3teX = xScaler.transform(teX0)
4
5yScaler = pp.StandardScaler()
6trY = yScaler.fit_transform(trY0)
7teY = yScaler.transform(teY0)

ایجاد و آموزش مدل مسئله تانک آلمانی

قبل از ایجاد مدل، باید ابتدا در مورد نوع آن تصمیم بگیریم. با توجه به اینکه یک مسئله و یک مجموعه داده کوچک داریم، همچنین می‌دانیم که بین اکثر ویژگی‌های ورودی با ویژگی هدف یک ارتباط خطی وجود دارد، از مدل رگرسیون خطی (Linear Regression) استفاده می‌کنیم و آن را بر روی مجموعه داده آموزش، آموزش می‌دهیم:

1Model = lm.LinearRegression()
2
3Model.fit(trX, trY)

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

انجام پیش‌بینی مسئله تانک آلمانی

برای انجام پیش‌بینی به شکل زیر عمل می‌کنیم:

1teP = Model.predict(teX)

توجه داشته باشید که این مقادیر پیش‌بینی شده در مقیاس اصلی ویژگی هدف نیست و برای اینکه آن را به مقیاس اصلی برگردانیم، باید به شکل زیر عمل کنیم:

1teP0 = yScaler.inverse_transform(teP)

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

1RegressionReport(teY0, teP0, 'Linear Regression Model (Test Dataset)')

که خواهیم داشت:

1Regression Report For Linear Regression Model (Test Dataset):
2MSE: 2.50
3RMSE: 1.58
4NRMSE: 0.79 %
5MAE: 1.10
6NMAE: 0.55 %
7MAPE: 0.55 %
8R2: 99.93 %

به این ترتیب مشاهده می‌کنیم که مدل ایجاد شده نیز به ضریب تعیین ۹۹/۹۳% دست یافته است که عدد بسیار خوبی است. این مدل به دلیل در دسترس بودن ویژگی‌های بیشتر و همچنین بهینه‌سازی وزن‌های خود، توانسته ضریب تعیین را به اندازه ۰/۰۵% بهبود دهد. برای رسم نمودار رگرسیون مدل رگرسیون خطی نیز مشابه قبل کد زیر را استفاده می‌کنیم:

1RegressionPlot(teY0, teP0, 'Linear Regression Model (Test Dataset)')

پس از اجرای این کد نیز نمودار زیر حاصل می‌شود:

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

به این ترتیب همانطور که انتظار داشتیم، یک نمودار بسیار مناسب حاصل می‌شود که در نقاط پراکندگی بسیار کمی حوله خط $$ y = x $$ دارند. به این ترتیب متوجه می‌شویم که برای حل اینگونه مسائل الزاماً نیاز نیست روابط پیچیده ریاضی را حل کنیم و مدل‌های یادگیری ماشین به جای ما می‌توانند معادل این کار را انجام دهد.

مدل رگرسیون خطی چگونه پیش‌بینی انجام داده است؟

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

$$
\hat{y}=b+w_1 \times x_1+w_2 \times x_2+\cdots+w_n \times x_n=b+\sum_{i=1}^n w_i \times x_i=W^T x+b
$$

در رابطه فوق تنها مقادیر b و w توسط الگوریتم‌های بهینه‌ساز (Optimizer) محاسبه می‌شوند. این موارد در داخل Model  ایجاد شده وجود دارد. اما توجه داشته باشید که قبل از وارد شدن ویژگی‌ها به مدل، آن‌ها را اصلاح مقیاس کردیم، به همین دلیل روابط حاصل پیچیده خواهد بود، به همین جهت می‌توانیم تمامی فرآیند Scaling و Inverse Scaling را حذف کنیم تا ضرایب (Coefficient) و بایاس (‌Bias) در مقیاس اصلی به دست آید. انجام این فرآیند برای تمامی مدل‌ها امکان‌پذیر نیست:

1nD = X0.shape[0]
2nDtr = round(0.8 * nD)
3
4trX0 = X0[:nDtr]
5teX0 = X0[nDtr:]
6trY0 = Y0[:nDtr]
7teY0 = Y0[nDtr:]
8
9Model = lm.LinearRegression()
10
11Model.fit(trX0, trY0)

حال می‌توانیم با استفاده از دو Attribute زیر مقادیر ضرایب و بایاس را استخراج کنید:

1W = Model.coef_
2B = Model.intercept_

جنس این دو متغیر را می‌توانیم به شکل زیر بررسی کنیم:

1print(type(W))
2print(type(B))

که خواهیم داشت:

1<class 'numpy.ndarray'>
2<class 'numpy.ndarray'>

به این ترتیب مشاهده می‌کنیم که هر دو متغیر آرایه هستند. حال می‌توانیم ابعاد آن‌ها را بررسی کنیم:

1print(W.shape)
2print(B.shape)

که خواهیم داشت:

1(1, 13)
2(1,)

به این ترتیب مشاهده می‌کنیم آرایه وزن ابعاد $$ 1 \times 13 $$ دارد که عدد ۱۳ نشان‌دهنده تعداد ویژگی‌های ورودی و عدد ۱ نشان‌دهنده تعداد ویژگی‌های هدف است. آرایه بایاس نیز باید یک عدد Float می‌بود که به دلیل وجود یک ویژگی هدف به یک آرایه با ابعاد ۱ در آمده است. به شکل زیر تعریف W   و B   را اصلاح می‌کنیم:

1W = Model.coef_[0]
2B = Model.intercept_[0]

حال باید اسم ویژگی‌های ورودی را نیز به ترتیبی که وجود دارد استخراج کنیم. به این منظور از کد زیر استفاده می‌کنیم:

1FeatureNames = [FeatureName for FeatureName in DF.columns if FeatureName != 'N']

این کد اسم تمامی ستون‌های دیتافریم ساخته شده به جز ستون N   را به ترتیب استخراج می‌کند. حال کد زیر را نوشته و اجرا می‌کنیم:

1print('N = ', end='')
2for w, FeatureName in zip(W, FeatureNames):
3    if '+' in FeatureName:
4        FeatureName = f'({FeatureName})'
5    if w > 0:
6        print(f' + {w:.2f} * {FeatureName}', end='')
7    else:
8        print(f' - {abs(w):.2f} * {FeatureName}', end='')
9if B > 0:
10    print(f' + {B:.2f}', end='\n')
11else:
12    print(f' - {B:.2f}', end='\n')

این کد به گونه‌ای طراحی شده است که فرمول یافته شده توسط مدل رگرسیون خطی را برای ما تشکیل می‌دهد و خروجی زیر حاصل می‌شود:

1N = 0.31 * Min + 0.73 * Max + 0.28 * Range - 1.14 * M + 0.33 * S - 14.07 * CV + 2.08 * (M + S) - 1.11 * (M + 2S) + 0.02 * Q1 + 0.02 * Q2 + 0.04 * Q3 - 0.03 * IQR - 0.02 * K + 9.31

به این ترتیب مشاهده می‌کنیم که یک رابطه ریاضی بسته حاصل می‌شود همانند رابطه‌ای که کارشناسان آمار محاسبه کرده بودند. توجه داشته باشید که ویژگی Range  ترکیب خطی دو ویژگی Min  و Max  است. ویژگی IQR  نیز ترکیب خطی دو ویژگی Q1  و Q3  است. این گزاره در مورد دو ویژگی M + S   و M + 2S   نیز صحیح است. از طرفی می‌دانیم که مدل رگرسیون خطی یک ترکیب خطی از ویژگی‌های ورودی ایجاد می‌کند، بنابراین این موارد می‌توانند حذف شوند و تنها ویژگی‌های زیر باقی بمانند:

1    Columns = ['Min', 'Max',
2               'M', 'S', 'CV',
3               'Q1', 'Q2', 'Q3',
4               'K', 'N']

در این شرایط اگر دوباره کد مربوط به فرمول ریاضی حاصل از مدل را اجرا کنیم، به رابطه زیر خواهیم رسید:

1N = 0.03 * Min + 1.01 * Max - 0.17 * M + 0.19 * S - 14.07 * CV + 0.04 * Q1 + 0.02 * Q2 + 0.01 * Q3 - 0.02 * K + 9.31

در این حالت، وزن‌های محاسبه شده به اعداد معقولی نزدیک می‌شود.

جمع‌بندی

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

  • مشابه این مسئله در چه مواردی مشاهده می‌شود؟
  • چرا امروزه شماره سریال‌ها به شکل دنباله عددی ساده ساخته نمی‌شود؟
  • چرا برای ارزیابی فرمول حاصل از روابط ریاضیاتی، مجموعه داده را به دو بخش Train و Test تقسیم نکردیم؟
  • کد مربوط به استخراج فرمول را تحلیل کنید.
  • اگر اصلاح مقیاس را با روش Min-Max Scaling انجام دهیم، رابطه زیر به دست می‌آید، این رابطه را چگونه می‌توان تحلیل کرد؟
1N = 0.00 * Min + 1.02 * Max - 0.10 * M + 0.06 * S - 0.02 * CV + 0.02 * Q1 + 0.01 * Q2 + 0.01 * Q3 - 0.01 * K + 0.00
  • چرا ضریب CV که قبلاً در حدود -14 بود به مقدار -0.02 رسید؟
  • تنظیمات تابع CreateDataset را تغییر دهید و دقت حاصل از مدل را به این تنظیمات ارتباط دهید.
  • اگر شماره سریال‌های 3,21,28,36,49,56 یافته شده باشند، اندازه جمعیت اولیه را با استفاده از فرمول کارشناسان آمار پیش‌بینی کنید.
  • برای مجموعه داده حاصل از کد زیر، نمودار همبستگی و نمودار نقطه‌ای را رسم کنید:
1DF = CreateDataset(2000, 100, 300, 5, 10)

تفاوت ایجاد شده در نمودارها به چه دلیلی است؟ در این شرایط دقت مدل‌ها و فرمول چه تغییر می‌کنید؟

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

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