قرار هستش در این اموزش با روش های ساده تا پیشرفته محاسبه زمان اجرا و وضعیت مصرف حافظه در پایتون رو با هم بررسی کنم پس ما من همراه باشید.
خیلی خلاصه بگم که توی ژوپیتر نوت بوک ابزار مورد علاقه من کلی ابزار هست که میتونیم از اونها استفاده کنیم و زمان اجرا رو محاسبه کنیم در کنارش توی پایتون خالص هم روش / روش هایی برای اینکار هست که در این مقاله با توجه به رویه یک دانشمند داده و مهندسی یادگیری ماشین بیشتر درباره ژوپیتر نوت بوک صحبت میکنم.
لینک ویدیویی روش های بررسی مدت زمان اجرای توابع و برنامه در پایتون و ژوپیتر نوت بوک در یوتوب
اگر که میخواهید میتونید راهنمای جامع ژوپیتر نوت بوک به صورت پست بلاگ رو مشاهده کنید و همچنین پلی لیست یوتوب ژوپیتر نوت بوک رو بررسی کنید.
پیش نیاز:
- دانش اولیه پایتونی
- دانش اولیه کار با ژوپیتر نوت بوک
جدول محتوایی
- روش های محاسب زمان اجرا در پایتون (خالص)
- روش های محاسبه زمان اجرا در ژوپیتر نوت بوک
- محاسبه مصرف Ram در ژوپیتر نوت بوک
روش های محاسبه زمان اجرا در پایتون
برای محاسبه در قالب یک سری کد پایتونی ساده ترین روش استفاده از کتابخونه time و محاسبه قبل و بعد از اجرای یک یا چند خط کد میباشد.
قبل از اینکه کد اصلی رو بزنم باید یک کدی داشته باشیم که اجرا بشه برای شروه فاکتوریل ۱۰۰۰ که مودش رو بر ۲ بگیریم :
import math def my_code(): print(f"factorial of 1000 % 2 is: {math.factorial(1000) % 2}")
حالا کد اصلی که با استفاده از time.clock() هست و میایم قبل و بعد از کد قرار میدیم:
import time start_time = time.clock() ### codes my_code() ### print (time.clock() - start_time, "seconds")
این روش دو عیب داره برامون اینکه ما نمیدونیم هر سطر چقدر منابع مصرف میکنه و اینکه به صورت متوسط چقدر منابع مصرف میشه که بریم به بخش بعدی تا برای هر کدوم از این عیب ها بهتون بگم که jupyter notebook چه خوابی برامون دیده ( البته که Ipython باید گفت)
دو مشکل داریم:
۱. زمان اجرای متوسط رو نداریم
۲. زمان اجرای هر بخش رو نداریم
روش های محاسبه زمان اجرا در ژوپیتر نوت بوک
فعلا هنوز کد قبلیمون رو داریم و به نظرم به اندازه کافی خوب هستش برای اینجا بریم و بررسی کنیم
# %time
به خاطر اینکه ما دفعات زیادی کد رو اجرا میکنیم و دستور print خروجی رو شلوع میکنه و io رو درگیر من کد مرجع رو تغییر دادم به:
import math def my_code(): save_some_where = "factorial of 1000 % 2 is: {math.factorial(1000) % 2}"
از بخش توابع جادویی با یک تابع جادویی به اسم time اشنا شدیم که برامون دقیقا مثل قسمت قبل مقدارش رو برمیگردونه برای مثال ببینید:
%time my_code()
البته یک قاعده ای رو داریم اگر دوتا % بذاریم پشت time برای کل اون سلول زمان رو حساب میکنه که زمانی که چند خط داریم از این رویه استفاده میکنیم ( یا خوانایی بهتر)
%%time my_code() my_code()
این تا حدی خوبه که با جزییات ما میدونیم هر بخش چقدر زمان صرفش شده ولی بهمون هر دفعه یک رقم جدید میده و مشکل اول زمان اجرای متوسط رو نداریم رو اینجا قرار هست با دستور timeit حل کنیم!
# %timeit
این دستور برای ما میاد و کدی / کدهایی که گفتیم رو چندین بار اجرا میکنه و بهمون یک خلاصه ای از عملکردش رو میده
بیایم اول مثل قبلی ها اجراش کنیم تا ببینیم چه چیزی بهمون میده و بعد مفصل تر دربارش صحبت کنیم:
%timeit my_code()
البته باز هم نسخه چند خطی اون با %% در دسترس هست
%%timeit my_code() my_code()
قبل از اینکه جلو تر بریم بیاییم ببینیم چه چیز هایی رو بهمون میگه:
143 ns ± 3.93 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
به طور متوسط ۱۴۳ نانو ثانیه زمان اجرا دستورات ماست و std ما ( انحراف معیار) ما حدود ۴ نانو ثانیه هست در کنارش دو چیز رو مطرح میکنه که این بر اساس یک تعداد run و یک تعداد loop هست
loop میشه گفت اجرای از اول کد هستش
run اجرای اون کد هستش
البته که میتونیم با دادن پارامتر هایی این رو تعیین کنیم
%timeit -n10 -r5 my_code()
که n تعداد اجراست و r تعداد حلقه هایی باید اجرا بشه
دیدن لیست کامل پارامتر ها و توضیحات این تابع
برای گرفتن خروجی این تابع مجیک میتوینم با فلگ o خروجیش رو در یک متغییر ذخیره کنیم
output_of_test = %timeit -n10 -r5 -o my_code()
که این خروجیش میتونیم ازش مورادی مثل بهترین ها و بدترین زمان های اجرا رو بگیریم
# best time times.best # worst time times.worst #all times times.timings
تا اینجا یکی از مشکلاتی که گفتم یعنی متوسط زمان اجرا رو حل کردیم ولی هنوز بهمون میزانی که هر خط کد طول میکشه اجرا بشه رو نمیگه بریم به بخش استفاده از ماژول های line_profiler و memory_profiler در ژوپیتر برای تشخیص میزان مصرف منابع و حافظه خط به خط کد پایتون:
نصب و استفاده از ماژول line_profiler برای تشخیص زمان اجرای خط به خط کد
با استفاده از ماژول line_profiler در ژوپیتر و Ipython میتونیم زمان اجرای خط به خط پایتون رو در بیاریم و بعدا بتونیم با این اطلاعات بهبود بدیم اون رو
ابتدا اون رو نصب کنیم با کمک دستور pip
pip install line_profiler
بعد از نصب میتونید با کمک load_ext اون رو توی ژوپیتر لود کنید
%load_ext line_profiler
و قبل از اینکه بریم و یک کدی بنویسیم که چندین قسمت داره تا درک بهتری داشته باشیم به کار.
def my_code(): factorial_5 = math.factorial(5) factorial_100 = math.factorial(100) factorial_250 = math.factorial(250) factorial_50 = math.factorial(50)
چیزی که میدونیم اینکه فاکتوریل اعداد بزرگتر بیشتر طول میکشه و میخواهیم بررسی کنیم 250 چقدر بیشتر از بقیه طول میکشه.
برای اینجا بعد از لود باید از lprun استفاده کنیم به این شکل که با فلگ f اسم تابع و خود تابع رو بفرستیم.
%lprun -f my_code my_code()
که بهمون این خروجی رو میده:
Timer unit: 1e-06 s Total time: 5.3e-05 s File: Function: my_code at line 1 Line # Hits Time Per Hit % Time Line Contents ============================================================== 1 def my_code(): 2 1 11.0 11.0 20.8 factorial_5 = math.factorial(5) 3 1 15.0 15.0 28.3 factorial_100 = math.factorial(100) 4 1 22.0 22.0 41.5 factorial_250 = math.factorial(250) 5 1 5.0 5.0 9.4 factorial_50 = math.factorial(50)
که الان میدونیم هر خط چقدر منابع مصرف میکنه حالا بیایم با یک کد دیگه هم بررسیش کنیم
import numpy as np from sklearn.linear_model import LinearRegression from sklearn.datasets import load_iris X , y = load_iris(return_X_y =True) %lprun -f LinearRegression().fit LinearRegression().fit(X, y)
برای اینکه نشون بدم میشه از توابعی که حتی خودمون نساختیم هم استفاده کنیم اینجا یک مدل خیلی ساده با استفاده از LinearRegression و دیتاست ایریس ساختم و اجراش کردم ببینم چطوری اجرا میشه که نتیجه به این شکل شد:
Timer unit: 1e-06 s Total time: 0.002846 s File: /home/amin/.local/lib/python3.6/site-packages/sklearn/linear_model/_base.py Function: fit at line 481 Line # Hits Time Per Hit % Time Line Contents ============================================================== 481 def fit(self, X, y, sample_weight=None): 482 """ 483 Fit linear model. 484 485 Parameters 486 ---------- 487 X : {array-like, sparse matrix} of shape (n_samples, n_features) 488 Training data 489 490 y : array-like of shape (n_samples,) or (n_samples, n_targets) 491 Target values. Will be cast to X's dtype if necessary 492 493 sample_weight : array-like of shape (n_samples,), default=None 494 Individual weights for each sample 495 496 .. versionadded:: 0.17 497 parameter *sample_weight* support to LinearRegression. 498 499 Returns 500 ------- 501 self : returns an instance of self. 502 """ 503 504 1 9.0 9.0 0.3 n_jobs_ = self.n_jobs 505 1 8.0 8.0 0.3 X, y = self._validate_data(X, y, accept_sparse=['csr', 'csc', 'coo'], 506 1 1320.0 1320.0 46.4 y_numeric=True, multi_output=True) 507 508 1 8.0 8.0 0.3 if sample_weight is not None: 509 sample_weight = _check_sample_weight(sample_weight, X, 510 dtype=X.dtype) 511 512 1 7.0 7.0 0.2 X, y, X_offset, y_offset, X_scale = self._preprocess_data( 513 1 6.0 6.0 0.2 X, y, fit_intercept=self.fit_intercept, normalize=self.normalize, 514 1 6.0 6.0 0.2 copy=self.copy_X, sample_weight=sample_weight, 515 1 999.0 999.0 35.1 return_mean=True) 516 517 1 8.0 8.0 0.3 if sample_weight is not None: 518 # Sample weight can be implemented via a simple rescaling. 519 X, y = _rescale_data(X, y, sample_weight) 520 521 1 10.0 10.0 0.4 if sp.issparse(X): 522 X_offset_scale = X_offset / X_scale 523 524 def matvec(b): 525 return X.dot(b) - b.dot(X_offset_scale) 526 527 def rmatvec(b): 528 return X.T.dot(b) - X_offset_scale * np.sum(b) 529 530 X_centered = sparse.linalg.LinearOperator(shape=X.shape, 531 matvec=matvec, 532 rmatvec=rmatvec) 533 534 if y.ndim < 2: 535 out = sparse_lsqr(X_centered, y) 536 self.coef_ = out[0] 537 self._residues = out[3] 538 else: 539 # sparse_lstsq cannot handle y with shape (M, K) 540 outs = Parallel(n_jobs=n_jobs_)( 541 delayed(sparse_lsqr)(X_centered, y[:, j].ravel()) 542 for j in range(y.shape[1])) 543 self.coef_ = np.vstack([out[0] for out in outs]) 544 self._residues = np.vstack([out[3] for out in outs]) 545 else: 546 self.coef_, self._residues, self.rank_, self.singular_ = \ 547 1 412.0 412.0 14.5 linalg.lstsq(X, y) 548 1 6.0 6.0 0.2 self.coef_ = self.coef_.T 549 550 1 3.0 3.0 0.1 if y.ndim == 1: 551 1 15.0 15.0 0.5 self.coef_ = np.ravel(self.coef_) 552 1 27.0 27.0 0.9 self._set_intercept(X_offset, y_offset, X_scale) 553 1 2.0 2.0 0.1 return self
نصب و استفاده از ماژول memory_profiler برای تشخیص میزان رم هر خط
ماژول بعدی که میخوام دربارش صحبت کنم memory_profiler هست که با استفاده از اون میتونیم خط به خط میزان استفاده رو توی پایتون مشخص کنیم.
برای شروع با دستور زیر اون رو نصب میکنیم
pip install memory_profiler
بعد از نصب میتونید با کمک load_ext اون رو توی ژوپیتر لود کنید
%load_ext memory_profiler
دقیقا مثل دستور قبلی و اینجا از mprun استفاده میکنیم برای نمونه برای تابعی که فاکتوریل های مختلفی اجرا میکرد:
%mprun -f LinearRegression().fit LinearRegression().fit(X, y)
همانطور که میبینید در خط اول مکان دقیق اون تابع رو برای ما مشخص کردم و از طرفی طبق پیامی که من گرفتم اجازه اجرا
NOTE: %mprun can only be used on functions defined in physical files, and not in the IPython environment.
رو به ما میده که متاسفانه نمیتونم از فانکشن هایی که توی ژوپیتر تعریف میکنیم این تابع رو استفاده کنیم.
خب اینجا هم خروجی رو بینید که برای اون تابع fit هست:
Filename: /home/amin/.local/lib/python3.6/site-packages/sklearn/linear_model/_base.py Line # Mem usage Increment Occurences Line Contents ============================================================ 481 99.5 MiB 99.5 MiB 1 def fit(self, X, y, sample_weight=None): 482 """ 483 Fit linear model. 484 485 Parameters 486 ---------- 487 X : {array-like, sparse matrix} of shape (n_samples, n_features) 488 Training data 489 490 y : array-like of shape (n_samples,) or (n_samples, n_targets) 491 Target values. Will be cast to X's dtype if necessary 492 493 sample_weight : array-like of shape (n_samples,), default=None 494 Individual weights for each sample 495 496 .. versionadded:: 0.17 497 parameter *sample_weight* support to LinearRegression. 498 499 Returns 500 ------- 501 self : returns an instance of self. 502 """ 503 504 99.5 MiB 0.0 MiB 1 n_jobs_ = self.n_jobs 505 99.5 MiB 0.0 MiB 1 X, y = self._validate_data(X, y, accept_sparse=['csr', 'csc', 'coo'], 506 99.5 MiB 0.0 MiB 1 y_numeric=True, multi_output=True) 507 508 99.5 MiB 0.0 MiB 1 if sample_weight is not None: 509 sample_weight = _check_sample_weight(sample_weight, X, 510 dtype=X.dtype) 511 512 99.5 MiB 0.0 MiB 1 X, y, X_offset, y_offset, X_scale = self._preprocess_data( 513 99.5 MiB 0.0 MiB 1 X, y, fit_intercept=self.fit_intercept, normalize=self.normalize, 514 99.5 MiB 0.0 MiB 1 copy=self.copy_X, sample_weight=sample_weight, 515 99.5 MiB 0.0 MiB 1 return_mean=True) 516 517 99.5 MiB 0.0 MiB 1 if sample_weight is not None: 518 # Sample weight can be implemented via a simple rescaling. 519 X, y = _rescale_data(X, y, sample_weight) 520 521 99.5 MiB 0.0 MiB 1 if sp.issparse(X): 522 X_offset_scale = X_offset / X_scale 523 524 def matvec(b): 525 return X.dot(b) - b.dot(X_offset_scale) 526 527 def rmatvec(b): 528 return X.T.dot(b) - X_offset_scale * np.sum(b) 529 530 X_centered = sparse.linalg.LinearOperator(shape=X.shape, 531 matvec=matvec, 532 rmatvec=rmatvec) 533 534 if y.ndim < 2: 535 out = sparse_lsqr(X_centered, y) 536 self.coef_ = out[0] 537 self._residues = out[3] 538 else: 539 # sparse_lstsq cannot handle y with shape (M, K) 540 outs = Parallel(n_jobs=n_jobs_)( 541 delayed(sparse_lsqr)(X_centered, y[:, j].ravel()) 542 for j in range(y.shape[1])) 543 self.coef_ = np.vstack([out[0] for out in outs]) 544 self._residues = np.vstack([out[3] for out in outs]) 545 else: 546 self.coef_, self._residues, self.rank_, self.singular_ = \ 547 99.5 MiB 0.0 MiB 1 linalg.lstsq(X, y) 548 99.5 MiB 0.0 MiB 1 self.coef_ = self.coef_.T 549 550 99.5 MiB 0.0 MiB 1 if y.ndim == 1: 551 99.5 MiB 0.0 MiB 1 self.coef_ = np.ravel(self.coef_) 552 99.5 MiB 0.0 MiB 1 self._set_intercept(X_offset, y_offset, X_scale) 553 99.5 MiB 0.0 MiB 1 return self
دیدگاهتان را بنویسید