[JAVA]利用ND4J实现SMID指标加快计算简单案例运用

SMID指令介绍

SIMD(Single Instruction, Multiple Data,单指令多数据)是一种并行处理方式,它允许在单个指令周期内对多个数据执行相同的操作。这种指令集广泛应用于各种计算任务,尤其是在需要处理大量数据的场合,如图形处理、科学计算、机器学习等。以下是一些常见的SIMD指令集及其特点:

1. SIMD 指令集

SSE (Streaming SIMD Extensions)

  • 发布时间:1999年,随Intel Pentium III处理器推出。
  • 特点:最初支持128位向量,可以同时处理4个单精度浮点数或2个双精度浮点数。

SSE2

  • 发布时间:2001年,随Intel Pentium 4处理器推出。
  • 特点:增加了对128位整数操作的支持,以及其他一些指令。

SSE3

  • 发布时间:2004年,随Intel Pentium 4处理器推出。
  • 特点:包含了一些新的指令,用于提高性能,如水平求和和扩展指令。

SSSE3 (Supplemental SSE3)

  • 发布时间:2006年,随Intel Core 2处理器推出。
  • 特点:提供了更多的字符串和文本处理指令。

SSE4 (SSE4.1, SSE4.2)

  • 发布时间:2007年(SSE4.1)和2008年(SSE4.2),随Intel Core 2处理器推出。
  • 特点:增加了更多的指令,包括图形和媒体处理指令。

AVX (Advanced Vector Extensions)

  • 发布时间:2011年,随Intel Sandy Bridge处理器推出。
  • 特点:支持256位向量,可以同时处理8个单精度浮点数或4个双精度浮点数。

AVX2

  • 发布时间:2013年,随Intel Haswell处理器推出。
  • 特点:扩展了AVX,增加了对整数操作的支持,以及其他一些指令。

AVX-512

  • 发布时间:2015年,随Intel Xeon Phi处理器推出。
  • 特点:支持512位向量,可以同时处理16个单精度浮点数或8个双精度浮点数

SIMD 指令的工作原理

SIMD指令通过以下方式工作:

  • 数据并行性:在单个CPU周期内,一个SIMD指令可以操作多个数据点。
  • 向量寄存器:SIMD指令集通常使用向量寄存器,这些寄存器比标准的寄存器宽,可以容纳多个数据点。
  • 向量化操作:通过向量化,开发者可以将多个数据操作组合成一个操作,减少指令的数量,提高效率。

SIMD 的优势

  • 性能提升:通过并行处理多个数据点,SIMD可以显著提高数据处理速度。
  • 简化编程:程序员可以编写更少的代码来实现相同的功能。
  • 减少内存访问:由于可以一次性处理多个数据,SIMD减少了内存访问的次数。

SIMD 的挑战

  • 数据对齐:为了有效地使用SIMD,数据需要在内存中对齐到特定的边界。
  • 代码复杂性:编写高效的SIMD代码可能比传统的标量代码更复杂。

SIMD指令集的引入和发展,为各种计算密集型应用提供了显著的性能提升,尤其是在处理大规模并行计算任务时

ND4j

ND4j描述

ND4J 是一个用于科学计算和深度学习的 Java 库,它提供了多种优化算法,这些算法在训练机器学习模型时非常重要,尤其是深度学习模型 。官网地址 deeplearning4j.konduit.ai/ ,以下是 ND4J 支持的一些优化算法:

  1. Stochastic Gradient Descent (SGD)  – 随机梯度下降是最基础的优化算法之一。
  2. Conjugate Gradient (CG)  – 共轭梯度法,通常用于解决线性方程组,也可以用于优化问题。
  3. LBFGS – Limited-memory Broyden-Fletcher-Goldfarb-Shanno,是一种拟牛顿法,适用于小到中等规模的优化问题。
  4. RMSprop – Root Mean Square Propagation,它通过调整学习率来适应每个参数的更新。
  5. Adam – Adaptive Moment Estimation,结合了动量(Momentum)和自适应学习率(如 RMSprop)的方法。
  6. Nesterov’s Accelerated Gradient (NAG)  – 也是一种动量方法,与标准的动量方法相比,它在计算梯度时考虑了参数的更新。
  7. Adadelta – 是一种自适应学习率方法,它旨在改善 AdaGrad 的快速学习率下降问题。
  8. AdaGrad – Adaptive Subgradient Methods,通过为每个参数分配不同的学习率来适应数据的稀疏性。
  9. Adamax – Adam的一种变体,对学习率的更新进行了修改。
  10. Nadam – 结合了 Nesterov 加速梯度(NAG)和 Adam 优化器的特点

ND4J利用SIMD加速

  1. 数据级并行处理:SIMD允许在单个CPU指令中同时处理多个数据点。ND4J通过在底层使用优化的数学库(如OpenBLAS、MKL等),这些库本身已经针对SIMD进行了优化,使得在进行矩阵运算时能够并行处理多个元素,从而提高效率。
  2. 向量化和批处理:ND4J在设计时就考虑
  3. 到了向量化操作,这意味着它可以将多个操作组合成单个指令,这样可以减少指令的数量并提高CPU的数据吞吐量。例如,当执行数组或矩阵运算时,ND4J会尽量使用SIMD指令来同时处理数组中的多个元素。
  4. 内存对齐:为了充分利用SIMD,数据在内存中的对齐非常重要。ND4J确保其数据结构(如INDArray)在内存中对齐,以适应SIMD的要求,这样可以减少内存访问时间并提高缓存效率。
  5. 循环展开和软件流水线:ND4J的底层实现会进行循环展开和软件流水线优化,这些优化可以更好地利用SIMD指令,减少循环的开销,并提高指令的执行效率。
  6. 平台特定优化:ND4J支持多种平台和架构,包括x86、ARM等,它会根据不同的CPU架构采用不同的SIMD指令集,如SSE、AVX、NEON等,以最大化性能

样例

@Test

public void testData() {
// 创建两个向量
INDArray vectorA = Nd4j.create(new double[]{1, 2, 3});
INDArray vectorB = Nd4j.create(new double[]{4, 5, 6});
// 向量加法
INDArray vectorSum = vectorA.add(vectorB);
System.out.println("Vector Sum: " + vectorSum);
// 标量乘法
// double scalar = 2;
INDArray scalarMultiplication = vectorA.mul(vectorB);
System.out.println("Scalar Multiplication: " + scalarMultiplication+"=="+scalarMultiplication.sumNumber());
}

查看运行结果,可以很清晰看到运用SMID指令,并使用OpenBLAS数学库优化多核并行计算

ND4J运用

maven引入

<dependency>
    <groupId>org.nd4j</groupId>
    <artifactId>nd4j-native-platform</artifactId>
    <version>1.0.0-M2.1</version>
</dependency>

创建INDArray

0,1和标量值初始化数组

创建数组最常用的两种方法是:

  • Nd4j.zeros(int…)
  • Nd4j.ones(int…)
//创建一个由2行2列组成的零填充数组
INDArray arry = Nd4j.zeros(2, 2);
System.out.println(arry); //结果[[ 0, 0],[ 0, 0]]
//要创建一个填充了8的数组
arry.addi(8);
System.out.println(arry);//结果[[ 8.0000, 8.0000],[ 8.0000, 8.0000]]

创建随机数组

Nd4j提供了一些创建INDArrays`的方法.

1.伪随机数。

`要在0到1范围内生成统一的随机数,对于3个或更多维度Nd4j.rand(int[])

// 创建一个由2行3列,要在0到1范围内生成统一的随机数数值
INDArray array= Nd4j.rand(2, 3);
System.out.println(arry);//[[ 0.6212, 0.2898, 0.5704],[ 0.9650, 0.6019, 0.8635]]

2.要生成平均零和标准偏差为1的高斯随机数,Nd4j.randn(int nRows, int nCols)或Nd4j.randn(int[])。 对于可重复性(即设置nd4j的随机数生成器种子),可以使用Nd4j.getRandom().setSeed(long)。

// 设置随机种子以保证结果的可复现性
Random random = Nd4j.getRandomFactory().getNewRandomInstance(12345L);
Nd4j.getRandom().setSeed(12345L);
// 指定高斯分布的参数:均值和标准差
double mean = 0.0;
double stdDev = 20.0;
// 创建一个形状为 [rows, columns] 的高斯分布数组
long rows = 5;
long columns = 3;
INDArray gaussianArray = Nd4j.randn(random,rows, columns).mul(stdDev).add(mean);
// 打印生成的数组
System.out.println("Generated Gaussian Distribution Array:");
System.out.println(gaussianArray);
结果
[[ -8.1492, 16.6402, -8.6924],
[ 7.1901, 5.3233, 9.1873],
[ 13.1388, 4.3149, 15.5846],
[ 23.4820, 7.8276, -35.1364],
[ 15.9777, -29.1674, 21.6758]]

Java数组创建NDArrays

ND4J为Java类型int、float、double组创建数组提供方法。 要从1D Java数组创建1D NDArray,请使用:

  • 创建行向量: Nd4j.create(float[])Nd4j.create(double[])
  • 创建列向量: Nd4j.create(float[],new int[]{length,1})Nd4j.create(double[],new int[]{length,1})
  • 创建二维数组, 使用 Nd4j.create(float[][])Nd4j.create(double[][]).
  • 创建多个维度的Java原始数组(double [][][]等)创建NDArrays,
// 创建1D列向量(实际上是1xN的2D数组,但通常视为1D向量)

INDArray oneDimensionalColumnVector = Nd4j.create(new double[]{1, 2, 3, 4, 5});
System.out.println("1D Column Vector:");
System.out.println(oneDimensionalColumnVector);
// 创建2D数组
INDArray twoDimensionalArray = Nd4j.create(new double[][]{{1, 2}, {3, 4}, {5, 6}});
System.out.println("2D Array:");
System.out.println(twoDimensionalArray);
// 创建多维数组(例如3D数组)
INDArray threeDimensionalArray = Nd4j.create(new double[][][]{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}});
System.out.println("3D Array:");
System.out.println(threeDimensionalArray);

ND4J加减乘除

1、加法

  •     INDArray add(INDArray other)  :元素对应相加,返回的张量是拷贝出来的
  •     INDArray addi(INDArray other) :元素对应相加,与上面不同的是,返回值不是拷贝出来的新数组,而是用计算结果替换原内存数据
  •     INDArray add(Number n):每个元素加上一个标量
  •     INDArray addi(Number n):每个元素加上一个标量,并覆盖原数组

2、减法

  •     INDArray sub(Number n):每个元素减去一个标量
  •     INDArray subi(Number n):每个元素减去标量,并覆盖原数组
  •     INDArray sub(INDArray other):对应元素相减
  •     INDArray subi(INDArray other):对应元素相减,并覆盖原数组

3、乘法     乘法分两种,对应元素相乘和矩阵乘法

  •     INDArray mul(INDArray other):对应元素相乘
  •     INDArray muli(INDArray other):对应元素相乘,并覆盖原数组
  •     INDArray mmul(INDArray other):矩阵相乘
  •     INDArray mmuli(INDArray other):矩阵相乘,并覆盖原数组 4、除法
  •     INDArray div(INDArray other):对应元素相除
  •     INDArray divi(INDArray other):对应元素相除并覆盖原数组
  •     INDArray div(Number n):每个元素除以一个标量
  •     INDArray divi(Number n):每个元素除以一个标量,并覆盖原数组

5、矩阵转置

  •     INDArray transpose()
  •     INDArray transposei()

总结一下:后面以i结尾的方法,表示in place,也就是会覆盖原内存空间的数据,和”传引用“一个意思

// 创建两个1D列向量
INDArray vectorA = Nd4j.create(new double[]{1, 2, 3, 4, 5});
INDArray vectorB = Nd4j.create(new double[]{5, 4, 3, 2, 1});
// 加法
INDArray addition = vectorA.add(vectorB);
System.out.println("Addition:");
System.out.println(addition);
// 减法
INDArray subtraction = vectorA.sub(vectorB);
System.out.println("Subtraction:");
System.out.println(subtraction);
// 乘法(元素逐元素相乘)
INDArray multiplication = vectorA.mul(vectorB);
System.out.println("Element-wise Multiplication:");
System.out.println(multiplication);
// 除法(元素逐元素相除)
INDArray division = vectorA.div(vectorB);
System.out.println("Element-wise Division:");
System.out.println(division);

ND4J幂运算、平方根、绝对值、平方以及三角函数

// 创建一个INDArray
INDArray array = Nd4j.create(new double[]{1, 2, 3, 4});
// 幂运算
INDArray powArray =Nd4j.math.pow(array, 2); // 每个元素求平方
System.out.println("Pow: " + powArray);
// 平方根
INDArray sqrtArray = Nd4j.math.sqrt(array);
System.out.println("Sqrt: " + sqrtArray);
// 绝对值
INDArray absArray = Nd4j.math.abs(array);
System.out.println("Abs: " + absArray);
// 平方
INDArray squareArray = Nd4j.math.square(array);
System.out.println("Square: " + squareArray);
// 三角函数
INDArray sinArray = Nd4j.math.sin(array);
System.out.println("Sin: " + sinArray);
INDArray cosArray = Nd4j.math.cos(array);
System.out.println("Cos: " + cosArray);
INDArray tanArray = Nd4j.math.tan(array);
System.out.println("Tan: " + tanArray);
结果
Pow: [ 1.0000, 4.0000, 9.0000, 16.0000]
Sqrt: [ 1.0000, 2.0000, 3.0000, 4.0000]
Abs: [ 1.0000, 2.0000, 3.0000, 4.0000]
Square: [ 1.0000, 4.0000, 9.0000, 16.0000]
Sin: [ 0.8415, -0.7568, 0.4121, -0.2879]
Cos: [ 0.6664, 0.7270, 0.9163, 0.9588]
Tan: [ 0.7864, 0.8896, 1.3032, 1.4248]

ND4J统计运算

统计运算通常用于获取数组的一些统计特性,如最大值、最小值、平均值、方差等

 INDArray array = Nd4j.create(new double[]{1, 2, 3, 4, 5});
// 最大值
double max = Nd4j.max(array).getDouble(0);
System.out.println("Max: " + max);

// 最小值
double min = Nd4j.min(array).getDouble(0);
System.out.println("Min: " + min);
// 平均值
double mean = Nd4j.mean(array).getDouble(0);
System.out.println("Mean: " + mean);
// 方差
double variance = Nd4j.var(array).getDouble(0);
System.out.println("Variance: " + variance);
// 标准差
double stdDev = Nd4j.std(array).getDouble(0);
System.out.println("Standard Deviation: " + stdDev);
//结果
Max: 5.0
Min: 1.0
Mean: 3.0
Variance: 2.5
Standard Deviation: 1.5811388300841898

指数对数运算

指数对数运算包括指数运算、自然对数运算等

INDArray array = Nd4j.create(new double[]{1, 2, 3, 4, 5});
// 指数运算
INDArray expArray = Nd4j.math.exp(array);
System.out.println("Exp: " + expArray);
// 自然对数运算
INDArray logArray = Nd4j.math.log(array);
System.out.println("Log: " + logArray);

// 常用对数运算(以10为底)
INDArray log10Array = Nd4j.math.log(array,10);
System.out.println("Log10: " + log10Array);
--结果
Exp: [ 2.7183, 7.3891, 20.0855, 54.5982, 148.4132]
Log: [ 1.0000, 2.0000, 3.0000, 4.0000, 5.0000]
Log10: [ 0, 0.3010, 0.4771, 0.6021, 0.6990]

标签

发表评论