ساخت تولیدکننده داده Keras برای ایجاد توالی از فریمهای ویدئویی و انجام تحلیل زمانی با RNN
مدلهای یادگیری عمیق برای آموزش به حجم بالایی از دادهها نیاز دارند و به دلیل پیچیدگیهای حافظه Space complexitites عملکرد ضعیفی دارند. انواع تولیدکننده داده برای رفع این مشکل، به جای اینکه دیتاست را در حافظه ذخیره کند، دادهها را در بسته تولید میکنند. در این حالت کاربر میتواند استفاده بهینهای از حافظه داشته باشد و علاوه بر آن مانع آن میشود که کامپایلر خطای “out-of-memory’ را نمایش دهد. شبکههای عصبی بازگشتی Recurrent Neural Networks (RNNs) به شبکههای عصبی گفته میشود که دارای حافظه هستند و میتوانند با دادههای زمانی Temporal data کار کنند و با استفاده از آنها میتوان روابط زمانی میان فریمهای ویدئویی را مشخص کرد.
در RNNها (در اینجا، LSTM)، اغلب دادههای تصویری باید در قالب “batches×timesteps× image_dimension” ساخته شوند. منظور از timesteps توالی فریمهایی است که از تک تک واحدهای بازگشتی Recurrent unit عبور میکنند و سعی میکنند پیش از رسیدن به توالی بعدی، اطلاعات میان آنها را یاد بگیرند. در این مقاله سعی داریم نحوه پیادهسازی تولیدکننده داده Keras/TensorFlow را توضیح دهیم و با استفاده از آنها دادههایی با فرمتهایی تولید کنیم که RNNها میتوانند با آنها کار کنند.
تولیدکننده داده Keras و پیادهسازی تولیدکننده در پایتون با یکدیگر تفاوت دارند اما کاربران معمولاً متوجه تفاوتهای میان این دو نمیشوند. کد از هر دوی آنها استفاده میکند. تولیدکنندهها در پایتون، توابع تکرارپذیری هستند که مقادیری “yield” (تولید) میکند و با استفاده از دستور loop میتوان آنها را تکرار کرد. تولیدکننده داده keras میتوانند همانند تولیدکننده پایتون، بستههایی از داده تولید کند. تفاوتی که در این دو تولیدکننده با هم دارند در نحوه دستیابی به آن است. این دو تولیدکننده از کلاس Sequence ارث میبرند و میتوانند دادهها را همزمان در چندین هسته بارگذاری کنند و بدین وسیله مدت زمان آموزش را کاهش دهند.
فرایند تولید توالی فریمها در بسته در دو مرحله اتفاق میافتد:
- ساختار دادهها به صورت توالی
- تغذیه دادهها به تولیدکننده
برای آنکه دادهها به آسانی به تولیدکننده داده تغذیه شوند، باید دادههای آموزشی و اعتبارسنجی را در دو پوشه جداگانه قرار دهیم و از این طریق دو تولیدکننده مجزا برای آنها بسازیم. هر یک از این پوشهها شامل پوشههای مختلف برای کلاسهای مختلف خواهند بود ( در صورتیکه قصد داریم فرایند دستهبندی را انجام دهیم). من قبلاً ویدئوها را به فریم تبدیل کردهام و آنها را در کلاسهای مناسب قرار دادهام ( من فریمها را شمارهگذاری کردم، این کار به من کمک کرد که مطمئن شوم به ترتیب ذخیره شدهاند). اگر فرض کنیم دادههای من شامل دو کلاس A و B هستند، ظاهر پوشه اینگونه خواهد بود:
Train/ |--ClassA/ |--Video1/ |--Frame1.png |--Frame2.png |... |--FrameN.png |--Video2/ |--Frame1.png |--Frame2.png |... |--FrameN.png|--ClassB/ |--Video3/ |--Frame1.png |--Frame2.png |... |--FrameN.png |--Video4/ |--Frame1.png |--Frame2.png |... |--FrameN.pngValidation/ |--ClassA/ |--Video5/ |--Frame1.png |--Frame2.png |... |--FrameN.png |--Video6/ |--Frame1.png |--Frame2.png |... |--FrameN.png|--ClassB/ |--Video7/ |--Frame1.png |--Frame2.png |... |--FrameN.png |--Video8/ |--Frame1.png |--Frame2.png |... |--FrameN.png
ابتدا برای هر فایل ویدئویی یک دیکشنری ایجاد کردم و برای هر فایل اسم و برچسب مناسب انتخاب کردم. سپس دیکشنری را به دیتافریم پاندا تبدیل کردم و آن را در قالب فایل .csv در یک پوشه جداگانه برای دادههای آموزشی و دادههای اعتبارسنجی ذخیره کردم.
[irp posts=”23445″]data = {'FileName':[],'Label':[],'ClassName':[]} for video_name is videos: for frames in frames_list: data['FileName'].append(os.path.join(dataset_path,frames)) data['Label'].append(dict_of_labels[video_name]) data['ClassName'].append(emotions)data_frame = pd.DataFrame(data) path = 'path to the corresponding folders'+d_type+'/{}_{}.csv'.format(video_name,frame_name)data_frame.to_csv(filename)
d_type مشخص میکند ورودی تابع دادههای آموزشی است یا دادههای اعتبارسنجی. پوشه فایل .csv اینگونه خواهد بود:
CSV_folder/ |-Train/ |-video1.csv |-video2.csv |-video3.csv |-video4.csv |-Validation/ |-video5.csv |-video6.csv |-video7.csv |-video8.csv
‘filegenerator’، تابع تولیدکننده پایتون، فایلهای .csv را قبول میکند و تعداد توالیهای مورد نیاز (طول زمانی) و گام زمانی را به عنوان پارامترهای خود دریافت میکند.
def filegenerator(CSV_folder,temporal_length,temporal_stride): ## Creates a python generator that 'yields' a sequence of frames every time based on the temporal lengtha and stride. for file in CSV_folder: data = pd.read_csv('path to each .csv file) labels = list(data.Label) img_list = list(data.FileName) samples = deque() sample_count = 0 for img in img_list: samples.append(img) if len(samples)== temporal_length: samples_c = copy.deepcopy(samples) samp_count += 1 for i in range(temporal_stride): samples.popleft() yield samples_c,labels[0] samples.popleft()#Eliminates the frame at the left most end to #######################accomodate the next frame in the sequence to #######################previous frame. ##Function to create the files structured based on the temporal requirements.: def seq_of_frames(folder,d_type,length,stride): for csv_file in os.listdir(folder+'/'+d_type) file_gen = filegenerator(csv_file,temporal_length,temporal_stride) iterator = True data_list = [] while iterator: try: X,y = next(file_gen) X = list(X) data_list.append([X,y]) except Exception as e: print("An exception has occured:",e) iterator = False return data_list
تابع در طول مسیرهای فایل ذخیرهشده در فایلهای .csv نمونهبرداری میکند و بر مبنای طول زمانی تعریفشده و گام، توالیای از فریمها ایجاد میکند. سپس میتوان برای ایجاد فریمها به صورت توالی ، این تابع را در حلقه Loop تکرار کرد. کدی که در این مقاله معرفی شده به من در ایجاد فایلهای .csv و تغذیه آنها به تولیدکننده پایتون برای آموزش مدل یادگیری عمیق کمک کرد.
training_data = seq_of_frames('path to folder containing .csv files','Train',5,2) validation_data= seq_of_frames('path to folder containing .csv files','Validation',5,2)
تابع ‘seq_of_frames’ فهرستی از مسیرهای توالی فریم و برچسبهای مربوطه باز میگرداند. سپس این فهرست به یک دیتافریم پاندا تبدیل میشود و تولیدکننده Keras از شاخصهای آن برای استخراج دادههای موجود در بستهها استفاده میکند.
در سندی که در TensorFlow قرار داده شده تمامی کلاسهای ‘Sequence’ برای پیادهسازی متد __len__() و __getitem__(index) ارائه شده است.
class DataGenerator(data_utils.Sequence): def __init__(self,data,batch_size,dim,n_classes,is_autoencoder,shuffle): #Initializing the values self.dim = dim self.data = data self.batch_size = batch_size self.list_IDs = np.arange(len(data)) self.n_classes = n_classes self.is_autoencoder = is_autoencoder self.shuffle = shuffle self.on_epoch_end()
من کلاس DataGenerator را فراخوانی کردم و این کلاس از کلاس Sequence ارث میبرد. البته من مدلام را در TensorFlow ساختهام و به همین دلیل از پیادهسازی Sequence آن استفاده کردم و میتوان آن را از tensorflow.python.keras.utils.data_utils بارگذاری کرد. پیادهسازی Keras آن را میتوانید از tf.keras.utils بارگذاری کنید. جابهجا کردن پیادهسازی Sequence میان این دو منجر به خطای Data Adaptor میشود.
منظور از آرگمان ‘data’ دیتافریمهایی است که شامل مسیرهای توالی فریم و برچسبهای مربوطه به عنوان ویژگیها هستند. ‘self,list_IDs’ فهرستی از تمامی شاخصهای داده است و متد __getitem__() از آن برای بارگذاری دادهها در بستهها استفاده میکند.
متد on_epoch_end() یک بار در آغاز و پایان تک تک دورههای چرخه آموزش اجرا میشود. این متد اغلب شاخصی به همریخته از داده بر میگرداند.
از متد __len__() برای به دست آوردن تعداد کل بستههای موجود در دادهها استفاده میشود.
def on_epoch_end(self): self.indexes = self.list_IDs #Load the indexes of the data if self.shuffle == True: np.random.shuffle(self.indexes) def __len__(self): return int(np.floor(len(self.data)/self.batch_size)) def __getitem__(self, index): #Generate batch at position 'index' index = self.indexes[index*self.batch_size :(index+1)*self.batch_size] #Generate a temporary list of indexes that forms a batch based on ##the index selected above. list_IDs_temp = [self.list_IDs[k] for k in index] #Generate batch X,y = self.__data_generation(list_IDs_temp) return X,y
متد __getitem__(index) موقعیتی به نام ‘index’ را به عنوان آرگمان خود میگیرد و آن را با اندازه بستههای دادهای Batch size
ادغام میکند تا بستهای داده از همان موقعیت بازگرداند.
def __data_generation(self,list_IDs_temp): X_data = [] y_data = [] for i,_ in enumerate(list_IDs_temp): #Iterating through each ######################################sequence of frames seq_frames = self.data.iloc[i,0] y = self.data.iloc[i,1] temp_data_list = [] for img in seq_frames: try: image = cv2.imread(img,0) ext_img = cv2.resize(image,self.dim) except Exception as e: '''Code you'd want to run in case of an exception/err''' temp_data_list.append(ext_img) X_data.append(temp_data_list) y_data.append(y)X = np.array(X_data) #Converting list to array y = np.array(y_data) if self.is_autoencoder == True: return X, X else: return X, keras.utils.to_categorical(y,num_classes=self.n_classes)[irp posts=”23572″]
متد __data__generation() فهرستی موقتی از شاخصها به عنوان آرگمان خود میگیرد و برای دسترسی به فهرست فریمهای مربوطه و برچسبهای آنها ، از آرگمان خود برای شاخصگذاری کردن دیتافریم استفاده میکند. سپس فریمها با استفاده از OpenCV خوانده میشوند و به عنوان آرایهای از فریمها ذخیره میشوند. من از خودرمزنگارها استفاده کردم، به همین دلیل تابعی اضافه کردم که در صورت نیاز، همان توالی از فریمها را به عنوان برچسب باز میگرداند. کل کلاس اینگونه خواهد بود:
class DataGenerator(data_utils.Sequence): def __init__(self,data,batch_size,dim,n_classes,is_autoencoder,shuffle): #Initializing the values self.dim = dim self.data = data self.batch_size = batch_size self.list_IDs = np.arange(len(data)) self.n_classes = n_classes self.is_autoencoder = is_autoencoder self.shuffle = shuffle self.on_epoch_end() def on_epoch_end(self): self.indexes = self.list_IDs if self.shuffle == True: np.random.shuffle(self.indexes) def __len__(self): return int(np.floor(len(self.data)/self.batch_size)) def __getitem__(self, index): index = self.indexes[index*self.batch_size: (index+1)*self.batch_size] list_IDs_temp = [self.list_IDs[k] for k in index] X,y = self.__data_generation(list_IDs_temp) return X,y def __data_generation(self,list_IDs_temp): X_data = [] y_data = [] for i,_ in enumerate(list_IDs_temp): batch_samples = self.data.iloc[i,0] y = self.data.iloc[i,1] temp_data_list = [] for img in batch_samples: try: image = cv2.imread(img,0) ext_img = cv2.resize(image,self.dim) except Exception as e: print('Value error ',e) temp_data_list.append(ext_img) X_data.append(temp_data_list) y_data.append(y) X = np.array(X_data) y = np.array(y_data) if self.is_autoencoder == True: return X, X else: return X, keras.utils.to_categorical(y,num_classes=self.n_classes)
و در آخر باید پارامترها را برای DataGenerator مشخص کنید و از آن برای ساخت نمونههای آموزشی و اعتبارسنجی استفاده کنید.
params = { 'batch_size':64, 'dim':(48,48), 'n_classes':2, 'is_autoencoder':True, 'shuffle':True } train_gen = DataGenerator(path_to_traindata,**params) validn_gen = DataGenerator(path_to_validationdata,**params)
پس از آنکه مدل RNN تعریف شد میتوانیم تولیدکنندهها را برای آموزش مدل، به تابع fit_generator مدل تغذیه کنیم. از آنجاییکه تولیدکننده از کلاس Sequence ارث میبرد، میتوانیم پردازش چندگانه انجام دهیم و به منظور تسریع فرایند آموزش تعداد workers را مشخص میکنیم.
model.fit(train_gen,epochs,validation_data=valid_gen,use_multiprocessing=True,workers=4)