在学习scikit_learn时,使用到了grid_to_graph方法,由于不懂该方法的作用以及为何产生得到的输出,因此对该方法进行深入的学习。
_make_edges_3d方法
要学习grid_to_graph方法,先得学习_make_edges_3d方法。
该方法的源码为:
```python
def _make_edges_3d(n_x, n_y, n_z=1):
"""Returns a list of edges for a 3D image.
Parameters
----------
n_x : int
The size of the grid in the x direction.
n_y : int
The size of the grid in the y direction.
n_z : integer, default=1
The size of the grid in the z direction, defaults to 1
"""
vertices = np.arange(n_x * n_y * n_z).reshape((n_x, n_y, n_z))
edges_deep = np.vstack((vertices[:, :, :-1].ravel(),
vertices[:, :, 1:].ravel()))
edges_right = np.vstack((vertices[:, :-1].ravel(),
vertices[:, 1:].ravel()))
edges_down = np.vstack((vertices[:-1].ravel(), vertices[1:].ravel()))
edges = np.hstack((edges_deep, edges_right, edges_down))
return edges
该方法的作用是返回一个3D图形的边的列表,参数说明:
n_x:网格在x方向上的大小。
n_y:网格在y方向上的大小。
n_z:网格在z方向上的大小。默认为1
使用二维数据
为了更清楚地说明,先使用二维数据对方法中的代码进行讲解:
n_x = 3
n_y = 2
# vertices = np.arange(n_x * n_y * n_z).reshape((n_x, n_y, n_z))
vertices = np.arange(n_x * n_y).reshape((n_x, n_y)) # 使用[0,1,2,3,4,5]得到3*2的二维数组
该方法得到一个二维数组
输出:
[[0 1]
[2 3]
[4 5]]
先简化代码中的方法,逐步得到代码的目的:
v1 = vertices[:, :-1]
v2 = vertices[:, 1:]
输出:
v1:# 取所有的行, 取(除最后一列的)所有列
[[0]
[2]
[4]]
v2:# 取所有的行, 取(除第一列的)所有列
[[1]
[3]
[5]]
v1_ravel = v1.ravel() # 降维,将v1转换为一维 [0 2 4]
v2_ravel = v2.ravel() # 降维,将v2转换为一维 [1 3 5]
e = np.vstack(v1) # 按垂直方向(行顺序)堆叠数组构成一个新的数组
e2 = np.hstack(v1) # 按水平方向(列顺序)堆叠数组构成一个新的数组 [0 2 4]
输出:
e:
[[0]
[2]
[4]]
e2
[0 2 4]
_make_edges_3d得到的是一个3D图形的边的列表
使用简化的_make_edges_3d方法是不是得到一个2D图形的边的列表呢?
使用三维数据,z=1
n_x = 3
n_y = 2
n_z = 1
vertices = np.arange(n_x * n_y * n_z).reshape((n_x, n_y, n_z)) # 使用[0,1,2,3,4,5]得到3*2*1的三维数组
输出vertices :
(3*2*1)
[[[0]
[1]]
[[2]
[3]]
[[4]
[5]]]
这样的输出对于不习惯3维数据的同学来说有点复杂。(看得懂的可以忽略这一步~)
我们先把数据看成2维的,如果数据是 0, 1, 2, 3, 4, 5
得到一维数据:0, 1, 2, 3, 4, 5
给维度加上框框 “[ ]”,得到一维数组 [0, 1, 2, 3, 4, 5]
若想要得到一个3行2列的数据,则可以表示为
0 1
2 3
4 5
给维度加上框框 “[ ]”,就可以得到
[
[0 1]
[2 3]
[4 5]
]若把[0 1]、 [2 3]、 [4 5]看成一个元素,那这个二维数组就变成了一个“一维数组”
从这一步可以看出,每一个[ ]就是框住了一个“维度”。最外层的"[ ]"看成一维,
那么,想要得到三维数据,就需要再把二维数据中的元素加上一个 [ ],来给它“增维”:
[
[[0] [1]]
[[2] [3]]
[[4] [5]]
]
得到三维数组之后,计算:
edges_deep = np.vstack((vertices[:, :, :-1].ravel(), vertices[:, :, 1:].ravel())) # 获取边的深度 这跟n_z有关 后面再介绍
# 输出 [] 因为数组在z上只有1个元素,切片后就没有元素了
edges_right = np.vstack((vertices[:, :-1].ravel(), vertices[:, 1:].ravel()))
再使用前面的方法分解一下这行代码:
v1 = vertices[:, :-1] # 除最后一列
v2 = vertices[:, 1:] # 除第一列
r1 = v1.ravel() # [0 2 4]
r2 = v2.ravel() # [1 3 5]
edges_right = np.vstack((r1, r2))
# [[0 2 4]
# [1 3 5]]
同理:
edges_down = np.vstack((vertices[:-1].ravel(), vertices[1:].ravel()))
# [[0 1 2 3]
# [2 3 4 5]]
edges = np.hstack((edges_deep, edges_right, edges_down))
# [[0 2 4 0 1 2 3]
# [1 3 5 2 3 4 5]]
这个方法主要是计算edges_deep、edges_right、edges_down这三个属性,那么这三个属性是代表什么意思呢?
从代码可以看出,edges_deep是取的z轴上的值,edges_right是取y轴上的值,edges_down是取x轴上的值。
edges 返回一个矩阵,也就是指定形状的3D图形的边的集合
下面是使用2维数据简化该方法,并得出输出结果:
n_x = 3
n_y = 2
vertices = np.arange(n_x * n_y).reshape((n_x, n_y))
edges_right = np.vstack((vertices[:, :-1].ravel(), vertices[:, 1:].ravel()))
edges_down = np.vstack((vertices[:-1].ravel(), vertices[1:].ravel()))
edges = np.hstack((edges_right, edges_down))
# print(edges)
# [[0 2 4 0 1 2 3]
# [1 3 5 2 3 4 5]]
n_voxels = n_x * n_y # 6
weights = np.ones(edges.shape[1]) # [1. 1. 1. 1. 1. 1. 1.]
diag = np.ones(n_voxels) # [1. 1. 1. 1. 1. 1.]
diag_idx = np.arange(n_voxels) # [0 1 2 3 4 5]
i_idx = np.hstack((edges[0], edges[1])) # [0 2 4 0 1 2 3 1 3 5 2 3 4 5]
j_idx = np.hstack((edges[1], edges[0])) # [1 3 5 2 3 4 5 0 2 4 0 1 2 3]
n1 = np.hstack((weights, weights, diag)) # [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
o1 = np.hstack((i_idx, diag_idx)) # [0 2 4 0 1 2 3 1 3 5 2 3 4 5 0 1 2 3 4 5]
o2 = np.hstack((j_idx, diag_idx)) # [1 3 5 2 3 4 5 0 2 4 0 1 2 3 0 1 2 3 4 5]
# 加入diag_idx表示填充对角线
n2 = (o1, o2)
c1 = (n1, n2)
c2 = (n_voxels, n_voxels)
graph = sparse.coo_matrix(c1, c2)
print(graph.toarray())
# print(graph)
# 输出:
# [[1. 1. 1. 0. 0. 0.]
# [1. 1. 0. 1. 0. 0.]
# [1. 0. 1. 1. 1. 0.]
# [0. 1. 1. 1. 0. 1.]
# [0. 0. 1. 0. 1. 1.]
# [0. 0. 0. 1. 1. 1.]]
# 对应为1的地方就是有边的地方