[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 支持的一些优化算法:
- Stochastic Gradient Descent (SGD) – 随机梯度下降是最基础的优化算法之一。
- Conjugate Gradient (CG) – 共轭梯度法,通常用于解决线性方程组,也可以用于优化问题。
- LBFGS – Limited-memory Broyden-Fletcher-Goldfarb-Shanno,是一种拟牛顿法,适用于小到中等规模的优化问题。
- RMSprop – Root Mean Square Propagation,它通过调整学习率来适应每个参数的更新。
- Adam – Adaptive Moment Estimation,结合了动量(Momentum)和自适应学习率(如 RMSprop)的方法。
- Nesterov’s Accelerated Gradient (NAG) – 也是一种动量方法,与标准的动量方法相比,它在计算梯度时考虑了参数的更新。
- Adadelta – 是一种自适应学习率方法,它旨在改善 AdaGrad 的快速学习率下降问题。
- AdaGrad – Adaptive Subgradient Methods,通过为每个参数分配不同的学习率来适应数据的稀疏性。
- Adamax – Adam的一种变体,对学习率的更新进行了修改。
- Nadam – 结合了 Nesterov 加速梯度(NAG)和 Adam 优化器的特点
ND4J利用SIMD加速
- 数据级并行处理:SIMD允许在单个CPU指令中同时处理多个数据点。ND4J通过在底层使用优化的数学库(如OpenBLAS、MKL等),这些库本身已经针对SIMD进行了优化,使得在进行矩阵运算时能够并行处理多个元素,从而提高效率。
- 向量化和批处理:ND4J在设计时就考虑
- 到了向量化操作,这意味着它可以将多个操作组合成单个指令,这样可以减少指令的数量并提高CPU的数据吞吐量。例如,当执行数组或矩阵运算时,ND4J会尽量使用SIMD指令来同时处理数组中的多个元素。
- 内存对齐:为了充分利用SIMD,数据在内存中的对齐非常重要。ND4J确保其数据结构(如INDArray)在内存中对齐,以适应SIMD的要求,这样可以减少内存访问时间并提高缓存效率。
- 循环展开和软件流水线:ND4J的底层实现会进行循环展开和软件流水线优化,这些优化可以更好地利用SIMD指令,减少循环的开销,并提高指令的执行效率。
- 平台特定优化: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]
发表评论