使用Octave来学习Machine Learning(二)

1,229 阅读9分钟

小之的公众号 : WeaponZhi

前言

上一篇我们介绍了 Octave 的一些基本情况,大家对 Octave 应该已经有了一个基本的了解,我相信看这篇文章的朋友已经在自己的电脑中安装好 Ocatve 了。矩阵的操作是 Octave 的一大特色。这一节,我将讲述 Octave 对于矩阵的一些操作,希望大家在看文章的过程中可以跟着一起敲一下代码,加深一下印象。

矩阵的生成

Octave 中,我们用一个中括号来表示一个矩阵,用分号来分隔每一行,即使在输入的时候不在同一行就像下面这样:

>> A = [1 2; 3 4; 5 6]
A = 
  1  2
  3  4
  5  6

>> A = [1 2;
> 3 4;
> 5 6]
A = 
  1  2
  3  4
  5  6

>> A = [1,2; 3,4; 5,6]
A = 
  1  2
  3  4
  5  6

注意到,你可以通过逗号来区分每行中的每一个元素,但我们一般不这样用,这样看起来不太清晰。那么如果要表示向量该怎么做呢?我们知道,行向量和列向量分别是一行三列和三行一列的矩阵,那举一反三的你一定知道该怎么定义了吧?

>> A = [1 2 3]
A = 
   1  2  3

>> A = [1; 2; 3]
A = 
   1
   2
   3

可以通过冒号来实现具有递进规则的行向量,两个冒号之间的数代表递进的 step 大小,很容易理解,和 Python 的操作有点像。

>> A = 1:0.1:1.5
A = 
  1.0000 1.1000 1.2000 1.3000 1.4000 1.5000

>> A = 1:5
A = 
  1 2 3 4 5

通过一些函数可以快速生成一些特殊矩阵

>> ones(2,3)
ans = 
   1 1 1
   1 1 1

>> 2*ones(2,3)
ans = 
   2 2 2
   2 2 2

>> zeros(2,3)
ans = 
   0 0 0
   0 0 0

>> eye(3)
ans = 
Diagonal Matrix
   1 0 0
   0 1 0
   0 0 1

>> rand(2,3)
ans = 
   0.480397 0.505024 0.056767
   0.336853 0.774152 0.535887

>> magic(3)
ans = 
   8 1 6
   3 5 7
   4 9 2

ones() 生成全是 1 的矩阵,你可以使用数字和 ones() 生成的矩阵相乘,它和 zeros() 还有 rand() 一样,第一个参数代表行数,第二个参数代表列数。zeros() 生成元素全是 0 的矩阵,而 rand() 可以生成元素是 0-1 之间随机数的矩阵。eye()可以生成单位矩阵,熟悉线性代数的朋友可能会对单位矩阵比较熟悉,它很有用,但线性代数的知识不是本文的重点。形式上可以理解为正斜对角线都是 1 的矩阵。

magic(n) 生成一个 n 阶矩阵,这个矩阵的特点就是不论横线,竖线还是对角线,加起来的值都是一样的,数字还不能重复,必须是从 1 到 n 的平方,很有趣吧。

矩阵的操作

讲述矩阵的操作之前,让我们先来定义一个 3x2 的矩阵 A,方便我们理解代码

>> A = [1 2; 3 4; 5 6]
A = 
  1 2
  3 4
  5 6

我们可以通过 size 和 length 函数来获取矩阵维度的相关信息

>> size(A)
ans = 
    3 2

>> size(A,1)
ans = 3

>> size(A,2)
ans = 2

>> length(A)
ans = 3

size(A) 返回一个行向量,这个行向量代表了 A 的维度,这里输出的 3 2 代表 A 是一个 3 行 2 列的矩阵。size()还可以添加第二个参数,size(A,1) 输出 A 的第一个维度的数量,也就是行数,2 代表的是列数。length(A) 返回的是 A 矩阵中最大维度的大小,所以这里返回的是 A 的行数 3,一般我们都是对向量使用 length() 来直接返回向量的长度。

我们来看看对于矩阵的各种读取操作吧。

>> A(3,2)
ans = 6

>> A(:)
ans =
    1
    2
    3
    4
    5
    6

>> A(1:6)
ans =
    1 3 5 2 4 6

A(3,2) 代表取 A 矩阵第三行第二列的元素,这个比较好理解。A(:) 会将矩阵转化为一个列向量,A(1:6) 将按列顺序输出 A 矩阵第 1 到 第 6 个元素。这些都还是比较简单的,后两个往往在求和的时候用的比较多,我们后面会说到。

我们来看一波天秀操作

>> A(:,2)
ans = 
     2
     4
     6

>> A(2,:)
ans = 
     3 4

>> A([2,3],:)
ans = 
     3 4
     5 6

>> A(:,2) = [1; 3; 5]
A = 
    1 1
    3 3
    5 5

>> A = [A,[1; 2; 3]]
A = 
    1 1 1 
    3 3 2
    5 5 3 

>> B = 5*ones(3)
B = 
    5 5 5
    5 5 5 
    5 5 5

>> [A B] % [A,B] % 号是注释
ans = 
     1 1 1 5 5 5 
     3 3 2 5 5 5
     5 5 3 5 5 5

>> [A; B]
ans = 
     1 1 1
     3 3 2
     5 5 3
     5 5 5 
     5 5 5

来,我们一点点看。A(:,2) 输出 A 的第二列,A(2,:) 输出 A 的第二行,A([2,3],:) 输出第二行和第三行,你可以把冒号换成数字这样就可以输出这几行的第几个元素了。A(:,2) = [1; 3; 5] 将 A 的第二列替换成 [1; 3; 5] 这个列向量。[A B] 和 [A, B] 的含义一样,将 B 并到 A 的右边。[A; B] 则是把 B 并到 A 的下面。

矩阵的运算

这节将讲一些矩阵运算的操作,涉及到一些线代的知识,如果有疑惑,可以自己去重温下,你大概只要知道矩阵相乘和逆矩阵是怎么一回事就行了。先来看一段代码

>> A = [1 2; 3 4; 5 6];
>> B = [1 1 1;2 2 2;];
>> C = [11 12; 13 14; 15 16];

>> A * B
ans =
      5  5  5
      11 11 11
      17 17 17

>> A .* C
ans = 
      11 24
      39 56
      75 96

我们先定义了三个矩阵,还记得吗,末尾加分号将不会打印出来。A * B 代表矩阵 A 和 B 的乘积,这是数学上的乘积方式,所以一个三行两列的矩阵乘以两行三列的矩阵,将得到一个三行三列的矩阵,这里就不具体说乘积运算的规则了。A .* C 会将 A 和 C 的同位置元素相乘,这就代表了 A 和 C 的维度必须要一样。

我们来看下矩阵和数字的操作

>> A + 1
ans =
     2   3
     4   5
     5   6

>> A * 2
ans = 
     2   4
     6   8
     10 12

>> A .^ 2
ans = 
      1  4
      9 16
     25 36

>> A / 2
ans = 
     0.50000 1.00000
     1.50000 2.00000
     2.50000 3.00000

>> 1 ./ A
ans = 
     1.00000 0.50000
     0.33333 0.25000
     0.20000 0.16667

A+1 将每个元素作加法,A * 2 把 A 中每个元素都乘以 2,当然 2 * A 也可以,结果一致,我们在上面曾经使用 5 * ones(3) 来快速生成三阶全是 5 的矩阵。A .^ 2 代表对 A 每个元素进行次方操作。A / 和 ./ 含义是一样的,但 1 / A 将会报错

>> 1 / A
error:operator /:nonconformat arguments

>> 1 / [2]
ans = 0.50000

>> 1 / 2
ans = 0.50000

除数必须得是 1x1 的矩阵或者是个数,总而言之它这是真正的算数除法运算了。

当然还有一些对元素做操作的运算,比如 log(A) 是每个元素求对数,exp(A) 是对每个元素求 e 的指数,abs(A) 是求绝对值,当然还有很多,就不一一列举了。大家可以通过 help 指令直接进行文档查阅。

下面看看转置矩阵和逆矩阵如何表示

>> A'
ans =  
    1 3 5
    2 4 6

>> (A')'
ans =
    1 2
    3 4
    5 6

>> flipud(A)
ans = 
    5 6
    3 4
    1 2

>> B = pinv(A)
B =
    -1.33333 -0.33333  0.66667
     1.08333  0.33333 -0.41667

>> A * B   % B * A
ans = 
    1.00000 0.00000
   -0.00000 1.00000

用单引号 ' 来表示矩阵的转置矩阵。flipud(A) 将矩阵翻转,这个函数一般用在翻转范围矩阵 flipud(eye(n)) ,这样就可以获得一个反对角线单位矩阵了。

pinv(A) 表示 A 的逆矩阵,逆矩阵和原矩阵相乘是单位矩阵,值得注意的是,不是每一个矩阵都有逆矩阵,但 pinv() 始终都能得到结果,实际上 pinv() 获取的是一个伪逆矩阵,但这不重要,你可以把 pinv() 当作对矩阵的求逆,这里就不具体深究了。

到目前为止,矩阵的运算还是比较简单的,相比起来,下面的运算就有点蒂花之秀了。

>> max(A)
ans =
      5 6

>> max(A,[],1)
ans = 
      5 6
    
>> max(A,[],2)
ans =
      2
      4
      6

>> [val, ind] = max(A)
val = 
      5 6
ind = 
      3 3

>> max(A,[3 3; 3 3; 3 3])
ans =
     3 3
     3 4
     5 6

>> A > 3
ans =
      0 0
      0 1
      1 1

>> find([1 2 3] > 1)
ans = 
      2 3

>> [r,c] = find(A > 3)
r = 
    3
    2 
    3

c = 
    1
    2
    2

max(A) 将求每列的最大值,并以行向量形式输出,默认形式等同于 max(A,[],1),如果求每列最大值,则把第三个参数改为2。用 [val, ind] 接收的话,val 的值为最大值,ind 为这个值在该列的索引位置。max(A,B) 将取每个位置中 A 与 B 较大的元素。min 和 max 操作是一样的。

A > 3 输出一个同维度的矩阵,符合条件的为 1 ,不符合条件的为 0。find() 函数中如果是一个向量,则返回符合条件的索引位置,如果是一个矩阵,则用 [r,c] 返回元素的索引,r 代表行号,c 代表列号,比如例子中第一个匹配值 A(3,1) 是 5 ,的确大于 3。

最后看下求和

>> sum(A,1)
ans = 
     9 12

>> sum(A,2)
ans = 
      3
      7
     11

>> sum(sum(A))
ans = 21

sum 和 max 一样,默认情况下是列运算,行向量输出,但参数设置为 2 的时候,则是行求和,列向量输出。所以就像例子中一样,如果我们要求一个矩阵所有元素的和,只需要做两次 sum(sum(A)) 即可。

总结

Octave 矩阵方面的介绍就这么多了,写的很多,权当一个笔记吧,实际上还有很多操作,大家可以使用 help 指令或者观看官方文档来进行学习。


欢迎关注我的公众号