基於飛槳框架的稀疏計算使用指南

本文作者-是 Yu 欸,華科在讀博士生,定期記錄並分享所學知識,博客關注者5w+。本文將詳細介紹如何在 PaddlePaddle 中利用稀疏計算應用稀疏 ResNet,涵蓋稀疏數據格式的礎知識、如何創建和操作稀疏張量,以及如何開發和訓練稀疏神經網絡模型。

項目完整代碼已上傳至飛槳星河社區:https://aistudio.baidu.com/projectdetail/8055035

在現代計算框架中,為了高效地處理和存儲大規模的數據集,尤其是在這些數據集中存在大量零值的情況下,採用稀疏數據結構變得尤為重要。飛槳是一個領先的深度學習平台,提供了強大的稀疏計算能力,支持從基本的稀疏張量操作到構建複雜的稀疏神經網絡。這些工具主要通過 paddle.sparse 命名空間來實現,使得開發者能夠高效處理大量包含零值的數據集,從而優化內存使用和計算速度。

本文將詳細介紹如何基於飛槳框架進行稀疏計算,包括稀疏數據格式的基礎知識、如何創建和操作稀疏張量,以及如何開發和訓練稀疏神經網絡模型,特別是如何實現和應用稀疏 ResNet。通過這些知識,我們可以更有效地利用計算資源,加速模型訓練過程,同時提高模型處理大規模稀疏數據的能力。

稀疏格式是一種特殊的數據存儲方式,旨在有效存儲和處理其中大部分元素為零的矩陣或張量。這種方法可以顯著減少存儲空間的需求,並提高數據處理的效率。常見的稀疏格式包括 COO(坐標列表格式)、CSR(壓縮稀疏行格式)等。

▎COO(Coordinate Format)

在 COO 格式中,只記錄非零元素的位置和值。這種格式由三個主要組件組成:indices、values 和 shape。indices 是一個二維數組,其中的每一列代表一個非零元素的坐標;values 存儲對應的非零元素值;shape 則描述了張量的維度。如下圖所示。

▎CSR(Compressed Sparse Row Format)

CSR 格式是一種更為緊湊的稀疏表示,專為快速的行訪問和矩陣乘法運算優化。在 CSR 中,通過三個數組 crows、cols 和 values 來表示稀疏矩陣。crows 存儲每一行第一個非零元素的索引,cols 存儲非零元素的列索引,而 values 則直接存儲這些非零元素的值。如下圖所示。

飛槳框架提供了完整的支持來創建和操作 COO 和 CSR 格式的稀疏張量。以下是基於飛槳框架創建和操作這些張量的具體方法。

▎創建 COO 格式的 SparseTensor

COO 格式(Coordinate List):

  • 這是一種常用的稀疏表示格式,其中非零元素通過其坐標列表進行存儲。

  • 使用 paddle.sparse.sparse_coo_tensor(indices,values, shape) 可以創建 COO 格式的稀疏張量,其中 indices 是一個二維整數張量,表示非零元素的坐標;values 是一個張量,包含與 indices 對應的值;shape 是一個定義張量形狀的整數列表或張量。

結構特點:

  • COO 格式通過一個坐標列表存儲非零元素的位置和相應的值。

  • 它使用三個數組:一個數組存儲行索引,一個存儲列索引,第三個存儲元素值。

適用場景:

  • 數據添加頻繁:當稀疏矩陣需要頻繁添加新的非零元素時,COO 格式是較好的選擇,因為它允許直接添加數據而不需重新構造整個數據結構。

  • 簡單結構:適合於那些結構簡單的矩陣,特別是在非零元素分佈較為隨機時。

示例代碼:飛槳框架中, sparse_coo_tensor 函數可用來創建 COO 格式的稀疏張量。

import paddle
indices = [[0, 1, 2], [1, 2, 0]]values = [1.0, 2.0, 3.0]dense_shape = [3, 3]coo = paddle.sparse.sparse_coo_tensor(indices, values, dense_shape)print(coo)

輸出:

Tensor(shape=[3, 3], dtype=paddle.float32, place=Place(cpu), stop_gradient=True,       indices=[[0, 1, 2],                [1, 2, 0]],       values=[1., 2., 3.])

在這個例子中,indices 定義了非零元素的位置,其中每個子數組的兩個數字分別代表行和列的坐標。

▎創建 CSR 格式的 SparseTenso

CSR 格式(Compressed Sparse Row)

  • 這是另一種常用的稀疏表示格式,主要用於優化行訪問的性能,其中非零元素通過行的壓縮方式進行存儲。

  • 使用 paddle.sparse.sparse_csr_tensor(crows, cols, values, dense_shape) 可以創建 CSR 格式的稀疏張量,其中crows定義了每一行非零元素開始的位置在 values 數組中的索引,這有助於快速定位行的起始點和終點。cols 則指示了非零元素在各自行中的列位置,values 提供了相應的值。dense_shape 指定了張量的整體形狀,即行數和列數。

結構特點:

  • CSR 格式通過行來壓縮存儲,使用三個數組:行指針數組、列索引數組、以及非零元素值數組。

  • 行指針數組的大小比實際行數多一個,用於表示每行的起始位置和結束位置。

適用場景:

  • 行操作優化:當需要高效地進行行相關的操作(如行切片、行求和)時,CSR 格式提供更優的性能。

  • 矩陣乘法:對於稀疏矩陣與稀疏或密集矩陣的乘法運算,CSR 格式通常會提供更好的性能。

  • 大規模數據處理:在處理大規模稀疏數據時,CSR 格式因其壓縮特性而節省內存。

示例代碼:為了創建 CSR 格式的稀疏張量,飛槳框架提供了 sparse_csr_tensor 函數。

import paddle
crows = [0, 2, 3, 5]cols = [1, 3, 2, 0, 1]values = [1, 2, 3, 4, 5]dense_shape = [3, 4]csr = paddle.sparse.sparse_csr_tensor(crows, cols, values, dense_shape)print(csr)

輸出:

Tensor(shape=[3, 4], dtype=paddle.int64, place=Place(cpu), stop_gradient=True,        crows=[0, 2, 3, 5],        cols=[1, 3, 2, 0, 1],        values=[1, 2, 3, 4, 5])

在這個例子中,crows 定義了每一行非零元素開始的位置在 values 數組中的索引,這有助於快速定位行的起始點和終點。

這種 CSR 格式的表示方式適用於數據稀疏且行訪問頻繁的場景。它通過壓縮行索引來減少內存使用,優化了對稀疏矩陣行的操作,使得行級操作更加高效。在處理行密集型操作(如行切片或行求和)時特別高效,也適合於稀疏矩陣的乘法等計算密集任務。

▎創建稀疏張量的相關參數詳解

在基於飛槳框架創建稀疏張量 API 中,參數的設計允許用戶靈活定義和操作稀疏數據結構。對於兩種類型的稀疏張量創建函數,參數主要涉及初始化數據的類型和結構,其中:

■ 共通參數

對於 sparse_coo_tensor 和 sparse_csr_tensor 函數,存在一些共通的參數,這些參數允許用戶指定如何構建和處理稀疏張量:

1) values (list|tuple|ndarray|Tensor):

  • 表示非零元素的實際數值。

  • 類似於索引參數,可以是 list、tuple、NumPy ndarray 或 Paddle Tensor。

2) shape (list|tuple, 可選):

  • 定義稀疏張量的形狀,如果未提供,則會根據 indices 或 crows 和 cols 的最大值自動推斷。

  • 必須是一個整數列表或元組,指定張量在每個維度的大小。

3) dtype (str|np.dtype, 可選):

  • 指定張量元素的數據類型,如 ‘float32’, ‘int64’ 等。

  • 如果未指定,則從 values 的數據類型自動推斷。

4) place (CPUPlace|CUDAPinnedPlace|CUDAPlace|str, 可選):

  • 決定張量的存儲設備,例如 CPU 或 GPU。

  • 如果未指定,則使用當前環境的預設設備。

5) stop_gradient (bool, 可選):

  • 指示是否對該張量進行梯度計算。

  • 在大多數深度學習應用中,非模型權重的張量通常設置為 True 以提高計算效率。

■ 特定格式的參數細節

除了上述共通參數外,COO 和 CSR 格式因其數據結構的不同而在參數應用上有所區別。

indices, crows, cols (list|tuple|ndarray|Tensor):

  • 對於 COO 格式,indices 參數是一個二維數組,用於直接指定每個非零元素的多維坐標。主要用於數據的隨機訪問和轉換操作,適用於那些非零元素分佈相對均勻的場景。

  • 對於 CSR 格式,crows 表示每一行的起始非零元素索引,而 cols 存儲這些非零元素的列索引。CSR 格式優化了行的連續訪問,非常適合矩陣乘法和其他行優先操作。

  • 這些參數可以是 Python 的 list 或 tuple,也可以是 NumPy ndarray 或 Paddle Tensor。

通過這些參數的靈活使用,飛槳框架允許開發者以高效且靈活的方式處理大規模稀疏數據集,從而在保持性能的同時減少內存消耗。

▎COO 格式和 CSR 格式的選擇建議

  • 如果應用主要涉及構建稀疏矩陣和逐項添加數據,COO 格式會更簡單且直接。

  • 如果應用需要高效的行操作或頻繁進行矩陣乘法,特別是在稀疏矩陣較大的情況下,CSR 格式是更好的選擇。

選擇哪種格式應基於具體應用需求,如操作類型、數據規模和性能要求。在飛槳框架中,你可以根據需要輕鬆地在兩種格式之間轉換,以適應不同的計算需求。

▎稀疏與稠密 Tensor 互轉

飛槳框架提供了一套簡單易用的接口,使得稀疏張量的使用與傳統的稠密張量操作體驗高度一致,從而降低了學習成本並便於開發者快速上手。這種設計允許在同一個模型中靈活地使用稠密和稀疏數據結構,而且方便轉換,這對於處理大規模數據集尤其重要,在深度學習、圖像處理和自然語言處理等領域有著廣泛的應用。

飛槳框架支持通過幾個簡單的 API,實現稀疏與稠密之間的轉換,這些操作保證了數據處理的靈活性和效率。如 Tensor.to_dense()可以將稀疏張量轉換為標準的密集張量, Tensor.to_sparse_coo(), 和 Tensor.to_sparse_csr() 可以將密集張量轉換為 COO 格式、CSR 格式的稀疏張量。

以下為稠密到稀疏的轉換代碼示例:

import paddle
# 創建一個稠密的 Tensordense = paddle.to_tensor([[0, 1, 0, 2],                           [0, 0, 3, 4]], dtype='float32')
# 將稠密 Tensor 轉換為 COO 格式的稀疏 Tensorcoo = dense.to_sparse_coo(sparse_dim=2)print(coo)# 輸出:# Tensor(shape=[2, 4], dtype=paddle.float32, place=Place(gpu:0), stop_gradient=True, #       indices=[[0, 0, 1, 1],#                [1, 3, 2, 3]], #       values=[1., 2., 3., 4.])
# 將稠密 Tensor 轉換為 CSR 格式的稀疏 Tensorcsr = dense.to_sparse_csr()print(csr)# 輸出:# Tensor(shape=[2, 4], dtype=paddle.float32, place=Place(gpu:0), stop_gradient=True, #       crows=[0, 2, 4], #       cols=[1, 3, 2, 3], #       values=[1., 2., 3., 4.])

這些轉換非常直觀,僅需要簡單的一步操作就可以完成,使得稀疏和稠密格式之間的交互變得簡潔高效。

飛槳框架的設計目標之一是提供一致的用戶體驗,無論是處理稀疏數據還是稠密數據。這意味著即便是在處理包含大量零值的數據集時,開發者也可以利用熟悉的接口和模式來構建和訓練模型。

▎API 設計的一致性

飛槳框架的稀疏模塊提供了與常規稠密操作相似的 API 接口,開發者無需學習新的 API 就能處理稀疏數據。例如:

  • 稀疏卷積層:稀疏模塊中的 SubmConv3D 直接對應常規卷積操作中的 Conv3D。二者參數非常相似,如 in_channels, out_channels, stride, padding 等。

  • 批歸一化和激活函數:稀疏模塊同樣提供了批歸一化和激活函數,如 BatchNorm3D 和 ReLU,其用法與常規模塊中的相同。

▎集成度:訓練和推理的處理流程

無論是稀疏還是稠密模型,飛槳框架中的訓練和推理流程保持一致。稀疏操作可以與飛槳框架的其他特性(如自動微分和優化器)無縫集成,使得構建和訓練稀疏模型與常規模型幾乎無異。

1) 定義模型:無論選擇稀疏還是稠密模型,模型定義的方式都是相似的,使用 paddle.nn.Layer 類來構建網絡層。

2) 編譯模型:使用 paddle.Model 對象來包裝定義好的網絡,然後編譯,包括設置優化器、損失函數和評估指標。

3) 訓練和評估:通過調用 .fit 和 .evaluate 方法來進行訓練和評估,這與處理稠密數據的流程完全一致。

▎稀疏 ResNet 的應用場景

在處理點雲數據、圖像識別或自然語言處理任務時,輸入數據通常具有很高的維度和稀疏性。例如,3D 點雲數據往往是非結構化的,大部分體積內沒有有效信息(即大部分體積是空的)。使用傳統的密集(dense)卷積網絡處理這類數據會帶來兩個主要問題:效率低下:對於大量的空白區域依然進行計算,消耗計算資源;存儲浪費:需要為大量的零值分配存儲資源。

稀疏 ResNet 解決了這些問題,通過僅在非零數據點上進行操作,從而大幅提高了計算和存儲效率。

▎構建稀疏 ResNet 模型

在飛槳框架中,稀疏 ResNet 可以通過 paddle.sparse 模塊中的稀疏卷積層(如 SubmConv3D)來實現。這些層專門用來處理稀疏數據。稀疏卷積層接受包含非零元素坐標和值的稀疏張量,並只在這些非零元素上執行卷積運算。通過構建包含這些稀疏卷積層的網絡(如 ResNet 結構中的基礎塊),可以高效處理稀疏數據。

創建稀疏 ResNet 主要涉及以下幾個步驟:

1) 創建稀疏張量:首先需要從稀疏數據(即大部分值為零的數據)中創建稀疏張量。這通常涉及指定非零數據點的坐標和相應的值。

2) 定義稀疏網絡結構:設計一個網絡結構,它包含適用於處理稀疏數據的特殊卷積層(如 Paddle 的 SubmConv3D)。這些層特別優化了內存和計算資源,只在數據非零的地方進行計算。

3) 前向傳播:將稀疏張量輸入到網絡中,執行前向傳播,網絡會在內部處理稀疏數據,並輸出結果。

4) 訓練和評估:就像使用常規神經網絡一樣,定義損失函數和優化器,然後在訓練數據上訓練網絡,最後在驗證數據上評估網絡的性能。

▎稀疏 ResNet 的關鍵組件

飛槳框架的 paddle.sparse 模塊提供了對稀疏數據操作的支持,包括稀疏張量的創建、轉換和計算功能。這些神經網絡層針對稀疏數據的特點進行了優化,以減少對零值的計算和存儲需求,提高處理效率。

1) 稀疏張量(Sparse Tensor):

  • 稀疏張量是一種特殊的數據結構,主要用於有效存儲和處理大部分元素為零的數據。

  • 在飛槳框架中,可以使用 paddle.sparse.sparse_coo_tensor 來創建稀疏張量,需要提供非零元素的坐標和值。

2) 稀疏卷積層(Sparse Convolution):

  • paddle.sparse.nn.Conv3D:標準的三維卷積層,支持在稀疏數據上的操作,適用於處理體積大的三維數據。

  • paddle.sparse.nn.SubmConv3D:子流形三維卷積層,用於處理3D 數據的稀疏子矩陣卷積層。該層允許在3D 體積數據中有效地進行卷積操作,無需將整個數據轉換為密集格式,特別適用於醫學影像和三維掃瞄等領域。

3) 批歸一化層(Batch Normalization)

  • paddle.sparse.nn.BatchNorm3D:批歸一化層,專為三維數據設計,可以與稀疏卷積層結合使用,以優化稀疏數據的特徵歸一化過程。

4) 池化層(Pooling Layers)

  • paddle.sparse.nn.MaxPool3D:三維最大池化層,用於在稀疏三維數據上執行池化操作,有助於降低數據的維度和提高模型的抽像能力。

5) 激活層(Activation Layers)

  • paddle.sparse.nn.ReLU、paddle.sparse.nn.ReLU6:標準 ReLU 和 ReLU6激活函數,支持在稀疏數據路徑中使用,與常規的激活函數使用方法相同,但針對稀疏數據進行了優化。

  • paddle.sparse.nn.LeakyReLU:LeakyReLU 激活層,包含小負斜率的 ReLU 變體,適用於在稀疏數據中增強模型的非線性處理能力。

  • paddle.sparse.nn.Softmax:Softmax 激活層,適用於稀疏數據路徑,使用方法與常規密集數據的 Softmax 相同,但特別針對稀疏數據進行了優化,常用於處理多分類問題。

▎構建稀疏 ResNet 模型的示例代碼

在飛槳框架中,稀疏 ResNet 的實現和使用與傳統的稠密網絡相似,這得益於飛槳框架稀疏模塊的設計,使得調用體驗與稠密高度一致,非常容易上手。通過利用稀疏技術,可以有效處理大規模稀疏數據集,提高計算效率,降低存儲需求,這在處理現代大數據應用時顯得尤為重要。

下面以稀疏 ResNet 為例,說明飛槳框架對稀疏神經網絡層的支持:

import paddlefrom paddle import sparsefrom paddle.sparse import nn as sparse_nn
# 定義3D稀疏卷積塊def sparse_conv_block(in_channels, out_channels, stride=1, padding=1, key=None):    block = paddle.nn.Sequential(        sparse_nn.SubmConv3D(in_channels, out_channels, kernel_size=3, stride=stride, padding=padding, bias_attr=False, key=key),        sparse_nn.ReLU()    )    return block
# 定義一個簡單的稀疏3D ResNet模型class SparseResNet(paddle.nn.Layer):    def __init__(self, in_channels):        super(SparseResNet, self).__init__()        self.layer1 = sparse_conv_block(in_channels, 16, key='layer1')        self.layer2 = sparse_conv_block(16, 32, stride=2, key='layer2')        self.layer3 = sparse_conv_block(32, 64, stride=2, key='layer3')
    def forward(self, x):        x = self.layer1(x)        x = self.layer2(x)        x = self.layer3(x)        return x
# 假設輸入數據batch_size = 1channels = 1depth = 100height = 100width = 100
# 創建稀疏張量的坐標和值coords = paddle.to_tensor([[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 1, 2, 1, 1], [0, 2, 2, 1, 2], [0, 1, 2, 2, 0]], dtype='int64')  # 5D坐標 (batch, channel, depth, height, width)values = paddle.to_tensor([1.0, 1.5, 2.0, 3.0, 3.5], dtype='float32')  # 每個值對應一個坐標shape = paddle.to_tensor([batch_size, channels, depth, height, width], dtype='int64')  # 5D形狀
# 創建稀疏張量x = sparse.sparse_coo_tensor(coords, values, shape)
# 實例化模型model = SparseResNet(channels)
# 使用模型進行預測output = model(x)print(output)

模型打印結果:

SparseResNet(  (layer1): Sequential(    (0): SubmConv3D(3, 16, kernel_size=[3, 3, 3], padding=1, data_format=NDHWC)    (1): BatchNorm(num_features=16, momentum=0.9, epsilon=1e-05, data_format=NDHWC)    (2): ReLU()    (3): SubmConv3D(16, 16, kernel_size=[3, 3, 3], padding=1, data_format=NDHWC)    (4): BatchNorm(num_features=16, momentum=0.9, epsilon=1e-05, data_format=NDHWC)    (5): ReLU()  )  (layer2): Sequential(    (0): SubmConv3D(16, 32, kernel_size=[3, 3, 3], stride=[2, 2, 2], padding=1, data_format=NDHWC)    (1): BatchNorm(num_features=32, momentum=0.9, epsilon=1e-05, data_format=NDHWC)    (2): ReLU()    (3): SubmConv3D(32, 32, kernel_size=[3, 3, 3], padding=1, data_format=NDHWC)    (4): BatchNorm(num_features=32, momentum=0.9, epsilon=1e-05, data_format=NDHWC)    (5): ReLU()  )  (layer3): Sequential(    (0): SubmConv3D(32, 64, kernel_size=[3, 3, 3], stride=[2, 2, 2], padding=1, data_format=NDHWC)    (1): BatchNorm(num_features=64, momentum=0.9, epsilon=1e-05, data_format=NDHWC)    (2): ReLU()    (3): SubmConv3D(64, 64, kernel_size=[3, 3, 3], padding=1, data_format=NDHWC)    (4): BatchNorm(num_features=64, momentum=0.9, epsilon=1e-05, data_format=NDHWC)    (5): ReLU()  ))

輸出:

Tensor(shape=[1, 1, 100, 100, 64], dtype=paddle.float32, place=Place(cpu), stop_gradient=False,        indices=[[0, 0, 0, 0],                [0, 0, 0, 0],                [0, 1, 1, 2],                [0, 1, 2, 2]],        values=[[0.        , 0.        , 0.08977110, 0.        , 0.        ,                0.        , 0.        , 0.16325581, 0.        , 0.        ,                0.08592274, 0.        , 0.        , 0.        , 0.07656589,                ……                0.12824626, 0.38880903, 0.        , 0.        , 0.23209766,                0.        , 0.        , 0.        , 0.24539268, 0.17324814,                0.        , 0.        , 0.        , 0.        ]])

飛槳框架的稀疏模塊可以創建類似於常規 ResNet 的模型架構,但使用的是稀疏卷積層替換傳統的密集卷積層。每個稀疏卷積層後通常跟隨一個批歸一化層和 ReLU 激活函數,形成一個基礎的稀疏殘差塊。

代碼來源:Paddle3D 的 sparse_resnet.py

▎代碼註釋

這段代碼定義了一個基於飛槳框架的稀疏3D 殘差網絡(SparseResNet3D),主要用於處理3D 點雲數據,如自動駕駛系統中的激光雷達掃瞄數據。它通過稀疏卷積層對體素化(voxelized)的點雲數據進行特徵提取和處理。

“””該符號內代碼註釋為新增”””

導入所需庫和模塊:

import numpy as npimport paddlefrom paddle import sparsefrom paddle.sparse import nnfrom paddle3d.apis import managerfrom paddle3d.models.layers import param_init

這些庫包括 numpy 用於數學運算,飛槳框架及其稀疏模塊用於深度學習操作,以及 paddle3d 的 API 和模型層初始化。

定義卷積函數:

def conv3x3(in_out_channels, out_out_channels, stride=1, indice_key=None, bias_attr=True):    """3x3 convolution with padding, specifically for SubM sparse 3D convolution."""    return nn.SubmConv3D(        in_out_channels, out_out_channels, kernel_size=3, stride=stride, padding=1, bias_attr=bias_attr, key=indice_key)
def conv1x1(in_out_channels, out_out_channels, stride=1, indice_key=None, bias_attr=True):    """1x1 convolution, also for SubM sparse 3D convolution."""    return nn.SubmConv3D(        in_out_channels, out_out_channels, kernel_size=1, stride=stride, padding=1, bias_attr=bias_attr, key=indice_key)

conv3x3和conv1x1是用於創建3D 稀疏卷積層的幫助函數,它們使用了飛槳框架的 SubmConv3D,這是一種專門處理稀疏數據的3D 卷積。

定義稀疏基礎塊類:

class SparseBasicBlock(paddle.nn.Layer):""" A basic building block for constructing sparse 3D ResNet with two convolutional layers."""
    expansion =1
def__init__(self, in_channels, out_channels, stride=1, downsample=None, indice_key=None):super(SparseBasicBlock, self).__init__()
self.conv1 = conv3x3(in_channels, out_channels, stride, indice_key, True)self.bn1 = nn.BatchNorm(out_channels, epsilon=1e-3, momentum=0.01)self.relu = nn.ReLU()self.conv2 = conv3x3(out_channels, out_channels, indice_key=indice_key, bias_attr=True)self.bn2 = nn.BatchNorm(out_channels, epsilon=1e-3, momentum=0.01)self.downsample = downsampleself.stride = stride
def forward(self, x):        identity = x
        out =self.conv1(x)        out =self.bn1(out)        out =self.relu(out)        out =self.conv2(out)        out =self.bn2(out)
ifself.downsample isnotNone:            identity =self.downsample(x)
        out = sparse.add(out, identity)        out =self.relu(out)return out

SparseBasicBlock 是 SparseResNet3D 的核心模塊,包括兩個稀疏卷積層、批歸一化和 ReLU 激活函數,以及可選的下采樣,用於殘差連接。

定義 SparseResNet3D 網絡:

@manager.MIDDLE_ENCODERS.add_component

class SparseResNet3D(paddle.nn.Layer):    """ The main Sparse 3D ResNet class, designed for processing voxelized point cloud data."""        def __init__(self, in_channels, voxel_size, point_cloud_range):        super(SparseResNet3D, self).__init__()
        # Initial conv layer        self.conv_input = paddle.nn.Sequential(            nn.SubmConv3D(in_channels, 16, 3, bias_attr=False, key='res0'),            nn.BatchNorm(16), nn.ReLU())
        # Subsequent layers with increasing channel depth and decreasing spatial dimensions        self.conv1 = paddle.nn.Sequential(            SparseBasicBlock(16, 16, indice_key='res0'),            SparseBasicBlock(16, 16, indice_key='res0'),)
        self.conv2 = paddle.nn.Sequential(            nn.Conv3D(16, 32, 3, 2, padding=1, bias_attr=False),  # downsample            nn.BatchNorm(32), nn.ReLU(),            SparseBasicBlock(32, 32, indice_key='res1'),            SparseBasicBlock(32, 32, indice_key='res1'),)
        self.conv3 = paddle.nn.Sequential(            nn.Conv3D(32, 64, 3, 2, padding=1, bias_attr=False),  # downsample            nn.BatchNorm(64), nn.ReLU(),            SparseBasicBlock(64, 64, indice_key='res2'),            SparseBasicBlock(64, 64, indice_key='res2'),)
        self.conv4 = paddle.nn.Sequential(            nn.Conv3D(64, 128, 3, 2, padding=[0, 1, 1], bias_attr=False),  # downsample            nn.BatchNorm(128), nn.ReLU(),            SparseBasicBlock(128, 128, indice_key='res3'),            SparseBasicBlock(128, 128, indice_key='res3'),)
        # Extra conv layer to further process features        self.extra_conv = paddle.nn.Sequential(            nn.Conv3D(128, 128, (3, 1, 1), (2, 1, 1), bias_attr=False),  # Adjust the spatial dimensions            nn.BatchNorm(128), nn.ReLU(),)
        # Calculate the grid size for the 3D data based on the provided voxel size and point cloud range        point_cloud_range = np.array(point_cloud_range, dtype=np.float32)        voxel_size = np.array(voxel_size, dtype=np.float32)        grid_size = (point_cloud_range[3:] - point_cloud_range[:3]) / voxel_size        grid_size = np.round(grid_size).astype(np.int64)        self.sparse_shape = np.array(grid_size[::-1]) + [1, 0, 0]        self.in_channels = in_channels        self.init_weight()
    def init_weight(self):        """ Initialize weights for convolutional layers and batch normalization layers."""        for layer in self.sublayers():            if isinstance(layer, (nn.Conv3D, nn.SubmConv3D)):                param_init.reset_parameters(layer)            if isinstance(layer, nn.BatchNorm):                param_init.constant_init(layer.weight, value=1)                param_init.constant_init(layer.bias, value=0)
    def forward(self, voxel_features, coors, batch_size):        """ The forward pass for processing input voxel features and coordinates."""        # Setup the sparse tensor with the specified shape and input features        shape = [batch_size] + list(self.sparse_shape) + [self.in_channels]        sp_x = sparse.sparse_coo_tensor(            coors.transpose((1, 0)),            voxel_features,            shape=shape,            stop_gradient=False)
        # Pass the sparse tensor through the sequential layers        x = self.conv_input(sp_x)        x_conv1 = self.conv1(x)        x_conv2 = self.conv2(x_conv1)        x_conv3 = self.conv3(x_conv2)        x_conv4 = self.conv4(x_conv3)
        # Final extra convolutional processing        out = self.extra_conv(x_conv4)
        # Convert the output back to a dense tensor and adjust dimensions for further processing        out = out.to_dense()        out = paddle.transpose(out, perm=[0, 4, 1, 2, 3])        N, C, D, H, W = out.shape        out = paddle.reshape(out, shape=[N, C * D, H, W])        return out

此類中定義了一系列卷積層和殘差塊,用於逐步處理和提取輸入點雲數據的特徵。網絡通過逐層降采樣來增加特徵深度並減小空間維度,最終輸出密集的特徵張量,適合後續的處理或學習任務。

飛槳框架不僅支持自定義稀疏神經網絡結構,也可以通過提供的 API 輕鬆地實現已有的經典結構,如 ResNet、VGG 等。對於這些經典網絡,通過替換標準的卷積層為相應的稀疏卷積層,可以使其適應稀疏數據的處理,從而拓展其應用到新的領域,如3D 點雲處理。

總的來說,飛槳框架在提供稀疏計算支持的同時,確保了開發體驗的一致性和直觀性,方便開發者在稀疏和稠密數據操作之間切換,同時保證數據處理高效。

▎官方開放課程

7月至10月特設《飛槳框架3.0全面解析》直播課程,邀請百度飛槳核心團隊數十位工程師傾囊相授,技術解析加代碼實戰,帶大家掌握核心框架、分佈式計算、產業級大模型套件及低代碼工具、前沿科學計算技術案例等多個方面的框架技術及大模型訓推優化經驗,實打實地幫助大家用飛槳框架3.0在實際開發工作中提效創新!

為了讓優秀的飛槳開發者們掌握第一手技術動態、讓企業落地更加高效,根據大家的呼聲安排史上最強飛槳技術大餐!涵蓋飛槳框架3.0、低代碼開發工具 PaddleX、大語言模型開髮套件 PaddleNLP、多模態大模型開髮套件 PaddleMIX、典型產業場景下硬件適配技術等多個方向,一起來看吧!

▎參考文獻

1. 官網 paddle.sparse 目錄

https://www.paddlepaddle.org.cn/documentation/docs/zh/api/paddle/sparse/Overview_cn.html

2. https://mp.weixin.qq.com/s/SD_P2K1HP3FVM5ADqbpmVQ