Hand gesture recognition, using graph neural network

هدف این پروژه، پیاده سازی یک مدل تشخیص علائم دست با استفاده از یک شبکه عصبی گراف هست. دیتاست مورد نظر برای این پروژه، شامل تقریبا 7150 عدد عکس از 8 عدد علائم دست هست. از آنجایی هدف پروژه استفاده از شبکه عصبی گرافی هست، به داده های گرافی نیاز داریم. برای تبدیل عکس به گراف، از MediaPipe Hand Landmarker استفاده شد. ورودی یک عکس از دست را می گیرد و مختصات نقاط زیر را از روی دست تشخیص می دهد.   هر گراف شامل راس و یال هست. هر راس نیز می تواند ویژگی هایی داشته باشد. در اینجا هر نقطه، راس گراف محسوب می شود و مختصات هر نقطه، ویژگی های راس هستند. یال ها هم مانند عکس بالا هست با این تفاوت که بعضی از یال ها را تغییر داده که بیشتر به اسکلت دست انسان شبیه باشد.

edges = [
         [0, 1], [0, 5], [0, 9], [0, 13], [0, 17],
         [1, 0], [1, 2], [2, 1], [2, 3], [3, 2],
         [3, 4], [4, 3], [5, 0], [5, 6], [6, 5],
         [6, 7], [7, 6], [7, 8], [8, 7], [9, 0],
         [9, 10], [10, 9], [10, 11], [11, 10], [11, 12],
         [12, 11], [13, 0], [13, 14], [14, 13], [14, 15],
         [15, 14], [15, 16], [16, 15], [17, 0], [17, 18],
         [18, 17], [18, 19], [19, 18], [19, 20], [20, 19]
]

edges = np.array(edges).T.astype('int64')
همانطور که در تصویر پیداست، یال تمامی گراف ها یکسان هست. از آنجایی که تمامی گراف ها بدون جهت هست، و پیاده سازی گراف بدون جهت در pytorch geometric هم به صورت گرافی هست که تمام یال های آن دو طرفه هستند، یال ها به صورت بالا تبدیل می شوند. علت ترانهاده کردن (T.)  یال ها هم به پیاده سازی pytorch geometric مربوط می شود. به طور کلی گراف و داده های گرافی هیچ وابستگی به مختصات مکانی ندارند، اما در اینجا چون مختصات مکانی هر راس به عنوان ویژگی آن راس حساب می شود، مختصات مکانی اهمیت پیدا می کند. به عنوان مثال اگر در تمام عکس ها، دست در مرکز عکس قرار داشته باشد، بعد از فرایند آموزش، مدل توانایی تشخیص علائم دستی که در گوشه های عکس قرار داشته باشد را ندارد. برای رفع کردن این مشکل، مختصات تمام راس ها را از راس 0 که معادل مچ دست هست کم می کنیم. با انجام این کار، راس 0 را مبدا مختصات قرار داده و مختصات بقیه راس هم نسبت به راس 0 عوض می شوند
for i in range(len(train_x)):
  x = train_x[i][0][0]
  y = train_x[i][0][1]
  z = train_x[i][0][2]
  for j in range(len(train_x[i])):
    train_x[i][j][0] = train_x[i][j][0] - x
    train_x[i][j][1] = train_x[i][j][1] - y
    train_x[i][j][2] = train_x[i][j][2] - z
برای ساخت و اموزش شبکه های عصبی گرافی، pytorch geometric یکی از مورد استفاده ترین کتابخانه ها هست. یک نمونه پیلدی سازی مدل، به شکل زیر است. از چند لایه Convolutional Layers برای message passing، یک لایه global mean pool برای به دست اوردن ویژگی کل گراف از روی راس ها، و یک linear برای کلاس بندی (classify) کردن نتیجه هست.
from torch.nn import Linear
import torch.nn.functional as F
from torch_geometric.nn import GCNConv
from torch_geometric.nn import global_mean_pool
class GCN(torch.nn.Module):
   def __init__(self):
        super(GCN, self).__init__()
        self.conv1 = GCNConv( , )
        self.conv2 = GCNConv( , )
        self.conv3 = GCNConv( , )
        self.lin = Linear( , 8)
        self.double()

    def forward(self, x, edge_index, batch):
        x = self.conv1(x, edge_index)
        x = x.relu()
        x = self.conv2(x, edge_index)
        x = x.relu()
        x = self.conv3(x, edge_index)
        x = x.relu()
        x = self.conv4(x, edge_index)

        x = global_mean_pool(x, batch)

        x = F.dropout(x, p=0.5, training=self.training)
        x = self.lin(x)

        return x
نتیجه مدل بعد 170 ایپاک آموزش، 83% شد. قطعا با پیدا کردن hyperparameter های مناسب، تغییر لایه های مدل و ... به دقت بهتری خواهیم رسید.

مقایسه graph neural network با convolutional neural network:

با اینکه دیتاست gnn و cnn یکی بودند، اما بعد از تغییر داده ها به داده های گرافی، حجم داده های گرافی 190 برابر کمتر از حجم داده های اولیه شد. فرایند آموزش gnn با 170 ایپاک 5 – 10 دقیقه شد، در حالی که فرایند آموزش برای cnn بیشتر از یک ساعت می باشد. اما با این حال مدل cnn دقت بهتری نسبت به gnn داشت. مدل gnn استفاده شده یک مدل ساده بوده و جای پیشرفت و بهبود دارد و می تواند به دقت بهتری برسد.