最近,我一直在享受从坐标中渲染图表和图形的乐趣,而且我对使用矩阵来转换坐标空间非常着迷。
我已经能够成功地缩放和反转二维坐标空间,但现在我的胃口被激起了。:)
我在哪里可以找到关于矩阵、矩阵数学(尤其是适用于 2 维和 3 维空间)的清晰、信息丰富、(免费)的教育材料?
最近,我一直在享受从坐标中渲染图表和图形的乐趣,而且我对使用矩阵来转换坐标空间非常着迷。
我已经能够成功地缩放和反转二维坐标空间,但现在我的胃口被激起了。:)
我在哪里可以找到关于矩阵、矩阵数学(尤其是适用于 2 维和 3 维空间)的清晰、信息丰富、(免费)的教育材料?
原始答案:我不确定您是否会喜欢数学课程通常如何介绍矩阵。作为一名程序员,你可能会更高兴抓住任何一本像样的 3D 图形书。它当然应该有非常具体的 3x3 矩阵。另外,找出可以教你投影变换的那些(投影几何是一个非常漂亮的低维几何领域并且易于编程)。
内容:
[Vector, __add__, reflect_y, rotate, dilate, transform]
[Matrix, __add__, __str__, __mul__, zero, det, inv, __pow__]
前言:根据我的教学经验,我认为别人参考的课程都是非常好的课程。这意味着,如果您的目标是像数学家那样理解矩阵,那么您一定要掌握整个课程。但是,如果您的目标更谦虚,这是我尝试更适合您的需求的东西(但仍以传达许多理论概念为目标,这与我最初的建议相矛盾。)
如何使用:
在矩阵之前是向量。您肯定知道如何处理 2 维和 3 维向量:
class Vector:
"""This will be a simple 2-dimensional vector.
In case you never encountered Python before, this string is a
comment I can put on the definition of the class or any function.
It's just one of many cool features of Python, so learn it here!
"""
def __init__(self, x, y):
self.x = x
self.y = y
现在你可以写了
v = Vector(5, 3)
w = Vector(7, -1)
但它本身并没有多大乐趣。让我们添加更多有用的方法:
def __str__(self: 'vector') -> 'readable form of vector':
return '({0}, {1})'.format(self.x, self.y)
def __add__(self:'vector', v: 'another vector') -> 'their sum':
return Vector(self.x + v.x, self.y + v.y)
def __mul__(self:'vector', number: 'a real number') -> 'vector':
'''Multiplies the vector by a number'''
return Vector(self.x * number, self.y * number)
这让事情变得更有趣,因为我们现在可以写:
print(v + w * 2)
并将答案(19, 1)
很好地打印为向量(如果示例看起来不熟悉,请考虑此代码在 C++ 中的外观)。
现在能够编写一切都很酷,1274 * w
但是您需要对图形进行更多的矢量操作。以下是其中的一些:您可以围绕点翻转矢量,可以围绕或轴(0,0)
反射它,可以顺时针或逆时针旋转它(在这里画图是个好主意)。x
y
我们来做一些简单的操作:
...
def flip(self:'vector') -> 'vector flipped around 0':
return Vector(-self.x, -self.y)
def reflect_x(self:'vector') -> 'vector reflected around x axis':
return Vector(self.x, -self.y)
print(v.flip(), v.reflect_x())
flip(...)
使用我下面的操作来表达?怎么样reflect_x
?现在你可能想知道为什么我省略了reflect_y
. 嗯,这是因为我想让你停下来写下你自己的版本。好的,这是我的:
def reflect_y(self:'vector') -> 'vector reflected around y axis':
return self.flip().reflect_x()
看,如果你看看这个函数是如何计算的,它实际上是非常微不足道的。但是突然发生了一件令人惊奇的事情:我能够仅使用现有的转换flip
和reflect_x
. 无论如何,我关心的是,reflect_y
可以在没有访问权限的派生类中定义x
并且y
它仍然可以工作!
数学家将这些函数称为运算符。他们会说这reflect_y
是一个由运算符组合得到的运算符flip
,reflect_x
用reflect_y = flip ○ reflect_x
(你应该看到小圆圈,一个 Unicode 符号25CB
)表示。
=
符号来表示两个操作产生相同的结果,就像上面的段落一样。这是一个“数学=
”,不能用程序来表达。所以如果我这样做
print(v.reflect_y())
我得到了结果(-5, 3)
。去画吧!
reflect_y ◦ reflect_y
。你会怎么命名它?这些操作很好也很有用,但你可能想知道为什么引入旋转这么慢。好的,我开始:
def rotate(self:'vector', angle:'rotation angle') -> 'vector':
??????
此时,如果您知道如何旋转矢量,您应该继续填写问号。否则,请耐心等待一个更简单的情况:逆时针旋转90
度数。这个在一张纸上并不难画:
def rotate_90(self:'vector') -> 'rotated vector':
new_x = - self.y
new_y = self.x
return Vector(new_x, new_y)
试
x_axis = Vector(1, 0)
y_axis = Vector(0, 1)
print(x_axis.rotate_90(), y_axis.rotate_90())
现在给(0, 1) (-1, 0)
. 自己运行吧!
flip = rotate_90 ◦ rotate_90
.无论如何,我不会再隐藏秘密成分了:
import math # we'll need math from now on
...
class Vector:
...
def rotate(self:'vector', angle:'rotation angle') -> 'rotated vector':
cos = math.cos(angle)
sin = math.sin(angle)
new_x = cos * self.x - sin * self.y
new_y = sin * self.x + cos * self.y
return Vector(new_x, new_y)
现在让我们尝试一些类似的东西:
print(x_axis.rotate(90), y_axis.rotate(90))
如果你期待和以前一样的结果(0, 1) (-1, 0)
,你一定会失望的。该代码打印:
(-0.448073616129, 0.893996663601) (-0.893996663601, -0.448073616129)
男孩,它丑吗!
符号:我会说我们在上面的例子中应用rotate(90)
了操作x
。我们获得的知识是rotate(90) != rotate_90
。
问题:这里发生了什么?rotate_90
用什么来表达rotate
?flip
用什么来表达rotate
?
这些旋转当然很有用,但它们并不是您需要做的所有事情,即使是 2D 图形也是如此。考虑以下转换:
def dilate(self:'vector', axe_x:'x dilation', axe_y:'y dilation'):
'''Dilates a vector along the x and y axes'''
new_x = axe_x * self.x
new_y = axe_y * self.y
return Vector(new_x, new_y)
这dilate
件事以一种可能不同的方式扩张x
和y
轴。
dilate(?, ?) = flip
在,中填上问号dilate(?, ?) = reflect_x
。我将使用这个dilate
函数来演示数学家称之为交换性的东西:也就是说,对于参数的每个值a
, b
, c
,d
你可以确定
dilate(a, b) ◦ dilate(c, d) = dilate(c, d) ◦ dilate(a, b)
练习:证明它。此外,对于所有可能的参数值,以下参数是否成立?
`rotate(a) ◦ rotate(b) = rotate(b) ◦ rotate(a)`
`dilate(a, b) ◦ rotate(c) = rotate(c) ◦ dilate(a, b)`
`rotate(a) ◦ __mul__(b) = __mul__(b) ◦ rotate(a)`
让我们总结一下我们在这里拥有的所有东西,我们在向量上的运算符x
flip
, reflect_x
, *
, rotate(angle)
,dilate(x, y)
从中可以做出一些非常疯狂的东西,例如
flip ◦ rotate(angle) ◦ dilate(x, y) ◦ rotate(angle_2) ◦ reflect_y + reflect_x = ???
当您创建越来越复杂的表达式时,人们会希望某种顺序可以突然将所有可能的表达式简化为有用的形式。不要害怕!神奇的是,上面表格的每个表达式都可以简化为
def ???(self:'vector', parameters):
'''A magical representation of a crazy function'''
new_x = ? * self.x + ? * self.y
new_y = ? * self.x + ? * self.y
return Vector(new_x, new_y)
用一些数字和/或参数代替?
s。
__mul__(2) ◦ rotate(pi/4)
dilate(x, y) ◦ rotate(pi/4)
这允许我们编写一个通用函数
def transform(self:'vector', m:'matrix') -> 'new vector':
new_x = m[0] * self.x + m[1] * self.y
new_y = m[2] * self.x + m[3] * self.y
return Vector(new_x, new_y)
这将采用任何 4 元组的数字,称为matrix,并将其应用于 vector x
。这是一个例子:
rotation_90_matrix = (0, -1, 1, 0)
print(v, v.rotate_90(), v.transform(rotation_90_matrix))
打印(5, 3) (-3, 5) (-3, 5)
。请注意,如果您将transform
任何矩阵应用于原点,您仍然会得到原点:
origin = Vector(0, 0)
print(origin.transform(rotation_90_matrix))
m
描述flip
, dilate(x, y)
,的元组是什么rotate(angle)
?当我们与Vector
课程分开时,这里有一个练习,适合那些想要测试他们的向量数学知识和 Pythonic 技能的人:
Vector
类中(您可以为向量重载多少标准操作符?查看我的答案)。正如我们在上一节中所发现的,矩阵可以被认为是一种速记,它允许我们以简单的方式对向量运算进行编码。例如,rotation_90_matrix
将旋转编码 90 度。
现在,当我们将注意力从向量转移到矩阵时,我们当然也应该有一个矩阵类。此外,在上面的那个函数Vector.transform(...)
中,矩阵的作用在某种程度上被歪曲了。在向量变化时固定更常见m
,所以从现在开始我们的转换将是矩阵类的方法:
class Matrix:
def __init__(self:'new matrix', m:'matrix data'):
'''Create a new matrix.
So far a matrix for us is just a 4-tuple, but the action
will get hotter once The (R)evolution happens!
'''
self.m = m
def __call__(self:'matrix', v:'vector'):
new_x = self.m[0] * v.x + self.m[1] * v.y
new_y = self.m[2] * v.x + self.m[3] * v.y
return Vector(new_x, new_y)
如果您不了解 Python,请__call__
重载(...)
for 矩阵的含义,以便我可以使用标准表示法来表示作用于向量的矩阵。此外,矩阵通常使用单个大写字母编写:
J = Matrix(rotation_90_matrix)
print(w, 'rotated is', J(w))
现在,让我们看看我们还能用矩阵做什么。请记住,矩阵m
实际上只是对向量进行编码的一种方式。请注意,对于两个函数m1(x)
,m2(x)
我可以创建一个新函数(使用lambda notation)m = lambda x: m1(x) + m2(x)
。事实证明,如果m1
和m2
由矩阵编码,您也可以m
使用矩阵对其进行编码!
你只需要添加它的数据,比如(0, 1, -1, 0) + (0, 1, -1, 0) = (0, 2, -2, 0)
. 以下是在 Python 中添加两个元组的方法,其中包含一些非常有用且高度 Pythonic 的技术:
def __add__(self:'matrix', snd:'another matrix'):
"""This will add two matrix arguments.
snd is a standard notation for the second argument.
(i for i in array) is Python's powerful list comprehension.
zip(a, b) is used to iterate over two sequences together
"""
new_m = tuple(i + j for i, j in zip(self.m, snd.m))
return Matrix(new_m)
现在我们可以编写类似J + J
or evenJ + J + J
的表达式,但要查看结果,我们必须弄清楚如何打印一个 Matrix。一种可能的方法是打印一个 4 元组的数字,但是让我们从Matrix.__call__
函数中得到一个提示,即数字应该被组织成一个2x2
块:
def as_block(self:'matrix') -> '2-line string':
"""Prints the matrix as a 2x2 block.
This function is a simple one without any advanced formatting.
Writing a better one is an exercise.
"""
return ('| {0} {1} |\n' .format(self.m[0], self.m[1]) +
'| {0} {1} |\n' .format(self.m[2], self.m[3]) )
如果您查看此功能的实际运行情况,您会发现还有一些改进空间:
print((J + J + J).as_block())
Matrix.__str__
,将数字四舍五入并将它们打印在固定长度的字段中。现在您应该能够编写旋转矩阵:
def R(a: 'angle') -> 'matrix of rotation by a':
cos = math.cos(a)
sin = math.sin(a)
m = ( ????? )
return Matrix(m)
练习:检查代码Vector.rotate(self, angle)
并填写问号。测试
from math import pi
print(R(pi/4) + R(-pi/4))
我们可以用单参数函数做的最重要的事情是组合它们:f = lambda v: f1(f2(v))
. 如何用矩阵反映它?这需要我们检查它是如何Matrix(m1) ( Matrix(m2) (v))
工作的。如果你展开它,你会注意到
m(v).x = m1[0] * (m2[0]*v.x + m2[1]*v.y) + m1[1] * (m2[2]*v.x + m2[3]*v.y)
同样对于m(v).y
, 如果你打开括号,它看起来与Matrix.__call__
使用一个新的 tuple 非常相似m
,例如m[0] = m1[0] * m2[0] + m1[2] * m2[2]
. 因此,让我们以此作为新定义的提示:
def compose(self:'matrix', snd:'another matrix'):
"""Returns a matrix that corresponds to composition of operators"""
new_m = (self.m[0] * snd.m[0] + self.m[1] * snd.m[2],
self.m[0] * snd.m[1] + self.m[1] * snd.m[3],
???,
???)
return Matrix(new_m)
练习:在这里填写问号。测试它
print(R(1).compose(R(2)))
print(R(3))
数学练习:证明R(a).compose(R(b))
总是与 相同R(a + b)
。
现在让我说实话:这个compose
函数实际上是数学家决定矩阵相乘的方式。这作为一个符号是有意义的:A * B
是一个描述 operator 的矩阵,A ○ B
正如我们接下来将看到的,还有更深层次的理由将其称为“乘法”。
要开始在 Python 中使用乘法,我们所要做的就是在一个Matrix
类中对其进行排序:
class Matrix:
...
__mul__ = compose
(R(pi/2) + R(pi)) * (R(-pi/2) + R(pi))
。试着自己先在一张纸上找到答案。+
和*
让我们为与dilate(a, b)
运算符对应的矩阵起一个好名字。现在 没有什么问题D(a, b)
,但我将借此机会介绍一个标准符号:
def diag(a: 'number', b: 'number') -> 'diagonal 2x2 matrix':
m = (a, 0, 0, b)
return Matrix(m)
试着print(diag(2, 12345))
看看为什么它被称为对角矩阵。
由于之前发现操作的组合并不总是可交换的,*
因此运算符对于矩阵也不总是可交换的。
A
, B
, 由R
和组成的例子diag
,这样A * B
不等于B * A
。这有点奇怪,因为数字的乘法总是可交换的,并且提出了是否compose
真的值得被称为的问题__mul__
。这里+
有很多满足的规则:*
A + B = B + A
A * (B + C) = A * B + A * C
(A + B) * C = A * C + B * C
(A * B) * C = A * (B * C)
A - B
and(A - B) + B = A
A - B
定义+
,*
和diag
? A - A
等于什么?将方法添加__sub__
到类Matrix
。如果你计算会发生什么R(2) - R(1)*R(1)
?它应该等于什么?(A * B) * C = A * (B * C)
等式称为关联性,特别好,因为这意味着我们不必担心将括号放在以下形式的表达式中A * B * C
:
print(R(1) * (diag(2,3) * R(2)))
print((R(1) * diag(2,3)) * R(2))
让我们找到常规数字0
和1
减法的类似物:
zero = diag(0, 0)
one = diag(1, 1)
通过以下易于验证的添加:
A + zero = A
A * zero = zero
A * one = one * A = A
规则变得完整,因为它们有一个简称:环公理。因此,数学家会说矩阵形成一个环,他们确实总是使用符号+
和*
在谈论环时,我们也应该如此。
使用规则可以轻松计算上一节中的表达式:
(R(pi/2) + R(pi)) * (R(-pi/2) + R(pi)) = R(pi/2) * R(-pi/2) + ... = one + ...
(R(a) + R(b)) * (R(a) - R(b)) = R(2a) - R(2b)
。是时候回到我们如何定义矩阵了:它们是一些你可以用向量做的操作的捷径,所以它是你可以实际绘制的东西。您可能想拿起笔或查看其他人建议的材料,以查看不同平面变换的示例。
在这些变换中,我们将寻找仿射变换,那些在任何地方看起来都“相同”(没有弯曲)的变换。例如,围绕某个点的旋转是(x, y)
合格的。现在这个不能表示为lambda v: A(v)
,但可以写成lambda v: A(v) + b
一些矩阵A
和向量的形式b
。
A
和b
使得pi/2
围绕该点的旋转(1, 0)
具有上述形式。它们是独一无二的吗?请注意,对于每个向量都有一个仿射变换,它是向量的移位。
仿射变换可能会拉伸或扩张形状,但它应该在任何地方都以相同的方式进行。现在我希望你相信任何图形的面积在变换下都会变化一个常数。对于由矩阵给出的变换,A
该系数称为 的行列式,A
可以将面积公式应用于两个向量A(x_axis)
和来计算A(y_axis)
:
def det(self: 'matrix') -> 'determinant of a matrix':
return self.m[0]*self.m[3] - self.m[1] * self.m[2]
作为健全性检查,diag(a, b).det()
等于a * b
。
如您所见,旋转矩阵的行列式始终相同:
from random import random
r = R(random())
print (r, 'det =', r.det())
一件有趣的事情det
是它是乘法的(如果你冥想的时间足够长,它就会从定义中得出):
A = Matrix((1, 2, -3, 0))
B = Matrix((4, 1, 1, 2))
print(A.det(), '*', B.det(), 'should be', (A * B).det())
你可以用矩阵做的一件有用的事情是编写一个由两个线性方程组成的系统
A.m[0]*v.x + A.m[1]*v.y = b.x
A.m[2]*v.x + A.m[3]*v.y = b.y
以更简单的方式:A(v) = b
. 让我们在(一些)高中教书时求解系统:将第一个方程乘以A.m[3]
,第二个乘以 -Am 1,然后加上(如果有疑问,请在纸上做)以求解v.x
。
如果你真的尝试过,你应该得到A.det() * v.x = (A.m[3]) * b.x + (-A.m[1]) * b.y
,这表明你总是可以v
通过乘以b
其他矩阵来得到。该矩阵称为的逆A
矩阵:
def inv(self: 'matrix') -> 'inverse matrix':
'''This function returns an inverse matrix when it exists,
or raises ZeroDivisionError when it doesn't.
'''
new_m = ( self.m[3] / self.det(), -self.m[1] / self.det(),
????? )
return Matrix(new_m)
如您所见,当矩阵的行列式为零时,此方法会失败。如果您真的想要,您可以通过以下方式捕获此异常:
try:
print(zero.inv())
except ZeroDivisionError as e: ...
self.det() == 0
。编写划分矩阵的方法并测试它。使用逆矩阵求解方程A(v) = x_axis
(A
如上定义)。逆矩阵的主要性质是A * A.inv()
总是等于one
这就是数学家用 -1 表示A.inv()
的A
原因。我们如何编写一个很好的函数来使用nA ** n
的表示法?请注意,天真的周期是 O(|n|),这肯定太慢了,因为这可以通过复杂度来完成:A
for i in range(n): answer *= self
log |n|
def __pow__(self: 'matrix', n:'integer') -> 'n-th power':
'''This function returns n-th power of the matrix.
It does it more efficiently than a simple cycle. A
while loop goes over all bits of n, multiplying answer
by self ** (2 ** k) whenever it encounters a set bit.
...
练习:在此函数中填写详细信息。测试它
X, Y = A ** 5, A ** -5
print (X, Y, X * Y, sep = '\n')
此函数仅适用于 的整数值n
,即使对于某些矩阵,我们也可以定义分数幂,例如平方根(换句话说,B
这样的矩阵B * B = A
)。
diag(-1, -1)
。这是唯一可能的答案吗?找一个没有平方根的矩阵的例子。在这里,我将在一个部分中向您介绍该主题!由于是复杂的题目,我很可能会失败,所以请提前原谅我。
首先,类似于我们如何拥有矩阵zero
和one
,我们可以通过执行 由任何实数组成一个矩阵diag(number, number)
。这种形式的矩阵可以加、减、乘、反转,结果将模拟数字本身发生的情况。因此,出于所有实际目的,可以说,例如,diag(5, 5)
是5。
但是,Python 还不知道如何处理形式的表达式A + 1
或5 * B
whereA
和B
are 矩阵。如果你有兴趣,你应该去做下面的练习或者看看我的实现(它使用了一个很酷的 Python 特性,叫做decorator);否则,只知道它已被实施。
Matrix
类中的运算符,以便在其中一个操作数是矩阵而另一个操作数是数字的所有标准操作中,数字会自动转换为diag
矩阵。另外,添加相等性比较。这是一个示例测试:
print( 3 * A - B / 2 + 5 )
现在这是第一个有趣的复数:矩阵J
,在开头介绍并等于Matrix((0, 1, -1, 0))
,有一个有趣的属性J * J == -1
(试试看!)。这意味着J
当然不是一个正常的数字,但是,正如我刚才所说,矩阵和数字很容易混合在一起。例如,
(1 + J) * (2 + J) == 2 + 2 * J + 1 * J + J * J = 1 + 3 * J
使用之前列出的规则。如果我们在 Python 中测试它会发生什么?
(1 + J) * (2 + J) == 1 + 3*J
那应该高兴地说True
。另一个例子:
(3 + 4*J) / (1 - 2*J) == -1 + 2*J
正如您可能已经猜到的那样,数学家不会称这些“疯狂数字”,但他们会做类似的事情——他们称形式为a + b*J
复数的表达式。因为这些仍然是我们Matrix
类的实例,所以我们可以用它们做很多操作:加法、减法、乘法、除法、幂——它们都已经实现了!矩阵不是很神奇吗?
我忽略了如何打印操作结果的问题 E = (1 + 2*J) * (1 + 3*J)
,使其看起来像一个表达式J
而不是2x2
矩阵。如果您仔细检查它,您会发现您需要以格式打印该矩阵的左列... + ...J
(还有一件好事:它正是E(x_axis)
!)那些知道两者之间的区别str()
并且repr()
应该看到命名一个函数是很自然的这将产生这种形式的表达,如repr()
。
练习:编写一个Matrix.__repr__
完全可以做到这一点的函数,并用它进行一些测试,例如(1 + J) ** 3
,首先在纸上计算结果,然后用 Python 进行尝试。
数学问题:的行列式是a + b*J
什么?如果您知道复数的绝对值是什么:它们是如何连接的?的绝对值是a
多少?的a*J
?
在这三部曲的最后一部分,我们将看到一切都是矩阵。我们将从一般M x N
矩阵开始,并找出如何将向量视为1 x N
矩阵以及为什么数字与对角矩阵相同。作为旁注,我们将把复数作为2 x 2
矩阵进行探索。
最后,我们将学习使用矩阵编写仿射和投影变换。
所以计划的课程是[MNMatrix, NVector, Affine, Projective]
。
我想如果你能忍受我直到这里,你可能会对这部续集感兴趣,所以我想听听我是否应该继续这个(以及在哪里,因为我很确定我已经超越了什么被认为是单个文档的合理长度)。
麻省理工学院在http://ocw.mit.edu/OcwWeb/Mathematics/上有很多他们的数学课程资料。一旦你掌握了基础知识,他们也会在线提供物理笔记。
这是http://en.wikipedia.org/wiki/Computer_graphics。其中两个关键概念是http://mathworld.wolfram.com/LinearTransformation.html和http://mathworld.wolfram.com/AffineTransformation.html。
这份 MIT 文档是深入了解转换基础知识的必备工具。
最适合初学者的书籍之一是 Carl Meyer 的“矩阵分析和应用线性代数”。
你可以在这里在线查看整本书(虽然它有版权水印): http: //www.matrixanalysis.com/DownloadChapters.html
您可能想看看第 5 章 pg。326 - 332 涵盖 3 维计算机图形中的旋转
您可能想看看I-Hsiung Lin, Yixiong Lin 的Geometric linear algebra (ISBN : 9812560874)。这本书专门针对您想要的东西(2 维和 3 维向量空间的线性变换),并用几何方法全面、渐进地处理它(每个维度 300 页)。恐怕它不是免费购买的,但你应该可以在任何好的大学图书馆找到它。否则,Bookfinder应该可以帮助您以相对适中的价格获得它。
Jim Hefferon 的免费线性代数教科书非常好。与太多的免费电子书不同,Jim 显然花时间制作了一本优秀的阅读器并介绍了线性代数。正式的数学写作并没有过多的负担,因为定理和证明往往过于密集而难以理解。它还包含许多非常出色的线性代数在现实世界应用中的示例——坐标变换只是其中一个示例。您无法超越价格,它还附带练习的可选解决方案。
PS如果坐标变换是你的事,在你完成线性代数之后,你可能会对微分几何感兴趣。
这些是我找到的信息。其中一些可能对您有价值:
理论:
(在 Google 书籍中搜索“矩阵”会为您提供很多讲座,其中一些与转换直接相关 -这是第一个结果,但我鼓励您检查更多。)
我也鼓励(我不知道这是不是正确的词,我只是在学习英语)你,在其中一本书中寻找这种信息(虽然它们不是免费的,但你可以找到大部分谷歌图书上的旧书):
每一个都有关于数学宝石的部分——那里有很多巧妙的技巧。这些书值得每一分钱。
还有 GPU Programming gems,所以你也可以试试。
实践:
如果我能找到更多,我会在这里编辑和添加链接,但老实说 - 我在使用谷歌大约 10 分钟内找到了这些链接。世界上最流行的浏览器存储关于一切的数据——是的,“一切”也意味着矩阵。
队友的欢呼声。
我认为您应该花几天时间在 3D 中使用向量进行点积和叉积。然后学习三角和向量之间的关系。在那之后,矩阵对你来说会更有意义。
Gilbert Strang 的 MIT-OCW 线性代数课程。一个不可思议的人的不可思议的讲座;如果您对矩阵的理解完全来自编程资源(如 MATLAB),那么线性代数课程肯定会为您提供用矩阵做疯狂事情的基础知识。
http://www.ocw.cn/OcwWeb/Mathematics/18-06Spring-2005/VideoLectures/index.htm