Tensor(张量)
Tensor(张量)是对一个数据 Blob(数据块)的”视图”,带有形状(shape)、步长(stride)和一个数据指针。它是一个多维、同构的矩阵,其中的元素具有相同的数据类型。Open3D 使用它来执行数值运算,同时也支持 GPU 运算。
import open3d.core as o3cimport numpy as npTensor 的创建
Section titled “Tensor 的创建”Tensor 可以从列表(list)、NumPy 数组(numpy array)或另一个 tensor 创建。通过向构造函数传入 o3c.Dtype 和/或 o3c.Device,可以构造具有特定数据类型和设备的 tensor。如果不传入,默认数据类型会从数据中推断,默认设备为 CPU。需要注意的是,当从列表或 NumPy 数组创建 tensor 时,底层内存不会共享,而是会创建一份拷贝。
# Tensor from list.a = o3c.Tensor([0, 1, 2])print("Created from list:\n{}".format(a))
# Tensor from Numpy.a = o3c.Tensor(np.array([0, 1, 2]))print("\nCreated from numpy array:\n{}".format(a))
# Dtype and inferred from list.a_float = o3c.Tensor([0.0, 1.0, 2.0])print("\nDefault dtype and device:\n{}".format(a_float))
# Specify dtype.a = o3c.Tensor(np.array([0, 1, 2]), dtype=o3c.Dtype.Float64)print("\nSpecified data type:\n{}".format(a))
# Specify device.a = o3c.Tensor(np.array([0, 1, 2]), device=o3c.Device("CUDA:0"))print("\nSpecified device:\n{}".format(a))Created from list:[0 1 2]Tensor[shape={3}, Int64, CPU:0, 0x55d0575a48f0]
Created from numpy array:[0 1 2]Tensor[shape={3}, Int64, CPU:0, 0x55d056caa8d0]
Default dtype and device:[0.0 1.0 2.0]Tensor[shape={3}, Float64, CPU:0, 0x55d05759c330]
Specified data type:[0.0 1.0 2.0]Tensor[shape={3}, Float64, CPU:0, 0x55d057568ca0]
Specified device:[0 1 2]Tensor[shape={3}, Int64, CUDA:0, 0x7f40ff000000]也可以通过调用拷贝构造函数,从另一个 tensor 创建新的 tensor。这是一种浅拷贝(shallow copy),data_ptr 会被拷贝,但它所指向的内存不会被拷贝。
# Shallow copy constructor.vals = np.array([1, 2, 3])src = o3c.Tensor(vals)dst = srcsrc[0] += 10
# Changes in one will get reflected in other.print("Source tensor:\n{}".format(src))print("\nTarget tensor:\n{}".format(dst))Source tensor:[11 2 3]Tensor[shape={3}, Int64, CPU:0, 0x55d057b157a0]
Target tensor:[11 2 3]Tensor[shape={3}, Int64, CPU:0, 0x55d057b157a0]Tensor 的属性
Section titled “Tensor 的属性”vals = np.array((range(24))).reshape(2, 3, 4)a = o3c.Tensor(vals, dtype=o3c.Dtype.Float64, device=o3c.Device("CUDA:0"))print(f"a.shape: {a.shape}")print(f"a.strides: {a.strides}")print(f"a.dtype: {a.dtype}")print(f"a.device: {a.device}")print(f"a.ndim: {a.ndim}")a.shape: SizeVector[2, 3, 4]a.strides: SizeVector[12, 4, 1]a.dtype: Float64a.device: CUDA:0a.ndim: 3拷贝与设备间传输
Section titled “拷贝与设备间传输”我们可以在主机(host)和多个设备(device)之间传输 tensor。
# Host -> Device.a_cpu = o3c.Tensor([0, 1, 2])a_gpu = a_cpu.cuda(0)print(a_gpu)
# Device -> Host.a_gpu = o3c.Tensor([0, 1, 2], device=o3c.Device("CUDA:0"))a_cpu = a_gpu.cpu()print(a_cpu)
# Device -> another Device.a_gpu_0 = o3c.Tensor([0, 1, 2], device=o3c.Device("CUDA:0"))a_gpu_1 = a_gpu_0.cuda(0)print(a_gpu_1)[0 1 2]Tensor[shape={3}, Int64, CUDA:0, 0x7f40ff000000][0 1 2]Tensor[shape={3}, Int64, CPU:0, 0x55d05c254780][0 1 2]Tensor[shape={3}, Int64, CUDA:0, 0x7f40ff000000]Open3D 定义了若干种标量 tensor 数据类型。
| 数据类型 | dtype | 字节数(byte_size) |
|---|---|---|
| 未初始化 Tensor | o3c.Dtype.Undefined | — |
| 32 位浮点数 | o3c.Dtype.Float32 | 4 |
| 64 位浮点数 | o3c.Dtype.Float64 | 8 |
| 8 位有符号整数 | o3c.Dtype.Int8 | 1 |
| 16 位有符号整数 | o3c.Dtype.Int16 | 2 |
| 32 位有符号整数 | o3c.Dtype.Int32 | 4 |
| 64 位有符号整数 | o3c.Dtype.Int64 | 8 |
| 8 位无符号整数 | o3c.Dtype.UInt8 | 1 |
| 16 位无符号整数 | o3c.Dtype.UInt16 | 2 |
| 32 位无符号整数 | o3c.Dtype.UInt32 | 4 |
| 64 位无符号整数 | o3c.Dtype.UInt64 | 8 |
| 布尔值 | o3c.Dtype.Bool | 1 |
我们可以对 tensor 的数据类型进行转换。强制转换可能会导致数据丢失。
# E.g. float -> inta = o3c.Tensor([0.1, 1.5, 2.7])b = a.to(o3c.Dtype.Int32)print(a)print(b)[0.1 1.5 2.7]Tensor[shape={3}, Float64, CPU:0, 0x55d056510450][0 1 2]Tensor[shape={3}, Int32, CPU:0, 0x55d0565104d0]# E.g. int -> floata = o3c.Tensor([1, 2, 3])b = a.to(o3c.Dtype.Float32)print(a)print(b)[1 2 3]Tensor[shape={3}, Int64, CPU:0, 0x55d0565103c0][1.0 2.0 3.0]Tensor[shape={3}, Float32, CPU:0, 0x55d05c9cddd0]通过直接内存映射与 NumPy 交互
Section titled “通过直接内存映射与 NumPy 交互”通过向构造函数传入 NumPy 数组(o3c.Tensor(np.array(...)))创建的 tensor,不会与该 NumPy 数组共享内存。若要实现内存共享,可以使用 o3c.Tensor.from_numpy(...) 和 o3c.Tensor.numpy(...)。二者中任意一方的改动都会反映到另一方。
# Using constructor.np_a = np.ones((5,), dtype=np.int32)o3_a = o3c.Tensor(np_a)print(f"np_a: {np_a}")print(f"o3_a: {o3_a}")print("")
# Changes to numpy array will not reflect as memory is not shared.np_a[0] += 100o3_a[1] += 200print(f"np_a: {np_a}")print(f"o3_a: {o3_a}")np_a: [1 1 1 1 1]o3_a: [1 1 1 1 1]Tensor[shape={5}, Int32, CPU:0, 0x55d057b15480]
np_a: [101 1 1 1 1]o3_a: [1 201 1 1 1]Tensor[shape={5}, Int32, CPU:0, 0x55d057b15480]# From numpy.np_a = np.ones((5,), dtype=np.int32)o3_a = o3c.Tensor.from_numpy(np_a)
# Changes to numpy array reflects on open3d Tensor and vice versa.np_a[0] += 100o3_a[1] += 200print(f"np_a: {np_a}")print(f"o3_a: {o3_a}")np_a: [101 201 1 1 1]o3_a: [101 201 1 1 1]Tensor[shape={5}, Int32, CPU:0, 0x55d057b15a60]# To numpy.o3_a = o3c.Tensor([1, 1, 1, 1, 1], dtype=o3c.Dtype.Int32)np_a = o3_a.numpy()
# Changes to numpy array reflects on open3d Tensor and vice versa.np_a[0] += 100o3_a[1] += 200print(f"np_a: {np_a}")print(f"o3_a: {o3_a}")
# For CUDA Tensor, call cpu() before calling numpy().o3_a = o3c.Tensor([1, 1, 1, 1, 1], device=o3c.Device("CUDA:0"))print(f"\no3_a.cpu().numpy(): {o3_a.cpu().numpy()}")np_a: [101 201 1 1 1]o3_a: [101 201 1 1 1]Tensor[shape={5}, Int32, CPU:0, 0x55d056cc3d30]
o3_a.cpu().numpy(): [1 1 1 1 1]通过 DLPack 内存映射与 PyTorch 交互
Section titled “通过 DLPack 内存映射与 PyTorch 交互”我们可以在 tensor 与 DLManagedTensor 之间相互转换。
import torchimport torch.utils.dlpack
# From PyTorchth_a = torch.ones((5,)).cuda(0)o3_a = o3c.Tensor.from_dlpack(torch.utils.dlpack.to_dlpack(th_a))print(f"th_a: {th_a}")print(f"o3_a: {o3_a}")print("")
# Changes to PyTorch array reflects on open3d Tensor and vice versath_a[0] = 100o3_a[1] = 200print(f"th_a: {th_a}")print(f"o3_a: {o3_a}")th_a: tensor([1., 1., 1., 1., 1.],)o3_a: [1.0 1.0 1.0 1.0 1.0]Tensor[shape={5}, Float32, CUDA:0, 0x7f409be00000]
th_a: tensor([100., 200., 1., 1., 1.],)o3_a: [100.0 200.0 1.0 1.0 1.0]Tensor[shape={5}, Float32, CUDA:0, 0x7f409be00000]# To PyTorcho3_a = o3c.Tensor([1, 1, 1, 1, 1], device=o3c.Device("CUDA:0"))th_a = torch.utils.dlpack.from_dlpack(o3_a.to_dlpack())o3_a = o3c.Tensor.from_dlpack(torch.utils.dlpack.to_dlpack(th_a))print(f"th_a: {th_a}")print(f"o3_a: {o3_a}")print("")
# Changes to PyTorch array reflects on open3d Tensor and vice versath_a[0] = 100o3_a[1] = 200print(f"th_a: {th_a}")print(f"o3_a: {o3_a}")th_a: tensor([1, 1, 1, 1, 1],)o3_a: [1 1 1 1 1]Tensor[shape={5}, Int64, CUDA:0, 0x7f40ff000200]
th_a: tensor([100, 200, 1, 1, 1],)o3_a: [100 200 1 1 1]Tensor[shape={5}, Int64, CUDA:0, 0x7f40ff000200]二元逐元素运算
Section titled “二元逐元素运算”支持的逐元素二元运算包括:
Add(+)Sub(-)Mul(*)Div(/)Add_(+=)Sub_(-=)Mul_(*=)Div_(/=)
需要注意的是,操作数必须位于相同的设备(Device)上、具有相同的数据类型(dtype),并且满足广播(broadcast)兼容性。
a = o3c.Tensor([1, 1, 1], dtype=o3c.Dtype.Float32)b = o3c.Tensor([2, 2, 2], dtype=o3c.Dtype.Float32)print("a +>{}".format(a + b))print("a ->{}".format(a - b))print("a *>{}".format(a * b))print("a />{}".format(a / b))a + 3.0 3.0]Tensor[shape={3}, Float32, CPU:0, 0x55d0573a0ed0]a - -1.0 -1.0]Tensor[shape={3}, Float32, CPU:0, 0x55d05ed01410]a * 2.0 2.0]Tensor[shape={3}, Float32, CPU:0, 0x55d05ed0a180]a / 0.5 0.5]Tensor[shape={3}, Float32, CPU:0, 0x55d05ed013f0]广播规则与 NumPy 的广播规则相同(参见 NumPy 广播规则)。类型转换会以避免数据丢失的方式自动进行。
# Automatic broadcasting.a = o3c.Tensor.ones((2, 3), dtype=o3c.Dtype.Float32)b = o3c.Tensor.ones((3,), dtype=o3c.Dtype.Float32)print("a +>\n{}\n".format(a + b))
# Automatic type casting.a = a[0]print("a + 1 = {}".format(a + 1)) # Float + Int -> Float.print("a +>{}".format(a + True)) # Float + Bool -> Float.
# Inplace.a -= Trueprint("a = {}".format(a))a + 2.0 2.0], [2.0 2.0 2.0]]Tensor[shape={2, 3}, 1}, Float32, CPU:0, 0x55d05ed0a440]
a + 1 = [2.0 2.0 2.0]Tensor[shape={3}, Float32, CPU:0, 0x55d05ed0baa0]a + 2.0 2.0]Tensor[shape={3}, Float32, CPU:0, 0x55d0565103e0] 0.0 0.0]Tensor[shape={3}, Float32, CPU:0, 0x55d05c2547a0]一元逐元素运算
Section titled “一元逐元素运算”支持的一元逐元素运算包括:
sqrt、sqrt_(原地,inplace)sin、sin_cos、cos_neg、neg_exp、exp_abs、abs_
a = o3c.Tensor([4, 9, 16], dtype=o3c.Dtype.Float32)print("a = {}\n".format(a))print("a.sqrt = {}\n".format(a.sqrt()))print("a.sin = {}\n".format(a.sin()))print("a.cos = {}\n".format(a.cos()))
# Inplace operationa.sqrt_()print(a) 9.0 16.0]Tensor[shape={3}, Float32, CPU:0, 0x55d05ed01410]
a.sqrt = [2.0 3.0 4.0]Tensor[shape={3}, Float32, CPU:0, 0x55d0beec40a0]
a.sin = [-0.756802 0.412118 -0.287903]Tensor[shape={3}, Float32, CPU:0, 0x55d056510330]
a.cos = [-0.653644 -0.91113 -0.957659]Tensor[shape={3}, Float32, CPU:0, 0x55d05ed013d0]
[2.0 3.0 4.0]Tensor[shape={3}, Float32, CPU:0, 0x55d05ed01410]Open3D 支持以下归约(reduction)运算:
sum—— 返回沿给定轴求和后的 tensor。mean—— 返回沿给定轴求均值后的 tensor。prod—— 返回沿给定轴求积后的 tensor。min—— 返回沿给定轴取最小值后的 tensor。max—— 返回沿给定轴取最大值后的 tensor。argmin—— 返回沿给定轴最小值索引的 tensor。argmax—— 返回沿给定轴最大值索引的 tensor。
vals = np.array(range(24)).reshape((2, 3, 4))a = o3c.Tensor(vals)print("a.sum = {}\n".format(a.sum()))print("a.min = {}\n".format(a.min()))print("a.ArgMax = {}\n".format(a.argmax()))a.sum = 276Tensor[shape={}, Int64, CPU:0, 0x55d056cc3d50]
a.min = 0Tensor[shape={}, Int64, CPU:0, 0x55d0beec4080]
a.ArgMax = 23Tensor[shape={}, Int64, CPU:0, 0x55d05ed0a440]# With specified dimension.vals = np.array(range(24)).reshape((2, 3, 4))a = o3c.Tensor(vals)
print("Along>\n{}".format(a.sum(dim=(0))))print("Along 2)\n{}\n".format(a.sum(dim=(0, 2))))
# Retention of reduced dimension.print("Shape without retention : {}".format(a.sum(dim=(0, 2)).shape))print("Shape with retention : {}".format(a.sum(dim=(0, 2), keepdim=True).shape))Along[[12 14 16 18], [20 22 24 26], [28 30 32 34]]Tensor[shape={3, 4}, 1}, Int64, CPU:0, 0x55d08bffade0]Along 2)[60 92 124]Tensor[shape={3}, Int64, CPU:0, 0x55d0583dd120]
Shape without retention : SizeVector[3]Shape with retention : SizeVector[1, 3, 1]切片、索引、getitem 与 setitem
Section titled “切片、索引、getitem 与 setitem”基本切片(basic slicing)通过传入整数、切片对象(start:stop:step)、索引数组或布尔数组来完成。切片和索引会产生 tensor 的视图(view),因此对其的任何改动也会反映到原始 tensor 上。
vals = np.array(range(24)).reshape((2, 3, 4))a = o3c.Tensor(vals)print("a = \n{}\n".format(a))
# Indexing __getitem__.print("a[1, 2] = {}\n".format(a[1, 2]))
# Slicing __getitem__.print("a[1:] = \n{}\n".format(a[1:]))
# slice object.print("a[:, 0:3:2, :] = \n{}\n".format(a[:, 0:3:2, :]))
# Combined __getitem__print("a[:-1, 0:3:2, 2] = \n{}\n".format(a[:-1, 0:3:2, 2])) 1 2 3], [4 5 6 7], [8 9 10 11]], [[12 13 14 15], [16 17 18 19], [20 21 22 23]]]Tensor[shape={2, 3, 4}, 4, 1}, Int64, CPU:0, 0x55d05ed03150]
a[1, 2] = [20 21 22 23]Tensor[shape={4}, Int64, CPU:0, 0x55d05ed031f0]
a[1:] =[[[12 13 14 15], [16 17 18 19], [20 21 22 23]]]Tensor[shape={1, 3, 4}, 4, 1}, Int64, CPU:0, 0x55d05ed031b0]
a[:, 0:3:2, :] =[[[0 1 2 3], [8 9 10 11]], [[12 13 14 15], [20 21 22 23]]]Tensor[shape={2, 2, 4}, 8, 1}, Int64, CPU:0, 0x55d05ed03150]
a[:-1, 0:3:2, 2] =[[2 10]]Tensor[shape={1, 2}, 8}, Int64, CPU:0, 0x55d05ed03160]vals = np.array(range(24)).reshape((2, 3, 4))a = o3c.Tensor(vals)
# Changes get reflected.b = a[:-1, 0:3:2, 2]b[0] += 100print("b = {}\n".format(b))print("a = \n{}".format(a)) 110]]Tensor[shape={1, 2}, 8}, Int64, CPU:0, 0x55d05ed01160] 1 102 3], [4 5 6 7], [8 9 110 11]], [[12 13 14 15], [16 17 18 19], [20 21 22 23]]]Tensor[shape={2, 3, 4}, 4, 1}, Int64, CPU:0, 0x55d05ed01150]vals = np.array(range(24)).reshape((2, 3, 4))a = o3c.Tensor(vals)
# Example __setitem__a[:, :, 2] += 100print(a)[[[0 1 102 3], [4 5 106 7], [8 9 110 11]], [[12 13 114 15], [16 17 118 19], [20 21 122 23]]]Tensor[shape={2, 3, 4}, 4, 1}, Int64, CPU:0, 0x55d0573a0ed0]当传入索引数组、布尔数组,或它们与整数/切片对象的组合时,会触发高级索引(advanced indexing)。需要注意的是,高级索引始终返回数据的拷贝(copy)(这与返回视图的基本切片相反)。
整数数组索引
Section titled “整数数组索引”整数数组索引允许根据多维索引从 tensor 中选取任意元素。传入的索引应当满足广播兼容性。
vals = np.array(range(24)).reshape((2, 3, 4))a = o3c.Tensor(vals)
# Along each dimension, a specific element is selected.print("a[[0, 1], [1, 2], [1, 0]] = {}\n".format(a[[0, 1], [1, 2], [1, 0]]))
# Changes not reflected as it is a copy.b = a[[0, 0], [0, 1], [1, 1]]b[0] += 100print("b = {}\n".format(b))print("a[[0, 0], [0, 1], [1, 1]] = {}".format(a[[0, 0], [0, 1], [1, 1]]))a[[0, 1], [1, 2], [1, 0]] = [5 20]Tensor[shape={2}, Int64, CPU:0, 0x55d05ed06570] 5]Tensor[shape={2}, Int64, CPU:0, 0x55d05ed093e0]
a[[0, 0], [0, 1], [1, 1]] = [1 5]Tensor[shape={2}, Int64, CPU:0, 0x55d05758e690]高级索引与基本索引的组合
Section titled “高级索引与基本索引的组合”当索引中至少有一个切片(:)、省略号(...)或 newaxis 时,行为可能会更复杂。这类似于对每个高级索引元素的索引结果进行拼接。在高级索引模式下,发送给高级索引引擎之前会进行一些预处理:
- 特定的索引位置会被转换为包含指定索引的 IndexTensor。
- 如果切片不是完整切片(full slice),则先对 tensor 进行切片,再对高级索引引擎使用完整切片。
dst = src[1, 0:2, [1, 2]] 分两步完成:temp = src[:, 0:2, :],然后 dst = temp[[1], :, [1, 2]]。
索引操作包含两部分:由基本索引定义的子空间,以及来自高级索引部分的子空间。
- 高级索引被切片、省略号或
newaxis分隔。例如x[arr1, :, arr2]。 - 高级索引彼此相邻。例如
x[..., arr1, arr2, :],而x[arr1, :, 1]不属于此类,因为这里的1是一个高级索引。
在第一种情况下,高级索引操作产生的维度会出现在结果数组的最前面,其后是子空间维度。在第二种情况下,高级索引操作产生的维度会插入到结果数组中与原数组相同的位置。
vals = np.array(range(24)).reshape((2, 3, 4))a = o3c.Tensor(vals)
print("a[1, 0:2, [1, 2]] = \n{}\n".format(a[1, 0:2, [1, 2]]))
# Subtle difference in selection and advanced indexing.print("a[(0, 1)] = {}\n".format(a[(0, 1)]))print("a[[0, 1] = \n{}\n".format(a[[0, 1]]))
a = o3c.Tensor(np.array(range(120)).reshape((2, 3, 4, 5)))
# Interleaving slice and advanced indexing.print("a[1, [[1, 2], [2, 1]], 0:4:2, [3, 4]] = \n{}\n".format( a[1, [[1, 2], [2, 1]], 0:4:2, [3, 4]]))a[1, 0:2, [1, 2]] =[[13 17], [14 18]]Tensor[shape={2, 2}, 1}, Int64, CPU:0, 0x55d05eceb810]
a[(0, 1)] = [4 5 6 7]Tensor[shape={4}, Int64, CPU:0, 0x55d05ed01170]
a[[0, 1] =[[[0 1 2 3], [4 5 6 7], [8 9 10 11]], [[12 13 14 15], [16 17 18 19], [20 21 22 23]]]Tensor[shape={2, 3, 4}, 4, 1}, Int64, CPU:0, 0x55d05ed03150]
a[1, [[1, 2], [2, 1]], 0:4:2, [3, 4]] =[[[83 93], [104 114]], [[103 113], [84 94]]]Tensor[shape={2, 2, 2}, 2, 1}, Int64, CPU:0, 0x55d056caa290]布尔数组索引
Section titled “布尔数组索引”当我们传入布尔数组作为索引,或索引由比较运算符返回时,会触发高级索引。布尔数组的维数应当与其所作用对象的维数完全一致。
a = o3c.Tensor(np.array([1, -1, -2, 3]))print("a = {}\n".format(a))
# Add constant to all negative numbers.a[a < 0] += 20print("a = {}\n".format(a)) -1 -2 3]Tensor[shape={4}, Int64, CPU:0, 0x55d05eceb810] 19 18 3]Tensor[shape={4}, Int64, CPU:0, 0x55d05eceb810]Open3D 支持以下逻辑运算符:
logical_and—— 返回逐元素逻辑与(AND)的 tensor。logical_or—— 返回逐元素逻辑或(OR)的 tensor。logical_xor—— 返回逐元素逻辑异或(XOR)的 tensor。logical_not—— 返回逐元素逻辑非(NOT)的 tensor。all—— 当 tensor 中所有元素均为真时返回 true。any—— 当 tensor 中存在任意元素为真时返回 true。allclose—— 当两个 tensor 在容差范围内逐元素相等时返回 true。isclose—— 返回逐元素执行 allclose 操作后的 tensor。issame—— 当且仅当两个 tensor 完全相同(甚至底层内存也相同)时返回 true。
a = o3c.Tensor(np.array([True, False, True, False]))b = o3c.Tensor(np.array([True, True, False, False]))
print("a AND>{}".format(a.logical_and(b)))print("a OR>{}".format(a.logical_or(b)))print("a XOR>{}".format(a.logical_xor(b)))print("NOT>{}\n".format(a.logical_not()))
# Only works for boolean tensors.print("a.any = {}".format(a.any()))print("a.all = {}\n".format(a.all()))
# If tensor is not boolean, 0 will be treated as False, while non-zero as true.# The tensor will be filled with 0 or 1 casted to tensor's dtype.c = o3c.Tensor(np.array([2.0, 0.0, 3.5, 0.0]))d = o3c.Tensor(np.array([0.0, 3.0, 1.5, 0.0]))print("c AND>{}".format(c.logical_and(d)))a AND False False False]Tensor[shape={4}, Bool, CPU:0, 0x55d05757ad90]a OR True True False]Tensor[shape={4}, Bool, CPU:0, 0x55d05ed0a1a0]a XOR True True False]Tensor[shape={4}, Bool, CPU:0, 0x55d0bedd9040]NOT True False True]Tensor[shape={4}, Bool, CPU:0, 0x55d0bf0c0c50]
a.any = Truea.all = False
c AND False True False]Tensor[shape={4}, Bool, CPU:0, 0x55d0bf0c0c50]a = o3c.Tensor(np.array([1, 2, 3, 4]), dtype=o3c.Dtype.Float64)b = o3c.Tensor(np.array([1, 1.99999, 3, 4]))
# Throws exception if the device/dtype is not same.# Returns false if the shape is not same.print("allclose : {}".format(a.allclose(b)))
# Throws exception if the device/dtype/shape is not same.print("isclose : {}".format(a.isclose(b)))
# Returns false if the device/dtype/shape/ is not same.print("issame : {}".format(a.issame(b)))allclose : Trueisclose : [True True True True]Tensor[shape={4}, Bool, CPU:0, 0x55d0bedd9040]issame : Falsea = o3c.Tensor([0, 1, -1])b = o3c.Tensor([0, 0, 0])
print("a >>{}".format(a > b))print("a >=>{}".format(a >= b))print("a <>{}".format(a < b))print("a <=>{}".format(a <= b))print("a ==>{}".format(a == b))print("a !=>{}".format(a != b))
# Throws exception if device/dtype is not shape.# If shape is not same, then tensors should be broadcast compatible.print("a >>{}".format(a > b[0]))a > True False]Tensor[shape={3}, Bool, CPU:0, 0x55d05ed04b10]a >= True False]Tensor[shape={3}, Bool, CPU:0, 0x55d0a7cdbf60]a < False True]Tensor[shape={3}, Bool, CPU:0, 0x55d056caa2e0]a <= False True]Tensor[shape={3}, Bool, CPU:0, 0x55d0565103e0] False False]Tensor[shape={3}, Bool, CPU:0, 0x55d05ed0a1a0]a != True True]Tensor[shape={3}, Bool, CPU:0, 0x55d0bf3f40e0]a > True False]Tensor[shape={3}, Bool, CPU:0, 0x55d05ed01130]非零元素运算
Section titled “非零元素运算”当 as_tuple 为 False(默认值)时,返回非零元素索引的 tensor。结果中的每一行包含输入中一个非零元素的索引。如果输入有 (n) 个维度,则结果 tensor 的大小为 ((z x n)),其中 (z) 是输入 tensor 中非零元素的总数。
当 as_tuple 为 True 时,返回一个由若干一维 tensor 组成的元组,每个维度对应一个,各 tensor 包含输入中所有非零元素的索引。如果输入有 (n) 个维度,则结果元组包含 (n) 个大小为 (z) 的 tensor,其中 (z) 是输入 tensor 中非零元素的总数。
a = o3c.Tensor([[3, 0, 0], [0, 4, 0], [5, 6, 0]])
print("a = \n{}\n".format(a))print("a.nonzero() = \n{}\n".format(a.nonzero()))print("a.nonzero(as_tuple = 1) = \n{}".format(a.nonzero(as_tuple=1))) 0 0], [0 4 0], [5 6 0]]Tensor[shape={3, 3}, 1}, Int64, CPU:0, 0x55d056510470]
a.nonzero() =[[0 1 2 2]Tensor[shape={4}, Int64, CPU:0, 0x55d05ed0a290], [0 1 0 1]Tensor[shape={4}, Int64, CPU:0, 0x55d0bf3f4090]]
a.nonzero(as_tuple = 1) =[[0 1 2 2], [0 1 0 1]]Tensor[shape={2, 4}, 1}, Int64, CPU:0, 0x55d05758e690]Pickle 支持
Section titled “Pickle 支持”自 Open3D v0.16.0 起,tensor 可以使用 pickle 进行序列化和反序列化。这对于将 tensor 保存到磁盘或从磁盘加载非常有用。
import osimport pickleimport tempfile
a = o3c.Tensor([1, 2, 3, 4])print(f'After serialization: {a}\n')with tempfile.TemporaryDirectory() as path: file_name = os.path.join(path, 'tensor') pickle.dump(a, open(file_name, 'wb')) b = pickle.load(open(file_name, 'rb')) print(f'After deserialization: {b}\n')
# Pickle tensor on GPU.a = o3c.Tensor([1, 2, 3, 4], device=o3c.Device('cuda:0'))print(f'After serialization: {a}\n')with tempfile.TemporaryDirectory() as path: file_name = os.path.join(path, 'tensor') pickle.dump(a, open(file_name, 'wb')) b = pickle.load(open(file_name, 'rb')) print(f'After deserialization: {b}\n')
# Pickle non-contiguous tensor.a = o3c.Tensor.ones((100))a = a[::2]print(f'Contiguous: {a.is_contiguous()}\n')with tempfile.TemporaryDirectory() as path: file_name = os.path.join(path, 'tensor') pickle.dump(a, open(file_name, 'wb')) b = pickle.load(open(file_name, 'rb')) print(f'Contiguous: {b.is_contiguous()}\n')After serialization: [1 2 3 4]Tensor[shape={4}, Int64, CPU:0, 0x559b079046e0]
After deserialization: [1 2 3 4]Tensor[shape={4}, Int64, CPU:0, 0x559b06ba7fc0]
After serialization: [1 2 3 4]Tensor[shape={4}, Int64, CUDA:0, 0x302000000]
After deserialization: [1 2 3 4]Tensor[shape={4}, Int64, CUDA:0, 0x302000200]
Contiguous: False
Contiguous: True