دسته‌بندی نشده

تشخیص چهره افراد با استفاده از شبکه سیامی

شناسایی-چهره-هوش-مصنوعی

در این مطلب میخواهیم با روند تشخیص چهره (بازشناسی چهره) توسط هوش مصنوعی آشنا شویم.

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

مثال بالا نمونه ای از یک فرآیند دسته بندی تصاویر با استفاده از هوش مصنوعی و یادگیری عمیق است،

اما در مسائل بازشناسی چهره برخلاف دسته‌بندی تصاویر، مقدار داده های زیادی برای آموزش (train کردن) شبکه نداریم!

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

بطور مثال با استفاده از چند تصویر از یک گروه تحت تعقیب، میخواهیم هوش مصنوعی‌ای طراحی کنیم که دوربین های فرودگاه ها و مکان های عمومی بتوانند آن ها را شناسایی کنند!!

در ادامه بصورت عملی با روند پیاده سازی این هوش مصنوعی آشنا میشویم.

نکته: کدها و تصاویر استفاده شده در ادامه مطلب، از گیت‌هاب استادرضوی برداشته شده است (لینک)

یادگیری معیار شباهت با استفاده از تابع فاصله

وقتیکه مقدار داده (عکس) کمی برای آموزش شبکه هوش مصنوعی داریم، یکی از بهترین راه ها استفاده از میزان شباهت بین تصاویر مربوط به یک فرد است. در اینجا ما باید یک تابع طراحی کنیم که درصد اختلاف و شباهت تصاویر را به ما نشان دهد.

تابع فاصله

برای اینکار ما تابعی ایجاد میکنیم که اگر دو تصویر داده شده به آن متعلق به یک نفر باشد، فاصله را از یه حدی کمتر نشان دهد. همینطور اگر دو تصویر داده شده به تابع مربوط به دو چهره مختلف بودند فاصله نشان داده شده بیشتر باشد؛

تابع-فاصله-شناسایی-چهره

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

شبکه عصبی سیامی (Siamese)

شبکه سیامی نوعی شبکه عصبی است که از شبکه های کانولوشنال تشکیل شده تا میزان شباهت و اختلاف بین ورودی ها را بسنجد. به بیان ساده تر: یک شبکه پیچشی (cnn) داریم، تصاویر را به این شبکه میدهیم و شبکه در نهایت به ما یک بردار به عنوان جواب برمیگرداند. هدف اینست که شبکه را طوری آموزش دهیم که اگر دو تصویر مربوط به یک چهره باشند، بردار نهایی آن ها نزدیک به هم باشد و اگر مربوط به دو کلاس (فرد) متفاوت بودند، بردارها نیز متفاوت باشند؛

شبکه سیامی برای شناسایی چهره

برای اینکار نیاز است که وزن های شبکه را به خوبی تنظیم کنیم:

تابع فاصله در شبکه سیامی

تابع هزینه سه گانه (تریپلت)

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

  1. تصویر اصلی فرد (لنگر – ANCHOR)
  2. چهره دیگری از همان فرد (مثبت – ANCHOR)
  3. تصویری از یک شخص دیگر (منفی – NEGATIVE)

تابع-هزینه-سه-تایی

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

همینطور میتوانیم با ماکزیمم گرفتن، فرمول نهایی زیر را بدست آوریم:

فرمول-تابع-تریپلت

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

اینکار باعث می شود که یادگیری برای شبکه عصبی دشوار باشد و در مواجه با داده های جدید عملکرد بهتری داشته باشد؛

نکته: با یادگیری تقویتی میتوان حتی این انتخاب های اولیه را نیز خودکار انجام داد!!

ساخت سیستم شناسایی چهره با استفاده از هوش مصنوعی (کتابخانه پایتورچ)

در توضیحات بالا با نحوه عملکرد یک شبکه برای بازشناسایی چهره آشنا شدیم. البته شبکه های سیامی فقط برای تشخیص چهره کاربرد ندارند، مثلا برای شناسایی امضا نیز میتوان این شبکه را بکار برد.

در زیر نحوه ی کدنویسی سیستم توضیح داده شده در بالا با استفاده از زبان پایتون و کتابخانه هوش مصنوعی پایتورچ آورده شده است:

وارد کردن کتابخانه های مورد نیاز

%matplotlib inline
%load_ext autoreload
%autoreload 2

import os
import sys
import random
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image

import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torch.autograd import Variable

import torchvision
import torchvision.utils
import torchvision.models as models
import torchvision.datasets as datasets
import torchvision.transforms as transforms
from torch.utils.data import DataLoader, Dataset

import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torch.autograd import Variable

from data_utils import create_validation_data

use_gpu = torch.cuda.is_available()

متد های کمکی:

</wp-p>
def get_model(model_name, pretrained=True):
return models.__dict__[model_name](pretrained)

def imshow(img, text=None):
img_np = img.numpy().transpose((1, 2, 0))
plt.axis("off")
if text:
plt.text(175, 8, text, style='italic', fontweight='bold',
bbox={'facecolor': 'white', 'alpha': 0.8, 'pad': 10})
plt.imshow(img_np)
plt.show()
<wp-p style="direction: ltr;">

وارد کردن داده ها

ما برای این پروژه از دیتاست Cambridge AT&T Face Dataset استفاده میکنیم که شامل تصویر 40 فرد مختلف و از هر فرد 10 چهره میباشد.

آدرس فولدر دیتاست خود را داخل کد زیر تغییر دهید.

نکته دیگر اینکه همانطور که میدانید برای یادگیری عمیق ما نیاز داده های بسیاری برای آموزش شبکه داریم، در اینطور مسائل که داده اولیه برای آموزش کم است، میتوان از تکنیک یادگیری انتقال استفاده کرد. یعنی از یک شبکه آموزش داده شده از قبل برای این مسئله استفاده میکنیم. در کد زیر از معماری resnet برای شبکه کانولوشنی استفاده کرده شده است.

</wp-p>
DATA_DIR = 'D:/datasets/att_faces/'
train_dir = f'{DATA_DIR}train'
valid_dir = f'{DATA_DIR}valid'

sz = 200
batch_size = 16
embed_size = 128
num_epochs = 50

cnn_name = 'resnet101'
<wp-p style="direction: ltr;">

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

</wp-p>
<wp-p style="direction: ltr;"># make validation dataset
if not os.path.exists(valid_dir):
create_validation_data(train_dir, valid_dir, split=0.20, ext='jpg')</wp-p>
<wp-p style="direction: ltr;">

در این قسمت زیر کلاس هایی را تعریف میکنیم که پیش پردازش اولیه بر روی دیتاست را برای ما ساده تر کنند. بطور مثال getiteme تصاویر انکر، مثبت و منفی را مشخص میکند(البته بصورت رندوم)

#Create a subclass of torch.utils.data.Dataset
#Override the following three methods:
#The constructor or __init__(): this method is required to initialize the dataset object.
#The __getitem__() method: this methos enables us to access each training data using its index. In other words, we can use the dataset as a simple list or sequence.
#The __len__() method: this method returns number of data in the dataset.

class TripletNetworkDataset(Dataset):

def __init__(self, imgs, transform=None):
self.imgs = imgs
self.transform = transform
if transform is None:
self.transform = transforms.ToTensor()

def __getitem__(self, index):
# Select anchor image and its label from dataset
anchor, anchor_label = self.imgs[index]

# Randomly select a positive and a negative example
positive = random.choice([img for img, lbl in self.imgs if lbl == anchor_label])
negative = random.choice([img for img, lbl in self.imgs if lbl != anchor_label])

# read the images (anchor, positive and negative images)
anc_img = Image.open(anchor).convert('RGB')
pos_img = Image.open(positive).convert('RGB')
neg_img = Image.open(negative).convert('RGB')

# perform any required transformation (if any)
if self.transform is not None:
anc_img = self.transform(anc_img)
pos_img = self.transform(pos_img)
neg_img = self.transform(neg_img)

return anc_img, pos_img, neg_img

def __len__(self):
return len(self.imgs)

Training data

zoom = int((1.0 + random.random() / 10.0) * sz)  # up to 10 percent zoom

tfms = transforms.Compose([
transforms.Resize((zoom, zoom)),
transforms.RandomCrop(sz),
transforms.RandomHorizontalFlip(),
transforms.RandomRotation(5),
transforms.ToTensor()
])

face_dataset = datasets.ImageFolder(train_dir)
train_ds = TripletNetworkDataset(face_dataset.imgs, transform=tfms)
train_dl = DataLoader(train_ds, batch_size=8, shuffle=True, num_workers=0)

Validation data

valid_tfms = transforms.Compose([
transforms.Resize((zoom, zoom)),
transforms.RandomCrop(sz),
transforms.RandomHorizontalFlip(),
transforms.RandomRotation(5),
transforms.ToTensor()
])

valid_face_dataset = datasets.ImageFolder(valid_dir)
valid_ds = TripletNetworkDataset(valid_face_dataset.imgs, transform=valid_tfms)
valid_dl = DataLoader(valid_ds, batch_size=batch_size, shuffle=True, num_workers=0)

Model: Triplet Netwok

class TripletNetwork(nn.Module):

def __init__(self, model, embed_size):
super(TripletNetwork, self).__init__()
num_features = model.fc.in_features
model.fc = nn.Sequential(
nn.Linear(num_features, 512),
nn.BatchNorm1d(512),
nn.ReLU(),
nn.Linear(512, embed_size))
self.model = model

def forward(self, anc, pos, neg):
f_anc = self.model(anc)
f_pos = self.model(pos)
f_neg = self.model(neg)
return f_anc, f_pos, f_neg

 

تابع هزینه سه‌تایی Triplet Loss

class TripletLoss(nn.Module):

def __init__(self, margin=0.2):
super(TripletLoss, self).__init__()
self.margin = margin

def forward(self, f_anc, f_pos, f_neg):
pos_distance = F.pairwise_distance(f_anc, f_pos)
neg_distance = F.pairwise_distance(f_anc, f_neg)
loss = torch.sum(torch.clamp(torch.pow(pos_distance, 2) - \
torch.pow(neg_distance, 2) + self.margin, min=0))
return loss

Train (آموزش مدل)

 

def train_step(model, train_dl, criterion, optimizer, scheduler=None):
model.train()
if scheduler:
scheduler.step()

N = len(train_dl.dataset)
steps = N // train_dl.batch_size
avg_loss = 0.0
for i, (anc, pos, neg) in enumerate(train_dl):
anc, pos, neg = Variable(anc).cuda(), Variable(pos).cuda(), Variable(neg).cuda()

# forward
f_anc, f_pos, f_neg = model(anc, pos, neg)

# loss
loss = criterion(f_anc, f_pos, f_neg)
avg_loss = (avg_loss * i + loss.data[0]) / (i + 1)

# backward
optimizer.zero_grad()
loss.backward()
optimizer.step()

# report
sys.stdout.flush()
sys.stdout.write("\r Training Step [{:2d}/{:2d}]: loss {:.5f}  ".format(i+1, steps, avg_loss))
print()

return model, avg_loss

def validate_step(model, valid_dl, criterion):
model.eval()

N = len(valid_dl.dataset)
steps = N // valid_dl.batch_size
avg_loss = 0.0
for i, (anc, pos, neg) in enumerate(valid_dl):
anc = Variable(anc, volatile=True).cuda()
pos = Variable(pos, volatile=True).cuda()
neg = Variable(neg, volatile=True).cuda()

f_anc, f_pos, f_neg = model(anc, pos, neg)
loss = criterion(f_anc, f_pos, f_neg)
avg_loss = (avg_loss * i + loss.data[0]) / (i + 1)

# report
sys.stdout.flush()
sys.stdout.write("\r Validation Step [{:2d}/{:2d}]: loss {:.5f}  ".format(i+1, steps, avg_loss))
print()

return avg_loss

def train(model, train_dl, valid_dl, criterion, optimizer, scheduler=None, num_epochs=100):
best_loss = float('inf')
best_weights = model.state_dict().copy()

train_loss_history, valid_loss_history = [], []
for epoch in range(num_epochs):
print(f'Epoch {epoch}\n--------')
model, train_loss = train_step(model, train_dl, criterion, optimizer, scheduler)
valid_loss = validate_step(model, valid_dl, criterion)
train_loss_history.append(train_loss)
valid_loss_history.append(valid_loss)
if valid_loss < best_loss:
best_loss = valid_loss
best_weights = model.state_dict().copy()
print()
loss_history = (train_loss_history, valid_loss_history)
return model, best_weights, loss_history

 

# data
train_dl = DataLoader(train_ds, batch_size=batch_size, shuffle=True,  num_workers=0)
valid_dl = DataLoader(valid_ds, batch_size=batch_size, shuffle=False, num_workers=0)

# model
cnn = get_model(cnn_name)
model = TripletNetwork(cnn, embed_size).cuda()

# loss and optimizer
criterion = TripletLoss().cuda()
optimizer = optim.Adam(model.parameters(), lr=0.0002)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.95)

Training

model, best_weights, loss_hist = train(model, train_dl, valid_dl,
criterion, optimizer, scheduler,
num_epochs=num_epochs)

 

plt.figure(figsize=(12, 4))
plt.plot(loss_hist[0], 'g', label='Training loss')
plt.plot(loss_hist[1], 'r', label='Validation loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.title("Training and Validation Loss");

 

torch.save(model.state_dict(), f'{cnn_name}-facereco-triplet-{num_epochs}.pth')

Testing اعتبارسنجی مدل

model.load_state_dict(torch.load('weights/resnet101-facereco-triplet-50.pth'))

valid_dl = DataLoader(valid_ds, batch_size=1, shuffle=True)

 

model.eval()

dataiter = iter(valid_dl)
for i in range(20):
x0, x1, x2 = next(dataiter)
concat = torch.cat((x0, x1, x2), 0)
f0, f1, f2 = model(Variable(x0).cuda(), Variable(x1).cuda(), Variable(x2).cuda())
pos_distance = F.pairwise_distance(f0, f1)
neg_distance = F.pairwise_distance(f0, f2)
imshow(torchvision.utils.make_grid(concat), 'Dissimilarities: ({:.2f}, {:.2f})'.format(pos_distance.cpu().data.numpy()[0][0],
neg_distance.cpu().data.numpy()[0][0]))

خروجی کد های بالا باید تصاویری به شکل زیر باشند:

تشخیص-چهره-با-هوش-مصنوعی

بازشناسی چهره (Face Recognition)

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

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

و در نهایت یک پروژه عملی با استفاده از پایتون و پایتورچ اجرا کردیم و توانستیم یک سیستم شناسایی چهره با هوش مصنوعی بسازیم؛

author-avatar

درباره محمد اسماعیلی

علاقه مند به مفاهیم هوش مصنوعی، دیتاساینس و سئو؛ مطالبی که برام جالب باشه رو اینجا می نویسم، و این دلیل بر متخصص بودن من در اون حوزه ها نمیشه😊

نوشته های مرتبط

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

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