Обнаружьте аномалии изображений Используя объяснимую нейронную сеть классификации одного класса

В этом примере показано, как обнаружить и локализовать аномалии, такие как трещины в конкретной использующей объяснимой классификации единых классов.

В подходах одного класса к обнаружению аномалии обучение полуконтролируется, означая, что сеть обучается на данных, состоящих только из примеров изображений без аномалий [1]. Несмотря на обучение на выборках только нормальных сцен, модель изучает, как различать нормальные и anamalous сцены. Изучение одного класса предлагает много преимуществ для проблем обнаружения аномалии:

  1. Представления аномалий могут быть недостаточными.

  2. Аномалии могут представлять дорогие или catastophic результаты.

  3. Может быть много видов аномалий, и виды аномалий могут переключить время жизни модели. Описание, какие "хорошие" взгляды как, чем часто более выполнимы, чем обеспечение данных, которые представляют все возможные аномалии в настройках реального мира.

Ключевая роль обнаружения аномалии для наблюдателя - человека, чтобы смочь изучить, почему обучивший сеть классифицирует изображения как аномалии. Объяснимая классификация добавляет предсказание класса с информацией, которая выравнивает по ширине, как нейронная сеть достигла своего решения классификации.

Этот пример исследует, как глубокое обучение одного класса может использоваться, чтобы создать точные классификаторы обнаружения аномалии. Пример также реализует объяснимую классификацию с помощью сети, которая возвращает тепловую карту с вероятностью, что каждый пиксель является аномальным.

Загрузите конкретные взломанные изображения для набора данных классификации

Этот пример работает с Конкретными Взломанными Изображениями для набора данных Классификации. Набор данных содержит изображения двух классов: Negative изображения без трещин, существующих на дороге и Positive изображения с трещинами. Набор данных обеспечивает 20 000 изображений каждого класса. Размер набора данных составляет 235 Мбайт.

Установите dataDir как желаемое местоположение набора данных.

dataDir = fullfile(tempdir,"ConcreteCracks");
if ~exist(dataDir,"dir")
    mkdir(dataDir);
end

Чтобы загрузить набор данных, перейдите к этой ссылке: https://md-datasets-cache-zipfiles-prod.s3.eu-west-1.amazonaws.com/5y9wdsg2zt-2.zip. Извлеките zip-файл, чтобы получить файл RAR, затем извлеките содержимое файла RAR в директорию, заданную dataDir переменная. Когда извлечено успешно, dataDir содержит два подкаталога: Negative и Positive.

Загрузите и предварительно обработайте данные

Создайте imageDatastore это читает и управляет данными изображения. Пометьте каждое изображение как Positive или Negative согласно имени его директории.

imdsP = imageDatastore(fullfile(dataDir,"Positive"),LabelSource="foldernames");
imdsN = imageDatastore(fullfile(dataDir,"Negative"),LabelSource="foldernames");

Отобразите пример каждого класса. Отобразите отрицательное, или хорошее, изображение без взломанных аномалий слева. В хорошем изображении малы недостатки и отклонения в структуре. Отобразите положительное, или аномальный, отобразите справа. Аномальное изображение показывает большую черную трещину, ориентированную вертикально.

samplePositive = preview(imdsP);
sampleNegative = preview(imdsN);
montage({sampleNegative,samplePositive})
title("Road Images Without (Left) and With (Right) Crack Anomalies")

Данные о разделе в обучение, валидацию и наборы тестов

Чтобы симулировать более типичный полуконтролируемый рабочий процесс, создайте набор обучающих данных только 250 хороших изображений. Включайте небольшую коллекцию аномальных учебных изображений, чтобы обеспечить лучшие результаты классификации. Создайте сбалансированный набор тестов с помощью 1 000 изображений от каждого класса. Создайте сбалансированный набор валидации с помощью 100 изображений каждого класса.

numTrainNormal = 250;
numTrainAnomaly = 10;
numTest = 1000;
numVal = 100;

[dsTrainPos,dsTestPos,dsValPos,~] = splitEachLabel(imdsP,numTrainAnomaly,numTest,numVal);
[dsTrainNeg,dsTestNeg,dsValNeg,~] = splitEachLabel(imdsN,numTrainNormal,numTest,numVal);

dsTrain = imageDatastore(cat(1,dsTrainPos.Files,dsTrainNeg.Files),LabelSource="foldernames");
dsTest = imageDatastore(cat(1,dsTestPos.Files,dsTestNeg.Files),LabelSource="foldernames");
dsVal = imageDatastore(cat(1,dsValPos.Files,dsValNeg.Files),LabelSource="foldernames");

Задайте анонимную функцию, addLabelFcn, это создает одногорячее закодированное представление информации о метке от входного изображения. Затем преобразуйте хранилища данных с помощью transform функционируйте таким образом, что хранилища данных возвращают массив ячеек данных изображения и соответствующего одногорячего закодированного массива. transform функция применяет операции, заданные анонимным addLabelFcn функция.

addLabelFcn = @(x,info) deal({x,onehotencode(info.Label,1)},info);
dsTrain = transform(dsTrain,addLabelFcn,IncludeInfo=true);
dsVal = transform(dsVal,addLabelFcn,IncludeInfo=true);
dsTest = transform(dsTest,addLabelFcn,IncludeInfo=true);

Увеличьте обучающие данные

Увеличьте обучающие данные при помощи transform функция с пользовательскими операциями предварительной обработки, заданными помощником, функционирует augmentDataForConcreteClassification. Функция помощника присоединена к примеру как к вспомогательному файлу.

augmentDataForConcreteClassification функция случайным образом применяет 90 вращений степени и горизонтальное и вертикальное отражение к каждому входному изображению.

dsTrain = transform(dsTrain,@augmentDataForConcreteClassification);

Пакетные обучающие данные

Создайте minibatchqueue Объект (Deep Learning Toolbox), который справляется с мини-пакетной обработкой наблюдений в пользовательском учебном цикле. minibatchqueue возразите также бросает данные к dlarray Объект (Deep Learning Toolbox), который включает автоматическое дифференцирование в применении глубокого обучения.

Задайте мини-пакетный формат экстракции данных как "SSCB" (пространственный, пространственный, канал, пакет). Установите DispatchInBackground аргумент значения имени как boolean, возвращенный canUseGPU. Если поддерживаемый графический процессор доступен для расчета, то minibatchqueue объект предварительно обрабатывает мини-пакеты в фоновом режиме в параллельном пуле во время обучения.

mbSize = 128;
mbqTrain = minibatchqueue(dsTrain,PartialMiniBatch="discard", ...
    MiniBatchFormat=["SSCB","CB"],MiniBatchSize=mbSize);

mbqVal = minibatchqueue(dsVal,MiniBatchFormat=["SSCB","CB"],MiniBatchSize=mbSize);

Вычислите статистику набора обучающих данных для входной нормализации

Вычислите среднее значение на канал учебных изображений для использования в нулевой средней нормализации.

queue = copy(mbqTrain);
queue.PartialMiniBatch = "return";

X = next(queue);
sumImg = sum(X,4);

while hasdata(queue)
    X = next(queue);
    sumImg = sumImg + sum(X,4);
end
trainSetMean = sumImg ./ dsTrain.numpartitions;
trainSetMean = mean(trainSetMean,[1 2]);

Создайте модель FCDD

Этот пример использует модель [1] полностью сверточного описания данных (FCDD). Основная идея о FCDD состоит в том, чтобы обучить сеть, чтобы произвести тепловую карту из входного изображения.

Этот пример использует сеть VGG-16 [3] обученный на ImageNet [4] как основная полностью сверточная сетевая архитектура. Пример замораживает большинство модели и случайным образом инициализирует и обучает итоговые сверточные этапы. Этот подход включает быстрое обучение с небольшими количествами входных обучающих данных.

vgg16 (Deep Learning Toolbox) функция возвращает предварительно обученную сеть VGG-16. Эта функция требует Модели Deep Learning Toolbox™ для пакета Сетевой поддержки VGG-16. Если этот пакет поддержки не установлен, то функция обеспечивает ссылку на загрузку.

pretrainedVGG = vgg16;

Заморозьте первые 24 слоя сети путем установки весов и смещения к 0.

numLayersToFreeze = 24;
net = pretrainedVGG.Layers(1:numLayersToFreeze);
for idx = 1:numLayersToFreeze
    if isprop(net(idx),"Weights")
        net(idx) = setLearnRateFactor(net(idx),Weights=0);
        net(idx) = setLearnRateFactor(net(idx),Bias=0);
    end
end

Добавьте итоговый сверточный этап. Этот этап похож на следующий сверточный этап VGG-16, но со случайным образом инициализированными и обучаемыми сверточными слоями и с нормализацией партии. Итоговая свертка 1 на 1 сжимает сетевой выход в тепловую карту с одним каналом. Последний слой является функцией потерь Псеудо-Хубера, используемой, чтобы стабилизировать обучение с потерей FCDD 1[] 2[].

finalLayers = [
    convolution2dLayer(3,512,Padding="same")
    batchNormalizationLayer
    reluLayer
    convolution2dLayer(3,512,Padding="same")
    batchNormalizationLayer
    reluLayer
    convolution2dLayer(1,1)
    functionLayer(@(x)sqrt(x.^2+1)-1,Name="pseudoHuber")];

Соберите полную сеть.

net = dlnetwork([net;finalLayers]);
lgraph = layerGraph(net);

Замените входной слой изображений в энкодере с новым входным слоем, который выполняет нулевую центральную нормализацию с помощью вычисленного среднего значения. Установите входной размер сети, равной размеру изображений в наборе данных.

inputSize = size(sampleNegative);
newInputLayer = imageInputLayer(inputSize,Normalization="zerocenter", ...
    Mean=extractdata(trainSetMean),Name="inputLayer");
lgraph = replaceLayer(lgraph,lgraph.Layers(1).Name,newInputLayer);

Задайте опции обучения

Задайте опции обучения для оптимизации Адама. Обучите сеть в течение 70 эпох.

learnRate = 1e-4;
averageGrad = [];
averageSqGrad = [];
numEpochs = 70;

Обучите сеть или загрузите предварительно обученную сеть

По умолчанию этот пример загружает предварительно обученную версию сети VGG-16 с помощью функции помощника downloadTrainedConcreteClassificationNet. Предварительно обученная сеть может использоваться, чтобы запустить целый пример, не ожидая обучения завершиться.

Чтобы обучить сеть, установите doTraining переменная в следующем коде к true. Обучите модель в пользовательском учебном цикле. Для каждой итерации:

  • Считайте данные для текущего мини-пакета с помощью next (Deep Learning Toolbox) функция.

  • Оцените градиенты модели с помощью dlfeval (Deep Learning Toolbox) функция и modelGradients функция помощника. Эта функция задана в конце этого примера.

  • Обновите сетевые параметры с помощью adamupdate (Deep Learning Toolbox) функция.

  • Обновите график потери.

Обучайтесь на одном или нескольких графических процессорах при наличии. Используя графический процессор требует Parallel Computing Toolbox™, и CUDA® включил NVIDIA® графический процессор. Для получения дополнительной информации смотрите Поддержку графического процессора Релизом (Parallel Computing Toolbox). Обучение занимает приблизительно 7 минут на Титане NVIDIA™ X.

doTraining = false;
if doTraining
   
    [hFig,batchLine] = initializeTrainingPlotConcreteClassification;
    start = tic;
    
    iteration = 0;
    for epoch = 1:numEpochs
        reset(mbqTrain);
        shuffle(mbqTrain);
    
        while hasdata(mbqTrain)
            [X,T] = next(mbqTrain);
            [grad,loss,state] = dlfeval(@modelGradients,net,X,T);
    
            iteration = iteration + 1;
    
            [net,averageGrad,averageSqGrad] = adamupdate(net, ...
                grad,averageGrad,averageSqGrad,iteration,learnRate);
    
            net.State = state;
    
            % Update the plot
            updateTrainingPlotConcreteClassification(batchLine,iteration,loss,start,epoch);
        end
    end
else
    trainedConcreteClassificationNet_url = 'https://www.mathworks.com/supportfiles/vision/data/trainedAnomalyDetectionNet.zip';
    downloadTrainedConcreteClassificationNet(trainedConcreteClassificationNet_url,dataDir);
    load(fullfile(dataDir,"trainedAnomalyDetectionNet.mat"));
end

Создайте модель классификации

Классифицируйте изображение как хорошее или аномальное использование тепловой карты аномалии, предсказанной сетью на двух шагах. Во-первых, создайте классификатор, который возвращает вероятность, что изображение имеет аномалию на основе тепловой карты, предсказанной обучившим сеть. Затем можно присвоить метку класса, чтобы протестировать изображения на основе вероятности, возвращенной классификатором.

Вычислите средний счет аномалии и известную метку основной истины (Positive или Negative) для каждого изображения в наборе валидации. Этот пример выбирает порог счета аномалии с помощью набора валидации, чтобы не пропускать информацию от набора тестов в проект классификатора.

reset(mbqVal);

scores = zeros(dsVal.numpartitions,1);
labels = zeros(dsVal.numpartitions,1);
idx = 1;
while hasdata(mbqVal)
    [X,T] = next(mbqVal);
    batchSize = size(X,4);
    Y = predict(net,X);
    Y = mean(Y,[1 2]);
    T = onehotdecode(T,[0 1],1,"double");
    scores(idx:idx+batchSize-1) = Y;
    labels(idx:idx+batchSize-1) = T;
    idx = idx+batchSize;
end

Соответствуйте сигмоидальному к баллам аномалии и сопоставленным меткам класса. Получившаяся анонимная функция, anomalyProbability, берет входной счет аномалии, предсказанный сетью, и возвращает вероятность, что счет описывает аномалию. Выходные параметры от anomalyProbability больше, чем 0,5 рассматриваются аномалиями.

coeff = glmfit(scores,labels,"binomial","link","logit");
sigmoid = @(x) 1./(1+exp(-x));
anomalyProbability = @(x) sigmoid(coeff(2).*x + coeff(1));
fplot(anomalyProbability)
xlabel("Anomaly Score")
ylabel("Probability of Anomaly")

Сигмоидальное значение по умолчанию имеет значение 0,5 в счете 0. Вычислите счет аномалии в который подходящее сигмоидальное, заданное anomalyProbability функция имеет значение 0,5.

threshold = -coeff(1)/coeff(2)
threshold = 0.1567

Оцените модель классификации

Предскажите тепловую карту аномалии и вычислите средний счет аномалии к каждому изображению в наборе тестов. Также получите метки основной истины каждого тестового изображения.

q = minibatchqueue(dsTest,MiniBatchSize=64,MiniBatchFormat=["SSCB","CB"]);
scores = zeros(dsTest.numpartitions,1);
labels = zeros(dsTest.numpartitions,1);
idx = 1;
while hasdata(q)
    [X,T] = next(q);
    batchSize = size(X,4);
    Y = predict(net,X);
    Y = mean(Y,[1 2]);
    T = onehotdecode(T,[0 1],1,"double");
    scores(idx:idx+batchSize-1) = Y;
    labels(idx:idx+batchSize-1) = T;
    idx = idx+batchSize;
end

Предскажите метки класса для набора тестов с помощью модели классификации, заданной anomalyProbability функция.

predictedLabels = anomalyProbability(scores) > 0.5;

Вычислите матрицу беспорядка и точность классификации для набора тестов. Модель классификации в этом примере очень точна и предсказывает небольшой процент ложных положительных сторон и ложных отрицательных сторон.

targetLabels = logical(labels);
M = confusionmat(targetLabels,predictedLabels);
confusionchart(M,["Negative","Positive"])
acc = sum(diag(M)) / sum(M,'all');
title(sprintf("Accuracy: %g",acc));

Объясните решения классификации

Просмотрите тепловую карту аномалии

Выберите и отобразите изображение правильно классифицированной аномалии. Этим результатом является истинная положительная классификация.

idxTruePositive = find(targetLabels & predictedLabels,1);
dsExample = subset(dsTest,idxTruePositive);
dataTP = preview(dsExample);
imgTP = dataTP{1};
imshow(imgTP)
title("True Positive Test Image")

Получите тепловую карту изображения аномалии путем вызова predict (Deep Learning Toolbox) функция на обучившем сеть. Сеть возвращает тепловую карту с меньшим размером, чем входное изображение, поэтому измените размер тепловой карты к размеру входного изображения.

hmapTP = predict(net,gpuArray(dlarray(single(imgTP),"SSCB")));
hmapTP = gather(extractdata(hmapTP));
hmapTP = anomalyProbability(hmapTP);
hmapTP = imresize(hmapTP,OutputSize=size(imgTP,[1 2]));

Отобразите наложение цветной тепловой карты на полутоновом представлении цветного изображения с помощью heatmapOverlay функция помощника. Эта функция задана в конце примера. Настройте непрозрачность наложенной тепловой карты установкой alpha к номеру в области значений [0, 1]. Для полностью непрозрачной тепловой карты задайте alpha как 1. Для полностью прозрачной тепловой карты задайте alpha как 0.

alpha = 0.4;
imshow(heatmapOverlay(imgTP,hmapTP,alpha))
title("Heatmap Overlay of True Positive Result")

Чтобы количественно подтвердить результат, отобразите средний счет аномалии тепловой карты истинного положительного тестового изображения, как предсказано сетью.

disp("Mean heatmap anomaly score of test image: "+scores(idxTruePositive(1)))
Mean heatmap anomaly score of test image: 1.2185

Просмотрите тепловую карту нормального изображения

Выберите и отобразите изображение правильно классифицированного нормального изображения. Этим результатом является истинная отрицательная классификация.

idxTrueNegative = find(~targetLabels & ~predictedLabels);
dsTestTN = subset(dsTest,idxTrueNegative);
dataTN = read(dsTestTN);
imgTN = dataTN{1};
imshow(imgTN)
title("True Negative Test Image")

Получите тепловую карту правильно предсказанного негатива путем вызова predict (Deep Learning Toolbox) функция на обучившем сеть. Измените размер тепловой карты к размеру входного изображения.

hmapTN = predict(net,gpuArray(dlarray(single(imgTN),"SSCB")));
hmapTN = gather(extractdata(hmapTN));
hmapTN = anomalyProbability(hmapTN);
hmapTN = imresize(hmapTN,OutputSize=size(imgTN,[1 2]));

Отобразите наложение цветной тепловой карты на полутоновом представлении цветного изображения с помощью heatmapOverlay функция помощника. Эта функция задана в конце примера. Много истинных отрицательных тестовых изображений, таких как это тестовое изображение, или имеют небольшие баллы аномалии через целое изображение или большие баллы аномалии в локализованном фрагменте изображения.

imshow(heatmapOverlay(imgTN,hmapTN,alpha))
title("Heatmap Overlay of True Negative Result")

Отобразите средний счет аномалии тепловой карты истинного отрицательного тестового изображения, как предсказано сетью.

disp("Mean heatmap anomaly score of test image: "+scores(idxTrueNegative(1)))
Mean heatmap anomaly score of test image: 0.0022878

Визуализируйте ложные положительные стороны

Ложные положительные стороны являются изображениями без взломанных аномалий, которые сеть классифицирует как аномальные. Отобразите любые ложные позитивные изображения от набора тестов в монтаже.

falsePositiveIdx = find(predictedLabels & ~targetLabels);
dataFP = readall(subset(dsTest,falsePositiveIdx));
numFP = length(falsePositiveIdx);
if numFP>0
    montage(dataFP(:,1),Size=[1,numFP]);
    title("False Positives in Test Set")
end

Используйте объяснение от сети, чтобы получить информацию в misclassifications.

Вычислите оверлейные программы тепловой карты.

hmapOverlay = cell(1,numFP);
for idx = 1:numFP
    img = dataFP{idx,1};
    map = predict(net,gpuArray(dlarray(single(img),"SSCB")));
    map = gather(extractdata(map));
    mapProb = anomalyProbability(map);
    hmap = imresize(mapProb,OutputSize=size(img,[1 2]));
    hmapOverlay{idx} = heatmapOverlay(img,hmap,alpha);
end

Ложные позитивные изображения показывают функции, такие как скалы, которые имеют подобные визуальные характеристики к трещинам. Во всех случаях это трудные проблемы классификации, в которых объяснимая природа сетевой архитектуры полезна для получения информации.

if numFP>0
    montage(hmapOverlay,Size=[1,numFP])
    title("Heatmap Overlays of False Positive Results")
end

Отобразите среднее множество тепловой карты аномалии ложных положительных тестовых изображений, как предсказано сетью. Средние баллы больше, чем порог, используемый моделью классификации, приводящей к misclassifications.

disp("Mean heatmap anomaly scores:"); scores(falsePositiveIdx)
Mean heatmap anomaly scores:
ans = 4×1

    0.3218
    0.1700
    0.2516
    0.1862

Визуализируйте ложные отрицательные стороны

Ложные отрицательные стороны являются изображениями с аномалиями, которые сеть классифицирует как хорошие. Отобразите любые ложные негативы от набора тестов в монтаже. Ложные негативы показывают относительно тонкие видимые трещины.

falseNegativeIdx = find(~predictedLabels & targetLabels);
dataFN = readall(subset(dsTest,falseNegativeIdx));
numFN = length(falseNegativeIdx);
if numFN>0
    montage(dataFN(:,1),Size=[1,numFN])
    title("False Negatives in Test Set")
end

Вычислите оверлейные программы тепловой карты.

hmapOverlay = cell(1,numFN);
for idx = 1:numFN
    img = dataFN{idx,1};
    map = predict(net,gpuArray(dlarray(single(img),"SSCB")));
    map = gather(extractdata(map));
    mapProb = anomalyProbability(map);
    hmap = imresize(mapProb,OutputSize=size(img,[1 2]));
    hmapOverlay{idx} = heatmapOverlay(img,hmap,alpha);
end

Отобразите оверлейные программы тепловой карты. Сеть присваивает большой счет аномалии вдоль трещин, как ожидалось.

if numFN>0
    montage(hmapOverlay,Size=[1,numFN])
    title("Heatmap Overlays of False Negative Results")
end

Отобразите среднее множество аномалии тепловой карты отрицательных положительных тестовых изображений, как предсказано сетью. Средние баллы меньше, чем порог, используемый сигмоидальной моделью классификации, приводящей к misclassifications.

disp("Mean heatmap anomaly scores:"); scores(falseNegativeIdx)
Mean heatmap anomaly scores:
ans = 4×1

    0.1477
    0.1186
    0.1241
    0.0920

Вспомогательные Функции

modelGradients функция помощника вычисляет градиенты и потерю для сети.

function [grad,loss,state] = modelGradients(net,X,T)
    [Y,state] = forward(net,X);
    loss = fcddLoss(Y,T);
    grad = dlgradient(loss,net.Learnables);
end

fcddLoss функция помощника вычисляет потерю FDCC.

function loss = fcddLoss(Y,T)
    normalTerm = mean(Y,[1 2 3]);
    anomalyTerm = log(1-exp(-normalTerm));
    
    isNegative = T(1,:) == 1;
    loss = mean(single(isNegative) .* normalTerm-single(~isNegative) .* anomalyTerm);
end

heatmapOverlay функция помощника накладывает цветную тепловую карту hmap на полутоновом представлении изображения img. Настройте прозрачность наложения тепловой карты путем определения alpha аргумент как номер в области значений [0, 1].

function out = heatmapOverlay(img,hmap,alpha)

    % Normalize to the range [0, 1]
    img = mat2gray(img);
    hmap = mat2gray(hmap);
    
    % Convert image to a 3-channel grayscale representation
    imgGray = im2gray(img);
    imgGray = repmat(imgGray,[1 1 3]);
    
    % Convert heatmap to an RGB image using a colormap
    cmap = parula(256);
    hmapRGB = ind2rgb(gray2ind(hmap,size(cmap,1)),cmap);
    
    % Blend results
    hmapWeight = alpha;
    grayWeight = 1-hmapWeight;
    out = im2uint8(grayWeight.*imgGray + hmapWeight.*hmapRGB);
end

Ссылки

[1] Лизнерский, Филипп, Лукаш Руфф, Роберт А. Вэндермеулен, Билли Джо Фрэнкс, Мариус Клофт и Клаус-Роберт Мюллер. "Объяснимая Глубокая Классификация Одного класса". Предварительно распечатайте, представленный 18 марта 2021. https://arxiv.org/abs/2007.01760.

[2] Ерш, Лукаш, Роберт А. Вэндермеулен, Билли Джо Фрэнкс, Клаус-Роберт Мюллер и Мариус Клофт. "Заново продумав Предположения в Глубоком Обнаружении Аномалии". Предварительно распечатайте, submitte, 30 мая 2020. https://arxiv.org/abs/2006.00339.

[3] Симонян, Карен и Эндрю Зиссермен. "Очень Глубоко Сверточные Сети для Крупномасштабного Распознавания Изображений". Предварительно распечатайте, представленный 10 апреля 2015. https://arxiv.org/abs/1409.1556.

[4] ImageNet. https://www.image-net.org.

Смотрите также

| (Deep Learning Toolbox) | (Deep Learning Toolbox) | (Deep Learning Toolbox) | (Deep Learning Toolbox)

Похожие темы