Этот пример демонстрирует подход, обрисованный в общих чертах в [1], в котором данные об облаке точек предварительно обрабатываются в кодирование voxelized и затем используются непосредственно с простой 3-D архитектурой сверточной нейронной сети, чтобы выполнить предметную классификацию. В более свежих подходах такой как [2], кодировка данных об облаке точек может быть более сложной и может быть изученной кодировкой, которая обучена от начала до конца наряду с сетью, выполняющей задачу обнаружения/сегментации классификации/объекта. Однако общий шаблон перемещения от неправильных неупорядоченных точек до структуры с координатной сеткой, которая может быть подана в convnets, остается подобным во всех этих подходах.
В этом примере мы работаем с Сиднеем Городской Набор данных Объектов. В этом примере мы используем сгибы 1-3 из данных как набор обучающих данных и сворачиваемся 4 как набор валидации.
dataPath = downloadSydneyUrbanObjects(tempdir); dsTrain = loadSydneyUrbanObjectsData(dataPath,[1 2 3]); dsVal = loadSydneyUrbanObjectsData(dataPath,4);
Анализируйте набор обучающих данных, чтобы изучить метки, существующие в данных и полном распределении меток.
dsLabels = transform(dsTrain,@(data) data{2}); labels = readall(dsLabels); figure histogram(labels)
От гистограммы очевидно, что существует проблема неустойчивости класса в обучающих данных в который определенные классы объектов как Car
и Pedestrian
намного более распространены, чем менее частые классы как Ute
.
Чтобы постараться не сверхсоответствовать и добавлять робастность в классификатор, некоторый объем рандомизированного увеличения данных обычно является хорошей идеей при обучении сети. Функции randomAffine2d
и pctransform дает возможность задавать рандомизированные аффинные преобразования на данных об облаке точек. Мы дополнительно добавляем некоторое рандомизированное дрожание на точку в каждую точку в каждом облаке точек. Функциональный augmentPointCloudData
включен в раздел функций поддержки ниже.
dsTrain = transform(dsTrain,@augmentPointCloudData);
Проверьте, что увеличение данных об облаке точек выглядит разумным.
dataOut = preview(dsTrain); figure pcshow(dataOut{1}); title(dataOut{2});
Мы затем добавляем, что простой voxelization преобразовывает к каждому облаку точки ввода, как обсуждено в предыдущем примере, чтобы преобразовать наше облако точки ввода в псевдоизображение, которое может использоваться со сверточной нейронной сетью. Используйте простую сетку заполнения.
dsTrain = transform(dsTrain,@formOccupancyGrid); dsVal = transform(dsVal,@formOccupancyGrid);
Исследуйте выборку финала voxelized объем, который мы подадим в сеть, чтобы проверить, что voxelixation работает правильно.
data = preview(dsTrain); figure p = patch(isosurface(data{1},0.5)); p.FaceColor = 'red'; p.EdgeColor = 'none'; daspect([1 1 1]) view(45,45) camlight; lighting phong title(data{2});
В этом примере мы используем простую 3-D архитектуру классификации как описано в [1].
layers = [image3dInputLayer([32 32 32],'Name','inputLayer','Normalization','none'),... convolution3dLayer(5,32,'Stride',2,'Name','Conv1'),... leakyReluLayer(0.1,'Name','leakyRelu1'),... convolution3dLayer(3,32,'Stride',1,'Name','Conv2'),... leakyReluLayer(0.1,'Name','leakyRulu2'),... maxPooling3dLayer(2,'Stride',2,'Name','maxPool'),... fullyConnectedLayer(128,'Name','fc1'),... reluLayer('Name','relu'),... dropoutLayer(0.5,'Name','dropout1'),... fullyConnectedLayer(14,'Name','fc2'),... softmaxLayer('Name','softmax'),... classificationLayer('Name','crossEntropyLoss')]; voxnet = layerGraph(layers); figure plot(voxnet);
Используйте стохастический градиентный спуск с импульсом с кусочной корректировкой расписания скорости обучения. Этот пример был запущен на графическом процессоре TitanX для графических процессоров с меньшей памятью, может быть необходимо уменьшать пакетный размер. Хотя 3D convnets имеют преимущество концептуальной простоты, у них есть недостаток больших объемов памяти useage в учебное время.
miniBatchSize = 32; dsLength = length(dsTrain.UnderlyingDatastore.Files); iterationsPerEpoch = floor(dsLength/miniBatchSize); dropPeriod = floor(8000/iterationsPerEpoch); options = trainingOptions('sgdm','InitialLearnRate',0.01,'MiniBatchSize',miniBatchSize,... 'LearnRateSchedule','Piecewise',... 'LearnRateDropPeriod',dropPeriod,... 'ValidationData',dsVal,'MaxEpochs',60,... 'DispatchInBackground',false,... 'Shuffle','never');
voxnet = trainNetwork(dsTrain,voxnet,options);
Training on single CPU. |======================================================================================================================| | Epoch | Iteration | Time Elapsed | Mini-batch | Validation | Mini-batch | Validation | Base Learning | | | | (hh:mm:ss) | Accuracy | Accuracy | Loss | Loss | Rate | |======================================================================================================================| | 1 | 1 | 00:00:12 | 0.00% | 3.23% | 2.6579 | 2.6466 | 0.0100 | | 4 | 50 | 00:01:53 | 31.25% | 29.03% | 2.1520 | 2.3095 | 0.0100 | | 8 | 100 | 00:03:33 | 28.12% | 36.77% | 2.2633 | 2.1510 | 0.0100 | | 12 | 150 | 00:05:11 | 43.75% | 46.45% | 2.0506 | 1.9057 | 0.0100 | | 16 | 200 | 00:06:49 | 37.50% | 52.26% | 1.8627 | 1.6161 | 0.0100 | | 20 | 250 | 00:08:35 | 50.00% | 59.35% | 1.8573 | 1.4587 | 0.0100 | | 24 | 300 | 00:10:14 | 34.38% | 58.06% | 1.8636 | 1.4360 | 0.0100 | | 27 | 350 | 00:11:51 | 62.50% | 61.94% | 1.4174 | 1.3093 | 0.0100 | | 31 | 400 | 00:13:31 | 65.62% | 64.52% | 1.1966 | 1.2727 | 0.0100 | | 35 | 450 | 00:15:09 | 56.25% | 61.94% | 1.3562 | 1.2473 | 0.0100 | | 39 | 500 | 00:16:49 | 62.50% | 66.45% | 1.2819 | 1.1354 | 0.0100 | | 43 | 550 | 00:18:27 | 56.25% | 65.16% | 1.4563 | 1.1351 | 0.0100 | | 47 | 600 | 00:20:05 | 56.25% | 66.45% | 1.3096 | 1.1142 | 0.0100 | | 50 | 650 | 00:21:40 | 56.25% | 65.16% | 1.0104 | 1.1023 | 0.0100 | | 54 | 700 | 00:23:21 | 75.00% | 70.32% | 0.9403 | 1.0848 | 0.0100 | | 58 | 750 | 00:25:00 | 65.62% | 71.61% | 1.0909 | 1.1003 | 0.0100 | | 60 | 780 | 00:25:59 | 65.62% | 72.26% | 0.9628 | 1.0406 | 0.0100 | |======================================================================================================================|
После структуры [1], этот пример только формирует набор обучения и валидации от Сидни Урбана Обджектса. Оцените эффективность обучившего сеть использования валидации, поскольку это не использовалось, чтобы обучить сеть.
valLabelSet = transform(dsVal,@(data) data{2}); valLabels = readall(valLabelSet); outputLabels = classify(voxnet,dsVal); accuracy = nnz(outputLabels == valLabels) / numel(outputLabels); disp(accuracy)
0.7226
Просмотрите матрицу беспорядка, чтобы изучить точность через различные категории меток
confusionchart(valLabels,outputLabels)
Неустойчивость метки, отмеченная в наборе обучающих данных, является проблемой в точности классификации. График беспорядка иллюстрирует более высокую точность и отзыв для пешехода, наиболее распространенного класса, чем для менее общих классов как фургон. Поскольку целью этого примера является к demonstate основной подход обучения сети классификации с данными об облаке точек, возможные следующие шаги, которые могли быть сделаны, чтобы улучшать производительность классификации, такую как передискретизация набора обучающих данных или достигнуть лучшего баланса метки или использования функции потерь, более устойчивой, чтобы пометить неустойчивость (e.g. взвешенная перекрестная энтропия), не будет исследоваться.
1) Voxnet: 3-я сверточная нейронная сеть для алгоритма распознавания в реальном времени, Дэниела Мэтураны, Себастьяна Шерера, 2015 Международных конференций IEEE/RSJ по вопросам Интеллектуальных Роботов и Систем (IROS)
2) PointPillars: Быстрые Энкодеры для Обнаружения объектов от Облаков точек, Алекса Х. Ленга, Sourabh Vora, и др., CVPR 2019
3) Сидни Урбан возражает набору данных, Аластеру Куэдросу, Джеймсу Андервуду, Бертрану Дуиллару, объектам Сидни Урбана
function datasetPath = 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"; if ~exist(fullfile(dataLoc,'sydney-urban-objects-dataset'),'dir') disp('Downloading Sydney Urban Objects Dataset...'); untar(url+name,dataLoc); end datasetPath = dataLoc.append('sydney-urban-objects-dataset'); end function ds = loadSydneyUrbanObjectsData(datapath,folds) % loadSydneyUrbanObjectsData Datastore with point clouds and % associated categorical labels for Sydney Urban Objects dataset. % % ds = loadSydneyUrbanObjectsData(datapath) constructs a datastore that % represents point clouds and associated categories for the Sydney Urban % Objects dataset. The input, datapath, is a string or char array which % represents the path to the root directory of the Sydney Urban Objects % Dataset. % % ds = loadSydneyUrbanObjectsData(___,folds) optionally allows % specification of desired folds that you wish to be included in the % output ds. For example, [1 2 4] specifies that you want the first, % second, and fourth folds of the Dataset. Default: [1 2 3 4]. if nargin < 2 folds = 1:4; end datapath = string(datapath); path = fullfile(datapath,'objects',filesep); % For now, include all folds in 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'); % Shuffle ds.Files = ds.Files(randperm(length(ds.Files))); end 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 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 function dataOut = formOccupancyGrid(data) grid = pcbin(data{1},[32 32 32]); occupancyGrid = zeros(size(grid),'single'); for ii = 1:numel(grid) occupancyGrid(ii) = ~isempty(grid{ii}); end label = data{2}; dataOut = {occupancyGrid,label}; end function dataOut = augmentPointCloudData(data) ptCloud = data{1}; label = data{2}; % Apply randomized rotation about Z axis. tform = randomAffine3d('Rotation',@() deal([0 0 1],360*rand),'Scale',[0.98,1.02],'XReflection',true,'YReflection',true); % Randomized rotation about z axis ptCloud = pctransform(ptCloud,tform); % Apply jitter to each point in point cloud amountOfJitter = 0.01; numPoints = size(ptCloud.Location,1); D = zeros(size(ptCloud.Location),'like',ptCloud.Location); D(:,1) = diff(ptCloud.XLimits)*rand(numPoints,1); D(:,2) = diff(ptCloud.YLimits)*rand(numPoints,1); D(:,3) = diff(ptCloud.ZLimits)*rand(numPoints,1); D = amountOfJitter.*D; ptCloud = pctransform(ptCloud,D); dataOut = {ptCloud,label}; end