Scheme 矩阵运算
基于 SICP 练习 2.37
引言
定义矩阵乘法的动机如下:
变换 | 矩阵 |
---|---|
线性变换 | 矩阵 |
线性变换作用在向量上 | 矩阵乘向量 |
线性变换叠加 | 矩阵乘法 |
本文实现如下操作:
- 向量点积
- 矩阵乘向量
- 矩阵乘矩阵
extend 和 reduce
后面会用到的两个函数。
连接表:
(define (extend seq1 seq2)
(if (null? seq1)
seq2
(cons (car seq1)
(extend (cdr seq1) seq2))))
左聚合,左折叠……这操作叫什么?
(define (reduce op seq)
(define (iter r s)
(if (null? s)
r
(iter (op r (car s)) (cdr s))))
(iter (car seq) (cdr seq)))
Map Reduce
顺便说一下我对MapReduce
的理解。
本来,对一批数据中的一行数据的处理就可能涉及该行本身或者涉及其他的行。而一旦一个处理涉及多行,它就很可能不能并行,比如求解线性方程组的高斯-赛德尔迭代法。
而如果一个处理能被分成两部分,一个只涉及一行的局部处理和另一个涉及多行的汇总处理(一横一纵),即map
和reduce
,就可以把处理清晰地分为可并行与不可并行两部分。
向量点积
(define (dot v w)
(reduce + (map * v w)))
从概念上讲,这里map
并行处理v, w。
矩阵乘向量
[ x 1 x 2 x 3 x 4 ] [ y 1 y 2 ] = [ x 1 y 1 + x 2 y 2 x 3 y 1 + x 4 y 2 ] \left[ \begin{matrix} x_1 & x_2 \\ x_3 & x_4 \end{matrix} \right] \left[ \begin{matrix} y_1 \\ y_2 \end{matrix} \right]= \left[ \begin{matrix} x_1y_1+x_2y_2 \\ x_3y_1+x_4y_2 \end{matrix} \right] [x1x3x2x4][y1y2]=[x1y1+x2y2x3y1+x4y2]
可以看出,结果中的某一行的计算只涉及矩阵中同一行号的那一行。比如,结果的第一行中没有
x
3
x_3
x3和
x
4
x_4
x4。所以我们可以对mat
进行map
:
(define (matrix-*-vector mat v)
(map (lambda (x) (dot x v)) mat))
map
规则就是和v
做点积。
矩阵转置
这个操作在 python 中很容易:
# 比如
> mat1
array([[1, 2, 3, 4],
[4, 5, 6, 6],
[6, 7, 8, 9]])
> list(zip(*mat1)) # attention!
array([[1, 4, 6],
[2, 5, 7],
[3, 6, 8],
[4, 6, 9]])
怎么在 scheme 中做同样的事呢?需要费些心思:
(define (transpose mat)
(reduce (lambda (x y) (map extend x y))
(map (lambda (x) (map list x)) mat)))
这里有三个map
,从上到下、从左到右解释一下:
-
x, y 是两个序列,用 extend 把 x, y 的同位元素连接起来。
所谓同位元素,可以看一下例子:
> (map + (list 1 2 3) (list 3 2 1)) (4 4 4)
-
对矩阵中的每一行做某种操作
-
把 x 中的每个元素装到一个 list 里面。根据上下文,x 就是矩阵中的行。
所以整体的效果是这样的:
- 将矩阵中的每一行中的每一个元素装到一个 list 里面
- 启动 reduce 过程,将每行的同位元素(已经装到 list 里面了)连接起来,reduce 结果也是一个 list,其中的每个元素都是同位相连的结果。同位操作是靠 map 表达的。
再回头看一下 python:
list(zip(*mat1))
zip
只是map
的特例,关键是*
,我还不知道怎么在 Scheme 中做同样的事。
矩阵乘矩阵
(define (matrix-*-matrix mat1 mat2)
(define mat2t (transpose mat2))
(map (lambda (row)
(matrix-*-vector mat2t row))
mat1))
观察矩阵相乘的过程,结果中的行恰是(矩阵乘法的)左操作数中的行右乘右操作数的转置。