درآمدی بر فرآیند یادگیریِ رانندگی در خودروهای Self-Driving

درآمدی بر فرآیند یادگیریِ رانندگی در خودروهای Self-Driving

Tesla، کمپانی خودروسازی تحت رهبری Elon Musk، با عرضهٔ مُدل S و پس از آزمایش‌های مستقل انجام‌شده توسط NHTSA (سازمان امنیت ملی ترافیک ایالات متحده)، موفق به کسب نتایج قابل‌قبولی در حوزهٔ ایمنی خودرو شد که نتیجۀ این آزمایش در مورد خودروهای خودران یا به اصطلاح Self-Driving Cars توجه بسیاری از سازمان‌ها، افراد و شاید هم دولوپرها را به خود جلب کرده است. 

با مشاهدهٔ‌ حرکت خودروهای خودران در جاده‌ها می‌بینیم به همان شیوه‌ای که افراد از چشمان خود برای دیدن جاده‌ها و از دستانشان برای چرخاندن فرمان در پیچ‌ها استفاده می‌کنند، ماشین‌های به اصطلاح Self-Driving (خودران) نیز از مجموعه‌ای از دوربین‌ها برای درک محیط پیرامون خود و همچنین یک مدل #یادگیری ژرف برای هدایت خودرو استفاده می‌کنند که درک چگونگی این فرایند چیزی است که در این مقاله قصد داریم مورد بررسی قرار دهیم. به طور خلاصه، پروسهٔ رانندگی خودروهای سِلف‌دِرایوینگ در پنج مرحلهٔ زیر خلاصه می‌گردد:

۱. جمع‌آوری و ثبت داده‌های محیط پیرامون
۲. آنالیز و پردازش داده‌های جمع‌آوری شده
۳. توسعهٔ مُدلی یادگیرنده که به درک محیط پیرامون می‌پردازد
۴. آموزش مُدلی که نحوۀ رانندگی کردن را یاد می‌گیرد
۵. و اصلاح مُدل به‌ گونه‌ای که با گذشت زمان بهبود یابد

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

همچنین باید توجه داشت که رانندگی در شرایط کاملاً ایمن و بدون در نظر گرفتن خطرات احتمالی، به عنوان یک رفتار رانندگی اشتباه تلقی شده و ممکن است عملکرد مدل را برای شرایط واقعی رانندگی بدون اینکه یک راننده پشت فرمان نشسته باشد تضعیف کند. برای مثال، رانندگی با سرعت کم (حدود 10 کیلومتر در ساعت) موجب می‌شود زاویهٔ فرمان به هنگام چرخش خودرو به عنوان یک زاویۀ صاف داخل داده‌ها ثبت شود و همین مسئله می‌تواند در سرعت‌های بالا مشکل‌ساز گردد. به طور کلی، رفتارهای رانندگی و زاویه فرمان مربوط به آن‌ها به چند دستۀ زیر تقسیم می‌شوند:

رانندگی مستقیم (مورد اول) یا به عبارتی: 

0 <= X < 0.2

حرکت چرخشی آرام (مورد دوم) یا به عبارتی:

0.2 <= X < 0.4

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

X >= 0.4

و بازگشت دوباره به مبدأ (مورد چهارم) و این در حالی است که فرمول محاسبۀ زاویه فرمان برابر است با X = 1 / r که در آن X زاویه فرمان و r شعاع چرخشی بر حسب متر است. به طور کلی، مورد چهارم از رفتارهای رانندگی یک خودروی بدون راننده یا همان «بازگشت دوباره به مبدأ» در طول فرایند جمع‌آوری داده‌ها بسیار حیاتی است؛ چرا که با استفاده از این داده‌ها خودرو یاد می‌گیرد تا در شرایطی که ضربه‌ای محکم یا ناگهانی به ماشین وارد شده یا در جاده دچار سانحه شده باشد، چگونه به مبدأ بازگردد. تمام داده‌های جمع‌آوری شده در فایلی مثلاً تحت عنوان driving_log.csv ذخیره می‌شوند که هر سطر آن شامل:

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

برای آموزش این مدل چیزی در حدود 100/000 زاویۀ فرمان در شرایط مختلف جمع‌آوری می‌شود است تا اطلاعات کافی برای آموزش مدل ارائه شود. همچنین بایستی توجه داشت که استفاده از نمونه‌های زیاد برای آموزش مدل ممکن است موجب به اصطلاح Overfitting مدل شود که در این صورت، با ارائۀ داده‌های جدید و یا در مواجهۀ با شرایط رانندگی در دنیای واقعی، مدل نمی‌تواند آنچه یاد گرفته را به داده‌های جدید نیز تعمیم دهد؛ بنابراین دقت کمتری را در رانندگی خواهد داشت.

آنالیز و پردازش داده‌های جمع‌آوری شده
مرحله دوم، آنالیز و آماده‌سازی داده‌های جمع‌آوری شده به منظور ساخت مُدل است که هدف در این مرحله تولید نمونه دیتای بیشتر به منظور Train (آموزش) مدل است. مثلاً تصویری را فرض کنید که توسط دوربین مرکزی جلوی خودرو با ابعاد 160*320 پیکسل گرفته شده باشد و همچنین کانال‌های RGB (سبز، قرمز و آبی) را دارا باشد؛ به عبارت دیگر، یک تصویر رنگی باشد. 

در ادامه، کانال‌های رنگی هر تصویر به صورت یک آرایۀ سه‌بُعدی در خواهند آمد که در آن هر پیکسل از 0 تا 255 مقداردهی شده است. در اینجا از ماژولی تحت عنوان Cropping2D از لایبرری Keras می‌توان استفاده کرد به منظور بُرش تصاویر اِعمال شده تا میزان نویز تصاویری که به مدل داده می‌شود، کاهش یابد (لازم به ذکر است که Keras یک لایبرری #اپن‌سورس برای یادگیری ژرف با بک‌اند لایبرری TensorFlow است که برای کاهش پیچیدگی‌های این لایبرری در آموزش مدل‌ها توسعه یافته است.)

همچنین در این مدل، از لایبرری OpenCV برای خواندن تصاویر از داخل فایل استفاده شده است و برای تولید نمونه دادهای بیشتر، تصاویر در امتداد محور عمودی چرخانده می‌شوند (لازم به ذکر است که OpenCV یک لایبرری اپن‌سورس برای استفاده در مدل‌های بینایی ماشین است که با زبان ++C نوشته شده است؛ بنابراین برای استفاده در مدل خودروی بدون رانندهٔ ذکر شده در این مقاله نیز گزینۀ مناسبی خواهد بود.) همان‌طور که پیش‌تر ذکر شد، به منظور تولید نمونه دادۀ بیشتر برای آموزش مدل، استفاده از تکنیک‌هایی همچون برش و چرخش تصاوير نیز مفید هستند:

center_image = cv2.imread(batch_sample[0].strip())
flip_center_image = cv2.flip(center_image, 1)

در همین راستا، زاویۀ فرمان خودرو برای تولید نمونه دادۀ بیشتر چرخانده می‌شود؛ برای این منظور زاویه در عدد 1- ضرب شده است:

flip_center_angle = transform_angle(center_angle * -1.0)

در گام بعدی، از لایبرری Numpy استفاده شده است تا تصاویر آموزشی (یک آرایه دوبُعدی به ابعاد 160*320 پیکسل) را به یک آرایهٔ سه‌بُعدی تغییر شکل دهد که از آن پس چنین تصویری برای استفاده در پروسهٔ‌ آموزش مدل آماده شده است (لایبرری Numpy یک لایبرری اپن‌سورس برای انجام محاسبات ریاضیاتی در پایتون است که برای آشنایی بیشتر با آن، می‌توانید به مقالهٔ درآمدی بر آمار با استفاده از لایبرری NumPy و زبان برنامه‌نویسی Python مراجعه نمایید.)

def transform_image(image):
    IMAGE_WIDTH = 160
    IMAGE_LENGTH = 320
    IMAGE_DEPTH = 3
    image = np.array(image, dtype='float32')
    return image.reshape(IMAGE_WIDTH, IMAGE_LENGTH, IMAGE_DEPTH)

توسعهٔ مُدلی یادگیرنده که به درک محیط پیرامون می‌پردازد
مرحلۀ سوم، طراحی یک مدل یادگیری ژرف است که این مدل یکسری فیچر (قابلیت) را از تصاویر جمع‌آوری شده استخراج خواهد کرد و هدف اصلی در این مرحله، مَپ (نگاشت) تصویر ورودی با 153600 پیکسل به خروجی یک مقدار از نوع دادۀ Float (اعشاری) است که در این مورد خاص، از مدل NVIDIA به عنوان معماری پایۀ شبکه می‌توان استفاده کرد که در آن هر لایه از شبکه قابلیتی خاص را برای هر Epoch (دوره) آموزشی فراهم می‌کند. حال آرایهٔ سه‌بُعدی تصاویر ورودی بایستی به بازۀ اعداد (1 و 0) نرمال‌سازی شود که برای این منظور، اعداد پیکسل‌های تصویر بر 255 تقسیم شده است چرا که این بزرگ‌ترین مقدار ممکن است که یک پیکسل می‌تواند داشته باشد:

model.add(Lambda(
    lambda x: x / 255.0 - 0.5,
    input_shape = (IMAGE_WIDTH, IMAGE_LENGTH, IMAGE_DEPTH)
))

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

CROP_TOP = 70
CROP_BOTTOM = 25
model.add(Cropping2D(cropping = ((CROP_TOP, CROP_BOTTOM), (0, 0))))

در این مرحله از کار، آرایهٔ سه‌بُعدی تصاویر به لایهٔ به اصلاح Convolutional در شبکه داده می‌شود تا یکسری ویژگی‌های کلیدی همچون علائم خط‌کشی جاده، جداول کنار پیاده‌روها از این آرایه استخراج شود که این دست داده‌ها برای پیش‌بینی زاویه فرمان بسیار مهم هستند:

model.add(Convolution2D(
    convolution_filter,
    kernel_size,
    kernel_size,
    border_mode = 'valid',
    subsample = (stride_size, stride_size),
    activation = 'relu'
))

همچنین از Dropout به منظور کاهش احتمال Overfiting مدل استفاده شده است؛ چرا که هدف اصلی توسعه مدلی است که توانایی رانندگی در جاده‌ها با شرایط مختلف را داشته باشد؛ نَه فقط مسیری که آن را یاد گرفته است:

DROPOUT = 0.2
model.add(Dropout(DROPOUT))

در نهایت، همان‌طور که در بلوک زیر مشاهده می‌کنید، مدل زاویه فرمان را که یک عدد از جنس Float (اعشاری) است در خروجی ارائه می‌دهد؛ این مقدار به Controller Area Network فرستاده می‌شود تا امکان هدایت خودرو را برای مدل فراهم کند:

model.add(Dense(1))

آموزش مُدلی که نحوۀ رانندگی کردن را یاد می‌گیرد
گام چهارم، آموزش مدل به‌ گونه‌ای است که خودش توانایی رانندگی کردن را دارا باشد که به طور کلی هدف از این مرحله به حداقل رساندن مقدار به اصطلاح Loss برای پیش‌بینی زاویۀ فرمان است (Loss در این مدل عبارت است از مُربع میانگین تفاوت زاویه‌ای که مدل پیش‌بینی کرده است با زاویه مورد انتظار) و گام کلیدی در فرآیند آموزش، بهم زدن تصادفی نمونه دیتای مورد استفاده برای آموزش است (به عبارت دیگر، همان دیتایی که در فایل driving_log.csv ذخیره شده است) تا میزان تعصب در داده‌ها نیز کاهش یابد (Shuffle در لغت به معنی بُر زدن است):

shuffle(samples)

مجموعه داده‌ها به دو قسمت 80٪ و 20٪ تقسیم خواهند شد که مجموعه دادهٔ اول برای Train و مجموعه داده دوم برای Test مدل مورد استفاده قرار می‌گیرد که در این مرحله دقت مدل به منظور پیش‌بینی زاویهٔ فرمان سنجیده می‌شود:

train_set, validation_set = train_test_split(
    samples,
    test_size = VALIDATION_SET_SIZE
)

برای بهینه‌سازی مُربع میانگین Loss، از تابع بهینه‌سازی Adam (مخفف Adaptive Moment Estimation) استفاده شده است که مزیت کلیدی استفاده از این تابع برای بهینه‌سازی در مقایسه با Gradient Descent (گرادیان کاهشی)، استفاده از به اصطلاح Momentum (تکانه) برای دستیابی به یک مقدار بهینه سراسری است:

adam = Adam(lr = LEARNING_RATE)
model.compile(optimizer = adam, loss = 'mse')

در واقع، Gradient Descent با پیدا کردن یک مقدار مینیمُم لوکال برای تابع Loss، آن را به عنوان مقدار مینیمم کلی تلقی کرده و الگوریتم در آن نقطه متوقف می‌شود و این در حالی است که با به‌کارگیری Momentum، الگوریتم از مقادیر مینیمم لوکال رد شده و مقدار مینیمم کلی را می‌یابد چرا که در ابتدای فرآیند یادگیری و به منظور افزایش حالت‌هایی که هر دفعه با تغییر پارامترهای مدل به‌وجود می‌آیند، سیستم یکسری رفتارهای نامعقول را از خود نشان می‌دهد؛ به عبارت دیگر، تغییر پارامترهای شبکه در ابتدای فرآیند یادگیری به روشی نامعقول انجام می‌شود!

در این مرحله با استفاده از متد fit شبکه را آموزش می‌دهیم؛ بدین معنی که شبکه را روی داده‌های آموزشی اصطلاحاً فیت می‌کنیم. با توجه به حجم عظیم تصاویر، امکان فیت (تنظیم) کردن کل مجموعۀ آموزشی الگوریتم در حافظه وجود ندارد و از همین روی از یک به اصطلاح Generator استفاده شده است تا مجموعۀ تصاویر آموزشی را به چند Batch (دسته) تقسیم کند:

history = model.fit_generator(
    train_generator,
    samples_per_epoch = samples_per_epoch,
    nb_epoch = EPOCH,
    verbose = VERBOSITY,
    callbacks = callbacks,
    validation_data = validation_generator,
    nb_val_samples = validation_samples
)

اصلاح مُدل به‌ گونه‌ای که با گذشت زمان بهبود یابد
مرحله پنجم و نهایی، اصلاح مدل به‌ گونه‌ای است تا دقت و قدرت آن در طول زمان بهبود یابد. به عبارت دیگر، می‌توان مدل را با معماری‌ها و پارامترهای مختلف آزمایش کرد تا تأثیر آن‌ها در میزان کاهش میانگین مُربع خطا بررسی شود اما نکته‌ای که در اینجا نیاز به توضیح دارد این است که امکان دستیابی به یک مدل به صورت کاملاً بهینه وجود ندارد، چرا که تغییر پارامترها هر کدام به نوعی موجب بهبود یک حالت خاص از مدل می‌شوند. برای مثال، می‌توان تغییراتی همچون موارد زیر را در مدل اعمال کرد که عبارتند از:

- کاهش زمان آموزش با به‌کارگیری GPU که موجب افزایش هزینۀ توسعۀ مدل می‌شود.
- کاهش نرخ یادگیری که موجب افزایش احتمال هم‌گرا شدن مدل به یک مقدار بهینه می‌شود اما از سوی دیگر منجر به افزایش زمان آموزش مدل می‌شود.
- کاهش زمان آموزش با به‌کارگیری از تصاویر سیاه و سفید که منجر به از دست دادن امکان استفاده از اطلاعاتی می‌شود که یک تصویر RGB (رنگی) برای دولوپر فراهم می‌کند.
- بهبود دقت تخمین گرادیان با به‌کارگیری مقادیر بزرگ برای دسته‌بندی داده‌های آموزشی که این مورد نیز منجر به اِشغال بیش‌ از حد حافظۀ سیستم می‌شود.
- استفاده از تعداد زیادی Sample (نمونه داده) در هر دوره از آموزش مدل به منظور کاهش نوسانات تابع Loss.

نتیجه‌گیری
توسعهٔ یک مدل برای رانندگی خودروهای به اصطلاح Self-Driving موجب شده تا در مورد مزایا و محدودیت‌های Machine Vision (بینایی ماشین) و Deep Learning (یادگیری ژرف یا عمیق) اطلاعات بیشتری کسب کنیم. همچنین در این مقاله آموختیم که چگونه می‌توان با استفاده از این دو مفهوم، مدلی را آموزش داد تا با استفاده از آن خودرویی بدون راننده و به قول معروف خودران، در یک مسیر رانندگی کند.

منبع


اکرم امراه‌نژاد