حمله تخاصمی (Adversarial Attack) با مدل FGSM در پایتون — راهنمای کاربردی

بسیاری از افرادی که در حوزه «یادگیری ماشین» (Machine Learning | ML) فعالیت و پژوهش میکنند به خوبی آگاه هستند که برخی از مدلهای یادگیری ماشین چقدر عملکرد تاثیرگذاری دارند. پژوهشهایی که در این حوزه انجام میشود موجب شده تا مدلها سریعتر، صحیحتر و موثرتر از قبل بشوند. اگرچه، جنبهای از طراحی و آموزش مدل که اغلب نادیده گرفته میشود امنیت و استحکام مدل است. این موضوع به طور خاص هنگامی اهمیت پیدا میکند که یک متخاصم سعی در گول زدن مدل داشته باشد. این مساله ممکن است بسیاری از افراد را حیرت زده کند که اضافه کردن برخی اختلالات (آشفتگیهای) نامحسوس در تصاویر، میتواند کارایی مدل را به شدت متفاوت کند. با در نظر گرفتن این موضوع، در این راهنما مثالی پیرامون یک دستهبند متنی ارائه خواهد شد. در این مطلب، به طور خاص از یکی از اولین و متداولترین نوع حملات با عنوان «حمله علامت گرادیان سریع» (Fast Gradient Sign Attack | FCSA) برای گول زدن یک «دستهبند» (Classifier) از نوع MNIST استفاده خواهد شد. با مطالعه این راهنما و کسب شناخت از حمله تخاصمی (Adversarial Attack) با مدل FGSM در پایتون، آگاهی مخاطبان پیرامون آسیبپذیریهای امنیتی مدلهای یادگیری ماشین افزایش پیدا میکند و بینشی پیرامون یادگیری ماشین تخاصمی به دست میآورند.
مدل مخرب
دستههای گوناگونی از حملات تخاصمی وجود دارند. هر یک از این حملات دارای هدف و فرضی متفاوت بر اساس دانش مهاجم هستند. اگرچه، به طور کلی هدف کلاهبرداری اضافه کردن مقداری انحراف (آشفتگی) به دادههای ورودی به منظور انجام دستهبندی غلط توسط مدل روی آنها است. چندین نوع از فرضیات از دانش مهاجم پیرامون مدل وجود دارد که دو مورد از آنها «جعبه سفید» (white-box) و «جعبه سیاه» (black-box) هستند. یک حمله جعبه سفید فرض میکند که مهاجم دارای دانش و دسترسی کامل به مدل شامل معماری، ورودیها، خروجیها و وزنهای آن است.
در یک حمله جعبه سیاه، فرض بر این است که حمله کننده تنها دارای دسترسی به ورودیها و خروجیهای مدل است و هیچ چیز درباره معماری یا وزنهای مدل نمیداند. همچنین، انواع گوناگونی از اهداف نیز وجود دارد که از این جمله میتوان به دستهبندی غلط و دستهبندی غلط منبع/هدف اشاره کرد. در دستهبندی غلط هدف متخاصم فقط این است که دستهبندی خروجی غلط باشد، اما اهمیتی نمیدهد که دستهبندی جدید چیست. دستهبندی منبع/هدف، بدین معنا است که متخاصم میخواهد یک تصویر که در اصل از یک کلاس منبع خاص است را در یک کلاس هدف خاص (اشتباه) دستهبندی کند. در مثال ارائه شده در این مطلب، حمله FGSM که یک حمله جعبه سفید است و هدف دستهبندی غلط انجام میشود، پیادهسازی شده است. با این اطلاعات پیشزمینهای، اکنون میتوان پیرامون جزئیات حمله بحث کرد.
حمله علامت گرادیان سریع
یکی از محبوبترین نوع حملات تخاصمی تا به امروز «Fast Gradient Sign Attack | FGSM» است و توسط «ایان گودفِلو» (Ian Goodfellow) و همکاران در مقالهای با عنوان «تشریح و بهرهبرداری از مثالهای تخاصمی» (Explaining and Harnessing Adversarial Examples) ارائه شده است. این حمله به طور قابل توجهی قدرتمند و نوآورانه است. FGSM برای حمله به شبکههای عصبی با نفوذ به روش یادگیری آنها یعنی «گرادیان» (Gradients) طراحی شده است.
ایده این کار ساده است و به جای کار برای کمینه کردن «زیان» (Loss) با تنظیم وزنها بر پایه «گرادیان بازگشت به عقب» (backpropagated gradients)، حمله دادههای ورودی را با هدف بیشینهسازی زیان برپایه بازگشت به عقب مشابهی تنظیم میکند. به عبارت دیگر، حمله از گرادیان زیان w.r.t داده ورودی استفاده و سپس داده ورودی را برای بیشینه کردن زیان تنظیم میکند. پیش از آنکه کد لازم برای این کار ارائه شود، نگاهی به مثال معروف پاندا FGSM انداخته و نکاتی پیرامون آن بیان میشود.
با توجه به تصویر بالا، x تصویر ورودی اصلی است که به درستی به عنوان «Panda» شناسایی میشود، y برچسب حقیقت زمینهای برای x محسوب میشود و θ پارامترهای مدل را نشان میدهد. (J(θ,x,y هزینهای است که برای آموزش دادن شبکه مورد استفاده قرار میگیرد. حمله به منظور محاسبه (xJ(θ,x,y∇، گرادیان را بازگشت به عقب به دادههای ورودی میدهد. سپس، دادههای ورودی را با یک گام کوچک (ϵ یا 0.007 در تصویر) در جهتی که ((sign(∇xJ(θ,x,y) زیان را بیشینه میسازد تنظیم میکند. تصویر آaفته حاصل شده ′x است که توسط شبکه هدف به اشتباه به عنوان «میمون دراز دست» (gibbon) دستهبندی شده، در حالی که میتوان به وضوح دید که یک پاندا است. خوشبختانه اکنون دیگر مفاهیم نهفته در پس این راهنما واضح است، بنابراین میتوان مستقیم به سراغ پیادهسازی رفت.
from __future__ import print_function import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim from torchvision import datasets, transforms import numpy as np import matplotlib.pyplot as plt
پیادهسازی حمله تخاصمی (Adversarial Attack) با مدل FGSM
در این بخش، به پارامترهای ورودی پرداخته خواهد شد و سپس حمله با پایتون پیادهسازی و آزمونهایی اجرا میشود.
ورودیها
تنها سه ورودی برای این روش وجود دارد که به صورت زیر تعریف شدهاند:
اپسیلونها (epsilons): لیست مقادیر اپسیلون برای استفاده در اجرا است. اهمیت دارد که ۰ را در لیست نگاه داشت زیرا نشانگر کارایی مدل در مجموعه تست اصلی است. همچنین، به طور مستقیم انتظار اپسیلون بزرگتر و آشفتگیهای قابل توجهتری میرود، اما در عین حال انتظار میرود که حملات موثرتری باشند که بیشتر بتوانند صحت مدل را کاهش دهند. از آنجا که طیف دادهها در اینجا [0,1] است، هیچ مقدار اپسیلونی نباید از یک تجاوز کند.
pretrained_model: مسیری به مدل MNIST از پیشآموزش داده شده با pytorch/examples/mnist است. برای سادگی، میتوان مدل از پیش آموزش داده شده را از اینجا (+) دانلود کرد.
use_cuda: «پرچم دودویی» (boolean flag) برای استفاد از CUDA در صورت تمایل و در دسترس بودن است. شایان توجه است که GPU با CUDA در این راهنما امری الزامی نیست، زیرا CPU هم زمان اجرای زیادی ندارد.
epsilons = [0, .05, .1, .15, .2, .25, .3] pretrained_model = "data/lenet_mnist_model.pth" use_cuda=True
مدل زیر حمله
همانطور که پیشتر بیان شد، مدل تحت حمله مشابه مدل MNIST موجود در pytorch/examples/mnist است. کاربر ممکن است مدل MNIST خودش را آموزش بدهد و ذخیره کند یا میتواند مدل آماده شده موجود را دانلود و استفاده کند. تعریف Net و «بارگذار داده آزمون» (test dataloader) در اینجا از مثال MNIST کپی شدهاند. هدف این بخش تعریف مدل و بارگذار داده و سپس مقداردهی اولیه مدل و بارگذاری وزنهای از پیش تعریف شده است.
# LeNet Model definition class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(1, 10, kernel_size=5) self.conv2 = nn.Conv2d(10, 20, kernel_size=5) self.conv2_drop = nn.Dropout2d() self.fc1 = nn.Linear(320, 50) self.fc2 = nn.Linear(50, 10) def forward(self, x): x = F.relu(F.max_pool2d(self.conv1(x), 2)) x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2)) x = x.view(-1, 320) x = F.relu(self.fc1(x)) x = F.dropout(x, training=self.training) x = self.fc2(x) return F.log_softmax(x, dim=1) # MNIST Test dataset and dataloader declaration test_loader = torch.utils.data.DataLoader( datasets.MNIST('../data', train=False, download=True, transform=transforms.Compose([ transforms.ToTensor(), ])), batch_size=1, shuffle=True) # Define what device we are using print("CUDA Available: ",torch.cuda.is_available()) device = torch.device("cuda" if (use_cuda and torch.cuda.is_available()) else "cpu") # Initialize the network model = Net().to(device) # Load the pretrained model model.load_state_dict(torch.load(pretrained_model, map_location='cpu')) # Set the model in evaluation mode. In this case this is for the Dropout layers model.eval()
خروجی:
Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz to ../data/MNIST/raw/train-images-idx3-ubyte.gz Extracting ../data/MNIST/raw/train-images-idx3-ubyte.gz Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz to ../data/MNIST/raw/train-labels-idx1-ubyte.gz Extracting ../data/MNIST/raw/train-labels-idx1-ubyte.gz Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz to ../data/MNIST/raw/t10k-images-idx3-ubyte.gz Extracting ../data/MNIST/raw/t10k-images-idx3-ubyte.gz Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz to ../data/MNIST/raw/t10k-labels-idx1-ubyte.gz Extracting ../data/MNIST/raw/t10k-labels-idx1-ubyte.gz Processing... Done! CUDA Available: True
حمله FGSM
اکنون، میتوان تابعی که مثالهای تخاصمی را با آشفته کردن دادههای اصلی میسازد تعریف کرد. تابع fgsm_attack سه ورودی را دریافت میکند. image همان تصویر سالم (x)، ورودی epsilon مقدار آشفتگی متناسب با پیکسل (ϵ)، و data_grad گرادیان زیان از زیان w.r.t تصویر ورودی ((xJ(θ,x,y∇) است. تابع در ادامه تصویر منحرف شده را به صورت زیر میسازد:
((perturbed_image=image+epsilon∗sign(data_grad)=x+ϵ∗sign(∇xJ(θ,x,y
در نهایت، به منظور نگهداری طیف اصلی دادهها، تصویر منحرف شده در دامنه [0,1] محدود شده است.
# FGSM attack code def fgsm_attack(image, epsilon, data_grad): # Collect the element-wise sign of the data gradient sign_data_grad = data_grad.sign() # Create the perturbed image by adjusting each pixel of the input image perturbed_image = image + epsilon*sign_data_grad # Adding clipping to maintain [0,1] range perturbed_image = torch.clamp(perturbed_image, 0, 1) # Return the perturbed image return perturbed_image
تابع تست
در نهایت، نتیجه مرکزی این راهنما از تابع test میآید. هر فراخوانی این تابع تست یک گام تست کامل را در مجموعه تست MNIST اجرا میکند و صحت نهایی را گزارش میدهد. اگرچه، شایان توجه است که این تابع نیز یک ورودی epsilon را دریافت میکند. این بدین دلیل به وقوع میپیوندد که تابع test صحت مدلی که تحت حمله متخاصم است را گزارش میکند. به طور مشخصتر باید گفت که برای هر نمونه در مجموعه تست، تابع گرادیان زیان w.r.t داده ورودی (data_grad) را محاسبه میکند، یک تصویر منحرف شده با (fgsm_attack (perturbed_data میسازد و سپس بررسی میکند که آیا مثال آشفته شده تخاصمی است. علاوه بر تست صحت مدل، تابع مثالهای تخاصمی موفقی را برای آنکه بعدا بصریسازی شوند ذخیره میکند و باز میگرداند.
def test( model, device, test_loader, epsilon ): # Accuracy counter correct = 0 adv_examples = [] # Loop over all examples in test set for data, target in test_loader: # Send the data and label to the device data, target = data.to(device), target.to(device) # Set requires_grad attribute of tensor. Important for Attack data.requires_grad = True # Forward pass the data through the model output = model(data) init_pred = output.max(1, keepdim=True)[1] # get the index of the max log-probability # If the initial prediction is wrong, dont bother attacking, just move on if init_pred.item() != target.item(): continue # Calculate the loss loss = F.nll_loss(output, target) # Zero all existing gradients model.zero_grad() # Calculate gradients of model in backward pass loss.backward() # Collect datagrad data_grad = data.grad.data # Call FGSM Attack perturbed_data = fgsm_attack(data, epsilon, data_grad) # Re-classify the perturbed image output = model(perturbed_data) # Check for success final_pred = output.max(1, keepdim=True)[1] # get the index of the max log-probability if final_pred.item() == target.item(): correct += 1 # Special case for saving 0 epsilon examples if (epsilon == 0) and (len(adv_examples) < 5): adv_ex = perturbed_data.squeeze().detach().cpu().numpy() adv_examples.append( (init_pred.item(), final_pred.item(), adv_ex) ) else: # Save some adv examples for visualization later if len(adv_examples) < 5: adv_ex = perturbed_data.squeeze().detach().cpu().numpy() adv_examples.append( (init_pred.item(), final_pred.item(), adv_ex) ) # Calculate final accuracy for this epsilon final_acc = correct/float(len(test_loader)) print("Epsilon: {}\tTest Accuracy = {} / {} = {}".format(epsilon, correct, len(test_loader), final_acc)) # Return the accuracy and an adversarial example return final_acc, adv_examples
اجرای حمله
آخرین بخش از پیادهسازی آن است که در واقع حمله اجرا شود. در اینجا، یک گام تست کامل برای هر مقدار اپسیلون در ورودی epsilons اجرا میشود. برای هر اپسیلون، صحت نهایی و برخی از مثالهای تخاصمی موفق برای ترسیم شدن در بخشهای آتی ذخیره میشوند. شایان توجه است که صحتهای پرینت شده چگونه با افزایش مقدار اپسیلونها، کاهش پیدا میکنند. حالت ϵ=0 که صحت تست اصلی را بدون حمله نشان میدهد نیز جالب توجه است.
accuracies = [] examples = [] # Run test for each epsilon for eps in epsilons: acc, ex = test(model, device, test_loader, eps) accuracies.append(acc) examples.append(ex)
خروجی:
Epsilon: 0 Test Accuracy = 9810 / 10000 = 0.981 Epsilon: 0.05 Test Accuracy = 9426 / 10000 = 0.9426 Epsilon: 0.1 Test Accuracy = 8510 / 10000 = 0.851 Epsilon: 0.15 Test Accuracy = 6826 / 10000 = 0.6826 Epsilon: 0.2 Test Accuracy = 4301 / 10000 = 0.4301 Epsilon: 0.25 Test Accuracy = 2082 / 10000 = 0.2082 Epsilon: 0.3 Test Accuracy = 869 / 10000 = 0.0869
نتایج
اولین نتیجه، نمودار «صحت» (accuracy) در مقایسه با «اپسیلون» (epsilon) است. همانطور که پیشتر اشاره شد، با افزایش اپسیلون، انتظار میرود که صحت مدل کاهش پیدا کند. این امر بدین دلیل است که هرچه اپسیلون بیشتر باشد این معنی را میدهد که گام بزرگتری در جهتی که زیان را بیشینه میکند برداشته شده است.
شایان توجه است که گرایش در این منحنی خطی نیست، حتی هنگامی که مقادیر اپسیلون فاصلهگذاری خطی شدهاند. برای مثال، صحت در ϵ=0.05 تنها حدود ٪۴ کمتر از ϵ=0 است، اما صحت در ϵ=0.2 به میزان ٪۲۵ کمتر از ϵ=0.15 است. همچنین، شایان توجه است که صحت مدل بر صحت تصادفی برای ۱۰ کلاس دستهبند بین ϵ=0.25 و ϵ=0.3 چیره میشود.
plt.figure(figsize=(5,5)) plt.plot(epsilons, accuracies, "*-") plt.yticks(np.arange(0, 1.1, step=0.1)) plt.xticks(np.arange(0, .35, step=0.05)) plt.title("Accuracy vs Epsilon") plt.xlabel("Epsilon") plt.ylabel("Accuracy") plt.show()

نمونه مثال تخاصمی
با توجه به مفهوم «no free lunch»، در این شرایط، با افزایش اپسیلون، صحت کل کاهش پیدا میکند، اما انحراف راحت تر قابل مشاهده میشود. در واقعیت، موازنهای بین کاهش صحت و آشفتگی وجود دارد که یک مهاجم باید آن را در نظر داشته باشد. در اینجا، مثالهایی از موفقیت نمونههای تخاصمی در هر مقدار اپسیلون ارائه شده است. هر سطر از نمودار یک مقدار اپسیلون متفاوت از خود نشان میدهد.
سطر اول مثالهای ϵ=0 هستند که تصوویر «پاک» اولیه را بدون هرگونه انحراف نشان میدهد. عنوان هر تصویر «original classification -> adversarial classification» را نشان میدهد. شایان توجه است که در همه شرایطها انسانها همچنان قادر به شناسایی دسته صحیح علارغم وجود نویز هستند.
# Plot several examples of adversarial samples at each epsilon cnt = 0 plt.figure(figsize=(8,10)) for i in range(len(epsilons)): for j in range(len(examples[i])): cnt += 1 plt.subplot(len(epsilons),len(examples[0]),cnt) plt.xticks([], []) plt.yticks([], []) if j == 0: plt.ylabel("Eps: {}".format(epsilons[i]), fontsize=14) orig,adv,ex = examples[i][j] plt.title("{} -> {}".format(orig, adv)) plt.imshow(ex, cmap="gray") plt.tight_layout() plt.show()

کد منبع کامل FGSM در پایتون
Jupyter Notebook fgsm_tutorial.py a few seconds ago Python File Edit View Language 327 # Results 328 # ------- 329 # 330 # Accuracy vs Epsilon 331 # ~~~~~~~~~~~~~~~~~~~ 332 # 333 # The first result is the accuracy versus epsilon plot. As alluded to 334 # earlier, as epsilon increases we expect the test accuracy to decrease. 335 # This is because larger epsilons mean we take a larger step in the 336 # direction that will maximize the loss. Notice the trend in the curve is 337 # not linear even though the epsilon values are linearly spaced. For 338 # example, the accuracy at :math:`\epsilon=0.05` is only about 4% lower 339 # than :math:`\epsilon=0`, but the accuracy at :math:`\epsilon=0.2` is 25% 340 # lower than :math:`\epsilon=0.15`. Also, notice the accuracy of the model 341 # hits random accuracy for a 10-class classifier between 342 # :math:`\epsilon=0.25` and :math:`\epsilon=0.3`. 343 # 344 345 plt.figure(figsize=(5,5)) 346 plt.plot(epsilons, accuracies, "*-") 347 plt.yticks(np.arange(0, 1.1, step=0.1)) 348 plt.xticks(np.arange(0, .35, step=0.05)) 349 plt.title("Accuracy vs Epsilon") 350 plt.xlabel("Epsilon") 351 plt.ylabel("Accuracy") 352 plt.show() 353 354 355 ###################################################################### 356 # Sample Adversarial Examples 357 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~ 358 # 359 # Remember the idea of no free lunch? In this case, as epsilon increases 360 # the test accuracy decreases **BUT** the perturbations become more easily 361 # perceptible. In reality, there is a tradeoff between accuracy 362 # degredation and perceptibility that an attacker must consider. Here, we 363 # show some examples of successful adversarial examples at each epsilon 364 # value. Each row of the plot shows a different epsilon value. The first 365 # row is the :math:`\epsilon=0` examples which represent the original 366 # “clean” images with no perturbation. The title of each image shows the 367 # “original classification -> adversarial classification.” Notice, the 368 # perturbations start to become evident at :math:`\epsilon=0.15` and are 369 # quite evident at :math:`\epsilon=0.3`. However, in all cases humans are 370 # still capable of identifying the correct class despite the added noise. 371 # 372 373 # Plot several examples of adversarial samples at each epsilon 374 cnt = 0 375 plt.figure(figsize=(8,10)) 376 for i in range(len(epsilons)): 377 for j in range(len(examples[i])): 378 cnt += 1 379 plt.subplot(len(epsilons),len(examples[0]),cnt) 380 plt.xticks([], []) 381 plt.yticks([], []) 382 if j == 0: 383 plt.ylabel("Eps: {}".format(epsilons[i]), fontsize=14) 384 orig,adv,ex = examples[i][j] 385 plt.title("{} -> {}".format(orig, adv)) 386 plt.imshow(ex, cmap="gray") 387 plt.tight_layout() 388 plt.show() 389 390 391 ###################################################################### 392 # Where to go next? 393 # ----------------- 394 # 395 # Hopefully this tutorial gives some insight into the topic of adversarial 396 # machine learning. There are many potential directions to go from here. 397 # This attack represents the very beginning of adversarial attack research 398 # and since there have been many subsequent ideas for how to attack and 399 # defend ML models from an adversary. In fact, at NIPS 2017 there was an 400 # adversarial attack and defense competition and many of the methods used 401 # in the competition are described in this paper: `Adversarial Attacks and 402 # Defences Competition <https://arxiv.org/pdf/1804.00097.pdf>`__. The work 403 # on defense also leads into the idea of making machine learning models 404 # more *robust* in general, to both naturally perturbed and adversarially 405 # crafted inputs. 406 # 407 # Another direction to go is adversarial attacks and defense in different 408 # domains. Adversarial research is not limited to the image domain, check 409 # out `this <https://arxiv.org/pdf/1801.01944.pdf>`__ attack on 410 # speech-to-text models. But perhaps the best way to learn more about 411 # adversarial machine learning is to get your hands dirty. Try to 412 # implement a different attack from the NIPS 2017 competition, and see how 413 # it differs from FGSM. Then, try to defend the model from your own 414 # attacks. 415 # 416 417
کل زمان اجرای این اسکریپت: ۳ دقیقه و ۷.۳۳۳ ثانیه
گام بعدی
خوشبختانه این راهنما بینش خوبی از یادگیری ماشین تخاصمی فراهم میکند. جهتهای بالقوه زیادی وجود دارند که میتوان در ادامه به سمت آنها رفت. این نوع از حمله، شروعی بسیار مقدماتی برای پژوهشهای حملات تخاصمی است و از سوی دیگر، موضوعی که به آن پرداخته میشود چگونگی مقابله با این حملههای تخاصمی برای دفاع از یک مدل یادگیری ماشین در مقابل یک متخاصم است.
در کنفرانس NIPS 2017 یک رقابت حمله و دفاع برگزار شد و بسیاری از روشهای مورد استفاده در این رقابت در این مقاله (+) معرفی شدهاند. کار روی روشهای دفاع نیز منجر به ایده ساخت مدلهای یادگیری ماشین «مستحکم» (robust) به صورت کلی هم برای آشفتگی طبیعی و هم آشفتگی ناشی از حملات تخاصمی شده است.
دیگر جهتی که میتوان در آن گام برداشت، حملات تخاصمی و دفاع در برابر آنها در دامنههای گوناگون است. پژوهشهای تخاصمی به دامنه تصاویر محدود نشدهاند. برای مثال، این حمله (+) را روی مدلهای گفتار به متن (speech-to-text models) میتوان مشاهده کرد. برای یادگیری بیشتر، میتوان یکی دیگر از مدلهای رقابتهای NIPS 2017 را پیادهسازی و تفاوتهای آن با FGSM را مشاهده کرد. سپس از مدل در مقابل حملات دفاع کرد.
اگر نوشته بالا برای شما مفید بود، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای هوش محاسباتی
- آموزش یادگیری عمیق (Deep learning)
- مجموعه آموزشهای آمار، احتمالات و دادهکاوی
- فراگیری مفاهیم هوش مصنوعی — مجموعه مقالات جامع وبلاگ فرادرس
- دادهکاوی (Data Mining) — از صفر تا صد
- یادگیری عمیق (Deep Learning) با پایتون — به زبان ساده
^^