جشنواره نوروزی سون لرن

آموزش TENSORFLOW - قسمت اول

دسته بندی: یادگیری ماشین
زمان مطالعه: 13 دقیقه
۰۱ فروردین ۱۳۹۹

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

  • Numpy
  • Scipy
  • Scikit-learn
  • Theano
  • TensorFlow
  • Keras
  • PyTorch
  • Pandas
  • Matplotlib

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

فهرست محتوای این مقاله

تنسورفلو چیست؟

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

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

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

تنسور

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

نمای مفهومی ابعاد تنسور

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

اعداد اسکالر یک تنسور با بُعد صفر، بردار تنسوری با بُعد یک، ماتریس‌ها بُعد دو و ... . برای مثال در تنسور سه بُعدی با آدرس m*n*p می‌توان به ماتریس mاُم و سطر n، ستون pاُم مراجعه کرد. برای درک بهتر، بیایید یک تمرین عملی داشته باشیم: (این تمرین توسط کتابخانه‌ی numpy و صرفاً برای توضیح مفهوم تنسور آورده بیان شده‌است.) فرض کنید که می‌خواهیم میانگین نمرات یک دانش‌آموز را در یک درس ذخیره کنیم. برای این‌کار از یک تنسور صفر بُعدی برای ذخیره‌ی این مقدار اسکالر استفاده می‌کنیم. (در کد‌های زیر تابع ndim تعداد ابعاد را بر‌می‌گرداند):

import numpy as np
tensor_0D = np.array(5)
print("Average grade: \n{}".format(tensor_0D))
print("Tensor dimensions: \n{}".format(tensor_0D.ndim))
Average grade: 5 Tensor dimensions: 0

حال فرض کنید می‌خواهیم نمرات سه درس یک دانشجو را ذخیره کنیم. از ساختار آرایه ای استفاده می‌کنیم که یک تنسور یک بُعدی است:

tensor_1D = np.array([4,6,8])
print("Subject grades: \n{}".format(tensor_1D))
print("Tensor dimensions: \n{}".format(tensor_0D.ndim))
Subject grades: [4 6 8] Tensor dimensions: 1

اگر بخواهیم نمرات سه درس یک دانشجو را که در هر درس سه مرتبه امتحان داده ذخیره کنیم، چه ساختار داده ای داریم؟ یک ماتریس یا تنسور سه بُعدی.

# 2D Tensor (matrix)
tensor_2D = np.array([[0, 1, 1],  # Subject 1
                      [2, 3, 3],  # Subject 2
                      [1, 3, 2]])  # Subject 3
print("Exam grades are:\n{}".format(tensor_2D))
print("Subject 1:\n{}".format(tensor_2D[0]))
print("Subject 2:\n{}".format(tensor_2D[1]))
print("Subject 3:\n{}".format(tensor_2D[2]))
print("Tensor dimensions: \n{}".format(tensor_2D.ndim))
Exam grades are: [[0 1 1] [2 3 3] [1 3 2]] Subject 1: [0 1 1] Subject 2: [2 3 3] Subject 3: [1 3 2] Tensor dimensions: 2

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

tensor_3D = np.array([[[0, 1, 1],  # First quarter
                      [2, 3, 3],
                      [1, 3, 2]],
                     [[1, 3, 2],  # Second quarter
                      [2, 4, 2],
                      [0, 1, 1]]])
print("Exam grades per quarter are:\n{}".format(tensor_3D))
print("First quarter:\n{}".format(tensor_3D[0]))
print("Second quarter:\n{}".format(tensor_3D[1]))
print("Tensor dimensions: \n{}".format(tensor_3D.ndim)

Exam grades per quarter are: [[[0 1 1] [2 3 3] [1 3 2]]

[[1 3 2] [2 4 2] [0 1 1]]] First quarter: [[0 1 1] [2 3 3] [1 3 2]] Second quarter: [[1 3 2] [2 4 2] [0 1 1]] Tensor dimensions: 3

اگر داده‌های مسأله‌ی قبل را برای سه دانش‌آموز متفاوت بخواهیم بخواهیم داشته باشیم:

tensor_4D = np.array([[[[0, 1, 1], # Jacob
                      [2, 3, 3],
                      [1, 3, 2]],
                     [[1, 3, 2],
                      [2, 4, 2],
                      [0, 1, 1]]],
                      [[[0, 3, 1], # Christian
                      [2, 4, 1],
                      [1, 3, 2]],
                     [[1, 1, 1],
                      [2, 3, 4],
                      [1, 3, 2]]],
                     [[[2, 2, 4], # Sofia
                      [2, 1, 3],
                      [0, 4, 2]],
                     [[2, 4, 1],
                      [2, 3, 0],
                      [1, 3, 3]]]])
print("The grades of each student are:\n{}".format(tensor_4D))
print("Jacob's grades:\n{}".format(tensor_4D[0]))
print("Christian's grades:\n{}".format(tensor_4D[1]))
print("Sofia's grades:\n{}".format(tensor_4D[2]))
print("Tensor dimensions: \n{}".format(tensor_4D.ndim))

The grades of each student are: [[[[0 1 1] [2 3 3] [1 3 2]]

[[1 3 2] [2 4 2] [0 1 1]]]

[[[0 3 1] [2 4 1] [1 3 2]]

[[1 1 1] [2 3 4] [1 3 2]]]

[[[2 2 4] [2 1 3] [0 4 2]]

[[2 4 1] [2 3 0] [1 3 3]]]] Jacob's grades: [[[0 1 1] [2 3 3] [1 3 2]]

[[1 3 2] [2 4 2] [0 1 1]]] Christian's grades: [[[0 3 1] [2 4 1] [1 3 2]]

[[1 1 1] [2 3 4] [1 3 2]]] Sofia's grades: [[[2 2 4] [2 1 3] [0 4 2]]

[[2 4 1] [2 3 0] [1 3 3]]] Tensor dimensions: 4

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

  • سری‌های زمانی: تنسور سه بعدی
  • عکس: تنسور چهار بعدی
  • ویدیو: تنسور پنج بعدی

برای درک بهتر، فرض کنید 64 عکس رنگی دارید که هر عکس 224*224 پیکسل است. چون عکس، رنگی است هر پیکسل سه مقدار داده (RGB) دارد و در کل 64*3*224*224 داده داریم و یک تنسور چهار بُعدی.

گراف‌ محاسباتی:

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

شکل شماره‌ی 2: یک گراف محاسباتی

یک گراف محاسباتی دارای ویژگی‌های زیر است:

  • رئوس برگ ( گره‌هایی که هیچ فرزندی ندارند، به‌عبارتی یال ورودی ندارند) همیشه تنسور هستند. بدین معنی که یک عملیات هرگز نمی‌تواند در ابتدای نمودار گراف رخ دهد، در نتیجه هر عملیاتی در گراف باید تنسور یا تنسورهایی را به عنوان ورودی بپذیرد و تنسور(هایی) را به عنوان خروجی تولید کند.
  • گراف محاسباتی می‌تواند یک عملیات پیچیده را در یک ترتیب سلسله مراتبی نشان دهد. عبارت e=c*d را می‌توان با جای‌گذاری c=a+b و d=b+1 به‌فرم زیر نوشت:

(e=(a+b) * (b+1

  • پیمایش نمودار به ترتیب معکوس منجر به شکل‌گیری زیرعباراتی است که برای ساخت عبارت نهایی با هم ترکیب شده‌اند.
  • در پیمایش نمودار رو به جلو، هر گره وابسته به نتیجه‌ی گره‌های ماقبل خود است. یعنی e وابسته به c و d وابسته به b، c وابسته به a و b است.
  • عملیات در گره‌های هم سطح، مستقل از یکدیگر است. این یکی از ویژگی‌های مهم گراف محاسباتی است. وقتی یک گراف به فرم شکل دو می‌سازیم، طبیعی است که گره‌ها در همان سطح به عنوان مثال گره‌های c و d از یکدیگر مستقل باشند. این به این معنی است که بیش از ارزیابی d ، نیازی به دانستن مقدار گره c باشد. بنابراین می‌توان عملیات این گره‌های مستقل را به‌صورت موازی انجام داد و اجرا کرد.

اجرای موازی در گراف‌های محاسباتی:

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

اجرای توزیع‌شده یا غیرمتمرکز

تنسورفلو این امکان را به کاربران خود می‌دهد تا از ابزار محاسبات موازی برای اجرای سریع عملیات استفاده کنند. گره‌ها یا عملیات محاسباتی به طور خودکار برای اجرای محاسبات موازی تنظیم می‌شوند. همه‌ی این کارها به‌طور داخلی اتفاق می‌افتد، به‌طور مثال در گراف بالا عملیات c می‌تواند بر روی CPU  انجام شود و عملیات d بر روی GPU. شکل سه دو چشم‌انداز اجرای توزیع‌شده را نشان می‌دهد:

شکل شماره‌ی 3: مفهوم نمایی اجرای توزیع شده

قسمت چپ در تصویر، یک اجرا از سیستم توزیع شده‌ی منفرد است که در آن تنها یک نشست منفرد (session) (session یا نشست مفهومی در تنسورفلو است که در خلال آن به متغیرهای یک گراف محاسباتی فضای حافظه اختصاص داده شده و متغیرها در طول آن نشست، شناخته شده‌اند.) یک کارگر (worker) منفرد ایجاد می‌کند و کارگر مسئول زمان‌بندی وظایف (Task Scheduling) بر روی دستگاه‌های مختلف است.

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

زیرگراف‌های محاسباتی:

زیرگراف در اصل یک گراف محاسباتی است که خود بخشی از یک گراف اصلی است. به‌طور مثال در شکل چهار یک زیرگراف محاسباتی می‌بینید که بخشی از گراف اصلی است. می‌توان گفت یک زیرگراف بیانگر یک زیرعبارت است، یعنی c یک زیرعبارت از e است. همچنین مطابق ویژگی آخر بیان شده برای گراف‌های محاسباتی، زیر گراف‌های موجود در سطح یکسان از یکدیگر مستقل‌اند و می‌توانند به صورت موازی اجرا‌ شوند و این منجر به افزایش بهره‌وری و سرعت اجرا خواهدبود.

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

تبادل داده میان گره‌ها و کارگرها:

تنسورفلو عملیات خود را در دستگاه‌های مختلفی که توسط کارگران اداره می‌شود، توزیع می‌کند. داده‌ها میان دستگاه‌ها به فرم تنسور رد و بدل می‌شوند. به طور مثال در گراف بالا که e=c*d است، c پس از محاسبه‌ به فرم تنسور درآمده و به گره در سطح بالاتر منتقل می‌شود. البته در حین انتقال داده‌ها تأخیر رخ می‌دهد که این تأخیر به اندازه‌‌ی تنسور وابسته است.

لزوم فشرده‌سازی داده‌ها:

تا اینجا فهمیدیم که ساختار داده‌ی میان گره‌های گراف محاسباتی به صورت تنسور است. تنسور‌ها میان گره‌ها با تأخیر جابجا می‌شوند و مقدار این تأخیر می‌تواند به ابعاد و اندازه‌ی تنسور و همچنین ویژگی‌های دستگاه‌های اجرایی گره‌ها وابسته باشد. یکی از ایده‌های اصلی جهت فشرده‌سازی کاهش اندازه‌ی تنسور روش Lossy است. فشرده‌سازی Lossy اندازه و ابعاد داده را کاهش می‌دهد و به مقدار ابعاد توجه نمی‌کند. به این معنی که ممکن است درخلال فشرده‌سازی مقدار آن نادرست و یا غیر دقیق شود. البته در یک عدد اعشار 32 بیتی می‌دانیم که بیت‌های کم ارزش تأثیر چندانی ندارند. پس تغییر و یا حذف آن‌ها باعث تغییر چندانی در نتیجه‌ی نهایی نمی‌شود؛ اما این حذف باعث کاهش حجم داده‌ها به‌صورت مؤثر می‌شود.

جمع‌بندی:

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

  • تنسورفلو از ساختار داده‌ی تنسور برای انجام عملیات استفاده می‌کند.
  • در تنسورفلو، ابتدا عملیاتی که می‌بایست انجام شود، تعریف (ایجاد گراف محاسباتی) و سپس اجرا می‌شود. این عمل باعث می‌شود که فرآیند به‌صورت بهینه اجرا شود و زمان محاسبات به‌طور قابل ملاحظه‌ای کاهش یابد.
  • تنسورفلو کدها را قادر می‌سازد تا به‌صورت موازی روی یک یا تعداد بیشتری CPU یا GPU اجرا شود.

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

چه امتیازی به این مقاله می دید؟
نویسنده المیرا ناصح

نظرات کاربران

اولین دیدگاه این پست رو تو بنویس !

ارسال دیدگاه
خوشحال میشیم دیدگاه و یا تجربیات خودتون رو با ما در میون بذارید :