رفتن به نوشته‌ها

آموزش تنسورفلو

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

خوشبختانه گوگل در ۲۰۱۷ یک قابل جدید به نام اجرای حریصانه (eager execution) رو به تنسورفلو اضافه کرد و باعث شد خیلی از سختی‌های که وجود داشت کم بشه! و در حال حاضر به نظرم راحت‌ترین راه استفاده از تنسورفلو استفاده از حالت اجرای حریصانه (eager execution) است. از تنسورفلو نسخه ۲، که احتمالا تا چند وقت دیگه ارائه می‌شه به صورت پیش‌فرض روی حالت حریصانه خواهد بود. و به نظرم افراد و شرکت‌هایی که از تنسورفلو  بخاطر مشکلاتش استفاده نمی‌کردند در طی یکی دو سال آینده به آن روی خواهند آورد!

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

از اونجایی که فک می‌کنم اجرای حریصانه (eager execution) روش قالب تنسورفلو خواهد بود در این آموزش تمام مثال‌ها با کمک eager execution توضیح داده خواهد شد.

۱) آموزش نصب تنسورفلو

بهترین (و البته به روزترین) راه برای نصب تنسورفلو مراجعه به سایت رسمی تنسورفلو است: https://www.tensorflow.org/install/ . تنسورفلو از زبان‌های مختلفی مثل پایتون، جاوا، گو (Go) و … پشتیبانی می‌کند و روی سیستم‌های عامل‌های مختلف مثل ویندوز، لینوکس و مک قابل اجراست (روی نسخه مک از جی‌پی‌یو پیشتیبانی نمی‌کند). که در این آموزش ما با پایتون کار خواهیم کرد.

۱-۱) نصب سریع (بدون جی‌پی‌یو)

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

الف) پایتون رو دانلود و نصب کنید (ترجیحا پایتون ۳.۶ به بعد رو نصب کنید). برای نصب پایتون می‌تونید به وبسایت https://www.python.org/ مراجعه کنید و متناسب با سیستم عامل‌تون نسخه مورد نظر رو نصب کنید.

ب) پای‌چارم (pycharm) رو دانلود و نصب کنید. راستش نصب پای‌چارم یا هر IDE دیگری ضرروی نیست ولی خیلی کد زدن رو برای شما خیلی راحت میکنه! برای نصب پای‌چارم می‌تونید به سایت jetbrain.com برید و اگر میخواهید نسخه فول ادیشن رو داشته باشید (و دانشجو هستید) می‌تونید به https://www.jetbrains.com/student/ برید تا به طور رایگان به شما لایسنس استفاده دهد.

ج) محیط مجازی (virtual environment) رو نصب کنید. محیط مجازی اصن چه بدرد میخوره؟ فرض کنید در یکی از پروژه‌هاتون از تنسورفلو نسخه ۰.۱۰ استفاده کردید و در یکی دیگه از تنسورفلو نسخه ۱.۱۰. مساله‌ای که خیلی متداول است اینه که اگر تنسورفلو خودتون رو به روزرسانی کنید خیلی از کدهایی که قبلا زدید اجرا نمیشن و اصلاحا کدهایی که زدید backward compatible نیستند. از طرف دیگه اگر نسخه اتون رو کاهش بدید به ۱.۱۰ تبدیل کنید خیلی از ویژگی‌های جدید رو از دست می‌دهید. برای رفع این مشکل در پایتون معمولا از محیط مجازی یا virtual environment استفاده می‌کنند. به صورت پیش‌فرض محیط مجازی با پایتون نصب نمیشه و بخاطر همین مجبوریم که خودمون نصب کنیم.

برای انتخاب محیط مجازی دوتا انتخاب دارید یا از اناکوندا (anaconda) استفاده کنید یا مستقیم خودتون اون رو نصب کنید. پکیج کامل anaconda شامل پکیج‌های مختلف لازم برای دیتاساینس هست که معمولا بیشتر از نیاز شما هست (البته نسخه miniconda هم وجود دارد که سبک‌تر است). ولی من اغلب ترجیح می‌دم که پکیج‌های خودم لازم دارم خودم نصب کنم. بخاطر همین از virtualenv استفاده می‌کنم.

کامند (command prompt) یا ترمینال (terminal) رو بازکنید و یکی (فقط یکی!) از دستورات زیر را اجرا کنید (در صورتی که در سیستم‌های یونیکس sudo هم اولش بزارید).

د) پای‌چارم (pycharm) رو اجرا کنید بعد از کلیک روی گزینه create new projcet و یک پروژه بسازید. همچنین دقت کنید که هنگام ساخت پروژه محیط مجازی خودتون هم ایجاد کنید. برای اینکار روی project interpreter کلیک کنید نوع پایتون رو مشخص کنید و اسم پروژه اتون هم هرچی که دوست داشتید قرار بدید و در نهایت هم روی گزینه create کلیک کنید.

در شکل بالا من از virtualenv استفاده کردم. در صورتی که شما از anaconda نصب کردید مرحله بعد رو لازم نیست انجام بدید و میتونید یک فایل پایتون ایجاد کنید و شروع کنید به کد زدن!

ه ) ولی در صورتی که virtualenv استفاده کردید باید تنسورفلو رو نصب کنید. نصب تنسورفلو خیلی راحت است و کافی است در داخل محیط pycharm ترمینال رو باز کنید (alt+f12) رو بزنید و سپس در داخل اون دستور sudo pip install tensorflow رو اجرا کنید:

همانظور که نگاه می‌کنید اول ادرس شما (venv) قرار داده است که به این معناست که هر پکیجی که نصب کنید در داخل venv قرار خواهد گرفت. در نتیجه وقتی دستور sudo pip install tensorflow را اجرا می‌کنید اخرین ورژن تنسورفلو در داخل این پکیج برای شما ساخته می‌شود و با مابقی پروژه‌های شما هیچ کاری نخواهد داشت.

به همین راحتی!!! حالا شما می‌تونید یک فایل پایتون (.py) ایجاد کنید و شروع کنید به کد زدن!

۲-۲ نصب کامل (جی‌پی‌یو)

این قسمت بیشتر برای کسانی است که میخوان با جی‌پی‌یو کد بزنند.

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

الف) بررسی کنید که آیا جی‌پی‌یو سیستم شما تنسورفلو رو ساپورت می‌کنه یا خیر؟ به عبارت دقیق‌تر باید بررسی کنید که کارت گرافیک شما از کودا (CUDA) پشتیبانی می‌کند یا خیر. می‌تونید این مورد رو در سایت رسمی انویدیا https://developer.nvidia.com/cuda-gpus بررسی کنید و نسخه کارت گرافیک شما نباید کمتر از ۳ باشه!

ب) به ترتیب آخرین نسخه درایورهای کارت گرافیک انویدیا سپس CUDA toolkit (هنگام نصب از built in driver استفاده نکنید چون ممکنه قدیمی باشد) و مرحله آخر هم CuDNN رو نصب کنید.

نکته ۱:‌ترتیب بالا رو رعایت کنید! یعنی اول درایورهاتون رو نصب (آپدیت) کنید بعد CUDA نصب کنید (موقع نصب built in درایور رو انتخاب نکنید چون ممکنه است از درایورهای قدیمی استفاده کند). و بعد CuDNN را نصب کنید.

نکته ۲: CUDA Toolkit و CuDNN باید با نیازمندی‌های تنسورفلو همخوانی داشته باشند (اگر اخرین نسخه رو نصب کرده باشید همخوانی دارند).

نکته ۳: باید نسخه CuDNN که دانلود کردید را در جایی که CuDNN نصب کردید کپی کنید تا نصب شما کامل شود.

ج) قسمت‌های ب، ج، د و ه نصب ساده را انجام بدهید! فقط حواستنون باشه موقع نصب تنسورفلو با دستور pip نسخه جی‌پی‌یو رو نصب کنید:

۲) به روزرسانی تنسورفلو

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

نکته: به روزرسانی تنسورفلو یکم خطرناکه 😀 و ممکن است کدهایی که قبلا زدید دیگه کار نکن یا به مشکل بخورید! کلا پکیج‌های یادگیری عمیق چون خیلی زود به روزرسانی میشن backward compatibility خوبی ندارند و به این موضوع دقت کنید.

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

۳) سلام دنیا در تنسورفلو

امیدوارم که تونسته باشید با موفقیت تنسورفلو را نصب/بروزرسانی کنید.

یک پروژه در پای‌چارم (pycharm) درست کنید و یک فایل پایتون (.py) ایجاد کنید و اولین برنامه خودتون رو در اون بنویسید.

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

همانطور که در بالا مشاهده می‌کنید خروجی شما tf.Tensor هست که از سه قسمت تشکیل شده است: ۱) مقدار ۲) اندازه (shape) و ۳) نوع داده (dtype). بعد از این احتمالا خواهید دید که در تنسورفلو همه چیز Tensor است!

۴) مثال مقدماتی در تنسورفلو

در این بخش به توضیح مفاهیم اولیه در تنسورفلو می‌پردازیم ولی قبل از آن بهتر است موارد زیر را مرور کرده باشید (مرور کنید! لازم نیست با جزییات زیاد همه موارد رو بلد باشید):

خوب اگر موارد بالا بلد هستید بیایید یک جمع خیلی ساده رو با تنسورفلو انجام بدیم. کاری که میخواهیم بکنیم این است که دو تا ماتریس ۲*۲ را باهم جمع کنیم:

کد بالا ما ۲ تا تنسور (Tensor) به نام‌های a و b تعریف کردیم و به ترتیب عملگرهای جمع و ضرب ماتریسی را روی آنها انجام دادیم. نتایج عملگرها در تنسورهای d و c ذخیره وسپس نمایش داده شدند. خیلی راحت بود نه؟

بیایید یک مثال بهتر رو بررسی کنیم.

اگر مطالب مربوط به یادگیری ماشین (به خصوص دیپ لرنینگ) رو مطالعه کرده باشید احتمالا خیلی اصطلاح دیفرانسیل پذیری (differentiate) رو دیدید و شنیدید. یکی از توانایی‌هایی که تنسورفلو و سایر فریمورک‌های یادگیری عمیق (مثل پای‌تورچ و تیانو و…) به ما می‌دهند مکانیزم دیفرانسیل پذیری خودکار (Automatic Differentiation Mechanism) است. یعنی چی؟ یعنی امکان محاسبه گرادیان را به ما می‌دهند. یعنی چی؟ بیایید یک مثال بررسی کنیم.

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

اگر مشتق این تابع را یک بار نسبت به x بگیریم و یک بار نسبت به y و اونوقت این دوتارو بهم در یک ماتریس (بردار) نمایش بدهیم به آن گرادیان میگن:

خب حالا در یک مثال دیگه فرض کنید می‌خواهیم شیب خط \(y=x^2\) را در نقطه ۳ به دست بیاریم. فرض کنید این کارو بخواهید خودتون انجام بدید. قاعدتا می‌گید مشتق \(y\) میشه \(2x\). که اگر ۳ را در آن قرار بدهیم میشه ۶. حالا فرض کنید تابع \(y\) ما پیچیده‌تر باشه یا اصن چند متغیره باشه! اینکه بخواهیم دستی مشتق تابع \(y\) رو حساب کنیم کار زمان‌گیری است و از طرفی وقتی چندتا مشتق داشته باشیم تو کدمون باید کلی حساب و کتاب انجام بدیم! تنسورفلو این کار رو به صورت اتوماتیک برای ما انجام میدهد. مثال \(y=x^2\) رو بیایید در تنسورفلو پیاده‌سازی کنیم:

اگر قطعه کد بالا رو اجرا کنید می‌بینید که تابع tape.gradient گرادیان تابع \(y\) رو نسبت به \(x\) برای ما حساب میکنه و مقدار آن را برای ما چاپ می‌کنه!

اگر به آرگومان‌های تابع tape.gradient دقت کنید می‌بینید که ۳ تا ورودی میگیرد که اولی (target) تابعی است که قرار است از آن گرادیان گرفته شود و دومی (sources) متغیر یا متغیرهایی که نسبت به آن میخواهیم گرادیان را حساب کنیم. زیبا نیست…؟

در کد بالا چندتا نکته وجود دارد که شاید بد نباشه به آنها بپردازیم:

اول از همه اینکه چرا از tf.get_variable استفاده کردیم؟ get_variable یک متغیر (tf.Variable) به ما برمیگرداند. در صورتی که اولین بار باشه این تابع رو با توجه به اسمش (در مثال من x) صدا میزنیم یک متغیر (tf.Variable) جدید ایجاد می‌کند و به ما برمی‌گرداند و در صورتی که قبلا ساخته شده باشد همان مقدار قبلی رو به شما برمی‌گرداند (دقیقا مثل تابع getter در برنامه‌نویسی است).

چرا از tf.constant استفاده نکردیم؟ یک تفاوت اصلی بین tf.constant و tf.Variable وجود دارد و اونم این است که tf.Variable قابل تغییر هستند ولی مقدار tf.constant ها خیر. یعنی شما در tf.Variableها چیزهایی رو میریزید که توسط شبکه عصبی (الگوریتم یادگیری ‌تون) قابل تغییر هستند ولی در tf.constant معمولا عباراتی رو قرار می‌دهیم که قرار نیست تغیر چندانی داشته باشند. بعدا تفاوت این دوتا رو دقیق‌تر خواهیم فهمید.

تنسورفلو چطوری عبارت بالا را ارزیابی می‌کند؟ توضیح دقیق اینکه چطوری تنسورفلو عبارت بالا رو محاسبه می‌کنه برمیگرده به گراف محاسباتی یا computational graph که توضیحش مفصل است. ولی به طور خیلی ساده میشه اینجوری گفت که تنسورفلو بر اساس متغیرهایی که شما تعریف کردید یک گراف میسازه (متغیرهای شما میشه گره‌های (node) گراف). هنگامی که یک متغیر رو خواستید مقدارشو ارزیابی کنید این گراف پیمایش میشه و مقدار آن محاسبه میشه. مثلا در گراف زیر فرض کنید مقدار e رو میخواهیم و دوتا متغیر a و b رو تعریف کردیم:

از پایین به بالا مقادیر حساب میشه تا در نهایت به e برسیم و مقدار آن را ارزیابی کنیم. حالا مساله اصلی اینجاست که چطوری گرادیان e رو نسبت به a و b بگیریم؟ میبنیم که گره e به چه متغیرهایی وابسته است؟ c و d پس یکبار مشتق آن را نسبت به c و یکبار نسبت به d میگیریم و همینجوری میاییم. خب حالا متغیرهای c و d به چه متغیرهایی وابسته هستند a و b. به همین ترتیب کل گراف محاسباتی را پیمایش می‌کنیم.

پس در نتیجه دیدیم که تنسورفلو چطوری محاسبات و گرادیان رو انجام می‌دهد. همانطور که میبینید که داده‌ها بین یال‌های گراف جریان (flow) دارند و هر گره مقداری که محاسبه کرده رو در یک متغیر به نام تنسور (Tensor) نگهداری می‌کنند و بخاطر همین به این پکیج میگن Tensor-Flow.

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

اگر به درستی محاسبه کنید چیزی شبیه زیر خواهیم داشت:

خوب بیایید یک مثال ماشین لرنینگ رو باهم بررسی کنیم. فرض کنید برای تابع خطای MSE با  پارامترهای w و b با مقدارهای [1,2]=w و b=1 میخواهیم گرادیان را حساب کنیم:

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

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

خب تا اینجا بعضی مفاهیم رو یاد گرفتیم. حالا بیایید یک مثال دنیای واقعی رو با رگرسیون خطی (linear regression) در تنسورفلو انجام بدیم.

۵) رگرسیون خطی در تنسورفلو

اگر کورس‌های یادگیری ماشین رو گذارنده باشید احتمالا اولین مثالی که به شما خواهند گفت بحث رگرسیون خطی (linear regression) است. ایده رگرسیون خطی خیلی ساده است یک سری داده به شما می‌دهند و قرار است شما معادله خطی رو پیدا کنید (\(y=aX+b\)) که به بهترین نحوه ممکن روی این داده‌ها بخوره (یا اصطلاحا fit بشه).

فرض کنید ما یک سری داده مربوط به قیمت خانه‌ها در شهر تهران در سال‌های مختلف داریم:

فرض کنید قیمت خونه‌ها به دلاره! و یه نفر از شما خواسته که قیمت خونه در سال ۲۰۱۸ در شهر تهران را حدس بزنید. اگر نمودار زیر نمودار قیمت خونه‌ها باشه (خط آبی رنگ). قرار است شما خطی (\(y=aX+b\)) رو پیدا کنید (قرمز رنگ) که نزدیک‌ترین پیش‌بینی به قیمت خونه در چند سال اخیر باشه:

خوب بیایید ابتدا این کار رو با استفاده از Numpy انجام بدیم.

کاری که قرار است انجام بدیم که این است که \(X\) , \(y\) رو به ما دادن و قرار است پارامترهای a و b رو حدس بزنیم (در معادله خط \(y=aX+b\)). برای پیدا کردن بهترین مقادیر a و b راه‌های مختلفی وجود دارد. یکی از راه‌های موجود استفاده از الگوریتم گرادیان نزولی (gradient decent) است. این الگوریتم در یافتن مقدار بهینه یا اصطلاحا local minima به ما کمک می‌کند. کاری که در این الگوریتم انجام می‌دهیم به ترتیب زیر است:

۱) پارامترهای موجود (a, b) را مقداردهی (initilization) اولیه می‌کنیم.

۲) تا وقتی که الگوریتم همگرا شود (به یک درصدی از خطا برسیم یا یک حد آستانه را رد کنیم) مراحل زیر را تکرار می‌کنیم:

۲-۱) گرادیان تابع y را نسبت به پارامترهای آن (a, b) پیدا می‌کنیم.

۲-۲) پارامترها موجود (a,b) را بر اساس نرخ یادگیری (learning_rate) و گرادیان آنها به به روزرسانی می‌کنیم.

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

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

همانطور که مشاهده می‌کنید در اینجا ما خودمان گرادیان رو حساب کردیم که در مثال بالا نسبتا راحت‌ بود ولی در مثال‌های پیچیده‌تر خیلی دشوار خواهد شد. از طرف دیگر ما برای به‌روزرسانی پارامترها از گرادیان نزولی استفاده کردیم که نسبت به روش‌هایی مثل Adam و Adagrad خیلی راحت‌تر است و در صورتی که بخواهیم بهینه‌سازی مثل Adam استفاده کنیم در Numpy با دردسرهای زیادی مواجه می‌شیم. خلاصه اینجوری هست که فریمورک‌های دیپ لرنینگ ظاهر شدند.

بیایید همین مثال رو در تنسورفلو مشاهده کنیم:

متغیرهایی که ثابت هستند مثل قیمت و سال (\(X\),\(y\)) را در tf.constant ذخیره کردیم. متغیرهایی که باید توسط شبکه حدس زده‌ شوند در متغیرها (tf.Variable) ذخیره کردیم (قبلا اشاره شد که tf.get_variable چیه). برای بهینه‌سازی (optimization) از الگوریتم گرادیان نزولی که در تنسورفلو پیاده‌سازی شده است کمک گرفتیم. کار بهینه‌ساز (optimizer) این است که مقدار متغیرهارو با توجه به گرادیان آنها و نرخ یادگیری (learning rate) برای ما به روزرسانی کند که این کار رو توسط تابع apply_gradients انجام میدهیم. این تابع گرادیان‌ها و متغییرها را می‌گیره و به روزرسانی را انجام می‌دهد (به طور دقیق‌تر یک ارایه [(grad_b,b),(grad_a,a)] به عنوان ورودی دریافت می‌کند و متناسب با آنها پارامترها را به روزرسانی می‌کند.

tf.GradientTape چیکار می‌کنه این وسط؟ عملیاتی (operation) که در داخل حوزه tf.GradientTape انجام میشه رکورد میشه. یعنی شما هرچی تو حوزه (context) این tf.GradientTape انجام بدید به عنوان متغیرهایی که باید تنسورفلو حواسش بهشون باشه در نظر گرفته میشن. مثلا شما cost رو نوشتید میره ببینه چه متغیرهایی به این cost وابسته هستند همه رو رکورد میکنه. این رکوردها اصطلاحا tape بهشون گفته میشه و برای اینکه تنسورفلو، دیفرانسیل اشو حساب کنه ازشون استفاده می‌کنه (Reverse mode differentiation). وقتی هم tape.gradient رو صدا میزنید در واقع می‌گید برو گرادیان تابع رو نسبت به متغیرهایی که بهش پاس دادید حساب کن.

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

منتشر شده در آموزشتنسورفلویادگیری عمیق

یک دیدگاه

  1. mahsa mahsa

    سلام
    در مورد لودکردن فولدرهای ترین وتست تصویر با روش tf امکانش هست راهنمایی کنید؟

دیدگاهتان را بنویسید

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