广播

术语广播描述了在算术运算期间,如何处理不同形状的数组。 Numpy 首先使用广播一词,但现在用于其他库,如 Tensorflow 和 Matlab;规则因库而异。

来自 Numpy 文档:

广播提供了一种向量化数组操作的方法,使循环在 C 而不是 Python 中出现。 它可以不制作不必要的数据副本而实现,并且通常可以产生高效实现。最简单的广播示例在数组乘以标量时发生。

  1. a = np.array([1.0, 2.0, 3.0])
  2. b = 2.0
  3. a * b
  4. # array([ 2., 4., 6.])
  5. v=np.array([1,2,3])
  6. print(v, v.shape)
  7. # [1 2 3] (3,)
  8. m=np.array([v,v*2,v*3]); m, m.shape
  9. '''
  10. (array([[1, 2, 3],
  11. [2, 4, 6],
  12. [3, 6, 9]]), (3, 3))
  13. '''
  14. n = np.array([m*1, m*5])
  15. n
  16. '''
  17. array([[[ 1, 2, 3],
  18. [ 2, 4, 6],
  19. [ 3, 6, 9]],
  20. [[ 5, 10, 15],
  21. [10, 20, 30],
  22. [15, 30, 45]]])
  23. '''
  24. n.shape, m.shape
  25. # ((2, 3, 3), (3, 3))

我们可以使用广播来将矩阵和数组相加:

  1. m+v
  2. '''
  3. array([[ 2, 4, 6],
  4. [ 3, 6, 9],
  5. [ 4, 8, 12]])
  6. '''

注意如果我们转置数组会发生什么:

  1. v1=np.expand_dims(v,-1); v1, v1.shape
  2. '''
  3. (array([[1],
  4. [2],
  5. [3]]), (3, 1))
  6. '''
  7. m+v1
  8. '''
  9. array([[ 2, 3, 4],
  10. [ 4, 6, 8],
  11. [ 6, 9, 12]])
  12. '''

通用的 NumPy 广播规则

操作两个数组时,NumPy 会逐元素地比较它们的形状。 它从最后的维度开始,并向前移动。 如果满足:

  • 他们是相等的,或者
  • 其中一个是 1

两个维度兼容。

数组不需要具有相同数量的维度。 例如,如果你有一个256×256×3的 RGB 值数组,并且你希望将图像中的每种颜色缩放不同的值,则可以将图像乘以具有 3 个值的一维数组。 根据广播规则排列这些数组的尾部轴的大小,表明它们是兼容的:

  1. Image (3d array): 256 x 256 x 3
  2. Scale (1d array): 3
  3. Result (3d array): 256 x 256 x 3

回顾

  1. v = np.array([1,2,3,4])
  2. m = np.array([v,v*2,v*3])
  3. A = np.array([5*m, -1*m])
  4. v.shape, m.shape, A.shape
  5. # ((4,), (3, 4), (2, 3, 4))

下列操作有效嘛?

  1. A
  2. A + v
  3. A.T + v
  4. A.T.shape

(SciPy 中的)稀疏矩阵

具有大量零的矩阵称为稀疏(稀疏是密集的反义)。 对于稀疏矩阵,仅仅存储非零值,可以节省大量内存。

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图1

另一个大型稀疏矩阵的例子:

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图2

来源

这是最常见的稀疏存储格式:

  • 逐坐标(scipy 称 COO)
  • 压缩稀疏行(CSR)
  • 压缩稀疏列(CSC)

让我们来看看这些例子

实际上还有更多格式

如果非零元素的数量与行(或列)的数量成比例而不是与行列的乘积成比例,则通常将一类矩阵(例如,对角)称为稀疏。

Scipy 实现

来自 Scipy 稀疏矩阵文档

  • 为了有效地构造矩阵,请使用dok_matrixlil_matrixlil_matrix类支持基本切片和花式索引,其语法与 NumPy 数组类似。 如下所示,COO 格式也可用于有效地构造矩阵
  • 要执行乘法或求逆等操作,首先要将矩阵转换为 CSC 或 CSR 格式。
  • CSR,CSC 和 COO 格式之间的所有转换都是高效的线性时间操作。

今天:CT 扫描

引言

数学真的可以拯救你的生命吗?当然可以!!” (可爱的文章)

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图3

(CAT 和 CT 扫描指代相同的过程。CT 扫描是更现代的术语)

本课程基于 Scikit-Learn 示例压缩感知:使用 L1 先验的层析成像重建(Lasso)

我们今天的目标

读取 CT 扫描的结果并构建原始图像。

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图4

对于(特定位置和特定角度的)每个 X 射线,我们进行单次测量。 我们需要从这些测量中构建原始图像。 此外,我们不希望患者经历大量辐射,因此我们收集的数据少于图片区域。

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图5

我们会看到:

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图6

来源:压缩感知

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图7

来源

导入

  1. %matplotlib inline
  2. import numpy as np, matplotlib.pyplot as plt, math
  3. from scipy import ndimage, sparse
  4. np.set_printoptions(suppress=True)

生成数据

引言

我们将使用生成的数据(不是真正的 CT 扫描)。 生成数据涉及一些有趣的 numpy 和线性代数,我们稍后会再回过头来看。

代码来自 Scikit-Learn 示例压缩感知:使用 L1 先验的层析成像重建(Lasso)

生成图像

  1. def generate_synthetic_data():
  2. rs = np.random.RandomState(0)
  3. n_pts = 36
  4. x, y = np.ogrid[0:l, 0:l]
  5. mask_outer = (x - l / 2) ** 2 + (y - l / 2) ** 2 < (l / 2) ** 2
  6. mx,my = rs.randint(0, l, (2,n_pts))
  7. mask = np.zeros((l, l))
  8. mask[mx,my] = 1
  9. mask = ndimage.gaussian_filter(mask, sigma=l / n_pts)
  10. res = (mask > mask.mean()) & mask_outer
  11. return res ^ ndimage.binary_erosion(res)
  12. l = 128
  13. data = generate_synthetic_data()
  14. plt.figure(figsize=(5,5))
  15. plt.imshow(data, cmap=plt.cm.gray);

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图8

generate_synthetic_data在做什么

  1. l=8; n_pts=5
  2. rs = np.random.RandomState(0)
  3. x, y = np.ogrid[0:l, 0:l]; x,y
  4. '''
  5. (array([[0],
  6. [1],
  7. [2],
  8. [3],
  9. [4],
  10. [5],
  11. [6],
  12. [7]]), array([[0, 1, 2, 3, 4, 5, 6, 7]]))
  13. '''
  14. x + y
  15. '''
  16. array([[ 0, 1, 2, 3, 4, 5, 6, 7],
  17. [ 1, 2, 3, 4, 5, 6, 7, 8],
  18. [ 2, 3, 4, 5, 6, 7, 8, 9],
  19. [ 3, 4, 5, 6, 7, 8, 9, 10],
  20. [ 4, 5, 6, 7, 8, 9, 10, 11],
  21. [ 5, 6, 7, 8, 9, 10, 11, 12],
  22. [ 6, 7, 8, 9, 10, 11, 12, 13],
  23. [ 7, 8, 9, 10, 11, 12, 13, 14]])
  24. '''
  25. (x - l/2) ** 2
  26. '''
  27. array([[ 16.],
  28. [ 9.],
  29. [ 4.],
  30. [ 1.],
  31. [ 0.],
  32. [ 1.],
  33. [ 4.],
  34. [ 9.]])
  35. '''
  36. (x - l/2) ** 2 + (y - l/2) ** 2
  37. '''
  38. array([[ 32., 25., 20., 17., 16., 17., 20., 25.],
  39. [ 25., 18., 13., 10., 9., 10., 13., 18.],
  40. [ 20., 13., 8., 5., 4., 5., 8., 13.],
  41. [ 17., 10., 5., 2., 1., 2., 5., 10.],
  42. [ 16., 9., 4., 1., 0., 1., 4., 9.],
  43. [ 17., 10., 5., 2., 1., 2., 5., 10.],
  44. [ 20., 13., 8., 5., 4., 5., 8., 13.],
  45. [ 25., 18., 13., 10., 9., 10., 13., 18.]])
  46. '''
  47. mask_outer = (x - l/2) ** 2 + (y - l/2) ** 2 < (l/2) ** 2; mask_outer
  48. '''
  49. array([[False, False, False, False, False, False, False, False],
  50. [False, False, True, True, True, True, True, False],
  51. [False, True, True, True, True, True, True, True],
  52. [False, True, True, True, True, True, True, True],
  53. [False, True, True, True, True, True, True, True],
  54. [False, True, True, True, True, True, True, True],
  55. [False, True, True, True, True, True, True, True],
  56. [False, False, True, True, True, True, True, False]], dtype=bool)
  57. '''
  58. plt.imshow(mask_outer, cmap='gray')
  59. # <matplotlib.image.AxesImage at 0x7efcd9303278>

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图9

  1. mask = np.zeros((l, l))
  2. mx,my = rs.randint(0, l, (2,n_pts))
  3. mask[mx,my] = 1; mask
  4. '''
  5. array([[ 0., 1., 0., 0., 0., 0., 0., 0.],
  6. [ 0., 0., 0., 0., 0., 0., 0., 0.],
  7. [ 0., 0., 0., 0., 0., 0., 0., 0.],
  8. [ 0., 0., 0., 1., 0., 0., 0., 0.],
  9. [ 0., 0., 0., 1., 0., 0., 0., 0.],
  10. [ 0., 0., 0., 0., 0., 0., 0., 1.],
  11. [ 0., 0., 0., 0., 0., 0., 0., 0.],
  12. [ 0., 0., 0., 1., 0., 0., 0., 0.]])
  13. '''
  14. plt.imshow(mask, cmap='gray')
  15. # <matplotlib.image.AxesImage at 0x7efcd9293940>

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图10

  1. mask = ndimage.gaussian_filter(mask, sigma=l / n_pts)
  2. plt.imshow(mask, cmap='gray')
  3. # <matplotlib.image.AxesImage at 0x7efcd922c0b8>

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图11

  1. res = np.logical_and(mask > mask.mean(), mask_outer)
  2. plt.imshow(res, cmap='gray');

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图12

  1. plt.imshow(ndimage.binary_erosion(res), cmap='gray');

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图13

  1. plt.imshow(res ^ ndimage.binary_erosion(res), cmap='gray');

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图14

生成投影

代码

  1. def _weights(x, dx=1, orig=0):
  2. x = np.ravel(x)
  3. floor_x = np.floor((x - orig) / dx)
  4. alpha = (x - orig - floor_x * dx) / dx
  5. return np.hstack((floor_x, floor_x + 1)), np.hstack((1 - alpha, alpha))
  6. def _generate_center_coordinates(l_x):
  7. X, Y = np.mgrid[:l_x, :l_x].astype(np.float64)
  8. center = l_x / 2.
  9. X += 0.5 - center
  10. Y += 0.5 - center
  11. return X, Y
  12. def build_projection_operator(l_x, n_dir):
  13. X, Y = _generate_center_coordinates(l_x)
  14. angles = np.linspace(0, np.pi, n_dir, endpoint=False)
  15. data_inds, weights, camera_inds = [], [], []
  16. data_unravel_indices = np.arange(l_x ** 2)
  17. data_unravel_indices = np.hstack((data_unravel_indices,
  18. data_unravel_indices))
  19. for i, angle in enumerate(angles):
  20. Xrot = np.cos(angle) * X - np.sin(angle) * Y
  21. inds, w = _weights(Xrot, dx=1, orig=X.min())
  22. mask = (inds >= 0) & (inds < l_x)
  23. weights += list(w[mask])
  24. camera_inds += list(inds[mask] + i * l_x)
  25. data_inds += list(data_unravel_indices[mask])
  26. proj_operator = sparse.coo_matrix((weights, (camera_inds, data_inds)))
  27. return proj_operator

投影运算符

  1. l = 128
  2. proj_operator = build_projection_operator(l, l // 7)
  3. proj_operator
  4. '''
  5. <2304x16384 sparse matrix of type '<class 'numpy.float64'>'
  6. with 555378 stored elements in COOrdinate format>
  7. '''

维度:角度(l // 7),位置(l),每个图像(l x l

  1. proj_t = np.reshape(proj_operator.todense().A, (l//7,l,l,l))

第一个坐标指的是线的角度,第二个坐标指代线的位置。

索引为 3 的角度的直线:

  1. plt.imshow(proj_t[3,0], cmap='gray');

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图15

  1. plt.imshow(proj_t[3,1], cmap='gray');

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图16

  1. plt.imshow(proj_t[3,2], cmap='gray');

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图17

  1. plt.imshow(proj_t[3,40], cmap='gray');

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图18

垂直位置 40 处的其他直线:

  1. plt.imshow(proj_t[4,40], cmap='gray');

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图19

  1. plt.imshow(proj_t[15,40], cmap='gray');

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图20

  1. plt.imshow(proj_t[17,40], cmap='gray');

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图21

X 射线和数据之间的交点

接下来,我们想看看直线如何与我们的数据相交。 请记住,这就是数据的样子:

  1. plt.figure(figsize=(5,5))
  2. plt.imshow(data, cmap=plt.cm.gray)
  3. plt.axis('off')
  4. plt.savefig("images/data.png")

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图22

  1. proj = proj_operator @ data.ravel()[:, np.newaxis]

角度为 17,位置为 40 的穿过数据的 X 射线:

  1. plt.figure(figsize=(5,5))
  2. plt.imshow(data + proj_t[17,40], cmap=plt.cm.gray)
  3. plt.axis('off')
  4. plt.savefig("images/data_xray.png")

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图23

它们相交的地方。

  1. both = data + proj_t[17,40]
  2. plt.imshow((both > 1.1).astype(int), cmap=plt.cm.gray);

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图24

那条 X 射线的强度:

  1. np.resize(proj, (l//7,l))[17,40]
  2. # 6.4384498372605989

角度为 3,位置为 14 的穿过数据的 X 射线:

  1. plt.imshow(data + proj_t[3,14], cmap=plt.cm.gray);

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图25

它们相交的地方。

  1. both = data + proj_t[3,14]
  2. plt.imshow((both > 1.1).astype(int), cmap=plt.cm.gray);

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图26

CT 扫描的测量结果在这里是一个小数字:

  1. np.resize(proj, (l//7,l))[3,14]
  2. # 2.1374953737965541
  3. proj += 0.15 * np.random.randn(*proj.shape)

关于*args

  1. a = [1,2,3]
  2. b = [4,5,6]
  3. c = list(zip(a, b))
  4. c
  5. # [(1, 4), (2, 5), (3, 6)]
  6. list(zip(*c))
  7. # [(1, 2, 3), (4, 5, 6)]

投影(CT 读取)

  1. plt.figure(figsize=(7,7))
  2. plt.imshow(np.resize(proj, (l//7,l)), cmap='gray')
  3. plt.axis('off')
  4. plt.savefig("images/proj.png")

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图27

回归

现在我们将尝试仅从投影中恢复数据(CT 扫描的测量值)。

线性回归:Xβ=y

我们的矩阵A是投影算子。 这是我们不同 X 射线上方的 4d 矩阵(角度,位置,xy):

  1. plt.figure(figsize=(12,12))
  2. plt.title("X: Projection Operator")
  3. plt.imshow(proj_operator.todense().A, cmap='gray')
  4. # <matplotlib.image.AxesImage at 0x7efcd414ed30>

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图28

我们正在求解原始数据x。 我们将 2D 数据展开为单个列。

  1. plt.figure(figsize=(5,5))
  2. plt.title("beta: Image")
  3. plt.imshow(data, cmap='gray')
  4. plt.figure(figsize=(4,12))
  5. # 我正在平铺列,使其更容易看到
  6. plt.imshow(np.tile(data.ravel(), (80,1)).T, cmap='gray')
  7. # <matplotlib.image.AxesImage at 0x7efcd3b1e278>

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图29

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图30

我们的向量y是展开的测量值矩阵:

  1. plt.figure(figsize=(8,8))
  2. plt.imshow(np.resize(proj, (l//7,l)), cmap='gray')
  3. plt.figure(figsize=(10,10))
  4. plt.imshow(np.tile(proj.ravel(), (20,1)).T, cmap='gray')
  5. # <matplotlib.image.AxesImage at 0x7efcd34f8710>

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图31

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图32

使用 Sklearn 线性回归重构图像

  1. from sklearn.linear_model import Lasso
  2. from sklearn.linear_model import Ridge
  3. # 用 L2(岭)惩罚重建
  4. rgr_ridge = Ridge(alpha=0.2)
  5. rgr_ridge.fit(proj_operator, proj.ravel())
  6. rec_l2 = rgr_ridge.coef_.reshape(l, l)
  7. plt.imshow(rec_l2, cmap='gray')
  8. # <matplotlib.image.AxesImage at 0x7efcd453d5c0>

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图33

  1. 18*128
  2. # 2304
  3. 18 x 128 x 128 x 128

L1 范数产生稀疏性

单位球 六、使用鲁棒回归的 CT 扫描的压缩感知 - 图34 在 L1 范数中是菱形。 它的极值是角:

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图35

来源

类似的视角是看损失函数的轮廓:

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图36

来源

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图37 是 L1 范数。 最小化 L1 范数会产生稀疏值。 对于矩阵,L1 范数等于最大绝对列范数。

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图38 是核范数,它是奇异值的 L1 范数。 试图最小化它会产生稀疏的奇异值 -> 低秩。

  1. proj_operator.shape
  2. # (2304, 16384)
  3. # 使用 L1(Lasso)惩罚重建 α 的最佳值
  4. # 使用 LassoCV 交叉验证来确定
  5. rgr_lasso = Lasso(alpha=0.001)
  6. rgr_lasso.fit(proj_operator, proj.ravel())
  7. rec_l1 = rgr_lasso.coef_.reshape(l, l)
  8. plt.imshow(rec_l1, cmap='gray')
  9. # <matplotlib.image.AxesImage at 0x7efcd4919cf8>

六、使用鲁棒回归的 CT 扫描的压缩感知 - 图39

这里的 L1 惩罚明显优于 L2 惩罚!