numpower/autograd

高性能数学库,支持自动微分和GPU加速。

0.1.2 2024-06-13 03:04 UTC

This package is auto-updated.

Last update: 2024-09-09 13:19:17 UTC


README

NumPower Autograd 通过启用反向模式(反向传播)自动微分扩展了NumPower扩展。自动微分是一种计算技术,用于高效准确地计算函数的导数。与数值微分相比,数值微分容易出错且效率低下,自动微分系统地应用链式法则来计算梯度。

反向模式自动微分,通常称为反向传播,在机器学习和优化任务中特别强大。它以高度有效的方式计算关于N维参数的标量函数的梯度,非常适合训练复杂模型。

NumPower Autograd 也支持GPU加速,利用图形处理单元的强大能力进行更快计算。这种能力提高了库的性能,尤其是对于大规模数学和机器学习任务。

通过集成反向传播和GPU支持,NumPower Autograd 提供高性能梯度计算能力,促进高级数学和机器学习应用。

要求

安装

$ composer require numpower/autograd

入门

NumPower\Tensor 类是一种N维数组,支持自动微分。它使用NumPower扩展的NDArray对象作为其引擎,保留了使用NDArray的许多灵活性,例如将其用作算术运算的运算符,简化代码阅读。

use NumPower\Tensor;

$a = new Tensor([[1, 2], [3, 4]], requireGrad: True);
$b = new Tensor([[5, 6], [7, 8]], requireGrad: True);

$c = (($a + $b) / $b)->sum();

$c->backward();

$dc_Da = $a->grad();
$dc_Db = $b->grad();

echo "out: \n";
echo $c;
echo "\ndc_Da: \n";
echo $dc_Da;
echo "dc_Db: \n";
echo $dc_Db;
out: 
5.4619045257568
dc_Da: 
[[0.2, 0.166667]
 [0.142857, 0.125]]
dc_Db: 
[[-0.04, -0.0555556]
 [-0.0612245, -0.0625]]

以下代码演示了使用变量和操作进行自动微分的一个简单示例。它计算了计算结果($c)关于其输入($a$b)的梯度。

使用支持CUDA的视频卡

如果您已编译了具有GPU利用能力的NumPower扩展,您可以通过在VRAM中分配您的Tensor在GPU上执行操作。操作将自动识别您的Tensor是在RAM还是VRAM中,并在正确的设备上执行适当的操作。

$a = new Tensor([[1, 2], [3, 4]], requireGrad: True, useGpu: True);
$b = new Tensor([[5, 6], [7, 8]], requireGrad: True, useGpu: True);

$c = ($a + $b)->sum(); // (Addition is performed on GPU)

$c->backward(); // Back propagation is performed on GPU

当处理多参数操作中的不同设备时,TensorNDArray的行为完全相同。除非参数是标量,否则操作的所有N维参数都必须存储在同一个设备上。

使用autograd进行简单训练

这里我们可以看到一个更实际的例子。让我们创建一个具有隐藏层和1个输出层的神经网络。一些常见的神经网络函数是现成的,可以通过NumPower\NeuralNetwork模块静态访问。

use NDArray as nd;
use NumPower\Tensor;
use NumPower\NeuralNetwork\Activations as activation;
use NumPower\NeuralNetwork\Losses as loss;

class SimpleModel
{
    public Tensor $weights_hidden_layer;
    public Tensor $weights_output_layer;
    public Tensor $hidden_bias;
    public Tensor $output_bias;
    private float $learningRate;

    public function __construct(int $inputDim = 2,
                                int $outputDim = 1,
                                int $hiddenSize = 16,
                                float $learningRate = 0.01
    )
    {
        $this->learningRate = $learningRate;
        // Initialize hidden layer weights
        $this->weights_hidden_layer = new Tensor(
            nd::uniform([$inputDim, $hiddenSize], -0.5, 0.5),
            name: 'weights_hidden_layer',
            requireGrad: True
        );
        // Initialize output layer weights
        $this->weights_output_layer = new Tensor(
            nd::uniform([$hiddenSize, $outputDim],-0.5, 0.5),
            name: 'weights_output_layer',
            requireGrad: True
        );
        // Initialize hidden layer bias
        $this->hidden_bias = new Tensor(
            nd::uniform([$hiddenSize],  -0.5, 0.5),
            name: 'hidden_bias',
            requireGrad: True
        );
        // Initialize output layer bias
        $this->output_bias = new Tensor(
            nd::uniform([$outputDim], -0.5, 0.5),
            name: 'output_bias',
            requireGrad: True
        );
    }

    public function forward(Tensor $x, Tensor $y): array
    {
        // Forward pass - Hidden Layer
        $x = $x->matmul($this->weights_hidden_layer) + $this->hidden_bias;
        $x = activation::ReLU($x); // ReLU Activation

        // Forward pass - Output Layer
        $x = $x->matmul($this->weights_output_layer) + $this->output_bias;
        $x = activation::sigmoid($x); // Sigmoid Activation

        // Binary Cross Entropy Loss
        $loss = loss::BinaryCrossEntropy($x, $y, name: 'loss');
        return [$x, $loss];
    }

    public function backward(Tensor $loss)
    {
        // Trigger autograd
        $loss->backward();

        // SGD (Optimizer) - Update Hidden Layer weights and bias
        $dw_dLoss = $this->weights_hidden_layer->grad();

        $this->weights_hidden_layer -= ($dw_dLoss * $this->learningRate);
        $this->weights_hidden_layer->resetGradients();

        $this->hidden_bias -= ($this->hidden_bias->grad() * $this->learningRate);
        $this->hidden_bias->resetGradients();

        // SGD (Optimizer) - Update Output Layer weights and bias
        $db_dLoss = $this->weights_output_layer->grad();

        $this->weights_output_layer -= ($db_dLoss * $this->learningRate);
        $this->weights_output_layer->resetGradients();

        $this->output_bias -= $this->output_bias->grad() * $this->learningRate;
        $this->output_bias->resetGradients();
    }
}

如果您想了解更多关于创建此模型的每个步骤,请访问我们的文档,并查看构建此模型的逐步说明。

上述模型可以用于多种不同的分类问题。为了简单起见,让我们看看我们的模型是否可以解决XOR问题。

$num_epochs = 4000;
$x = new Tensor(nd::array([[0, 0], [1, 0], [1, 1], [0, 1]]), name: 'x');
$y = new Tensor(nd::array([[0], [1], [0], [1]]), name: 'y');

$model = new SimpleModel();

for ($current_epoch = 0; $current_epoch < $num_epochs; $current_epoch++) {
    // Forward Pass
    [$prediction, $loss] = $model->forward($x, $y);
    // Backward Pass
    $model->backward($loss);
    echo "\n Epoch ($current_epoch): ".$loss->getArray();
}

echo "\nPredicted:\n";
print_r($model->forward($x, $y)[0]->toArray());