مجموعه کاملی از فوت و فن های کار با Jupyter Notebook - بخش 1/4

برای نوشتن کدهای پایتون محیط های متنوعی مثل Pycharm و VSCode وجود دارند که البته هر کدام از این ها هم کلی امکانات و قابلیت فوق العاده دارند. با این حال برای من که چندسالی هست که علاقه مند به Data science و Machin learning هستم این محیط ها خیلی جذاب نیستند و در عوض به خاطر  سادگی و قابلیت های منحصر به فرد،  من تقریبا فقط و فقط از Jupyter Notebook ها برای کدنویسی استفاده می کنم. حالا سوال پیش می آید که اصلا jupyter  (ژوپیتر) یعنی چی؟ جواب  این هست کهjupyter  از حرف های اول سه زبان برنامه نویسی پر کاربرد Julia، Python و R تشکیل شده است. چرا؟ چون که ژوپیتر در ابتدا و در اصل برای اجرا کردن کدهای این زبان های برنامه نویسی توسعه داده شده است. با این حال، در حال حاضر علاوه بر این سه زبان برنامه نویسی، ژوپیتر از زبان های مختلف دیگری هم (کمابیش) پشتیبانی می کند.

حالا اگر بخواهیم به صورت ساده jupyter notebook را تعریف کنیم می توانیم بگوییم که ژوپیتر نوت بوک یک برنامه (یا کتابخانه) است که با آن می توانیم به صورت تعاملی در مرورگرمان یک فایل حاوی کد، عکس و … بسازیم و آن را در مرورگر ویرایش و اجرا کنیم. البته قابلیت های ژوپیتر فراتر از اجرا کردن یک کد با یک زبان برنامه نویسی خاص مثل پایتون است و با آن میتوانیم مجموعه متنوعی از کار ها را انجام بدهیم.

ژوپیتر نوت بوک ها یکی از ابزارهای اصلی تقریبا همه متخصصین علم داده ها (Data Science) هستند و از دو جهت تاثیر گسترده ای بر حوزه علم داده ها داشته اند: اول از همه ژوپیتر نوت بوک ها فضایی برای تکرار و آزمایش فعالیت های مختلف علم داده ها را در اختیار ما قرار می دهند و همین پیاده سازی فرآیندهای تکرارشونده علم داده ها را برای ما راحت تر می کند. ثانیا دلیل دیگر تاثیرگذاری ژوپیتر نوت بوک این است که آن ها از زبان Markdown پشتیبانی می کنند؛ یعنی به عبارتی دیگر ما هم می توانیم در آن ها هم کد بنویسیم و هم می توانیم برای کدهایمان (فراتر از کامنت هایی که قبلا یا الان استفاده می کنیم) توضیحاتی مثل متن و شکل قرار دهیم. همین مساله باعث شده که به طور خاصی ژوپیتر نوت بوک ها در آموزش دادن مفاهیم علم داده ها، برنامه نویسی، پیاده سازی مقاله ها و … نقشی انقلابی داشته باشند.

خود یک ژوپیتر نوت بوک هم از دو مولفه اصلی تشکیل می شوند: کرنل (Kernel) و داشبورد (Dashboard). کرنل وظیفه این را دارد که کدی که ما نوشته ایم را اجرا کند. به طور مثال، ژوپیتر نوت بوک ها به صورت پیشفرض دارای یک کرنل پایتون هستند که کدهای پایتون را اجرا می کند. داشبورد هم به ما این امکان را می دهد که نوت بوک ها را ببینیم، ویرایش کنیم و حتی کرنل مورد استفاده را تغییر دهیم یا ببندیم.

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

نصب ژوپیتر

اگر از توزیع آناکوندا پایتون استفاده کنیم خبر خوش این است که کتابخانه ژوپیتر به صورت پیشفرض برای ما نصب شده است (به علاوه کلی کتابخانه دیگر که احتمالا به کار ما می آیند.) . اما در صورتی که از این توزیع استفاده نمی کنید مثل تقریبا بقیه کتابخانه های پایتون به سادگی و با دستور pip install jupyter می توانید آن را در محیط خط فرمان مثل شکل زیر نصب کنید.

راه اندازی و استفاده از ژوپیتر نوت بوک ها

برای بالا آوردن یک محیط ژوپیتر و استفاده از آن کافی است در محلی که می خواهیم فایل نوت بوک در آن ذخیره شود یک محیط خط فرمان را باز کنیم و دستور jupyter notebook را در آن اجرا کنیم. با اجرای این دستور یک وب سرور لوکال به صورت پیشفرض بر روی پورت ۸۸۸۸ ایجاد می شود که می توانیم آن را به صورت یک صفحه وب که البته به طور خودکار در مرورگر پیشفرض ما (مثلا گوگل کروم) بالا می آید هم ببینیم. اگر محیط ژوپیتر هم به صورت خودکار باز نشد می توانیم در مرورگر خودمان آدرسhttp://localhost:8888 را وارد کنیم.

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

jupyter notebook --no-browser

صفحه ای که به صورت خودکار برای ما باز می شود صفحه ای به شکل زیر است:

همانطور که می بینیم تب “Files” تمام فایل ها و فولدرهایی که در دایرکتوری که ژوپیتر نوت بوک را باز کرده ایم (دستور jupyter notebook را اجرا کرده ایم) نشان می دهد. تب Running هم به ما لیست تمامی نوت بوک های در حال اجرا و فعال را نشان می دهد. تب کلاستر هم برای کنترل موازی سازی به کار می رود و با آن معمولا کاری نداریم.

 برای ایجاد یک نوت بوک جدید در محیط ژوپیتر بر روی New کلیک می کنیم و بسته به نسخه پایتون نصب شده ( ۲ یا ۳ یا حتی زبان های برنامه نویسی دیگر که در ادامه به آن خواهیم رسید) نوت بوک جدیدی می سازیم ( من چون کرنل های مختلف ژوپیتر را قبلا نصب کرده ام در این جا گزینه های بیشتری می بینم ولی شما احتمالا فقط Python 3 یا Python 2 را خواهید دید.)

بعد از ساخت نوت بوک با صفحه ای مثل عکس زیر مواجه می شویم که طبیعتا همانطور که انتظار داریم یک نوت بوک است!

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

نوت بوک های ژوپیتر اساسا از بلاک هایی به نام سلول (Cells) تشکیل می شوند. ما در این سلول ها می توانیم کد، متن، عکس و انواع مختلفی از محتواها را قرار دهیم. برای ایجاد یک سلول جدید بر روی علامت به اضافه در تولبار بالای صفحه کلیک می کنیم. به علاوه می توانیم با استفاده از دستور alt+enter سلول جدید بعد از سلول فعلی اضافه کنیم.

 وقتی که یک سلول جدید ساختیم با انتخاب یکی از گزینه های موجود شکل زیر نوع سلول را مثلا یا به صورت Code برای اجرای کدها، Markdown برای قرار دادن انواع محتوا ها و Header برای قرار دادن عنوان اصلی در نوت بوک در بیاوریم (با Raw NBConvert فعلا کاری نداریم)

اجرای کد در سلول های ژوپیتر

حالا که یک سلول جدید ساختیم می توانیم در آن  هر کد پایتونی که می خواهیم را بنویسیم.  برای اجرا کردن آن کد هم می توانیم کلیدهای ctrl+enter را فشار دهیم یا از تولبار بالای نوت بوک بر روی گزینه Run کلیک کنیم. بعد از این کار ژوپیتر کد را اجرا می کند و در صورت وجود، خروجی را برای ما در همان نوت بوک چاپ می کند.

به همین صورت اگر وقتی که در یک سلول هستیم shift+enter را فشار دهیم علاوه بر این که کد داخل آن سلول اجرا می شود به صورت مستقیم هم به سلول بعد می رویم. اگر shift+enter را بزنیم و سلولی بعد از سلول فعلی نداشته باشیم به صورت خودکار یک سلول جدید برای ما ساخته می شود.

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

زمانی هم که اجرای یک سلول تمام شد، یک عدد درون براکت نشان داده می شود که به ما می گوید که این سلول بر اساس چه ترتیبی در این نوت بوک اجرا شده است.


زمانی که کد در حال اجرا است و به هر دلیلی می خواهیم اجرا کد را متوقف کنیم ( مثلا زمان اجرا بیش از حد طولانی شده است) دکمه های Ctrl + C را بر روی کیبرد فشار می دهیم. در این حالت خطای موسوم به KeyboardInterrupt به ما باز گردانده می شود.

چند میان بر خیلی کاربردی

یک ویژگی خوب دیگر ژوپیتر این است که می توانیم در آنمثل بسیاری از محیط های کدنویسی از قابلیت auto-completion کد استفاده کنیم تا هم در وقت خودمان صرفه جویی کنیم و هم اشتباهات تایپی کمتری داشته باشیم. برای استفاده از قابلیت auto-completion ژوپپتر مثل خیلی از محیط های ویرایش کد وقتی یک قسمت از کد را تایپ می کنیم با یک بار فشار دادن کلید Tab می توانیم ببینیم چه گزینه هایی در پیش رو داریم.

یک ویژگی مرتبط که شاید منحصر به فرد ژوپیتر نوت بوک باشد این است که ما می توانیم با یک بار فشار دادن دکمه های shift+Tab آرگومان ها ورودی های مورد نیاز یک تابع را ببینیم. این دستور خیلی به درد می خورد و به نظر من کاربردی ترین ویژگی ژوپیتر است . مثلا تقریبا همیشه من از این دستور برای این که ببنیم توابع چه آرگومان هایی دارند یا حتی کپی کردن آرگومان ها استفاده می کنم.

حالا با دو بار فشار دادن دکمه های shift+Tab می توانیم علاوه بر آرگومان ها ورودی های مورد نیاز یک تابع، مستندات آن تابع (docstring) را هم ببینیم.

اگر در یک سلول و قبل از یک تابع علامت ?را قرار دهیم و آن سلول را اجرا کنیم خروجی مشابهی با دو بار فشار دادن دکمه های shift+Tab به ما در یک صفحه pop-up نمایش داده می شود.

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

وقتی هم که یک سلول را انتخاب می کنیم با فشردن دکمه های که در جدول زیر می بینیم، رفتارهای مختلفی را می توانیم مشاهده کنیم (برای انتخاب سلول کافی است بر روی قسمت سمت چپ سلول کلیک کنیم):

چاپ خروجی ها در ژوپیتر نوت بوک

ژوپیتر به صورت پیشفرض تنها نتیجه آخرین خط از سلول را به عنوان خروجی آن سلول چاپ می کند ( البته اگر نتیجه آخرین خطر کد در یک متغیر ذخیره نشده باشد.). یعنی دیگر بدون استفاده از دستور printهم می توانیم خروجی ها را چاپ کنیم.

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

یک راه جالب تر برای چاپ همزمان چندین متغیر در یک سلول که نیازی به دستور printهم ندارد این است که تنظیمات موسوم به ast_note_interactivity ژوپیتر نوت بوک را تغییر دهیم تا هر کدام از متغیرهایی را که می خواهیم بدون این که در آخرین خط از سلول باشند، چاپ کنیم. برای تغییر این گزینه می توانیم در یک سلول (ترجیحا سلول اول نوت بوک) کد زیر را اجرا کنیم.

from IPython.core.interactiveshell import 
InteractiveShellInteractiveShell.ast_node_interactivity = "all"


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

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

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

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

مشکل روش بالا این است که برای زمانی که با حلقه for یا while کار می کنیم و به طور خاص زمانی که با این حلقه ها می خواهیم چندین نمودار رسم کنیم (مثل عکس زیر) این روش جواب نمی دهد.

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

اگر هم به صورت کلی بخواهیم خروجی سلول نمایش داده نشود ( مثلا وقتی که داریم یک کتابخانه را نصب می کنیم یا زمانی که اضافه کردن یک کتابخانه به ما یک هشدار ( warning) می دهد (مثلا این هشدار که تابع فلان در نسخه بعدی کتابخانه منسوخ خواهد شد) می توانیم کد capture%% را که یک دستور جادویی ژوپیتر است و در بخش های بعدی به صورت مفصل تری با آن ها آشنا می شویم به ابتدای آن سلول اضافه کنیم.

چاپ خروجی های زیباتر

به صورت پیشفرض ژوپیتر فارغ از این که از دستور ()print استفاده کنیم یا نکنیم همیشه یک خروجی یکنواخت و با فونت و ساختار مشابه به ما می دهد. با این حال ممکن است بعضی وقت ها بخواهیم بخشی از خروجی ما متفاوت باشد ( مثلا مقدار یک متغیری که چاپ می کنیم بولد شده باشد.) باز یکی دیگر از قابلیت های منحصر به فرد ژوپیتر نوت بوک ها این است که می توانیم نحوه نمایش خروجی سلول ها را هم با استفاده از کدهای HTML و Markdown شخصی سازی کنیم. به طور مثال با اجرای کد زیر، خروجی که ژوپیتر نوت بوک به ما نمایش به صورت سبز و بولد خواهد بود.

from IPython.display import Markdown,display 
Markdown('**<div style= "color:green"> Green and Bold</div>** ')
متاسفانه نوشتن این جور مطالب فنی و کددار در ویرگول کمی طاقت فرسا هست و معمولا خروجی اون چیزی نمی شود که انتظار دارم. مثلا برای نوشتن همین مطلب به خاطر این که یک لحظه اینترنت قطع شد تمامی گام های بعدی که انجام دادم با وجود وصل شدن اینترنت ذخیره نشدند و برای همین مجبور شدم که از اول دوباره یک سری گام ها را انجام بدهم و البته بی خیال برخی از مطالب مثل magic functions برای این پست بشوم و آن مطالب را به صورت چند بخش در آینده منتشر کنم (امیدوارم!). با این وجود، پست اصلی در بلاگ شخصی خودم کمی به روزتر و البته به خاطر محدودیت های کدنویسی در ویرگول خواناتر و شاید زیباتر هست.