Классификация облака точек с помощью глубокого обучения PointNet

В этом примере показано, как обучить сеть PointNet для классификации облака точек.

Данные облака точек получаются различными датчиками, такими как лидар, радар и камеры глубины. Эти датчики захватывают 3-D информацию о положении объектов в сцене, что полезно для многих приложений в автономном управлении автомобилем и дополненной реальности. Например, различение транспортных средств от пешеходов имеет решающее значение для планирования пути автономного транспортного средства. Однако подготовка устойчивых классификаторов с данными облака точек является сложной проблемой из-за разреженности данных по объекту, окклюзий объектов и шума датчиков. Было показано, что методы глубокого обучения решают многие из этих проблем, изучая устойчивые представления функций непосредственно из данных облака точек. Одним из основополагающих методов глубокого обучения для классификации облака точек является PointNet [1].

Этот пример обучает классификатор PointNet на наборе данных Sydney Urban Objects, созданном Сиднейским университетом [2]. Этот набор данных обеспечивает сбор данных облака точек, полученных из городского окружения с помощью датчика лидара. Набор данных имеет 100 маркированных объектов из 14 различных категорий, таких как автомобиль, пешеходный и автобус.

Загрузка набора данных

Загрузите и извлеките набор данных Sydney Urban Objects во временную директорию.

downloadDirectory = tempdir;
datapath = downloadSydneyUrbanObjects(downloadDirectory);

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

foldsTrain = 1:3;
foldsVal = 4;
dsTrain = loadSydneyUrbanObjectsData(datapath,foldsTrain);
dsVal = loadSydneyUrbanObjectsData(datapath,foldsVal);

Прочитайте один из обучающих выборок и визуализируйте его с помощью pcshow.

data = read(dsTrain);
ptCloud = data{1,1};
label = data{1,2};

figure
pcshow(ptCloud.Location,[0 1 0],"MarkerSize",40,"VerticalAxisDir","down")
xlabel("X")
ylabel("Y")
zlabel("Z")
title(label)

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

dsLabelCounts = transform(dsTrain,@(data){data{2} data{1}.Count});
labelCounts = readall(dsLabelCounts);
labels = vertcat(labelCounts{:,1});
counts = vertcat(labelCounts{:,2});

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

figure
histogram(labels)

Гистограмма метки показывает, что набор данных несбалансирован и смещен к автомобилям и пешеходам, что может предотвратить обучение устойчивого классификатора. Можно устранить дисбаланс классов путем избыточной дискретизации нечастых классов. Для набора данных Sydney Urban Objects дублирование файлов, соответствующих нечастым классам, является простым методом для устранения дисбаланса классов.

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

rng(0)
[G,classes] = findgroups(labels);
numObservations = splitapply(@numel,labels,G);
desiredNumObservationsPerClass = max(numObservations);
files = splitapply(@(x){randReplicateFiles(x,desiredNumObservationsPerClass)},dsTrain.Files,G);
files = vertcat(files{:});
dsTrain.Files = files;

Увеличение количества данных

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

dsTrain = transform(dsTrain,@augmentPointCloud);

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

data = preview(dsTrain);
ptCloud = data{1,1};
label = data{1,2};

figure
pcshow(ptCloud.Location,[0 1 0],"MarkerSize",40,"VerticalAxisDir","down")
xlabel("X")
ylabel("Y")
zlabel("Z")
title(label)

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

Предварительная обработка данных

Для подготовки данных облака точек к обучению и прогнозированию требуются два шага предварительного предсказания.

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

minPointCount = splitapply(@min,counts,G);
maxPointCount = splitapply(@max,counts,G);
meanPointCount = splitapply(@(x)round(mean(x)),counts,G);

stats = table(classes,numObservations,minPointCount,maxPointCount,meanPointCount)
stats=14×5 table
       classes        numObservations    minPointCount    maxPointCount    meanPointCount
    ______________    _______________    _____________    _____________    ______________

    4wd                      15               140              1955              751     
    building                 15               193              8455             2708     
    bus                      11               126             11767             2190     
    car                      64                52              2377              528     
    pedestrian              107                20               297              110     
    pillar                   15                80               751              357     
    pole                     15                13               253               90     
    traffic lights           36                38               352              161     
    traffic sign             40                18               736              126     
    tree                     24                53              2953              470     
    truck                     9               445              3013             1376     
    trunk                    42                32               766              241     
    ute                      12                90              1380              580     
    van                      28                91              5809             1125     

Из-за большого количества внутрикласса и между межклассами в число точек на класс выбор значения, подходящего всем классам, затруднен. Одним из эвристических является выбор достаточного количества точек, чтобы адекватно захватить форму объектов, не увеличивая при этом вычислительные затраты путем обработки слишком большого количества точек. Значение 1024 обеспечивает хороший компромисс между этими двумя гранями. Можно также выбрать оптимальное число точек на основе эмпирического анализа. Однако это выходит за возможности этого примера. Используйте transform функция для выбора 1024 точек в наборах обучения и валидации.

numPoints = 1024;
dsTrain = transform(dsTrain,@(data)selectPoints(data,numPoints));
dsVal = transform(dsVal,@(data)selectPoints(data,numPoints));

Последним шагом предварительной обработки является нормализация данных облака точек между 0 и 1 с учетом больших различий в области значений значений данных. Например, объекты ближе к датчику лидара имеют меньшие значения по сравнению с объектами, которые находятся дальше. Эти различия могут препятствовать сходимости сети во время обучения. Использование transform нормализовать данные облака точек в наборах обучения и валидации.

dsTrain = transform(dsTrain,@preprocessPointCloud);
dsVal = transform(dsVal,@preprocessPointCloud);

Предварительный просмотр дополненных и предварительно обработанных обучающих данных.

data = preview(dsTrain);
figure
pcshow(data{1,1},[0 1 0],"MarkerSize",40,"VerticalAxisDir","down");
xlabel("X")
ylabel("Y")
zlabel("Z")
title(data{1,2})

Задайте модель PointNet

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

Модель энкодера PointNet дополнительно состоит из четырех моделей, за которыми следует операция max.

  1. Вход преобразования

  2. Общая модель MLP

  3. Модель преобразования функций

  4. Общая модель MLP

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

Задайте параметры модели энкодера PointNet

Общие модели MLP и преобразования параметризованы количеством входа каналов и скрытыми размерами каналов. Значения, выбранные в этом примере, выбираются путем настройки этих гиперпараметров на наборе данных Sydney Urban Objects. Обратите внимание, что если необходимо применить PointNet к другому набору данных, необходимо выполнить дополнительную настройку гиперпараметра.

Установите размер входного канала модели входного преобразования равный трем, а размер скрытого канала равным 64, 128 и 256 и используйте initializeTransform вспомогательная функция, перечисленная в конце этого примера, чтобы инициализировать параметры модели.

inputChannelSize = 3;
hiddenChannelSize1 = [64,128];
hiddenChannelSize2 = 256;
[parameters.InputTransform, state.InputTransform] = initializeTransform(inputChannelSize,hiddenChannelSize1,hiddenChannelSize2);

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

inputChannelSize = 3;
hiddenChannelSize = [64 64];
[parameters.SharedMLP1,state.SharedMLP1] = initializeSharedMLP(inputChannelSize,hiddenChannelSize);

Установите размер входного канала модели преобразования функций равным 64 и скрытые размеры каналов равными 64, 128 и 256 и используйте initializeTransform вспомогательная функция, перечисленная в конце этого примера, чтобы инициализировать параметры модели.

inputChannelSize = 64;
hiddenChannelSize1 = [64,128];
hiddenChannelSize2 = 256;
[parameters.FeatureTransform, state.FeatureTransform] = initializeTransform(inputChannelSize,hiddenChannelSize,hiddenChannelSize2);

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

inputChannelSize = 64;
hiddenChannelSize = 64;
[parameters.SharedMLP2,state.SharedMLP2] = initializeSharedMLP(inputChannelSize,hiddenChannelSize);

Задайте параметры модели классификатора PointNet

Модель классификатора PointNet состоит из общего MLP, полносвязной операции и активации softmax. Установите размер входа модели классификатора 64 и размер скрытого канала 512 и 256 и используйте initalizeClassifier вспомогательная функция, перечисленная в конце этого примера, чтобы инициализировать параметры модели.

inputChannelSize = 64;
hiddenChannelSize = [512,256];
numClasses = numel(classes);
[parameters.ClassificationMLP, state.ClassificationMLP] = initializeClassificationMLP(inputChannelSize,hiddenChannelSize,numClasses);

Задайте функцию PointNet

Создайте функцию pointnetClassifier, перечисленный в разделе Model Function в конце примера, чтобы вычислить выходы модели PointNet. Модель функции принимает за вход данные облака точек, параметры усвояемой модели, состояние модели и флаг, который определяет, возвращает ли модель выходы для обучения или предсказания. Сеть возвращает предсказания для классификации входного облака точек.

Задайте функцию градиентов модели

Создайте функцию modelGradients, перечисленный в разделе Model Gradients Function примера, который принимает за вход параметры модели, состояние модели и мини-пакет входных данных и возвращает градиенты потерь относительно настраиваемых параметров в моделях и соответствующих потерь.

Настройка опций обучения

Обучите на 10 эпох и загрузите данные пакетами по 128. Установите начальную скорость обучения равную 0,002, а L2 коэффициент регуляризации равным 0,01.

numEpochs = 10;
learnRate = 0.002;
miniBatchSize = 128;
l2Regularization = 0.01;
learnRateDropPeriod = 15;
learnRateDropFactor = 0.5;

Инициализируйте опции оптимизации Адама.

gradientDecayFactor = 0.9;
squaredGradientDecayFactor = 0.999;

Обучите PointNet

Обучите модель с помощью пользовательского цикла обучения.

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

Для каждой итерации:

  • Считайте пакет данных.

  • Оцените градиенты модели.

  • Применить L2 регуляризацию веса.

  • Использование adamupdate для обновления параметров модели.

  • Обновите график процесса обучения.

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

После завершения learnRateDropPeriod эпох, уменьшите скорость обучения в множитель learnRateDropFactor.

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

avgGradients = [];
avgSquaredGradients = [];

Обучите модель, если doTraining является true. В противном случае загружает предварительно обученную сеть

Обратите внимание, что обучение было проверено на NVIDIA Titan X с 12 ГБ памяти графический процессор. Если ваш графический процессор имеет меньше памяти, у вас может закончиться память во время обучения. Если это произойдет, опустите miniBatchSize. Обучение этой сети занимает около 5 минут. В зависимости от вашего оборудования графический процессор, это может занять больше времени.

doTraining = false;

if doTraining
    
    % Create a minibatchqueue to batch data from training and validation
    % datastores. Use the batchData function, listed at the end of the
    % example, to batch the point cloud data and one-hot encode the label 
    % data.
    numOutputsFromDSRead = 2;
    mbqTrain = minibatchqueue(dsTrain,numOutputsFromDSRead,...
        "MiniBatchSize", miniBatchSize,...
        "MiniBatchFcn",@batchData,...
        "MiniBatchFormat",["SCSB" "BC"]);
    
    mbqVal = minibatchqueue(dsVal,numOutputsFromDSRead,...
        "MiniBatchSize", miniBatchSize,... 
        "MiniBatchFcn",@batchData,...
        "MiniBatchFormat",["SCSB" "BC"]);
 
    % Use the configureTrainingProgressPlot function, listed at the end of the
    % example, to initialize the training progress plot to display the training
    % loss, training accuracy, and validation accuracy.
    [lossPlotter, trainAccPlotter,valAccPlotter] = initializeTrainingProgressPlot;
    
    numClasses = numel(classes);
    iteration = 0;
    start = tic;
    for epoch = 1:numEpochs
        
        % Shuffle data every epoch.
        shuffle(mbqTrain);
      
        % Iterate through data set.
        while hasdata(mbqTrain)
            iteration = iteration + 1;
            
            % Read next batch of training data.
            [XTrain, YTrain] = next(mbqTrain);            
            
            % Evaluate the model gradients and loss using dlfeval and the
            % modelGradients function.
            [gradients, loss, state, acc] = dlfeval(@modelGradients,XTrain,YTrain,parameters,state);
            
            % L2 regularization.
            gradients = dlupdate(@(g,p) g + l2Regularization*p,gradients,parameters);
            
            % Update the network parameters using the Adam optimizer.
            [parameters, avgGradients, avgSquaredGradients] = adamupdate(parameters, gradients, ...
                avgGradients, avgSquaredGradients, iteration,...
                learnRate,gradientDecayFactor, squaredGradientDecayFactor);
            
            % Update the training progress.
            D = duration(0,0,toc(start),"Format","hh:mm:ss");
            title(lossPlotter.Parent,"Epoch: " + epoch + ", Elapsed: " + string(D))
            addpoints(lossPlotter,iteration,double(gather(extractdata(loss))))
            addpoints(trainAccPlotter,iteration,acc);
            drawnow
        end
        
        % Evaluate the model on validation data.
        cmat = sparse(numClasses,numClasses);
        while hasdata(mbqVal)
            
            % Read next batch of validation data.
            [XVal, YVal] = next(mbqVal);

            % Compute label predictions.
            isTraining = false;
            YPred = pointnetClassifier(XVal,parameters,state,isTraining);
            
            % Choose prediction with highest score as the class label for
            % XTest.
            [~,YValLabel] = max(YVal,[],1);
            [~,YPredLabel] = max(YPred,[],1);
            
            % Collect confusion metrics.
            cmat = aggreateConfusionMetric(cmat,YValLabel,YPredLabel);
        end
        
        % Update training progress plot with average classification accuracy.
        acc = sum(diag(cmat))./sum(cmat,"all");
        addpoints(valAccPlotter,iteration,acc);
        
        % Upate the learning rate.
        if mod(epoch,learnRateDropPeriod) == 0
            learnRate = learnRate * learnRateDropFactor;
        end
        
        % Reset training and validation data queues.
        reset(mbqTrain);
        reset(mbqVal);
    end

else
    % Download pretrained model parameters, model state, and validation
    % results.
    pretrainedURL = 'https://ssd.mathworks.com/supportfiles/vision/data/pointnetSydneyUrbanObjects.zip'; 
    pretrainedResults = downloadPretrainedPointNet(pretrainedURL);
   
    parameters = pretrainedResults.parameters;
    state = pretrainedResults.state;
    cmat = pretrainedResults.cmat;
    
    % Move model parameters to the GPU if possible and convert to a dlarray.
    parameters = prepareForPrediction(parameters,@(x)dlarray(toDevice(x,canUseGPU)));
    
    % Move model state to the GPU if possible.
    state = prepareForPrediction(state,@(x)toDevice(x,canUseGPU));
end

Отобразите матрицу неточностей валидации.

figure
chart = confusionchart(cmat,classes);

Вычислите среднюю точность обучения и валидации.

acc = sum(diag(cmat))./sum(cmat,"all")
acc = 0.5742

Из-за ограниченного количества обучающих выборок в наборе данных Sydney Urban Objects увеличение точности валидации сверх 60% является сложным. Модель легко перегружает обучающие данные в отсутствие увеличения, заданного в augmentPointCloudData вспомогательная функция. Для повышения робастности классификатора PointNet требуется дополнительное обучение.

Классификация данных облака точек с помощью PointNet

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

ptCloud = pcread("car.pcd");
X = preprocessPointCloud(ptCloud);
dlX = dlarray(X{1},"SCSB");

Предсказание меток облака точек с помощью pointnetClassifier модельная функция.

YPred = pointnetClassifier(dlX,parameters,state,false);
[~,classIdx] = max(YPred,[],1);

Отобразите облако точек и предсказанную метку с самым высоким счетом.

figure
pcshow(ptCloud.Location,[0 1 0],"MarkerSize",40,"VerticalAxisDir","down")
title(classes(classIdx))

Функция градиентов модели

Функция modelGradients принимает за вход мини-пакет данных dlX, соответствующий целевой dlY, и настраиваемые параметры, и возвращает градиенты потери относительно настраиваемых параметров и соответствующих потерь. Потеря включает член регуляризации, разработанный, чтобы гарантировать, что матрица преобразования функций, предсказанная энкодером PointNet, приблизительно ортогональна. Чтобы вычислить градиенты, вычислите modelGradients функция, использующая dlfeval функция в цикле обучения.

function [gradients, loss, state, acc] = modelGradients(X,Y,parameters,state)

% Execute the model function.
isTraining = true;
[YPred,state,dlT] = pointnetClassifier(X,parameters,state,isTraining);

% Add regularization term to ensure feature transform matrix is
% approximately orthogonal.
K = size(dlT,1);
B = size(dlT, 4);
I = repelem(eye(K),1,1,1,B);
dlI = dlarray(I,"SSCB");
treg = mse(dlI,pagemtimes(dlT,permute(dlT,[2 1 3 4])));
factor = 0.001;

% Compute the loss.
loss = crossentropy(YPred,Y) + factor*treg;

% Compute the parameter gradients with respect to the loss. 
gradients = dlgradient(loss, parameters);

% Compute training accuracy metric.
[~,YTest] = max(Y,[],1);
[~,YPred] = max(YPred,[],1);
acc = gather(extractdata(sum(YTest == YPred)./numel(YTest)));

end

Функция классификатора PointNet

The pointnetClassifier функция принимает в качестве входов данные облака точек dlX, параметры усвояемой модели, состояние модели и флаг isTraining, который определяет, возвращает ли модель выходы для обучения или предсказания. Затем функция вызывает энкодер PointNet и многослойный перцептрон, чтобы извлечь классификационные функции. Во время обучения отсев наносят после каждой операции перцептрона. После последнего перцептрона a fullyconnect операция преобразует классификационные функции в количество классов, и активация softmax используется для нормализации выхода в распределение вероятностей меток. Распределение вероятностей, состояние обновленной модели и матрица преобразования функций, предсказанная энкодером PointNet, возвращаются как выходы.

function [dlY,state,dlT] = pointnetClassifier(dlX,parameters,state,isTraining)

% Invoke the PointNet encoder.
[dlY,state,dlT] = pointnetEncoder(dlX,parameters,state,isTraining);

% Invoke the classifier.
p = parameters.ClassificationMLP.Perceptron;
s = state.ClassificationMLP.Perceptron;
for k = 1:numel(p) 
     
    [dlY, s(k)] = perceptron(dlY,p(k),s(k),isTraining);
      
    % If training, apply inverted dropout with a probability of 0.3.
    if isTraining
        probability = 0.3; 
        dropoutScaleFactor = 1 - probability;
        dropoutMask = ( rand(size(dlY), "like", dlY) > probability ) / dropoutScaleFactor;
        dlY = dlY.*dropoutMask;
    end
    
end
state.ClassificationMLP.Perceptron = s;

% Apply final fully connected and softmax operations.
weights = parameters.ClassificationMLP.FC.Weights;
bias = parameters.ClassificationMLP.FC.Bias;
dlY = fullyconnect(dlY,weights,bias);
dlY = softmax(dlY);
end

Функция энкодера PointNet

The pointnetEncoder функция обрабатывает вход dlX с помощью входного преобразования, общего MLP, преобразования признака, второго общего MLP и операции max и возвращает результат операции max.

function [dlY,state,T] = pointnetEncoder(dlX,parameters,state,isTraining)
% Input transform.
[dlY,state.InputTransform] = dataTransform(dlX,parameters.InputTransform,state.InputTransform,isTraining);

% Shared MLP.
[dlY,state.SharedMLP1.Perceptron] = sharedMLP(dlY,parameters.SharedMLP1.Perceptron,state.SharedMLP1.Perceptron,isTraining);

% Feature transform.
[dlY,state.FeatureTransform,T] = dataTransform(dlY,parameters.FeatureTransform,state.FeatureTransform,isTraining);

% Shared MLP.
[dlY,state.SharedMLP2.Perceptron] = sharedMLP(dlY,parameters.SharedMLP2.Perceptron,state.SharedMLP2.Perceptron,isTraining);

% Max operation.
dlY = max(dlY,[],1);
end

Общая многослойная функция перцептрона

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

function [dlY,state] = sharedMLP(dlX,parameters,state,isTraining)
dlY = dlX;
for k = 1:numel(parameters) 
    [dlY, state(k)] = perceptron(dlY,parameters(k),state(k),isTraining);
end
end

Функция Перцептрона

Функция perceptron обрабатывает вход dlX с помощью свертки, нормализации партии . и операции relu и возвращает выход операции ReLU.

function [dlY,state] = perceptron(dlX,parameters,state,isTraining)
% Convolution.
W = parameters.Conv.Weights;
B = parameters.Conv.Bias;
dlY = dlconv(dlX,W,B);

% Batch normalization. Update batch normalization state when training.
offset = parameters.BatchNorm.Offset;
scale = parameters.BatchNorm.Scale;
trainedMean = state.BatchNorm.TrainedMean;
trainedVariance = state.BatchNorm.TrainedVariance;
if isTraining
    [dlY,trainedMean,trainedVariance] = batchnorm(dlY,offset,scale,trainedMean,trainedVariance);
    
    % Update state.
    state.BatchNorm.TrainedMean = trainedMean;
    state.BatchNorm.TrainedVariance = trainedVariance;
else
    dlY = batchnorm(dlY,offset,scale,trainedMean,trainedVariance);
end

% ReLU.
dlY = relu(dlY);
end

Функция преобразования данных

The dataTransform функция обрабатывает входные dlX использование общей MLP, операции max и другой общей MLP для предсказания матрицы преобразования T. Матрица преобразования применяется к входу dlX с помощью операции умножения пакетной матрицы. Функция возвращает результат умножения пакетной матрицы и матрицы преобразования.

function [dlY,state,T] = dataTransform(dlX,parameters,state,isTraining)

% Shared MLP.
[dlY,state.Block1.Perceptron] = sharedMLP(dlX,parameters.Block1.Perceptron,state.Block1.Perceptron,isTraining);

% Max operation.
dlY = max(dlY,[],1);

% Shared MLP.
[dlY,state.Block2.Perceptron] = sharedMLP(dlY,parameters.Block2.Perceptron,state.Block2.Perceptron,isTraining);

% Transform net (T-Net). Apply last fully connected operation as W*X to
% predict tranformation matrix T.
dlY = squeeze(dlY); % N-by-B
T = parameters.Transform * stripdims(dlY); % K^2-by-B

% Reshape T into a square matrix.
K = sqrt(size(T,1));
T = reshape(T,K,K,1,[]); % [K K 1 B]
T = T + eye(K);

% Apply to input dlX using batch matrix multiply. 
[C,B] = size(dlX,[3 4]);  % [M 1 K B]
dlX = reshape(dlX,[],C,1,B); % [M K 1 B]
Y = pagemtimes(dlX,T);
dlY = dlarray(Y,"SCSB");
end

Функции инициализации параметра модели

initializeTransform Функция

The initializeTransform функция принимает за вход размер канала и количество скрытых каналов для двух общих MLP, и возвращает инициализированные параметры в struct. Параметры инициализируются с помощью инициализации веса He [3].

function [params,state] = initializeTransform(inputChannelSize,block1,block2)
[params.Block1,state.Block1] = initializeSharedMLP(inputChannelSize,block1);
[params.Block2,state.Block2] = initializeSharedMLP(block1(end),block2);

% Parameters for the transform matrix.
params.Transform = dlarray(zeros(inputChannelSize^2,block2(end)));
end

initializeSharedMLP Функция

Функция initializeSharedMLP принимает за вход размер канала и скрытый размер канала и возвращает инициализированные параметры в struct. Параметры инициализируются с помощью инициализации веса He.

function [params,state] = initializeSharedMLP(inputChannelSize,hiddenChannelSize)
weights = initializeWeightsHe([1 1 inputChannelSize hiddenChannelSize(1)]);
bias = zeros(hiddenChannelSize(1),1,"single");
p.Conv.Weights = dlarray(weights);
p.Conv.Bias = dlarray(bias);

p.BatchNorm.Offset = dlarray(zeros(hiddenChannelSize(1),1,"single"));
p.BatchNorm.Scale = dlarray(ones(hiddenChannelSize(1),1,"single"));

s.BatchNorm.TrainedMean = zeros(hiddenChannelSize(1),1,"single");
s.BatchNorm.TrainedVariance = ones(hiddenChannelSize(1),1,"single");

params.Perceptron(1) = p;
state.Perceptron(1) = s;

for k = 2:numel(hiddenChannelSize)
    weights = initializeWeightsHe([1 1 hiddenChannelSize(k-1) hiddenChannelSize(k)]);
    bias = zeros(hiddenChannelSize(k),1,"single");
    p.Conv.Weights = dlarray(weights);
    p.Conv.Bias = dlarray(bias);
    
    p.BatchNorm.Offset = dlarray(zeros(hiddenChannelSize(k),1,"single"));
    p.BatchNorm.Scale = dlarray(ones(hiddenChannelSize(k),1,"single"));
    
    s.BatchNorm.TrainedMean = zeros(hiddenChannelSize(k),1,"single");
    s.BatchNorm.TrainedVariance = ones(hiddenChannelSize(k),1,"single");

    params.Perceptron(k) = p;
    state.Perceptron(k) = s;
end
end

initializeClassificationMLP Функция

The initializeClassificationMLP функция принимает за вход размер канала, размер скрытого канала и количество классов и возвращает инициализированные параметры в struct. Общая MLP инициализируется с помощью He-инициализации веса, а конечная полностью связанная операция инициализируется с помощью случайных Гауссовых значений.

function [params,state] = initializeClassificationMLP(inputChannelSize,hiddenChannelSize,numClasses)
[params,state] = initializeSharedMLP(inputChannelSize,hiddenChannelSize);

weights = initializeWeightsGaussian([numClasses hiddenChannelSize(end)]);
bias = zeros(numClasses,1,"single");
params.FC.Weights = dlarray(weights);
params.FC.Bias = dlarray(bias);
end

initializeWeightsHe Функция

The initializeWeightsHe функция инициализирует параметры с помощью инициализации He.

function x = initializeWeightsHe(sz)
fanIn = prod(sz(1:3));
stddev = sqrt(2/fanIn);
x = stddev .* randn(sz);
end

initializeWeightsGaussian Функция

The initializeWeightsGaussian функция инициализирует параметры с помощью Гауссовой инициализации со стандартным отклонением 0,01.

function x = initializeWeightsGaussian(sz)
x = randn(sz,"single") .* 0.01;
end

Функции предварительной обработки данных

preprocessPointCloudData Функция

The preprocessPointCloudData функция извлекает данные точек X, Y, Z из входных данных и нормализует данные между 0 и 1. Функция возвращает нормированные данные X, Y, Z.

function data = preprocessPointCloud(data)

if ~iscell(data)
    data = {data};
end

numObservations = size(data,1);
for i = 1:numObservations
    % Scale points between 0 and 1.
    xlim = data{i,1}.XLimits;
    ylim = data{i,1}.YLimits;
    zlim = data{i,1}.ZLimits;
    
    xyzMin = [xlim(1) ylim(1) zlim(1)];
    xyzDiff = [diff(xlim) diff(ylim) diff(zlim)];
    
    data{i,1} = (data{i,1}.Location - xyzMin) ./ xyzDiff;
end
end

selectPoints Функция

The selectPoints функция производит выборку нужного числа точек. Когда облако точек содержит больше, чем желаемое число точек, функция использует pcdownsample для случайного выбора точек. В противном случае функция наследует данные для создания требуемого числа точек.

function data = selectPoints(data,numPoints) 
% Select the desired number of points by downsampling or replicating
% point cloud data.
numObservations = size(data,1);
for i = 1:numObservations    
    ptCloud = data{i,1};
    if ptCloud.Count > numPoints
        percentage = numPoints/ptCloud.Count;
        data{i,1} = pcdownsample(ptCloud,"random",percentage);   
    else    
        replicationFactor = ceil(numPoints/ptCloud.Count);
        ind = repmat(1:ptCloud.Count,1,replicationFactor);
        data{i,1} = select(ptCloud,ind(1:numPoints));
    end 
end
end

Функции увеличения данных

The augmentPointCloudData функция случайным образом вращает облако точек вокруг оси Z, случайным образом сбрасывает 30% точек и случайным образом дрожит местоположение точки с помощью Гауссова шума.

function data = augmentPointCloud(data)
   
numObservations = size(data,1);
for i = 1:numObservations
    
    ptCloud = data{i,1};
    
    % Rotate the point cloud about "up axis", which is Z for this data set.
    tform = randomAffine3d(...
        "XReflection", true,...
        "YReflection", true,...
        "Rotation",@randomRotationAboutZ);
    
    ptCloud = pctransform(ptCloud,tform);
    
    % Randomly drop out 30% of the points.
    if rand > 0.5
        ptCloud = pcdownsample(ptCloud,'random',0.3);
    end
    
    if rand > 0.5
        % Jitter the point locations with Gaussian noise with a mean of 0 and 
        % a standard deviation of 0.02 by creating a random displacement field.
        D = 0.02 * randn(size(ptCloud.Location));
        ptCloud = pctransform(ptCloud,D);   
    end
    
    data{i,1} = ptCloud;
end
end

function [rotationAxis,theta] = randomRotationAboutZ()
rotationAxis = [0 0 1];
theta = 2*pi*rand;
end

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

aggregateConfusionMetric Функция

The aggregateConfusionMetric функция пошагово заполняет матрицу неточностей на основе предсказанных результатов YPred и ожидаемые результаты YTest.

function cmat = aggreateConfusionMetric(cmat,YTest,YPred)
YTest = gather(extractdata(YTest));
YPred = gather(extractdata(YPred));
[m,n] = size(cmat);
cmat = cmat + full(sparse(YTest,YPred,1,m,n));
end

initializeTrainingProgressPlot Функция

The initializeTrainingProgressPlot функция конфигурирует два графика для отображения потерь обучения, точности обучения и точности валидации.

function [plotter,trainAccPlotter,valAccPlotter] = initializeTrainingProgressPlot()
% Plot the loss, training accuracy, and validation accuracy.
figure

% Loss plot
subplot(2,1,1)
plotter = animatedline;
xlabel("Iteration")
ylabel("Loss")

% Accuracy plot
subplot(2,1,2)
trainAccPlotter = animatedline('Color','b');
valAccPlotter = animatedline('Color','g');
legend('Training Accuracy','Validation Accuracy','Location','northwest');
xlabel("Iteration")
ylabel("Accuracy")
end

replicateFiles Функция

The replicateFiles функция случайным образом масштабирует набор файлов и возвращает набор файлов с numDesired элементы.

function files = randReplicateFiles(files,numDesired)
n = numel(files);
ind = randi(n,numDesired,1);
files = files(ind);
end

downloadSydneyUrbanObjects Функция

The downloadSydneyUrbanObjects функция загружает набор данных и сохраняет его во временной директории.

function datapath = downloadSydneyUrbanObjects(dataLoc)

if nargin == 0
    dataLoc = pwd;
end

dataLoc = string(dataLoc);

url = "http://www.acfr.usyd.edu.au/papers/data/";
name = "sydney-urban-objects-dataset.tar.gz";

datapath = fullfile(dataLoc,'sydney-urban-objects-dataset');
if ~exist(datapath,'dir')
    disp('Downloading Sydney Urban Objects data set...');
    untar(url+name,dataLoc);
end

end

loadSydneyUrbanObjectsData Функция

The loadSydneyUrbanObjectsData функция создает хранилище данных для загрузки облака точек и данных о метках из набора данных Sydney Urban Objects.

function ds = loadSydneyUrbanObjectsData(datapath,folds)

if nargin == 0
    return;
end

if nargin < 2
    folds = 1:4;
end

datapath = string(datapath);
path = fullfile(datapath,'objects',filesep);

% Add folds to datastore.
foldNames{1} = importdata(fullfile(datapath,'folds','fold0.txt'));
foldNames{2} = importdata(fullfile(datapath,'folds','fold1.txt'));
foldNames{3} = importdata(fullfile(datapath,'folds','fold2.txt'));
foldNames{4} = importdata(fullfile(datapath,'folds','fold3.txt'));
names = foldNames(folds);
names = vertcat(names{:});

fullFilenames = append(path,names);
ds = fileDatastore(fullFilenames,'ReadFcn',@extractTrainingData,'FileExtensions','.bin');
end

batchData Функция

The batchData функция собирает данные в пакеты и перемещает данные в графический процессор для обработки.

function [X,Y] = batchData(ptCloud,labels)
X = cat(4,ptCloud{:});
labels = cat(1,labels{:});
Y = onehotencode(labels,2);
end

extractTrainingData Функция

Функция extractTrainingData извлекает облако точек и данные о метках из набора данных Sydney Urban Objects.

function dataOut = extractTrainingData(fname)

[pointData,intensity] = readbin(fname);

[~,name] = fileparts(fname);
name = string(name);
name = extractBefore(name,'.');
name = replace(name,'_',' ');

labelNames = ["4wd","building","bus","car","pedestrian","pillar",...
    "pole","traffic lights","traffic sign","tree","truck","trunk","ute","van"];

label = categorical(name,labelNames);

dataOut = {pointCloud(pointData,'Intensity',intensity),label};

end

readbin Функция

The readbin функция считывает данные облака точек из двоичных файлов объекта Sydney Urban.

function [pointData,intensity] = readbin(fname)
% readbin Read point and intensity data from Sydney Urban Object binary
% files.

% names = ['t','intensity','id',...
%          'x','y','z',...
%          'azimuth','range','pid']
% 
% formats = ['int64', 'uint8', 'uint8',...
%            'float32', 'float32', 'float32',...
%            'float32', 'float32', 'int32']

fid = fopen(fname, 'r');
c = onCleanup(@() fclose(fid));

fseek(fid,10,-1); % Move to the first X point location 10 bytes from beginning
X = fread(fid,inf,'single',30);
fseek(fid,14,-1);
Y = fread(fid,inf,'single',30);
fseek(fid,18,-1);
Z = fread(fid,inf,'single',30);

fseek(fid,8,-1);
intensity = fread(fid,inf,'uint8',33);

pointData = [X,Y,Z];
end

downloadPretrainedPointNet Функция

The downloadPretrainedPointNet функция загружает предварительно обученную модель pointnet.

function data = downloadPretrainedPointNet(pretrainedURL)
% Download and load a pretrained pointnet model.
if ~exist('pointnetSydneyUrbanObjects.mat', 'file')
    if ~exist('pointnetSydneyUrbanObjects.zip', 'file')
        disp('Downloading pretrained detector (5 MB)...');
        websave('pointnetSydneyUrbanObjects.zip', pretrainedURL);
    end
    unzip('pointnetSydneyUrbanObjects.zip');
end
data = load("pointnetSydneyUrbanObjects.mat");
end

prepareForPrediction Функция

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

function p = prepareForPrediction(p,fcn)

for i = 1:numel(p)
    p(i) = structfun(@(x)invoke(fcn,x),p(i),'UniformOutput',0);
end

    function data = invoke(fcn,data)
        if isstruct(data)
            data = prepareForPrediction(data,fcn);
        else
            data = fcn(data);
        end
    end
end

% Move data to the GPU.
function x = toDevice(x,useGPU)
if useGPU
    x = gpuArray(x);
end
end

Ссылки

[1] Charles, R. Qi, Hao Su, Mo Kaichun, and Leonidas J. Guibas. PointNet: Глубокое обучение on Point Sets for 3D Classification and Segmentation (неопр.) (недоступная ссылка). В 2017 году IEEE Conference on Компьютерное Зрение and Pattern Recognition (CVPR), 77-85. Гонолулу, HI: IEEE, 2017. https://doi.org/10.1109/CVPR.2017.16.

[2] de Deuge, Mark, Alastair Quadras, Calvin Hung и Bertrand Douillard. «Неконтролируемое обучение функциям для классификации сканов наружного 3D». В Австралийской конференции по робототехнике и автоматизации 2013 (ACRA 13). Сидней, Австралия: АКРА, 2013.

[3] He, Kaiming, Xiangyu Zhang, Shaoqing Ren, and Jian Sun. «Delving Deep Into Rectifiers: Overpassing Human-Level Performance on ImageNet Classification». В 2015 году IEEE International Conference on Компьютерное Зрение (ICCV), 1026-34. Сантьяго, Чили: IEEE, 2015. https://doi.org/10.1109/ICCV.2015.123.

Похожие темы