美文网首页知识图谱
DL4J中文文档/ND4J/概述

DL4J中文文档/ND4J/概述

作者: hello风一样的男子 | 来源:发表于2019-01-26 09:52 被阅读37次

    本用户指南旨在解释(并提供示例)ND4J中的主要功能。

    • 简介
      • NDArrays:它们在内存中是如何存储的?
      • 视图:当两个或更多NDArrays引用相同的数据
    • 创建NDArrays
      • 0,1和标量值初始化数组
      • 随机数组
      • 从Java数组创建NDArrays
      • 从其它NDArrays创建NDArrays
      • 混杂的NDArray创建方法
    • 获取和设置单个值
    • 获取和设置NDArrays的部分值
      • getRow() 与 putRow()
      • 子数组 : get(), put() 与 NDArrayIndex
      • 沿维张量
      • 切分
    • 在NDArrays上进行操作
      • 标量操作
      • 转换操作
      • 累加(缩减)操作
      • 索引累加操作
      • 广播与向量操作
    • 布尔索引:根据条件有选择地应用操作
    • 工作间
      • 工作间:范围恐慌
    • 高级和混杂话题
      • 设置数据类型
      • 变形
      • 扁平化
      • 置换
      • sortRows/sortColumns
      • 直接访问BLAS操作
    • 系列化
    • 速查:ND4J方法概述
    • 常见问题解答:常见问题

    简介

    NDArray本质上是一个n维数组:即一个具有一定维数的数字矩形数组。

    一些你应该熟悉的概念:

    • NDArray的rank是维度数。二维数组的rank为2,三维数组的rank为3,依此类推。你可以创建具有任意rank的NDArrays。
    • NDArray的(shape)形状定义了每个维度的大小。假设我们有一个有3行5列的二维数组。这个NDArray的形状是[3,5]
    • NDArray的长度定义了数组中元素的总数。长度始终等于构成形状的值的乘积。
    • NDArray的步幅定义为每个维度中相邻元素的间隔(在底层数据缓冲区中)。步幅是按维度定义的,因此一个rank 为 n的 NDArray有n个步幅值,每个维度一个。请注意,大多数情况下,你不需要了解(或关注)步幅-只需注意这是ND4J内部的运作方式。下一节有一个步幅的例子。
    • NDArray的数据类型指的是一个NDArray的数据类型(例如, float 或 double 精度)。注意在nd4j中是全局的设置,所以所有的NDArrays应该有相同的数据类型。设置数据类型会在这个文档的后面再讨论。

    就索引而言这里有一些事情需要知道。首先,维度0是行,维度1是列:因此INDArray.size(0)是行的数量,INDArray.size(1)是列的数量,索引是0开始的:因此行有从0到INDArray.size(0)-1的索引,对于其他维度,依此类推。

    在本文档中,我们将使用NDArray术语来描述n维数组的一般概念;术语NDArray具体指的是ND4J定义的Java接口。实际上,这两个术语可以互换使用。

    NDArrays:它们在内存中是如何存储的?

    接下来的几段描述了ND4J背后的一些架构。理解这一点并不是使用ND4J所必需的,但它可能有助于你了解幕后发生的事情。NDArrays作为一个单一的扁平的数字数组(或者更一般地说,作为单个连续的内存块)存储在内存中,因此与典型的Java多维数组(例如,浮点[]][2] [][][][]有很大的不同。

    物理上,INDArray背后的数据是堆外存储的:也就是说,它存储在Java虚拟机(JVM)之外。这具有许多优点,包括性能、与高性能BLAS库的互操作性以及避免JVM在高性能计算中的一些缺点(例如,由于整数索引,Java数组限于2 ^ 31 - 1(21亿4000万)个元素)。

    在编码方面,可以按C(行主要)或Fortran(列主要)顺序对NDArray进行编码。有关行与列主顺序的更多详细信息,请参阅维基百科。ND4J可以同时使用C和F顺序数组的组合。大多数用户只能使用默认的数组排序,但请注意,如果需要,可以对给定的数组使用特定的排序。

    下图显示了简单的3x3(2d)NDArray是如何存储在内存中的,

    C vs. F order image.gif

    在如下的数组,我们有:

    • Shape = [3,3] (3 行 , 3 列)
    • Rank = 2 (2 维)
    • Length = 9 (3x3=9)
    • Stride
      • C顺序步幅:[3,1]: 在缓冲区中连续行中的值按3分割在缓冲区中连续列中的值按1分割
      • F顺序步幅:[1,3]: 在缓冲区中连续行中的值按1分割在缓冲区中连续列中的值按3分割

    视图:当两个或更多NDArrays引用相同的数据

    NDArrays中的一个关键概念是两个NDArrays实际上可以指向内存中相同的底层数据。通常,我们有一个数组引用另一个数组的某个子集,而这只发生在某些操作(如INDArray.get(), INDArray.transpose(), INDArray.getRow()等)中。这是一个强大的概念,值得理解。

    这主要有两个动机:

    1. 有相当大的性能优势,尤其是在避免复制数组方面
    2. 我们在NDArrays上如何执行操作方面获得了很大的权力。

    考虑一个简单的操作,比如一个大型(10000x1000)矩阵上的矩阵转置。使用视图,我们可以在不执行任何复制的情况下(即,大O标记法中的O(1))在恒定时间内执行矩阵转置,从而避免复制所有数组元素的相当大的成本。当然,有时我们确实想制作一份副本-这时我们可以使用INDArray.dup()来获取副本。例如,要获得转置矩阵的副本,请使用INDArray out = myMatrix.transpose().dup()。在这个dup()调用之后,原始数组myMatrix和数组out之间将没有链接(因此,对其中一个的更改不会影响另一个)。

    所以看看视图是如何强大的,考虑一个简单的任务:将1.0添加到更大数组的第一行,myarray。我们可以很容易地做到这一点,在一行中:

    myArray.getRow(0).addi(1.0)

    让我们来分析一下这里发生的事情。首先,getRow(0)操作返回一个INDArray,是原始数据的视图。注意myarray和 myArray.getRow(0)都指向内存中的相同区域:

    getRow(0) image.gif

    然后,在addi(1.0)被执行后,我们有如下状态

    getRow(0).addi(1.0) image.gif

    如我们所见,对myArray.getRow(0)返回的NDArray的更改将反映在原始数组myArray中;同样,对myArray的更改将反映在行向量中。

    创建NDArrays

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

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

    • Nd4j.zeros(int...)
    • Nd4j.ones(int...)

    数组的形状被指定为整数。例如,要创建一个由3行5列组成的零填充数组,请使用 Nd4j.zeros(3,5)

    这些通常可以与其他操作结合使用其他值创建数组。例如,要创建一个填充了10的数组:

    INDArray tens = Nd4j.zeros(3,5).addi(10)

    上面的初始化工作分为两个步骤:首先分配一个3x5数组,用零填充,然后向每个值加10。

    随机数组

    Nd4j提供了一些创建INDArrays的方法,其中内容是伪随机数。

    要在0到1范围内生成统一的随机数,请使用Nd4j.rand(int nRows, int nCols)(对于二维数组)或Nd4j.rand(int[]) (对于3个或更多维度)。

    同样,要生成平均零和标准偏差为1的高斯随机数,请使用 Nd4j.randn(int nRows, int nCols) or Nd4j.randn(int[])。

    对于可重复性(即设置nd4j的随机数生成器种子),可以使用Nd4j.getRandom().setSeed(long)

    从Java数组创建NDArrays

    ND4J为从Java 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})

    对于 2维数组, 使用 Nd4j.create(float[][])Nd4j.create(double[][]).

    为了从具有3个或多个维度的Java原始数组(double [][][]等)创建NDArrays,一种方法是使用以下:

    double[] flat = ArrayUtil.flattenDoubleArray(myDoubleArray);
    int[] shape = ...;  //这是数组形状
    INDArray myArr = Nd4j.create(flat,shape,'c');
    
    
    image.gif

    从其它NDArrays创建NDArrays

    从其它数组创建数组的方法主要有三种:

    • 使用INDArray.dup()创建现有NDArray的精确副本
    • 将数组创建为现有NDArray的子集
    • 合并多个现有NDArrays以创建新的NDArrays

    对于第二种情况,您可以使用getRow(), get()等。有关详细信息,请参见获取和设置NDArrays的部分。

    合并NDArrays的两种方法是Nd4j.hstack(INDArray...)Nd4j.vstack(INDArray...)

    hstack(水平堆叠)将具有相同行数的多个矩阵作为参数,并将它们水平堆叠以生成新的数组。但是,输入NDArrays可以有不同数量的列。

    示例:

    int nRows = 2;
    int nColumns = 2;
    //创建值为0的INDArray
    INDArray zeros = Nd4j.zeros(nRows, nColumns);
    //创建所有值为1的INDArray 
    INDArray ones = Nd4j.ones(nRows, nColumns);
    //水平堆叠
    INDArray hstack = Nd4j.hstack(ones,zeros);
    System.out.println("### HSTACK ####");
    System.out.println(hstack);
    
    
    image.gif

    输出:

    ### HSTACK ####
    [[1.00, 1.00, 0.00, 0.00],
    [1.00, 1.00, 0.00, 0.00]]
    
    
    image.gif

    vstack (垂直堆叠) 是hstack的垂直等效值。输入数组的列数必须相同。

    示例:

    int nRows = 2;
    int nColumns = 2;
    // 创建值为0的INDArray
    INDArray zeros = Nd4j.zeros(nRows, nColumns);
    // 创建所有值为1的INDArray
    INDArray ones = Nd4j.ones(nRows, nColumns);
    //垂直堆叠
    INDArray vstack = Nd4j.vstack(ones,zeros);
    System.out.println("### VSTACK ####");
    System.out.println(vstack);
    
    
    image.gif

    输出:

    ### VSTACK ####
    [[1.00, 1.00],
     [1.00, 1.00],
     [0.00, 0.00],
     [0.00, 0.00]]
    
    
    image.gif

    ND4J.concat 沿维度组合数组。

    示例:

    int nRows = 2;
    int nColumns = 2;
    //创建一个所有值为0的INDArray
    INDArray zeros = Nd4j.zeros(nRows, nColumns);
    //创建一个所有值为1的INDArray
    INDArray ones = Nd4j.ones(nRows, nColumns);
    // 在0维上连接
    INDArray combined = Nd4j.concat(0,zeros,ones);
    System.out.println("### COMBINED dimension 0####");
    System.out.println(combined);
    //在1维上连接
    INDArray combined2 = Nd4j.concat(1,zeros,ones);
    System.out.println("### COMBINED dimension 1 ####");
    System.out.println(combined2);
    
    
    image.gif

    输出:

    ### COMBINED dimension 0####
    [[0.00, 0.00],
     [0.00, 0.00],
     [1.00, 1.00],
     [1.00, 1.00]]
    ### COMBINED dimension 1 ####
    [[0.00, 0.00, 1.00, 1.00],
     [0.00, 0.00, 1.00, 1.00]]
    
    
    image.gif

    ND4J.pad 用于填充数组

    示例:

    int nRows = 2;
    int nColumns = 2;
    // 创建一个所有值为1的INDArray
    INDArray ones = Nd4j.ones(nRows, nColumns);
    // 填充INDArray
    INDArray padded = Nd4j.pad(ones, new int[]{1,1}, Nd4j.PadMode.CONSTANT );
    System.out.println("### Padded ####");
    System.out.println(padded);
    
    
    image.gif

    输出:

    ### Padded ####
    [[0.00, 0.00, 0.00, 0.00],
     [0.00, 1.00, 1.00, 0.00],
     [0.00, 1.00, 1.00, 0.00],
     [0.00, 0.00, 0.00, 0.00]]
    
    
    image.gif

    另一种偶尔有用的方法是Nd4j.diag(INDArray in)。此方法有两种用途,具体取决于中的参数:

    • 如果in是向量,diag输出一个对角线等于数组in(其中N是in的长度)的NxN矩阵。
    • 如果in是一个NxN矩阵,diag将输出一个从in的对角线获取的向量。

    混杂的NDArray创建方法

    要创建大小为N的单位矩阵(对角线上的值都为1,其它值都为零),可以使用Nd4j.eye(N)

    要使用元素[a, a+1, a+2, ..., b]创建行向量,可以使用linspace命令:

    Nd4j.linspace(a, b, b-a+1)

    Linspace可以与reshape操作结合使用以获得其他形状。例如,如果要一个包含5行和5列、值为1到25(包括1到25)的二维NDArray,可以使用以下内容:

    Nd4j.linspace(1,25,25).reshape(5,5)

    获取和设置单个值

    对于INDArray,可以使用要获取或设置的元素的索引来获取或设置值。对于维度为n数组(即具有n个维度的数组),需要n个索引。

    注意:单独获取或设置值(例如,在for循环中一次一个值)在性能方面通常是一个坏主意。如果可能,尝试使用其他一次对大量元素进行操作的INDArray方法。

    要从二维数组中获取值,可以使用:INDArray.getDouble(int row, int column)

    对于任何维度的数组,可以使用INDArray.getDouble(int...)。例如,要获取索引i、j、k处的值,请使用 INDArray.getDouble(i,j,k)

    要设置值,请使用putScalar方法之一:

    • INDArray.putScalar(int[],double)
    • INDArray.putScalar(int[],float)
    • INDArray.putScalar(int[],int)

    这里,int[]是索引,double/float/int是要放置在该索引上的值。

    在某些情况下可能有用的一些附加功能是NDIndexIterator类。NDIndexIterator允许你按定义的顺序获取索引(具体来说,对于rank为3的数组,C顺序遍历顺序为[0,0,0]、[0,0,1]、[0,0,2]、…、[0,1,0]、…等)。

    要迭代二维数组中的值,可以使用:

    NdIndexIterator iter = new NdIndexIterator(nRows, nCols);
    while (iter.hasNext()) {
        int[] nextIndex = iter.next();
        double nextVal = myArray.getDouble(nextIndex);
        //用这个值做些事情
    }
    
    
    image.gif

    获取和设置NDArrays的部分值

    getRow() 与 putRow()

    要从INDArray中获取单行,可以使用INDArray.getRow(int)。这显然会返回一个行向量。这里需要注意的是,这一行是一个视图:对返回行的更改将影响原始数组。这有时非常有用(例如:myArr.getRow(3).addi(1.0) 将1.0添加到较大数组的第三行);如果需要行的副本,请使用getRow(int).dup()

    同样,要获取多行,请使用INDArray.getRows(int...)。这将返回一个行已堆叠的数组;但是请注意,这将是原始行的副本(而不是视图),由于NDArrays存储在内存中的方式,因此此处不可能有视图。

    对于设置单行,可以使用 myArray.putRow(int rowIdx,INDArray row)。这会将myarray的rowidxth行设置为包含在indarray行中的值。

    子数组 : get(), put() 与 NDArrayIndex

    Get:

    更强大和通用的方法是使用INDArray.get(NDArrayIndex...)。此功能允许你基于某些索引获取任意子数组。这也许最好用一些例子来解释:

    要获取单行(和所有列),可以使用:
    myArray.get(NDArrayIndex.point(rowIdx), NDArrayIndex.all())

    要获取行(行A(含)到行(不含)和所有列的范围,可以使用:
    myArray.get(NDArrayIndex.interval(a,b), NDArrayIndex.all())

    要获取所有行和第二列,可以使用:
    myArray.get(NDArrayIndex.all(),NDArrayIndex.interval(0,2,nCols))

    尽管上面的示例仅适用于二维数组,但NDArrayIndex方法扩展到3个或更多维度。对于三维,你将提供3个NDArrayIndex对象,而不是两个,如上所述。
    请注意,NDArrayIndex.interval(...), .all().point(int)方法始终返回底层数组的视图。因此,.get()返回的数组更改将反映在原始数组中。

    Put:

    同样的NDArrayIndex方法也用于将元素放入另一个数组:在本例中,你使用INDArray.put(INDArrayIndex[], INDArray toPut)方法。显然,NDArray toput的大小必须与提供的索引所暗示的大小匹配。

    还要注意myArray.put(NDArrayIndex[],INDArray other)在功能上等同于执行myArray.get(INDArrayIndex...).assign(INDArray other)。同样,这是因为.get(INDArrayIndex...)返回底层数组的视图,而不是副本。

    沿维张量

    (注:与当前版本相比,0.4-rc3.8及更早版本的ND4J返回的沿维度张量结果略有不同)。

    沿维张量是一种强大的技术,但一开始可能有点难以理解。沿维张量(以下简称为TAD)背后的思想是得到一个低阶子数组,它是原始数组的视图。

    “沿维张量”方法采用两个参数:

    • 要返回的张量的索引(在0到numTensors-1的范围内)
    • 执行TAD操作的维度(1个或多个值)

    The simplest case is a tensor along a single row or column of a 2d array. Consider the following diagram (where dimension 0 (rows) are indexed going down the page, and dimension 1 (columns) are indexed going across the page):

    最简单的情况是沿二维数组的单个行或列的张量。考虑下面的关系图(其中维度0(行)在页面下方被索引,维度1(列)在页面上方被索引):

    Tensor Along Dimension image.gif

    注意,在所有情况下,一个维的tensorAlongDimension调用的输出都是行向量。

    为了理解我们为什么得到这个输出:考虑上图中的第一个案例。在那里,我们取第0(第一)个张量沿维数0(维数0为行);当我们沿维数0移动时,值(1,5,2)在一条线上,因此输出。同样地,tensorAlongDimension(1,1)是第二个(index=1)沿维数1的张量;当我们沿维数1移动时,值(5,3,5)在一条线上。

    TAD操作也可以沿多个维度执行。例如,通过指定两个维度来执行TAD操作,我们可以使用它从3D(或4D或5D…)数组中获取二维子数组。同样,通过指定3个维度,我们可以使用它从4d或更高维度获得3d。

    有两件事我们需要知道的输出,为TAD操作是有用的。

    首先,我们需要我们能得到的张量的数量,对于一组给定的维度。为了确定这一点,我们可以使用“沿维度的张量数量”方法,即INDArray.tensorssAlongDimension(int... dimensions)。此方法只返回沿指定维度的张量数。在上面的例子中,我们有:

    • myArray.tensorssAlongDimension(0) = 3
    • myArray.tensorssAlongDimension(1) = 3
    • myArray.tensorssAlongDimension(0,1) = 1
    • myArray.tensorssAlongDimension(1,0) = 1

    (在后两种情况下,请注意,沿维度的张量将提供与原始数组相同的数组输出,即,我们从二维数组获得二维输出)。

    一般来说,张量的个数是由剩余尺寸的乘积给出的,张量的形是由原形状中指定维度的大小给出的。

    以下是一些例子:

    • 对于输入形状[a,b,c],tensorssAlongDimension(0)给出b*c张量,tensorAlongDimension(i,0)返回形状 [1,a]的张量。
    • 对于输入形状[a,b,c],tensorssAlongDimension(1)给出一个a*c张量,tensorAlongDimension(i,1)返回形状[1,b]的张量。
    • 对于输入形状[a,b,c],tensorsalongdimension(0,1)给出c张量,tensorAlongDimension(i,0,1)返回形状[a,b]的张量。
    • 对于输入形状[a,b,c],tensorssAlongDimension(1,2)给出张量,tensorAlongDimension(i,1,2)返回形状[b,c]的张量。
    • 对于输入形状[a,b,c,d], tensorssAlongDimension(1,2) 给出了一个a*d张量,tensorAlongDimension(i,1,2)返回了形状[b,c]的张量。
    • 对于输入形状[a,b,c,d],tensorssAlongDimension(0,2,3)给出b张量,tensorAlongDimension(i,0,2,3)返回形状为[a,c,d]的张量。

    切分

    [本节:即将到来。]

    在NDArrays上进行操作

    ND4J有操作的概念,你可能想用一个INDArray来做(或去做)很多事情。例如,ops用于应用tanh操作、添加标量或执行元素操作。

    ND4J定义了五种类型的操作:

    • Scalar(标量)
    • Transform(转换)
    • Accumulation(累加)
    • Index Accumulation(索引累加)
    • Broadcast(广播)

    以及两种执行方法:

    • 直接在整个INDArray上,或
    • 沿一个维度

    在讨论这些操作的细节之前,让我们先考虑一下就地和复制操作之间的区别。

    许多操作既有就地操作,也有复制操作。假设我们要添加两个数组。Nd4j为此定义了两种方法:INDArray.add(INDArray)INDArray.addi(INDArray)。前者(add)是一个复制操作;后者是一个就地操作-i在addi中表示就地操作。此约定(…i表示就地,没有 i表示复制)适用于通过INDArray接口可访问的其他操作。

    假设我们有两个INDArrays x和y,我们做INDArray z = x.add(y)INDArray z = x.addi(y)操作。这些操作的结果如下所示。

    Add image.gif

    Addi image.gif

    注意,使用x.add(y)操作时,不会修改原始数组x。相比之下,对于就地版本x.addi(y),数组x被修改。在添加操作的两个版本中,都会返回包含结果的INDArray。但是请注意,在addi操作的情况下,结果数组us实际上只是原始数组x。

    标量操作

    标量操作是一种元素操作,它也接受一个标量(即一个数字)。标量操作的例子有add、max、multiple、set和divide操作(有关完整列表,请参见上一链接)。

    许多方法,例如INDArray.addi(Number)INDArray.divi(Number)实际上在后台执行标量操作,因此,在可用时,使用这些方法更方便。

    要更直接地执行标量操作,可以使用以下示例:

    Nd4j.getExecutioner().execAndReturn(new ScalarAdd(myArray,1.0))

    请注意,myarray是通过此操作修改的。如果这不是你想要的,请使用 myArray.dup()

    与其余的操作不同,标量操作没有对沿维度执行它们的合理解释。

    转换操作

    转换操作是诸如元素方向的对数、余弦、tanh、校正线性等操作。其他示例包括加法、减法和复制操作。转换操作通常以元素为导向的方式使用(例如,对每个元素使用tanh),但情况并非总是如此——例如,softmax通常沿维度执行。

    要直接(在完整的NDArray上)执行元素相关的tanh操作,可以使用:

    INDArray tanh = Nd4j.getExecutioner().execAndReturn(new Tanh(myArr))与上面提到的标量操作一样,使用上面方法的转换操作是就地操作:即修改了NDArray myArr,并且返回的数组tanh实际上是与输入myarr相同的对象。同样,如果需要副本,可以使用myArr.dup()

    Transforms类还定义了一些方便的方法,例如:INDArray tanh = Transforms.tanh(INDArray in,boolean copy);这相当于使用 Nd4j.getExecutioner()的方法。

    累加(缩减)操作

    在执行累加时,在整个NDArray上执行累加与在特定维度(或维度)上执行累加之间有一个关键区别。在第一种情况下(对整个数组执行),只返回一个值。在第二种情况下(沿维度累加),将返回一个新的NDArray。

    要获取数组中所有值的总和,请执行以下操作:

    double sum = Nd4j.getExecutioner().execAndReturn(new Sum(myArray)).getFinalResult().doubleValue();

    或同等(更方便)

    double sum = myArray.sumNumber().doubleValue();

    还可以沿维度执行累加操作。例如,要获取每列中所有值的总和(每列=沿维度0或“每行中的值”),可以使用:

    INDArray sumOfColumns = Nd4j.getExecutioner().exec(new Sum(myArray),0);

    或同等,

    INDArray sumOfColumns = myArray.sum(0)

    假设这是在3x3输入数组上执行的。从视觉上看,这个维度0操作的求和操作如下:

    Sum along dimension 0 image.gif

    请注意,这里输入的形状为[3,3](3行,3列),输出的形状为[1,3](即,我们的输出是行向量)。如果我们改为沿着维度1进行操作,我们将得到一个形状为[3,1]且值为(12,13,11)的列向量。

    沿维度的累加也概括为具有3个或更多维度的NDArrays。

    索引累加操作

    索引累加操作与累加操作非常相似。区别在于它们返回的是一个整数索引,而不是一个双精度值。
    索引累加操作的示例有IMax(argmax)、IMin(argmin)和IAMax(绝对值的argmax)。
    要获取数组中最大值的索引,请执行以下操作:

    int idx = Nd4j.getExecutioner().execAndReturn(new IAMax(myArray)).getFinalResult();

    索引累加操作通常在沿维度执行时最有用。例如,要获取每列(每列=沿维度0)中最大值的索引,可以使用:

    INDArray idxOfMaxInEachColumn = Nd4j.getExecutioner().exec(new IAMax(myArray),0);

    假设这是在3x3输入数组上执行的。从视觉上看,沿维度0操作的argmax/IAMax操作如下:

    Argmax / IAMax image.gif

    与上述累加运算一样,输出的形状为[1,3]。同样,如果我们沿着维度1进行操作,我们将得到一个形状为[3,1],值为(1,0,2)的列向量。

    广播与向量操作

    ND4J还定义了广播和向量操作。
    一些更有用的操作是向量操作,如addRowVector和muliColumnVector。
    例如,考虑操作 x.addRowVector(y),其中x是矩阵,而y是行向量。在这种情况下,addRowVector操作将行向量y添加到矩阵x的每一行,如下所示。

    addRowVector image.gif

    与其他操作一样,有就地和复制版本。这些操作还有列-列版本,例如addColumnVector,它将列向量添加到原始INDArray的每一列。

    布尔索引:根据条件有选择地应用操作

    [本节:即将到来]

    Link: Boolean Indexing Unit Tests

    工作间

    工作间是ND4J的一个特性,通过更有效的内存分配和管理,可以提高性能。具体来说,工作空间是为周期性工作负载而设计的,例如训练神经网络,因为它们允许堆外内存重用(而不是在循环的每次迭代中持续分配和释放内存)。净效果是提高性能和减少内存使用。
    有关工作间的详细信息,请参见以下链接:

    工作空间:范围恐慌

    有时,对于工作区,你可能会遇到一个异常,例如:

    org.nd4j.linalg.exception.ND4JIllegalStateException: Op [set] Y argument uses leaked workspace pointer from workspace [LOOP_EXTERNAL]
    For more details, see the ND4J User Guide: nd4j.org/userguide#workspaces-panic
    
    
    image.gif

    org.nd4j.linalg.exception.ND4JIllegalStateException: Op [set] Y argument uses outdated workspace pointer from workspace [LOOP_EXTERNAL]
    For more details, see the ND4J User Guide: nd4j.org/userguide#workspaces-panic
    
    
    image.gif

    理解范围恐慌异常

    简而言之:这些异常意味着在工作区中分配的INDArray被错误地使用(例如,缺陷或某些方法的错误实现)。这有两个原因:

    1. INDArray已从定义的工作区“泄漏”出去。
    2. INDArray在正确的工作区内使用,但来自上一次迭代。

    在这两种情况下,INDArray指向的底层堆外内存都已失效,无法再使用。
    导致工作区泄漏的事件序列示例:

    1. 工作间 W 被打开
    2. INDArray X 分配在了工作间W
    3. 工作间 W 被关闭, 因此X的内存不再有效。
    4. INDArray X在某些操作中使用,导致异常。

    导致过期工作间指针的事件序列示例:

    1. 工作间 W被打开 (第1次迭代)
    2. INDArray X 在工作间W中分配 (第1次迭代)
    3. 工作间 W 被关闭 (第1次迭代)
    4. 工作间 W被打开 (第2次迭代)
    5. INDArray X (来自第1次迭代) 在其它操作中被使用,抛出异常。

    范围恐慌异常的解决方法和修复方法

    根据原因,有两种基本解决方案。
    第一。如果已经实现了一些自定义代码(或者正在手动使用工作间),这通常表示代码中存在错误。通常,您有两种选项:

    1. 使用INDArray.detach()方法从所有工作间分离INDArray。其结果是,返回的数组不再与工作区关联,可以在任何工作区内部或外部自由使用。
    2. 首先不要在工作间中分配数组。您可以使用:try(MemoryWorkspace scopedOut = Nd4j.getWorkspaceManager().scopeOutOfWorkspaces()){ <your code here> }暂时“关闭”工作间。其结果是,try块中的任何新数组(例如,通过nd4j.create创建的)都不会与工作区关联,并且可以在工作区之外使用。
    3. 使用INDArray.leverage()leverageTo(String)migrate()方法之一,将数组移动/复制到父工作间。有关更多详细信息,请参见这些方法的javadoc。

    第二,如果你使用工作间为Deeplearning4j的一部分,并且没有实现任何自定义功能(即,你没有编写自己的层、数据管道等),那么(在遇到这种情况的时候),这很可能表示底层库中存在一个bug,通常应该通过GitHub报告问题。一个可能的解决方法是使用以下代码禁用工作区:

    .trainingWorkspaceMode(WorkspaceMode.NONE)
    .inferenceWorkspaceMode(WorkspaceMode.NONE)
    
    
    image.gif

    如果异常是由于数据管道中的问题造成的,则可以在AsyncShieldDataSetIteratorAsyncShieldMultiDataSetIterator中尝试包装 DataSetIteratorMultiDataSetIterator

    无论是哪种原因,如果你确定你的代码是正确的,最后的解决方案是尝试禁用范围恐慌。请注意,这是不推荐的,如果存在合法问题,可能会导致JVM崩溃。为了实现禁用范围恐慌,请在执行代码之前使用Nd4j.getExecutioner().setProfilingMode(OpExecutioner.ProfilingMode.DISABLED)

    高级和混杂话题

    设置数据类型

    ND4J目前允许用浮点或双精度值来支持INDArrays。默认值为单精度(浮点)。要将nd4j全局用于数组的顺序设置为双精度,可以使用:

    Nd4j.setDataType(DataBuffer.Type.DOUBLE);
    
    
    image.gif

    注意,这应该在使用ND4J操作或创建数组之前完成。
    或者,可以在启动JVM时设置属性:

    -Ddtype=double
    
    
    image.gif

    变形

    [本节:即将到来]

    扁平化

    扁平化是给定数组的一些遍历顺序,将一个或多个INDArrays转换为单个平面数组(行向量)的过程。
    ND4J为此提供了以下方法:

    Nd4j.toFlattened(char order, INDArray... arrays)
    Nd4j.toFlattened(char order, Collection<INDArray>)
    
    
    image.gif

    Nd4j还提供了带有默认顺序的重载toFlattened方法。参数order必须是“c”或“f”,并定义从数组中获取值的顺序:参数order为c导致使用数组索引对数组进行扁平化,其顺序为[0,0,0]、[0,0,1]等(对于三维数组),而参数order为f导致按[0,0,0]、[1,0,0]等顺序获取值。

    置换

    [本节:即将到来]

    sortRows/sortColumns

    [本节:即将到来]

    直接访问BLAS操作

    [本节:即将到来]

    系列化

    ND4J提供了许多格式的INDArrays序列化。下面是一些二进制和文本序列化的示例:

    import org.nd4j.linalg.api.ndarray.INDArray;
    import org.nd4j.linalg.factory.Nd4j;
    import org.nd4j.serde.binary.BinarySerde;
    
    import java.io.*;
    import java.nio.ByteBuffer;
    
    INDArray arrWrite = Nd4j.linspace(1,10,10);
    INDArray arrRead;
    
    //1\. 二进制格式
    //   Close the streams manually or use try with resources.
    try (DataOutputStream sWrite = new DataOutputStream(new FileOutputStream(new File("tmp.bin")))) {
        Nd4j.write(arrWrite, sWrite);
        }
    
    try (DataInputStream sRead = new DataInputStream(new FileInputStream(new File("tmp.bin")))) {
        arrRead = Nd4j.read(sRead);
        }
    
    //2.使用java.nio.ByteBuffer的二进制格式;
    ByteBuffer buffer = BinarySerde.toByteBuffer(arrWrite);
    arrRead = BinarySerde.toArray(buffer);
    
    //3\. 文本格式
    Nd4j.writeTxt(arrWrite, "tmp.txt");
    arrRead = Nd4j.readTxt("tmp.txt");
    
    // 读取csv格式:
    // 被声明的writeNumpy方法 
    arrRead =Nd4j.readNumpy("tmp.csv", ", ");
    
    
    image.gif

    The nd4j-serde directory provides packages for Aeron, base64, camel-routes, gsom, jackson and kryo.

    速查:ND4J方法概述

    本节以摘要形式列出了ND4J中最常用的操作。关于其中大部分的更多详细信息,可以在本页后面找到。
    在本节中,假设arr、arr1等为INDArrays。

    创建NDArrays:

    • 创建一个零初始化数组:Nd4j.zeros(nRows, nCols)或 Nd4j.zeros(int...)
    • 创建一个一初始化数组: Nd4j.ones(nRows, nCols)
    • 创建一个NDArray的副本(复制):arr.dup()
    • 从double[]创建行/列向量: myRow = Nd4j.create(myDoubleArr), myCol = Nd4j.create(myDoubleArr,new int[]{10,1})
    • 从double[][]创建二维NDArray: Nd4j.create(double[][])
    • 堆叠一组数组以形成较大的数组:Nd4j.hstack(INDArray...), Nd4j.vstack(INDArray...)分别用于水平和垂直
    • 均匀随机数NDArrays:Nd4j.rand(int,int), Nd4j.rand(int[])
    • Normal(0,1) 随机 NDArrays: Nd4j.randn(int,int), Nd4j.randn(int[])

    确定INDArray的大小/尺寸:

    以下方法由INDArray接口定义:

    • 获取维度数: rank()
    • 对于 2维 NDArrays 仅有: rows(), columns()
    • 第i维的大小: size(i)
    • 获取所有维度的大小,以int[]返回: shape()
    • 确定数组中元素的总数: arr.length()
    • 也参见: isMatrix(), isVector(), isRowVector(), isColumnVector()

    获取和设置单个值:

    • 获取第i行第j列的值: arr.getDouble(i,j)
    • 从一个三维以上的数组获取一个值: arr.getDouble(int[])
    • 在一个数组中设置单个值: arr.putScalar(int[],double)

    标量操作:标量操作接受一个double/float/int值,并对每个值执行一个操作,就像元素操作一样,有就地操作和复制操作。

    • 添加标量: arr1.add(myDouble)
    • 减去一个标量: arr1.sub(myDouble)
    • 乘以一个标量: arr.mul(myDouble)
    • 除以标量: arr.div(myDouble)
    • 反向减法 (scalar - arr1): arr1.rsub(myDouble)
    • 反向除法 (scalar / arr1): arr1.rdiv(myDouble)

    元素操作:注意:有复制(添加、mul等)和就地(addi、muli)操作。前者:arr1未修改。后者中:arr1被修改

    • 加: arr1.add(arr2)
    • 减: arr.sub(arr2)
    • 乘: add1.mul(arr2)
    • 除: arr1.div(arr2)
    • 赋值(将arr1中的每个值设置为arr2中的值):arr1.assign(arr2)

    缩减操作(SUM等);请注意,这些操作对整个数组进行操作。调用.doubleValue()从返回的数字中获取一个双精度值。

    • 所有元素的总和: arr.sumNumber()
    • 所有元素的乘积: arr.prod()
    • L1和L2范数: arr.norm1()arr.norm2()
    • 各元素标准差: arr.stdNumber()

    线性代数操作:

    • 矩阵乘法: arr1.mmul(arr2)
    • 矩阵转置: transpose()
    • 求矩阵的对角线: Nd4j.diag(INDArray)
    • 矩阵求逆: InvertMatrix.invert(INDArray,boolean)

    获取更大的NDArray的一部分:注意:所有这些方法都返回

    • 获取一行 (仅二维数组NDArrays): getRow(int)
    • 获取多行作为一个矩阵(仅二维数组): getRows(int...)
    • 设置一行 (仅二维数组NDArrays): putRow(int,INDArray)
    • 获取前3行,所有列: Nd4j.create(0).get(NDArrayIndex.interval(0,3),NDArrayIndex.all());

    元素转换 (Tanh, Sigmoid, Sin, Log etc):

    • 使用 Transforms: Transforms.sin(INDArray), Transforms.log(INDArray), Transforms.sigmoid(INDArray)
    • 直接地 (方法 1): Nd4j.getExecutioner().execAndReturn(new Tanh(INDArray))
    • 直接地 (方法 2) Nd4j.getExecutioner().execAndReturn(Nd4j.getOpFactory().createTransform("tanh",INDArray))

    常见问题解答:常见问题

    Q: ND4J支持稀疏数组吗?

    目前:不支持,未来计划支持。

    Q: 是否可以动态增大或缩小INDArray上的大小?

    在当前版本的ND4J中,这是不可能的。不过,我们将来可能会添加此功能。

    有两种可能的解决办法:

    1. 分配一个新数组并进行复制(例如.put()操作)
    2. 最初,预先分配一个大于所需的NDArray,然后对该数组的视图进行操作。然后,由于你需要一个更大的数组,请在原始预分配的数组上获得更大的视图。

    相关文章

      网友评论

        本文标题:DL4J中文文档/ND4J/概述

        本文链接:https://www.haomeiwen.com/subject/xanbjqtx.html