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

آموزش تنسورفلو قسمت چهارم (ذخیره و بازیابی)

در قسمت‌های قبلی با برخی از مفاهیم تنسورفلو آشنا شدیم و دیدیم که چطوری می‌توان یک مدل شبکه عصبی را آموزش داد. در این قسمت به توضیح ذخیره‌سازی و بازیابی ‌مدل‌ها در تنسورفلو می‌پردازیم و می‌بینیم که چطور می‌تونیم یک مدل را ذخیره و بعدا از آن استفاده کنیم. اینکه بتونیم یک مدل را ذخیره کنیم و بعدها از آن استفاده کنیم، میتونه خیلی مفید باشه. مثلا فرض کنید آموزش شبکه عصبی‌تون ۱ هفته طول بکشه و این وسط برق بره! یا بخواهید مدل رو از دوباره اجرا کنید و این امکان رو به مابقی بدید که دقیقا همان نتایجی که شما گرفتید به دست بیارند و هزاران کار دیگه‌ای که ممکن است با مدل ذخیره شد‌ه‌اتون دوست داشته باشید انجام بدهید. در تمام مثال‌های بالا، لازم است که مدل‌اتون روی دیسک ذخیره (save) کنید و بعدا دوباره بازیابی‌اش (restore) کنید. همچنین در انتهای این آموزش، به Serving در تنسورفلو میپردازیم و میبینیم که چطوری می‌تونیم یک مدل ساده رگرسیون خطی را با استفاده از flask روی سرور قرار دهیم.

قبل از ادامه مطالب را بخوانید بد نیست با موارد زیر آشنا باشید:

اگر پیش‌زمینه پایتونی داشته باشید احتمالا اولین چیزی که برای ذخیره و بازیابی در پایتون به ذهنتون می‌آد استفاده از پیکل (pickle) است. پیکل برای سریالایز (serialize)  و دی‌سریالایز کردن objectهای پایتونی به کار می‌آد. ولی objectهایی که ما در تنسورفلو با آنها سروکار داریم serializable نیستند و به همین خاطر نمیتونیم از پیکل استفاده کنیم!

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

۱) استفاده از tf.train.Checkpoint
۲) استفاده از tf.train.Saver
۳) استفاده از tf.saved_model.simple_save که یک wrapper روی tf.train.Saver است.
۴) استفاده از ModelCheckpoint در کراس

۱) استفاده از tf.train.Checkpoint

tf.train.Checkpoint به مراتب از tf.train.Saver که در اغلب آموزش‌های قدیمی تنسورفلو مشاهده می‌کنید بهتر است. علت آن این است که از eager execution پشتیبانی می‌کند، ذخیره و بازیابی بر اساس object است و نه اسم متغیرها و به طور کلی میشه گفت tf.train.Checkpoint نسخه جدید‌تری از tf.train.Saver است. این روش در داکیومنت تنسورفلو به عنوان راه پیشنهادی معرفی شده است:

Prefer tf.train.Checkpoint over tf.train.Saver for new code.

اگر به سازنده tf.train.Checkpoint نگاهی بندازید می‌بینید که مجموعه‌‌ای از keyword args ها دریافت می‌کند. یعنی چی؟ یعنی هرچقدر که بخواهیم می‌تونیم keyword و arguman به آن پاس بدهیم. یعنی دوست داشته باشید می‌تونید ۱۰ تا پارامتر ورودی براش بفرستید یا ۳تا بفرستید یا اصن هیچی نفرستید.

سازنده کلاس Checkpoint در تنسورفلو
سازنده کلاس tf.train.Checkpoint در تنسورفلو

پارامترهای ورودی که برای tf.train.Checkpoint میفرستیم باید قابلیت checkpoint داشته باشند یا اصطلاحا مقادیر آنها checkpointable state داشته باشند. مثلا: tf.train.Optimizer، tf.Variable، tf.keras.Layers، tf.keras.Model و … چیزهایی هستند که ما معمولا به سازنده میفرستیم.  همچنین در صورتی که یک متغیر به متغیرهای دیگر وابسته باشد تمام متغیرهای وابسته هم ذخیره خواهند شد (مثلا اگر مدل‌اتون رو ذخیره کنید تمام متغیرها به همراه آن ذخیره خواهند شد). چیزهایی مثل عملگر (operation) ضرب یا جمع به سازنده Checkpoint بفرستید به شما خطا خواهد داد. سوال: اگر چیزی برای سازنده نفرستیم چه اتفاقی می‌افته؟

خب بیایید با یک مثال ساده tf.train.Checkpoint را در تنسورفلو بررسی کنیم. فرض کنید یک برنامه ساده‌ داریم و می‌خواهیم دوتا عدد را باهم جمع کنیم و این برنامه را میخواهیم ذخیره کنیم و بعدا مقدار اولیه اعداد را به دست بیاریم. برای جمع کردن دوتا عدد و ذخیره آنها چیزی که می‌تونیم بنویسیم میتونه شبیه زیر باشه:

همانطور که ملاحضه می‌کنید سازنده Checkpoint به هر تعدادی که بخواهید ورودی دریافت می‌کند (در مثال بالا ۲تا براش فرستادیم v1 و v2). اگر کد بالا رو اجرا کنید می‌بینید که یک فولدر به نام test برای شما ساخته می‌شود که توی اون ۲ تا فایل myVar با پسوندهای .data .index و یک فایل checkpoint ساخته شده است. راجع به اینکه هرکدام از این فایلها چی‌ هستند بعدا صحبت می‌کنیم.

خب حالا بیایید ببینیم چطوری می‌تونیم مقدارمون رو بازیابی کنیم. برای بازیابی کاری که باید بکنیم تقریبا مشابه بالاست. فقط تابع‌امون به جای save میشه restore! همچنین برای اینکه مطمئن باشیم بازیابی کردیم یا نه، مقدار متغیرهامون رو به ۱۰ و ۸۰ تغییر می‌دهیم (این چک کردن رو با کمک تابع assert_consumed هم میتوانیم انجام بدهیم):

اگر کد بالا رو اجر کنید می‌بینید که مقدار‌ قبلی مقدار v1 یعنی ۹ چاپ خواهد شد و مقدار فعلی‌ آن (۸۰) نادیده گرفته شده است که به این معناست که کارمون رو به درستی انجام دادیم.

در کد بالا، برای بازیابی از تابع restore استفاده کردیم. ورودی این تابع، آدرس یک فایل checkpoint را می‌گیرد و آن را بازیابی می‌کند. در مثالی که ما زدیم، آدرسش میتونه اینجوری باشه:

ckpnt.restore('./test/myVar-1')

ولی ما از تابعی به نام tf.train.latest_checkpoint استفاده کردیم و فقط اسم فولدر را به عنوان ورودی دادیم. احتمالا علتش هم حدس می‌زنید؟ اگر چندتا checkpoint مختلف داشته باشیم این تابع در فولدری که آدرسشو مشخص کردیم جستجو می‌کند و اونی که آخر از همه ساخته شده است را برای ما بازیابی می‌کند.

خب همانطور که در بالا اشاره کردیم، در هنگام فراخوانی تابع checkpoint.save سه نوع فایل برای ما ساخته خواهد شد (در واقع پسوندهای این فایل‌ها برای ما اهمیت دارند):

۱) فایل‌های .data: این فایل متغیرهای ما (weights & biases) را نگهداری می‌کند و معمولا حجم‌اش از دوتا فایل دیگر بیشتر است. معمولا ما چندین data فایل داریم که می‌تونه متعلق به بازه‌های زمانی (time stamps) مختلف باشد چون وزن‌های شبکه در طول زمان تغییر می‌کنند و ما معمولا بعد از مدتی آنها را ذخیره می‌کنیم. یا دلیل دیگری که چندین فایل با پسوند .data داشته باشیم این است که متعلق به shardهای دیگر باشند (منظور از shard این است که مدل رو به صورت توزیع شده (distributed) آموزش بدیم و هرکدام از فایل‌ها توسط یک گره (node) ایجاد شده باشند). اگر دقت کنید فرمت فایل‌ها به صورت زیر است:

<prefix>-<save_counter>.data-<shard_index>-of-<number_of_shards>

prefix اسمی است که برای ذخیره checkpoint انتخاب کردیم،save_counter تعداد checkpointهایی که تا بحال گرفتیم که با هر بار فراخوانی تابع save افزایش پیدا میکنه، shard_index اندیس اون سیستمی که روی آن این وزن‌ها بدست آمده است و number_of_shards تعداد گره‌هایی که روی هر سیستم وجود دارد را مشخص می‌کند. مثلا اگر کد زیر را اجرا کنید:

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

فایل‌های ساخته شده در تنسورفلو در هنگام فراخوانی تابع  checkpoint.save

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

۲) فایل‌های .index: همانطور که از اسمش مشخص است این فایل وظیفه index کردن وزن‌های تنسور‌ها را دارد. به عبارت دیگر این فایل یک key value است که میگه فلان متغیر وزن که در فایل .data ذخیره شده است متعلق به تنسور فلان است. وظیفه این فایل لینک کردن متغیرها به مقادیرشون است.

۳) فایل‌ checkpoint: این فایل مثل یک تقویم میمونه که میگه کدوم فایل‌ها در چه زمانی checkpoint گرفته شدند به طور دقیق‌تر ترتیب زمانی chekpointها در این فایل ذخیره می‌شوند.

یک نکته مهمی رو اگر دقت کرده باشید این است که ما در هنگام ذخیره و بازیابی، session را به توابع save و initialize_or_restore پاس ‌می‌دهیم. علت آن هم این است که مقدار واقعی وزن‌ها (weight) تنها در Session نگهداری می‌شود. تنها session است که میداند مقدار واقعی تنسورهای ما چیست! در واقع به هنگام فراخوانی save، مقادیر تنسورهای شما در session وجود دارد و آن را به تابع save پاس می‌دهیم تا روی فایل .data ذخیره شود. در هنگام بازیابی session شما هیچ اطلاعاتی راجع به اینکه چه مقادیری قبلا در تنسورهای شما وجود داشته است ندارند و با استفاده از تابع initialize_or_restore مقادیر قبلی به session فعلی انتقال داده می‌شود.

۲) استفاده از tf.train.Saver

همانطور که در بخش قبلی به آن اشاره شد، راه‌حل توصیه شده برای ذخیره و بازیابی مدل‌ها در تنسورفلو استفاده از tf.train.Checkpoint است ولی چون tf.train.Saver در اغلب‌ آموزش‌های قدیمی استفاده می‌شود و یه جورایی پدربزرگ tf.train.Checkpoint محسوب میشه بد نیست که با آن آشنا بشیم.

استفاده از tf.train.Saver تقریبا مشابه tf.train.Checkpoint است و با دو تابع save و restore سروکار داریم ولی قبل از اینکه به پیاده‌سازی بپردازیم، بیایید سازنده tf.train.Saver را باهم بررسی کنیم:

پارامترهای ورودی سازنده tf.train.Saver

همانطور که ملاحظه می‌کنید در اینجا برخلاف قبل تعداد زیادی آرگومان ورودی داریم. اولین و شاید مهمترین var_list است که مشخص می‌کنیم چه متغیرهای قرار است ذخیره شوند. مثل قبل می‌تونیم یک دیکشنری یا لیست پاس بدهیم و بگیم چه متغیرهایی را برای ما ذخیره کن و چه متغیرهای را ذخیره نکن. برخلاف قبل که ما می‌تونیستم بگیم Model و Layer و Optimizer را ذخیره کن در اینجا فقط متغیرهارو می‌تونیم ذخیره کنیم و در صورتی که چیزی برای var_list نفرستیم به صورت پیش‌فرض تمام متغیرهای آموزش‌دادنی (trainable) را ذخیره می‌کند. به قطعه کد زیر نگاه کنید:

در بالا انواع حالت‌هایی که از var_list استفاده کنیم آورده شده است (saver الی saver3). در Saver هم مانند قبل اگر عملگری (ضرب، جمع یا…) را برای ذخیره‌سازی ارسال کنیم اکسپشن خواهیم خورد.

آرگومان‌های دیگری در سازنده کلاس Saver وجود دارد که ما اغلب با آنها کاری نداریم. مثلا max_to_keep مشخص می‌کند که چه تعدادی checkpoint ذخیره کنیم (تعداد بیشتر به معنای این است که فضای دیسک بیشتری لازم خواهید داشت) یا مثلا sharded مشخص می‌کند که آیا شما چندین shard دارید یا خیر و آرگومان‌های دیگر که در اینجا می‌تونید توضیحات مربوط به آنها را بخوانید.

در صورتی که کد بالا را اجرا کنید، متوجه یک چیز متفاوت با tf.train.Checkpoint خواهید شد و آن هم اضافه شدن یک فایل جدید با پسوند .meta در کنار فایل‌های خروجی شما است. meta شامل اطلاعات سریالایز شده MetaGraphDefProtoBuf است! چی هست این؟ در این فایل اطلاعات مربوط به تعریف گراف (اینکه داده‌ها چطوری در گراف جریان دارند) و اطلاعات اضافی (metadata) که گراف‌مون برای اجرا نیاز دارد (مثلا نرخ یادگیری، عملگرها، annotationها و…) ذخیره شده است.

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

خب حالا یه سوالی! فرض کنید ما این فایل meta رو نداشته باشیم (مثل حالت checkpoint)! آیا بازم می‌تونیم مدل‌امون رو بازیابی کنیم و ازش استفاده کنیم؟ برای اینکه مدل رو بازیابی کنیم نیاز به MetaGraphDef داریم! اگر به کد اصلی پایتون مدل‌مون دسترسی داشته باشیم و آن را دوباره اجرا کنیم تمام اطلاعات لازم برای metaGraphDef برای ما از نو ساخته خواهد شد. اگر کد رو نداشته باشیم چی؟ در صورتی که با استفاده از checkpoint کل مدل رو ذخیره کرده باشید امکانش هست یعنی اگر همچین چیزی بنویسید:

در غیر این صورت چون نمی‌دونید گرافتون و نحوه جریان داده در گراف به چه صورت اتفاق می‌افتد، امکان بازیابی نداریم.

علاوه بر اینکه با استفاده از فایل meta می‌تونید تعریف گراف‌تون رو بازیابی کنید می‌تونید به تک‌تک هایپرپارامترها، عملگرها و… در گراف‌تون درسترسی پیدا کنید مثلا کد زیر رو ببینید:

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

تفاوت دیگری که tf.train.Saver با tf.train.Checkpoint می‌کند در هنگام بازیابی است. همانطور که دقت کردید در هنگام بازیابی قبلا از تابع:

status.initialize_or_restore(sess)

استفاده کردیم ولی در حالت tf.train.Saver مستقیما سشن را به saver.restore پاس می‌دهیم:

saver.restore(sess, './saver_test/myVar')

۳) استفاده از tf.saved_model.simple_save

همانطور که در بالا هم اشاره کردیم، این تابع در واقع یک wrapper روی tf.train.Saver است که استفاده از اون رو بسیار راحت‌تر میکنه! بخصوص برای وقتی که میخواهید مدل‌اتون رو serve کنید یا به قولی آن را روی سرور و برای پروداکشن قرار بدهید.

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

اونوقت تنسورفلو خودش تشخیص میدهد که چه متغیرهایی برای بازیابی مدل شما لازم است و آنها را ذخیره می‌کند. مثلا اگر مدل رگرسیون خطی که در قبلا پیاده‌ کردیم رو بخواهیم از این روش استفاده کنیم کدمون شبیه زیر خواهد بود:

اگر خروجی فایل‌هایی که کد بالا تولید می‌کند را مشاهده کنید، متوجه تغییراتی می‌شوید:

خروجی tf.saved_model.simple_save در تنسورفلو

همانطور که در شکل بالا ملاحضه می‌کنید در اینجا خبری از فایل chekpoint و meta نیست و این دو با یک فایل pb (پروتکل بافر protocol buffer) جایگزین شده‌اند. پروتکل بافر دیگه چیه؟ اگر اندکی کنجکاو بوده باشید و فایل‌های index و data را باز میکردید می‌بینید که فرمت‌ آنها باینری هستند و قابل خواندن نیست. در واقع، تمام فایل‌هایی که در تنسورفلو وجود دارند فرمتشون براساس Protocol Buffer است. میشه پروتکل بافر رو چیزی شبیه json تشبیه کرد که فرمت آن مثل زیر است:

پروتکل بافر میتونه به صورت متنی (text) و دودویی(binary) ذخیره شود. موقعی که ما آن را به صورت متنی ذخیره کنیم پسوند فایل خروجی به صورت file_name.pbtxt و در حالت باینری به صورت file_name.pb خواهد بود. در حالت پیش‌فرض به صورت باینری ذخیره می‌شود چون حجم آن به مراتب کمتر خواهد بود. در نتیجه خروجی simple_save یک فایل پروتکل بافر است که اطلاعات مربوط به MetaGraphDef (اطلاعات گراف و جریان داده‌ها) در آن ذخیره می‌شود. (اطلاعات دیگری هم ذخیره میشود که فعلا ازش می‌گذریم).

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

تابع load در فولدری که به آن آدرس دادید میگرده و MetaGraphDef را برای شما برمی‌گرداند که می‌توانید از آن استفاده کنید (نحوه بازیابی و ذخیره کردن به این روش را در قسمت‌ انتهایی با جزییات بیشتری بررسی خواهیم کرد).

۴) استفاده از ModelCheckpoint در کراس

در کراس برای اینکه بتونیم Checkpointها رو لوود یا ذخیره کنیم باید از callback استفاده کنیم. callback‌ها توابعی هستند که در هنگام آموزش مدل trigger می‌شوند (به یک event حساس هستند و وقتی اون event اتفاق افتاد این توابع اجرا می‌شوند). پس در نتیجه هنگامی که میخواهیم مدل‌امون رو آموزش بدهیم کافی است یک callback داشته باشیم که checkpointها ما را ذخیره کند. این کار به راحتی در کراس به صورت زیر قابل پیاده‌سازی است:

البته دقت کنید ModelCheckpoint تنها وزن‌های مدل را ذخیره می‌کند (meta را ذخیره نمی‌کند). اگر میخواهید کل مدل‌اتون رو ذخیره کنید باید از تابع save و load_model در کراس استفاده کنید:

تابع save در کراس برای شما یک فایل h5py درست خواهد کرد که در آن:

  • معماری مدل، که به شما اجازه می‌دهد از دوباره مدل را بسازید (مشابه همون meta)
  • وزن‌های مدل (weight & bias)
  • تنظیمات آموزش (optimizer و loss)
  • وضعیت optimizer، که به شما اجازه می‌دهد دقیقا از همون مرحله که قطع کردید آموزش را ادامه بدید.

همچنین اگر دوست دارید که وزن‌های مدل‌اتون رو در یک شبکه دیگر استفاده کنید که بعضی لایه‌های آن مشترک است (مثلا یک LSTM آموزش دادید، یک شبکه دیگر هم آموزش می‌دهید که توش LSTM دارد تو کارهایی مثل انتقال یادگیری (transfer learning) و fine tunning) می‌تونید از توابع save_weights و load_weights استفاده کنید. برای مثال به کد زیر رو نگاه کنید:

۵) تنسورفلو Serving

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

کار اولی که باید انجام بدید این است که مدل‌اتون رو مشخص کنید. من برای سادگی از همون مدل رگرسیون خطی استفاده می‌کنم ولی در اینجا برای راحتی از eager استفاده نمیکنم (البته فرق چندانی هم نمیکنه!). کدی که زدیم چیزی شبیه زیر بود:

خب قدم بعدی لوود کردن مدل است. همانطور که در بالا هم اشاره شد برای بازیابی مدل از تابع tf.saved_model.loader.load استفاده می‌کنیم یعنی:

چیزی که تابع tf.saved_model.loader.load به شما برمی‌گرداند یک MetaGraphDef است که اطلاعات کامل گراف قبلی در آن ذخیره شده است (دقت داشته باشید قبل از لوود کردن گراف، تنسورفلو هیچ اطلاعاتی راجع به اون چیزی که قبلا ذخیره کردید ندارد). همچنین شما sess را به آن پاس می‌دهید و وزن‌های session قبلی هم به این session منتقل می‌شود. در نتیجه اگر الان تابع tf.get_default_graph صدا بزنید، اطلاعات همان گرافی را به شما می‌دهد که قبلا ذخیره‌اش کرده بودید. برای اینکه بدونید چه اتفاقی می‌افتد کد زیر رو اجرا کنید:

اگر کد بالا رو اجرا کنید می‌بینید که در حلقه اول هیچ اتفاقی نمیافتد چون تنسورفلو هیچ اطلاعاتی از گراف شما ندارد و هیچ مدلی هم تاکنون نساخته‌اید. به محض اینکه تابع tf.saved_model.loader.load را صدا می‌زنید تمام operation‌های گرافی که قبلا ذخیره کردیم را چاپ می‌کنیم!

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

کد بالا چیز عجیب و غریبی نیست! چندتا مقدار ثابت (constant) هستند که با استفاده از آنها ابتدا اسم تنسوری که لازم داریم رو بازیابی می‌کنیم و بعد با استفاده از تابع get_tensor_by_name، تنسور معادل‌شو واکشی می‌کنیم. تو مدل رگرسیون خطی‌مون قرار بود که x رو بدیم و y رو حدس بزنیم. پس اینجا هم فقط این دو تنسور را واکشی کردیم.

قسمت بعدی میرسه به بخش سرور و API. برای این بخش از من flask استفاده می‌کنم. یک API با آدرس api/predict می‌نویسم که یک عدد (همون سال) بگیرد و یک عدد (قیمت خانه) را برگرداند.
همچنین دقت کنید که ما در هنگام آموزش ورودی‌هامون رو نرمالایز میکردیم، پس اینجا هم موقعی که میدهیم به مدل‌مون باید آن را نرمالایز کنیم.

API که من نوشتم به صورت زیر است:

ابتدا ورودی متد POST رو می‌گیرم (x_in) بعد آن را نرمالایز می‌کنم. سپس مقدار نرمال شده را به مدل می‌دهم تا y یا همون قیمت خونه را حدس بزند. مقداری که مدل بازمیگرداند مقداری نرمالایز است (بین ۰ الی ۱) پس باید آن را به قیمت خونه تبدیل کنم!

و تمام!!! الان کدتون رو اجرا کنید و ریکوست بدید (سال را بفرستید) به شما قیمت خانه را برمیگرداند.

تنسورفلو Serving و خروجی مدل رگرسیون خطی

کد کامل این قسمت رو می‌تونید در اینجا مشاهده کنید.

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

منتشر شده در آموزشتنسورفلوکراس

نظر

  1. mahsa mahsa

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

    • هادیفر هادیفر

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

  2. mahsa mahsa

    میخوام داده هارو به فرمت tfrecord در بیارم و بعد بخونم اما با ینکه ورودی هام به فرمت jpg هستند اما باز قبول نمیکته و خطا میده!!!ممنون

    • هادیفر هادیفر

      با این اطلاعاتی که شما فرمودید فک نکنم کسی بتونه کمکتون کنه باید اندکی توضیحات بیشتری ارائه کنید. پیشنهادم اینه سوالتون در استک بپرسید: ۱) مشکلتون رو اندکی دقیق‌تر توضیح بدید ۲) قسمت اصلی کدتون رو قرار بدید که فکر می‌کنید مشکل از اونجاست ۳) اکسپشنی که میخورید هم قرار بدهید. مطمئن باشید تو استک خیلی سریع جوابشو بهتون خواهند داد.
      موفق باشید

  3. mahsa mahsa

    بله حتما مچکرم

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

    ممنون از نوشته‌هات.

    • هادیفر هادیفر

      ممنون از لطف شما 🙂

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

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