小姐姐的Master在读应用数学,所以之前的各种软件还是要捡回来一波
比如Octave
课程内用的是matlab,然而octave被大大安利过,所以可以学一波
matlab太大就不装了吧
胡说,matlab也是一个很努力的宝贝好吗???
matlab新版本内置应用有点好用欸
而且看别人使用的时候有一种在用jupyter的感觉,就是笔记和代码交互且代码可以实时run出来的那种!
先存着,以后有需要再研究。
PS:今天旁边坐了一个跟我一样喜欢边coding边念叨的小哥哥,可惜不是很好看hhhhh
Basic Operations
你现在已经掌握不少机器学习知识了 在这段视频中 我将教你一种编程语言 Octave语言 你能够用它来非常迅速地 实现这门课中我们已经学过 或者将要学的 机器学习算法
过去我一直尝试用不同的编程语言 来教授机器学习 包括C++、Java、 Python、Numpy 和 Octave 我发现当使用像 Octave这样的 高级语言时 学生能够更快 更好地学习 并掌握这些算法
事实上 在硅谷 我经常看到的情况是 进行大规模的 机器学习项目的人 通常会使用的 程序语言 就是Octave, Octave是一种很好的原始语言(prototyping language) 使用Octave 你能快速地实现你的算法 剩下的事情 你只需要 进行大规模的资源配置 你只用再花时间 用C++或Java这些语言 把算法重新实现就行了 因为我们知道 开发项目的时间 或者说你的时间 是很宝贵的 机器学习的时间也是很宝贵的 所以 如果你能 让你的学习算法在Octave上快速的实现 基本的想法实现以后 再用C++或者Java去改写 这样 你就能节省出 大量的时间
据我所见 人们使用最多的 用于机器学习的原始语言 是Octave、MATLAB Python、NumPy 和 R Octave很好 因为它是开源的 当然 MATLAB也很好 但它不是每个人都 买得起的 但是 如果你能够使用MATLAB 你也可以在这门课里面使用 如果你会Python、NumPy 或者R语言 我也见过有人用 R 的 但是 据我所知 这些人不得不中途放弃了 因为这些语言在开发上比较慢 而且 因为这些语言 Python、NumPy的语法 相较于Octave来说 还是更麻烦一点 正因为这样 也因为我们最开始 用Octave来写程序 所以我强烈建议你 不要用NumPy或者R来完整这门课的作业 我建议你 在这门课中 用Octave来写程序 接下来 本视频将快速地介绍 一系列的命令 目标是迅速地展示 通过这一系列Octave的命令 让你知道Octave能用来做什么 我们的网站会提供 所有我在视频中提到的 内容的文本 所以 当你看完这个视频 想查询一些命令时 你可以查看这些资料 这些都放在网上了
总之 我建议你 先看教学视频 之后 把Octave安装到电脑上 最后 去这门课的网站上 下载这门课的 相关文档和视频 然后 你可以试着 在Octave中键入一些 有趣的命令 让程序运行在你的电脑上 这样你可以看到程序是怎么运行的
让我们开始吧 这里是我的Windows桌面 启动Octave 现在打开Octave 这是Octave命令行 现在让我示范 最基本的Octave代码
输入5 + 6 然后得到11 ;输入3 - 2; 5×8;1/2;2 ^ 6 得到64; 这些都是基本的数学运算
>> 5+6
ans = 11
>> 3-2
ans = 1
>> 5*8
ans = 40
>> 1/2
ans = 0.50000
>> 2^6
ans = 64
你也可以做逻辑运算 例如 1==2 计算结果为 false ( 假 ) 这里的百分号命令表示注释, 1==2 计算结果为假 这里用0表示;
1 ~= 2 这是真的 因此返回1 请注意 不等于符号的写法 是这个波浪线加上等于符号 ( ~= ) 而不是等于感叹号加等号 ( != ) 这是和其他一些 编程语言中不太一样的地方
让我们看看逻辑运算 1 && 0 使用双&符号 表示逻辑与 1 && 0判断为假 1和0的或运算 1 || 0 其计算结果为真
还有异或运算 如XOR ( 1, 0 ) 其返回值为1
>> 1 == 2 %false
ans = 0
>> 1 ~= 2 %true
ans = 1
>> 8>1 && 0 %AND
ans = 0
>> 9>1 || 1 %OR
ans = 1
>> xor(1,0)
ans = 1
从左向右写着 Octave 324.x版本 其计算结果等于11 这是默认的Octave提示 它显示了当前Octave的版本 以及相关的其它信息 如果你不想看到那个提示 这里有一个隐藏的命令 输入命令 PS('>> '); 现在你看到的就是等待命令的快捷提示 这句话在中间有一个字符串 ('>> '); 这是我喜欢的命令行样子 这里敲一个回车 抱歉 写错了 这样才对 要写成PS1这样 现在命令提示已经变得简化了 这样看起来很棒
>> PS1('>>>');
>>>
接下来 我们将谈到Octave的变量 现在写一个变量 对变量A赋值为3 并按下回车键 显示变量A等于3 如果你想分配一个变量 但不希望在屏幕上显示结果 你可以在命令后加一个分号 可以抑制打印输出 敲入回车后 不打印任何东西。 A等于3 只是不显示出来 其中这句命令不打印任何东西 现在举一个字符串的例子 变量b等于"hi" 现在 如果我输入b 则会显示字符串变量b的值"hi" C等于3大于等于1 所以 现在C变量的值是真 如果你想打印出变量 或显示一个变量 你可以像下面这么做 设置A等于圆周率π 如果我要打印该值 那么只需键入A 像这样 就打印出来了
>>>a = 3
a = 3
>>>a = 3; #分号抑制打印
>>>
对于更复杂的屏幕输出 也可以用DISP命令显示 Disp( A )就相当于像这样打印出A 你也可以用该命令来显示字符串 输入disp sprintf 小数 0.2% 逗号 A 像这样 通过这条命令将打印出字符串 打印显示为“两位小数:3.14” 这是一种 旧风格的C语言语法 如果 就学过C语言的同学来说 你可以使用这种基本的语法来将结果打印到屏幕 Sprintf命令生成一个字符串 不仅仅是 字符串“2 decimal:3.14” 其中的“0.2%F”表示 代替A放在这里 并显示A值的小数点后两位数字 同时DISP 命令对字符串做出操作 DISP命令输出 Sprintf产生的字符串 Sprintf命令 和DISP命令显示字符串 再说一个细节 例如 sprintf命令的六个小数 0.6%F ,A 这应该打印π 的6位小数形式 最后 看起来像这样 也有一些控制输出长短格式的快捷命令 默认情况下 是字符串 显示出的小数位有点多 短 ( short ) 格式 是默认的输出格式 只是打印小数数位的第一位 相关这方面的内容还需要你继续练习
>>>a = 3.14;
>>>a
a = 3.1400
>>>disp(a);
3.1400
>>>disp(sprintf('2 decimals: %0.2f', a));
2 decimals: 3.14
>>>a=pi
a = 3.1416
>>>format long
>>>a
a = 3.14159265358979
>>>format short
>>>a
a = 3.1416
下面 让我们来看看向量和矩阵 比方说 建立一个矩阵A 输入1 2 ; 3 4 ; 5 6 这会产生一个 三行两列的矩阵A 其第一行是1 2 第二行是3 4 第三行是5 6 分号的作用 从本质上来说 就是在矩阵内换行到下一行 此外 还有其他的方法来建立矩阵A 输入A矩阵的值 1 2 分号 3 4 分号 5 6 这是另一种方法 对A矩阵进行赋值 考虑到这是一个三行两列的矩阵
>>>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
你同样可以用向量 建立向量V并赋值1 2 3 V是一个行向量 或者说是一个3 ( 列 )×1 ( 行 ) 的向量 一个胖胖的Y向量 或者说 一行三列的矩阵 注意不是三行一列 如果我想 分配一个列向量 我可以写“1;2;3” 现在便有了一个 3 行 1 列 的向量 同时这是一个列向量 下面是一些更为有用的符号 V等于1:0.1:2 这个该如何理解呢 这个集合V是一组值 从数值1开始 增量或说是步长为0.1 直到增加到2 按照这样的方法对向量V操作 可以得到一个行向量 这是一个1行11列的矩阵 其矩阵的元素是1 1.1 1.2 1.3 依此类推 直到数值2
>>>v = [1 2 3]
v =
1 2 3
>>>v = [1; 2; 3]
v =
1
2
3
>>>v = 1:0.1:2
v =
Columns 1 through 4:
1.0000 1.1000 1.2000 1.3000
Columns 5 through 8:
1.4000 1.5000 1.6000 1.7000
Columns 9 through 11:
1.8000 1.9000 2.0000
>>>v = 1:6
v =
1 2 3 4 5 6
现在 我也可以 建立一个集合V并用命令“1:6”进行赋值 这样V就被赋值了 1至6的六个整数 这里还有一些其他的方法来生成矩阵 例如“ones(2, 3)” 也可以用来生成矩阵 其结果为一个两行三列的矩阵 不过矩阵中的所有元素都为1 当我想生成一个 元素都为2 两行三列的矩阵 就可以使用这个命令 你可以把这个方法当成一个 生成矩阵的快速方法 当你想生成一个三维2×2×2的矩阵时 你就可以用这个“ones”命令 比方说 w是一个有三个1的 行向量 或者说一行 由三个同样的1组成的向量 你也可以说 w为一个 一行三列的零矩阵 一行三列的A矩阵里的元素全部是零
>>>ones(2,3)
ans =
1 1 1
1 1 1
>>>w = ones(1,3)
w =
1 1 1
还有很多的方式来生成矩阵 如果我对W进行赋值 用Rand命令建立一个一行三列的矩阵 因为使用了Rand命令 则其一行三列的元素均为随机值 如果我使用 “rand(3, 3)”命令 这就生成了一个 3×3的矩阵 并且其所有元素均为随机 数值介于0和1之间 所以 正是因为这一点 我们可以得到 数值均匀介于0和1之间的元素
>>>w = rand(3,3)
w =
0.91025 0.82671 0.14067
0.90400 0.34350 0.51289
0.25501 0.24975 0.80750
如果 你知道什么是高斯随机变量 或者 你知道什么是正态分布的随机变量 你可以设置集合W 使其等于一个一行三列的N矩阵 并且 来自三个值 一个平均值为0的高斯分布 方差 或者等于1的标准偏差 还可以设置地更复杂
벵>>>w = randn(1,3)
w =
-0.052546 -1.786869 0.754202
例如 W减去6 再加上10的平方 两者相乘 Rand命令生成一个1行10000列的矩阵 把分号放到末尾 这样结果就打印不出来 那这样会得到什么呢 这样就可以 得到 一个有10000元素的向量 想知道具体是多少 我们也可把它打印出来 这将产生一个这样的矩阵 生成了这个叫做 data 的对象 是吧? 这就是一个 有着10000个元素的矩阵W 如果我现在 用绘制直方图命令 绘制出一个直方图 使用Octave的 打印直方图命令 你只需要数秒钟就可以将它绘制出来 这是一个对随机变量W 绘制出的直方图 这里是-6+0 乘上十倍的高斯随机变量 这样 可以绘制出一个 有着更多条的 乃至50个条的直方图来 这样 就有一个 均值减去6的高斯直方图 因为这里是 -6加10的平方根并与这项相乘 因此 这个高斯随机变量的方差 是10 且其标准偏差为10的平方根 3.1
w = -6 + sqrt(10)*(randn(1,10000));
hist(w)
hist(w, 50)
最后 说一个生成矩阵的 特殊命令I 其实 I也可说是一个双关语字标识 设置一个4阶单位矩阵 这是一个4×4矩阵 所以I为“eye(4)” 通过上面的命令得到4×4矩阵 I可以等于5阶单位阵 6阶单位阵 那么就有 6阶单位阵 eye( 3 )是一个3阶方阵
>> eye(4)
ans =
Diagonal Matrix
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
在本节视频的最后 还有一个比较有用的命令 那就是帮助命令 例如 你可以键入help i 它就会将矩阵的相关信息显示出来 命令Q可以退出Octave 你也可以键入help rand 将会显示出有关rand函数的相关帮助文档 以及相关的随机数生成函数 甚至可以使用命令help help 将会显示出help命令的使用方法
>> help
For help with individual commands and functions type
help NAME
(replace NAME with the name of the command or function you would
like to learn more about).
For a more detailed introduction to GNU Octave, please consult the
manual. To read the manual from the prompt type
doc
GNU Octave is supported and developed by its user community.
For more information visit http://www.octave.org.
以上讲解的内容 都是Octave的基本操作 希望你能通过上面的讲解 自己练习一些矩阵、乘、加等操作 将这些操作在Octave中熟练 在接下来的视频中 将会涉及 更多复杂的命令 并使用它们在Octave中对数据进行更多的操作
Moving Data Around移动数据
在第二段关于 Octave的 辅导课视频中 我将开始介绍 如何在 Octave 中移动数据 具体来说 如果你有一个机器学习问题 你怎样把数据加载到 Octave 中? 怎样把数据存入一个矩阵? 如何对矩阵进行相乘? 如何保存计算结果? 如何移动这些数据 并用数据进行操作?
和之前一样 这是我的 Octave 窗口 我们继续沿用上次的窗口 我键入 A 得到我们之前构建的矩阵 A 也就是用这个命令生成的 A = 这是一个三行二列的矩阵
Octave 中的 size() 命令 返回矩阵的尺寸 所以 size(A) 命令返回3 2 实际上 size() 命令返回的 是一个 1×2 的矩阵 我们可以用 sz 来存放 设置 sz = size(A) 因此 sz 就是一个1×2的矩阵 第一个元素是3 第二个元素是2 所以如果键入 size(sz) 看看 sz 的尺寸 返回的是1 2 表示是一个1×2的矩阵 1 和 2 分别表示 矩阵 A 的维度 (此处口误 应为 sz 的维度 译者注)
>> A = [1 2; 3 4; 5 6]
A =
1 2
3 4
5 6
>> size(A)
ans =
3 2
>> sz = size(A)
sz =
3 2
>> size(sz)
ans =
1 2
>> size(A,1)
ans = 3
>> size(A,2)
ans = 2
你也可以键入 size(A, 1) 这个命令会返回 A 矩阵的第一个元素 A 矩阵的第一个维度的尺寸 也就是 A 矩阵的行数 同样 命令 size(A, 2) 将返回2 也就是 A 矩阵的列数 也就是 A 矩阵的列数 如果你有一个向量 v 假如 v = 假如 v = 然后键入 length(v) 这个命令将返回 最大维度的大小 你也可以键入 length(A) 由于矩阵 A 是一个3×2的矩阵 因此最大的维度 应该是3 因此该命令会返回3 但通常我们还是对向量使用 length 命令 比如 length() 比如 length() 而不是对矩阵使用 length 命令 因为毕竟有点容易让人弄混
>> V = [1 2 3 4]
V =
1 2 3 4
>> length(V)
ans = 4
>> length(A)
ans = 3
下面让我们来看看 如何在系统中 加载数据和寻找数据 当我们打开 Octave 时 我们通常已经在一个 默认路径中 这个路径是 Octave 的安装位置 pwd 命令可以显示出 Octave 当前所处路径 Octave 当前所处路径 所以现在我们就在这个目录下 cd 命令 意思是改变路径 我可以把路径改为C:\Users\ang\Desktop 这样当前目录就变为了桌面 如果键入 ls ls 来自于一个 Unix 或者 Linux 命令 ls 命令将列出 我桌面上的所有路径 因此这些就是 我桌面上的所有文件了
>> pwd
ans = C:\Users\xin
>> cd 'E:\TEMPsrc\octave'
>> pwd
ans = E:\TEMPsrc\octave
>> ls
事实上 我的桌面上 有两个文件 featuresX.dat 和 priceY.dat 是两个我想解决的机器学习问题 这是我的桌面 这是 featuresX 文件 featuresX 文件如这个窗口所示 是一个含有两列数据的文件 这其实就是我的房屋价格数据 我想应该是 数据集中有47行 第一个房子样本 面积是2104平方英尺 有3个卧室 第二套房子面积为1600 有3个卧室 等等 priceY 是这个文件 也就是 训练集中的价格数据 所以 featuresX 和 priceY 就是两个存放数据的文档 那么应该怎样把数据读入 Octave 呢? 好的 我们只需要键 键入 featuresX.dat 这样 我将加载了 featuresX 文件 同样地我可以加载 priceY.dat 其实有好多种办法可以完成 如果你把命令写成 字符串的形式 load('featureX.dat') 也是可以的 这里打错了 这跟刚才的命令效果是相同的 只不过是把文件名 写成了一个字符串的形式 现在文件名被存在一个 字符串中 Octave 中使用引号 来表示字符串 就像这样 这就是一个字符串 因此我们读取的文件 文件名由这个字符串给出
load featuresX.dat
load priceY.dat
load('featuresX.dat')
另外 who 命令 能显示出 在我的 Octave 工作空间中的所有变量 因此 who 命令显示出 当前 Octave 储存的变量 包括 featureX 和 priceY 同样还包括 在此之前你创建的 那些变量
>> who
Variables in the current scope:
a ans b c
所以我可以键入 featuresX 回车 来显示 featuresX 这些就是存在里面的数据 还可以键入 size(featuresX) 得出的结果是 47 2 代表这是一个47×2的矩阵 类似地 输入 size(priceY) 结果是 47 1 表示这是一个47维的向量 是一个列矩阵 存放的是训练集中的所有价格 Y 的值 who 函数能让你看到 当前工作空间中的所有变量
同样还有另一个 whos 命令 能更详细地进行查看 因此 在 who 后面加一个 s 同样也列出我所有的变量 不仅如此 还列出了变量的维度 我们看到 A 是一个 3×2的矩阵 X 是一个47×2的矩阵 priceY 是一个47×1的矩阵 也就是一个向量 同时还显示出 需要占用多少内存空间 以及数据类型是什么 double 意思是双精度浮点型 这也就是说 这些数都是实数 是浮点数
>> whos
Variables in the current scope:
Attr Name Size Bytes Class
==== ==== ==== ===== =====
a 1x1 8 doubl
e
ans 1x17 17 char
b 1x1 8 doubl
e
c 1x1 8 doubl
e
d 3x2 48 doubl
e
Total is 26 elements using 89 bytes
如果你想删除某个变量 你可以使用 clear 命令 因此 我们键入 clear featuresX 然后再输入 whos 命令 你会发现 featuresX 消失了
>> who
Variables in the current scope:
a ans b c d
>> clear a
>> who
Variables in the current scope:
ans b c d
另外 我们怎么储存数据呢? 我们来看 我们设变量 v 为 priceY(1:10) 这表示的是将向量 Y 的 前10个元素存入 v 中 我们输入 who 或者 whos Y 是一个47×1的向量 因此现在 v 就是10×1的向量 因为刚才设置了 v = priceY(1:10) 这便将 v 的值 设为了 Y 的前十个元素 假如我们想把它存入硬盘 那么用 save hello.mat v 命令 这个命令 会将变量 v 存成一个叫 hello.mat 的文件 让我们回车 现在我的桌面上 就出现了一个新文件 名为 hello.mat 由于我的电脑里 也同时安装了 MATLAB 所以这个图标 上面有 MATLAB 的标识 因为操作系统把文件识别为 MATLAB 文件 所以如果在你的电脑上 图标显示的不一样的话 也没有关系
>> save hello.mat d
现在我们清除所有变量 直接键入 clear 这样将删除工作空间中的所有变量 所以现在工作空间中啥都没了
>> clear
>> who
但如果我载入 hello.mat 文件 我又重新读取了变量 v 因为我之前 把变量 v存入了 hello.mat 文件中 所以我们刚才用 save 命令做了什么 这个命令把数据 按照二进制形式储存 或者说是更压缩的二进制形式 因此 如果 v 是很大的数据 那么压缩幅度也更大 占用空间也更小 如果你想把数据 存成一个人能看懂的形式 那么可以键入 save hello.txt v -ascii 这样就会把数据 存成一个文本文档 或者将数据的 ascii 码存成文本文档 现在 我键入了这个命令以后 我的桌面上 就有了 hello.txt 文件 就有了 hello.txt 文件 如果打开它 我们可以发现 这个文本文档存放着我们的数据 这就是读取和储存数据的方法
>> v = [1 2; 3 4; 5 6; 7 8; 9 0]
v =
1 2
3 4
5 6
7 8
9 0
< -ascii %save as text(ASCII)
接下来我们再来讲讲操作数据的方法 假如 A 还是那个矩阵 跟刚才一样还是那个 3×2 的矩阵 现在我们加上索引值 比如键入 A(3,2) 这将索引到 A 矩阵的 (3,2) 元素 A 矩阵的 (3,2) 元素 这就是我们通常 书写矩阵的形式 写成 A 下标32 下标32
3和2分别表示 矩阵的第三行 和第二列对应的元素 因此也就对应 6 我也可以键入 A(2,:) 来返回 第二列的所有元素 因此 冒号表示 该行或该列的所有元素 因此 A(2,:) 表示 A 矩阵的第二行的所有元素 类似地 如果我键入 A(:,2) 这将返回 A 矩阵第二列的所有元素 这将得到 2 4 6 这表示返回 A 矩阵的第二列的所有元素 因此这就是 矩阵 A 的第二列 就是 2 4 6 你也可以在运算中 使用这些较为复杂的索引
>> A = [1 2; 3 4; 5 6]
A =
1 2
3 4
5 6
>> A(3,2)
ans = 6
>> A(2,:)
ans =
3 4
>> A(:,2)
ans =
2
4
6
我再给你展示几个例子 可能你也不会经常使用 但我还是输入给你看 A(,:) 这个命令意思是 取 A 矩阵第一个索引值为1或3的元素 也就是说我取的是 A 矩阵的第一行和 第三行的每一列 第三行的每一列 这是 A 矩阵 因此 输入 A(, :) 返回第一行 返回第三行 冒号表示的是 取这两行的每一列元素 也就是第一行 和第二行的所有元素(此处口误 应为第三行 译者注) 因此返回结果为 1 2 5 6
>> A([1 3], :)
ans =
1 2
5 6
可能这些比较复杂一点的 索引操作 你不会经常用到 我们还能做什么呢 这依然是 A 矩阵 A(:,2) 命令返回第二列 你也可以为它赋值 所以我可以取 A 矩阵的第二列 然后将它赋值为 10 11 12 如果我这样做的话 我实际上是取出了 A 的第二列 然后把一个列向量赋给了它 因此现在 A 矩阵的第一列还是 1 3 5 第二列就被替换为 10 11 12 接下来一个操作 让我们把 A 设为 A = ] 这样做的结果是 在原矩阵的右边 附加了一个新的列矩阵 附加了一个新的列矩阵 现在 见证奇迹的时刻... 噢 我又犯错了 应该放分号的 现在 A 矩阵就是这样了 明白吗? 我希望你听懂了 所以 这是个列矩阵 而我们所做的 就是把 A 矩阵设置为 原来的 A 矩阵 再在右边附上一个 新添加的列矩阵 我们的原矩阵 A 就是右边这个6个元素 就是右边这个6个元素 所以我们就是把 A 矩阵 右边加上了一个 新的列向量 所以现在 A 矩阵 变成这样一个 3×3 的矩阵
>> A(:,2) = [10;11;12]
A =
1 10
3 11
5 12
>> A = [A, [100;101;102]]
A =
1 10 100
3 11 101
5 12 102
最后 还有一个小技巧 我也经常使用 如果你就输入 A(:) 这是一个很特别的语法结构 意思是把 A 中的所有元素 放入一个单独的列向量 这样我们就得到了一个 9×1 的向量 这些元素都是 A 中的元素排列起来的
>> A(:)
ans =
1
3
5
10
11
12
100
101
102
再来几个例子好了 我还是把 A 重新设为 假如说 我再设一个 B 为 我可以新建一个矩阵 C C = 这个意思就是 这是我的矩阵 A 这是我的矩阵 B 我设 C = 这样做的结果就是 把这两个矩阵直接连在一起 矩阵 A 在左边 矩阵 B 在右边 这样组成了 C 矩阵 就是直接把 A 和 B 合起来 我还可以设 C = 这里的分号表示 把分号后面的东西放到下面 所以 的作用 依然还是把两个矩阵 放在一起 只不过现在是上下排列 所以现在 A 在上面 B 在下面 C 就是一个 6×2 矩阵 简单地说 分号的意思就是换到下一行 所以 C 就包括上面的 A 然后换行到下面 然后在下面放上一个 B 另外顺便说一下 这个命令 跟 是一样的 这两种写法的结果是相同的
>> A = [1 2; 3 4; 5 6];
>> B = [11 12; 13 14; 15 16];
>> C = [A B]
C =
1 2 11 12
3 4 13 14
5 6 15 16
>> C = [A; B]
C =
1 2
3 4
5 6
11 12
13 14
15 16
好了 通过以上这些操作 希望你现在掌握了 怎样构建矩阵 也希望我展示的这些命令 能让你很快地学会 怎样把矩阵放到一起 怎样取出矩阵 并且把它们放到一起 组成更大的矩阵 通过几句简单的代码 Octave 能够很方便地 很快速地帮助我们 组合复杂的矩阵以及对数据进行移动 这就是移动数据这一节课
在下一段视频中 我们将一起来谈谈 怎样利用数据进行更为复杂的计算 希望这节课的内容 能让你明白 在 Octave 中 怎样用几句简单的命令 很快地对数据进行移动 包括加载和储存一个向量 或矩阵 加载和存储数据 把矩阵放在一起 构建更大的矩阵 用索引对矩阵某个特定元素进行操作等等 我知道可能我一下子 讲了很多命令 所以我认为对你来讲
最好的学习方法是 下课后复习一下我键入的这些代码 好好地看一看 从课程的网上 把代码的副本下载下来 重新好好看看这些副本 然后自己在 Octave 中 把这些命令重新输一遍 慢慢开始学会使用这些命令 当然 没有必要把这些命令都记住 你也不可能记得住 你要做的就是 从这段视频里 了解一下你可以用哪些命令 做哪些事 这样在你今后需要 编写学习算法时 如果你要找到某个 Octave 中的命令 你可能回想起 你之前在这里学到过 然后你就可以查找 课程中提供的程序副本 这样就能很轻松地找到 你想使用的命令了 好了 这就是 移动数据这节课的全部内容
在下一段视频中 我将开始向你介绍 怎样进行一些 更复杂的计算 怎样对数据进行计算 怎样对数据进行计算 同时开始实现学习算法
Computing on Data
现在 你已经学会了在Octave中 如何加载或存储数据 如何把数据存入矩阵 等等 在这段视频中 我将向你介绍 如何对数据进行运算 稍后我们将使用这些 运算操作来实现我们的学习算法
现在我们开始吧 这是我的 Octave 窗口 我现在快速地 初始化一些变量 比如设置A 为一个3×2的矩阵 设置B为 一个3 × 2矩阵 设置C为 2 × 2矩阵 现在 我想算两个矩阵的乘积 比如说 A × C 我只需键入A×C 这是一个 3×2 矩阵乘以 2×2 矩阵 得到这样一个3×2矩阵 你也可以对每一个元素 做运算 方法是做点乘运算A .*B 这么做 Octave将矩阵 A 中的每一个元素 与矩阵 B 中的 对应元素相乘 这是A 这是B 这是A .* B 比如说 这里第一个元素 1乘以11得到11 第二个元素是 2乘以12得到24 这就是两个矩阵的 元素位运算 通常来说 在Octave中 点号一般 用来表示元素位运算 这里是一个矩阵A 这里我输入A .^ 2 这将对矩阵A中 每一个元素平方 所以 1的平方是1 2的平方是4 等等
>> A = [1 2; 3 4; 5 6];
>> B = [11 12; 13 14; 15 16];
>> C = [1 1; 2 2];
>> A*C
ans =
5 5
11 11
17 17
>> A .* B
ans =
11 24
39 56
75 96
>> A .^ 2
ans =
1 4
9 16
25 36
我们设V是一个向量 设V为 是列向量 你也可以输入 1 ./ V 得到每一个元素的倒数 所以这样一来 就会分别算出 1/1 1/2 1/3 矩阵也可以这样操作 1 ./ A 得到 A中每一个元素的倒数
>> V = [1; 2; 3];
>> 1 ./ V
ans =
1.00000
0.50000
0.33333
>> 1 ./ A
ans =
1.00000 0.50000
0.33333 0.25000
0.20000 0.16667
同样地 这里的点号 还是表示对每一个元素进行操作 我们还可以进行求对数运算 也就是对每个元素 进行求对数运算 还有自然数e的幂次运算 就是以e为底 以这些元素为幂的运算 所以这是e 这是e的平方 这是e的立方 v 矩阵是这样的 我还可以用 abs 来对 v 的每一个元素 求绝对值 当然这里 v 都是正数 我们换成另一个 这样对每个元素求绝对值 得到的结果就是 这些非负的元素 还有 -v 给出V中每个元素的相反数 这等价于 -1 乘以 v 不过一般就直接用 -v 就好了 其实就等于 -1*v 还有什么呢?
>> log(V)
ans =
0.00000
0.69315
1.09861
>> exp(V)
ans =
2.7183
7.3891
20.0855
>> abs(V)
ans =
1
2
3
还有一个技巧 比如说 我们想对v中的每个元素都加1 那么我们可以这么做 首先构造一个 3行1列的1向量 然后把这个1向量跟原来的向量相加 因此 v 向量从 增至 我用了一个 length(v) 命令 因此这样一来 ones(length(v) ,1) 就相当于 ones(3,1) 所以这是ones(3,1) 对吧 然后我做的是 v + ones(3,1) 也就是将 v 的各元素 都加上这些1 这样就将 v 的每个元素 增加了1 另一种更简单的方法是 直接用 v+1 所以这是 v v + 1 也就等于 把 v 中的每一个元素 都加上1
>> v = [1;2;3]
v =
1
2
3
>> v + ones(length(v), 1)
ans =
2
3
4
>> v + ones(3,1)
ans =
2
3
4
>> v + 1
ans =
2
3
4
现在 让我们来谈谈更多的操作 这是我的矩阵A 如果你想要求它的转置 那么方法是用A‘ 这是单引号符号 并且是左引号 可能你的键盘上 有一个左引号 和一个右引号 这里用的是左引号 也就是标准的引号 因此 A’ 将得出 A 的转置矩阵 当然 如果我写 (A‘)’ 也就是 A 转置两次 那么我又重新得到矩阵 A
>> A
A =
1 2
3 4
5 6
>> A'
ans =
1 3 5
2 4 6
还有一些有用的函数 假如说 小写a 是 这是一个1行4列矩阵 假如说 val=max(a) 这将返回 A矩阵中的最大值 在这里是15 我还可以写 = max(a) 这将返回 a矩阵中的最大值 存入val 以及该值对应的索引 因此元素15对应的索引值为2 存入ind 所以 ind 等于2
>> a = [1 15 2 0.5]
a =
1.00000 15.00000 2.00000 0.50000
>> val = max(a)
val = 15
>> [val, ind] = max(a)
val = 15
ind = 2
特别注意一下 如果你用命令 max(A) A是一个矩阵的话 这样做就是对每一列 求最大值 等下再仔细讲讲
我们还是用这个例子 这个 小a 矩阵 如果输入 a<3 这将进行逐元素的运算 所以 第一个元素 是小于3的 因此返回1 a的第二个元素 不小于3 所以 这个值是0 表示"非" 第三个和第四个数字 仍然是小于3 2和0.5都小于3 因此 这返回 也就是说 对a矩阵的每一个元素 与3进行比较 然后根据每一个元素与3的大小关系 返回1和0表示真与假
现在 如果我写 find(a<3) 这将告诉我 a 中的哪些元素 是小于3的 是小于3的 在这里就是第一 第三和第四个元素 是小于3的
a =
1.00000 15.00000 2.00000 0.50000
>> a < 3
ans =
1 0 1 1
>> find(a < 3)
ans =
1 3 4
下一个例子 设A = magic(3) magic 函数返回什么呢 让我们查看 magic 函数的帮助文件
magic 函数将返回 一个矩阵 称为魔方阵或幻方 (magic squares) 它们具有以下 这样的数学性质 它们所有的行和列和对角线 加起来都等于相同的值 当然据我所知 这在机器学习里 基本用不上 但我可以用这个方法 很方便地生成一个 3行3列的矩阵 而这个魔方矩阵这神奇的方形屏幕。 每一行 每一列 每一个对角线 三个数字加起来 都是等于同一个数 我只有在演示功能 或者上课教 Octave 的时候 会用到这个矩阵 在其他有用的机器学习应用中 这个矩阵其实没多大作用 让我来看看别的 如果我输入 = find( A>=7 ) 这将找出所有A矩阵中 大于等于7的元素 因此 r 和 c 分别表示行和列 这就表示 第一行第一列的元素大于等于7 第三行第二列的元素大于等于7 第二行第三列的元素大于等于7 我们来看看 第二行第三列的元素 就是 A(2,3) 是等于7的 就是这个元素 确实是大于等于7的 顺便说一句 其实我从来都 不去刻意记住这个 find 函数 到底是怎么用的 我只需要会用 help 函数就可以了 每当我在使用这个函数 忘记怎么用的时候 我就可以用 help 函数 键入 help find 来找到帮助文档
>> A = magic(3)
A =
8 1 6
3 5 7
4 9 2
>> [r, c] = find(A >= 7)
r =
1
3
2
c =
1
2
3
好吧 最后再讲两个内容 一个是求和函数 这是 a 矩阵 键入 sum(a) 就把 a 中所有元素加起来了 如果我想把它们都乘起来 键入 prod(a) prod 意思是 product(乘积) 它将返回 这四个元素的乘积 floor(a) 是向下四舍五入 因此对于 a 中的元素 0.5将被下舍入变成0 还有 ceil(A) 表示向上四舍五入 所以0.5将上舍入变为 最接近的整数 也就是1
>> a
a =
1.00000 15.00000 2.00000 0.50000
>> sum(a)
ans = 18.500
>> prod(a)
ans = 15
>> floor(a)
ans =
1 15 2 0
>> ceil(a)
ans =
1 15 2 1
还有 我们来看 键入 type(3) 这通常得到一个3×3的矩阵 如果键入 max(rand(3), rand(3)) 这样做的结果是 返回两个3×3的随机矩阵 并且逐元素比较 取最大值 所以 你会发现所有这些 数字几乎都比较大 因为这里的每个元素 都实际上是 两个随机生成的矩阵 逐元素进行比较 取最大的那个值 这是刚才生成的 3×3魔方阵 A 假如我输入 max(A,[],1) 这样做会得到 每一列的最大值 所以第一例的最大值 就是8 第二列是9 第三列的最大值是7 这里的1表示 取A矩阵第一个维度的最大值 相对地 如果我键入 max(A,[],2) 这将得到每一行的最大值 所以 第一行的最大值 是等于8 第二行最大值是7 第三行是9 所以你可以用这个方法 来求得每一行或每一列的最值
>> max(rand(3), rand(3))
ans =
0.957477 0.083887 0.459507
0.799441 0.975439 0.927632
0.888604 0.942436 0.612661
>> A
A =
8 1 6
3 5 7
4 9 2
>> max(A, [], 1)
ans =
8 9 7
另外 你要知道 默认情况下 max(A)返回的是 每一列的最大值 如果你想要 找出整个矩阵A的最大值 你可以输入 max(max(A)) 像这样 或者你可以将 A 矩阵转成 一个向量 然后键入 max(A(:)) 这样做就是把 A 当做一个向量 并返回 A 向量中的最大值
>> max(max(A))
ans = 9
>> max(A(:))
ans = 9
最后 让我们把 A 设为一个 9行9列的魔方阵 别忘了 魔方阵具有的特性是 每行每列和对角线的求和都是相等的 这是一个9×9的魔方阵 我们来求一个 sum(A,1) 这样就得到每一列的总和 所以这样做就是 把 A 的每一列进行求和 从这里我们也可以看出 这也验证了 一个9×9的魔方阵 确实每一列加起来都相等 都为369 现在我们来求每一行的和 键入sum(A,2) 这样就得到了 A 中每一行的和 A 中每一行的和 加起来还是369
>> A = magic(5)
A =
17 24 1 8 15
23 5 7 14 16
4 6 13 20 22
10 12 19 21 3
11 18 25 2 9
>> sum(A,1)
ans =
65 65 65 65 65
>> sum(A,2)
ans =
65
65
65
65
65
现在我们来算 A 的对角线元素的和 看看它们的和 是不是也相等 我们现在构造一个 9×9 的单位矩阵 键入 eye(9) 设为I9 然后我们要用 A 逐点乘以这个单位矩阵 这是矩阵A 我现在用 A 逐点乘以 eye(9) 这样做的结果是 两个矩阵对应元素 将进行相乘 除了对角线元素外 其他元素都会得到0 然后我对刚才求到的结果 键入sum(sum(A.*eye(9)) 这实际上是求得了 这个矩阵对角线元素的和 确实是369
>> sum(sum(A.*eye(5)))
ans = 65
你也可以求另一条对角线的和 这个是从左上角到右下角的 你也可以求另一条对角线 从左下角到右上角 这个和 这个命令会有点麻烦 其实你不需要知道这个 我只是想给你看 如果你感兴趣的话可以听听 让我们来看看 flipup/flipud 表示向上/向下翻转 如果你用这个命令的话 计算的就是副对角线上 所有元素的和 还是会得到369 我来给你演示一下 eye(9) 矩阵是这样 那么 flipup(eye(9)) 将得到一个单位矩阵 并且将它翻转 不好意思打错了 应该是flipud 翻转以后所有的1就变成副对角线了
>> eye(9)
ans =
Diagonal Matrix
1 0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0
0 0 0 1 0 0 0 0 0
0 0 0 0 1 0 0 0 0
0 0 0 0 0 1 0 0 0
0 0 0 0 0 0 1 0 0
0 0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 0 1
>> flipud(eye(9))
ans =
Permutation Matrix
0 0 0 0 0 0 0 0 1
0 0 0 0 0 0 0 1 0
0 0 0 0 0 0 1 0 0
0 0 0 0 0 1 0 0 0
0 0 0 0 1 0 0 0 0
0 0 0 1 0 0 0 0 0
0 0 1 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0
最后再说一个命令 然后就下课 假如 A 是一个 3×3的魔方阵 同样地 如果你想 这个矩阵的逆矩阵 键入 pinv(A) 通常称为伪逆矩阵 但这个名字不重要 你就把它看成是 矩阵 A 求逆 因此这就是 A 矩阵的逆矩阵 设 temp = pinv(A) 然后再用temp 乘以 A 这实际上得到的就是 单位矩阵 对角线为1 其他元素为0 稍微圆整一下就是 好了 这样我们就介绍了 如何对矩阵中的数字 进行各种操作
>> A = magic(3)
A =
8 1 6
3 5 7
4 9 2
>> pinv(A)
ans =
0.147222 -0.144444 0.063889
-0.061111 0.022222 0.105556
-0.019444 0.188889 -0.102778
>> temp = pinv(A)
temp =
0.147222 -0.144444 0.063889
-0.061111 0.022222 0.105556
-0.019444 0.188889 -0.102778
>> temp * A
ans =
1.00000 0.00000 -0.00000
-0.00000 1.00000 0.00000
0.00000 0.00000 1.00000
在运行完某个 学习算法之后 通常一件最有用的事情 是看看你的结果 或者说让你的结果可视化 在接下来的视频中 我会非常迅速地告诉你 如何很快地画出 如何只用一两行代码 你就可以快速地可视化你的数据 画出你的数据 这样你就能更好地理解 你使用的学习算法
Plotting Data
当开发学习算法时 往往几个简单的图 可以让你更好地 理解算法的内容 并且可以完整地检查下 算法是否正常运行 是否达到了算法的目的 例如在之前的 视频中 我谈到了 绘制成本函数J(θ) 可以帮助 确认梯度下降算法是否收敛 通常情况下 绘制数据 或学习算法所有输出 也会启发你 如何改进你的学习算法 幸运的是 Octave有非常 简单的工具用来生成大量 不同的图 当我用学习算法时 我发现 绘制数据 绘制学习算法等 往往是 我获得想法 来改进算法的重要部分 在这段视频中 我想告诉你一些 Octave的工具来绘制和可视化你的数据
这是我的 Octave 窗口 我们先来快速生成一些数据 用来绘图 我先设置t 等于这个数列 这是t 是从0到0.98的集合 让我们设置y1等于sin 2*pi*4*t (此处pi表示π) 如果我想绘制正弦函数 这是很容易的 我只需要输入plot(t, y1) 并回车 就出现了这个图 横轴是 t变量 纵轴是y1 也就是我们 刚刚所输出的正弦函数
>> t=[0:0.01:0.98];
>> y1 = sin(2*pi*4*t);
>> plot(t,y1);
让我们设置y2 等于cos 2*pi*4*t 而如果我输入plot t逗号y2 Octave将会 消除之前的正弦图 并且用这个余弦图来代替它 这里纵轴cos(x)从1开始
>> t=[0:0.01:0.98];
>> y2 = cos(2*pi*4*t);
>> plot(t,y2);
如果我 要同时表示 正弦和余弦曲线 我要做的就是 输入plot(t, y1) 这是我的正弦函数 我使用函数hold on hold on函数 的功能是将 新的图像绘制在 旧的之上 我现在绘制t y2 我要以不同的颜色绘制余弦函数 所以我在这里输入 带引号的r 我将绘制余弦函数 在这之上 而不是替换了现有的图 r表示所使用的颜色 再加上命令xlabel('time') 来标记X轴即水平轴 输入ylabel('value') 来标记垂直轴的值 同时我也可以
来标记我的两条函数曲线 用这个命令 legend('sin', 'cos') 将这个 图例放在右上方 表示这两条曲线表示的内容 最后输入title('myplot') 在图像的顶部显示这幅图的标题 如果你想保存 这幅图像,你输入print -dpng 'myplot.png' png是一个图像 文件格式 如果你 这样做了 它可以让你保存为一个文件 如果我这样做 让我先改一下路径 像这样 然后我将它打出来 这需要一点时间 而这取决于你的 Octave的配置设置 可能需要几秒钟 但改变 路径到我的桌面 现在Octave需要几秒钟的时间来保存它 如果我现在去到我的桌面 先最小化这些窗口 这就是 Octave所保存的myplot.png 这就是保存为PNG的文件
>> plot(t, y1);
>> hold on;
>> plot(t, y2, 'r');
>> xlabel('time')
>> ylabel('value')
>> legend('sin', 'cos')
>> title('my plot')
>> print -dpng 'myplot.png'
Octave也可以保存为很多其他的格式 你可以键入help plot 如果你想试试 其他格式的文件 而不是 PNG 你可以把图片 保存为其他格式 最后如果你想 删掉这个图像 命令close会让这个图像关掉 如果我键入 close 这个图像 就从我的桌面消失了
>> close
Octave也可以让你为图像标号 你键入figure(1); plot(t, y1); 将显示 第一张图 绘制了变量t y1 如果你想绘制第二个图 你可以指定一个不同的数字编号 键入figure(2); plot(t, y2); 正如这样 现在我的桌面上 其实有2个图 图1和图2 此时一个绘制正弦 函数 另一个绘制了余弦函数
>> figure(1); plot(t, y1);
>> figure(2); plot(t, y2);
这是另一个我经常使用的命令 subplot命令 我们要使用subplot(1,2,1) 它将图像 分为一个 1*2的格子 也就是前两个参数 然后它使用 第一个格子 也就是最后一个参数1的意思 所以,将我的图像分成 1*2的格子 我现在使用 第一个格子 如果我键入这个 那么这个图像显示在左边 如果键入plot(t, y1) 现在这个图 显示在第一个格子 如果我键入subplot(1,2,2) 那么我就要 使用第二个格子 键入plot(t, y2); 现在y2显示在右边 也就是第二个格子 最后一个命令 你可以 改变轴的刻度 比如改成 也就是设置了 右边图的x轴 和y轴的范围 具体而言 它将 右图中的横轴 的范围调整至0.5到1 竖轴的范围为 -1到1 而且 你不需要记住所有这些命令 如果你需要 改变坐标轴或者 需要知道axis命令 你可以 用Octave中 help命令了解细节
>> subplot(1,2,1);
>> plot(t, y1);
>> subplot(1,2,2);
>> plot(t, y2);
>> axis([0.5 1 -1 1])
最后 还有几个命令 clf清除 一幅图像 这里有一个独特的特点 让我们设置A等于 一个5×5 magic方阵 现在A是这个5*5 的矩阵 我有时用一个巧妙的方法 来可视化矩阵 也就是imagesc(A) 它将会 绘制一个5*5的矩阵 一个5*5的彩色格图 不同的颜色对应 A矩阵中的不同值 具体地说 我还可以使用函数colorbar 让我用一个 更复杂的命令 imagesc(A) colorbar colormap gray 这实际上是在同一时间运行三个命令 运行imagesc然后运行 colorbar 然后运行colormap gray 它生成了 一个颜色图像 一个灰度分布图 并在 右边也加入一个颜色条 所以这个颜色条 显示不同深浅的颜色所对应的值
>> clf;
>> A = magic(5);
>> imagesc(A)
>> imagesc(A), colorbar, colormap gray;
具体地 左上 A矩阵的元素 是17 所以对应 的是这样中等的灰度 而与此相反的第二个 元素 也就是 A(1,2)元素 代表的值为24 它对应于 这里的这个方块 是接近白色的灰度 较小的值比如 A多少呢 A(4,5) 为3对应着 你可以看到在我的颜色条 它对应于 一个更暗的灰度 这里是另一个例子 我可以绘制一个较大的 比如magic(15) 给你一个15* 15 magic方阵 这将会是一幅 15*15的magic方阵值的图
最后 总结一下这段视频 你看到我所做的 是使用逗号连接函数调用 这里是你如何真正做到这一点 如果我键入a=1 b=2 c=3 然后按Enter键 其实这是将这 三个命令同时执行 或者是 将三个命令一个接一个执行 它将输出所有这三个结果 这很像 a=1; b=2; c=3; 如果我用分号来代替逗号 没有输出出任何东西 所以你知道 这里我们称之为 逗号连接的命令或函数调用 只是另一种 Octave中更便捷的方式 将多条命令例如imagesc colorbar colormap 将这多条命令写在同一行中 就是这样 现在你知道如何绘制 Octave中不同的图像
在下面的视频中 下一个主要内容 我将告诉你怎样在Octave中 写控制语句 比如if while for语句 并且定义和使用函数
Control Statements: for, while, if statement
在这段视频中 告诉你怎样 为你的 Octave 程序写控制语句 诸如 "for" "while" "if" 这些语句 并且如何定义和使用方程
这是我们的 Octave 窗口 我先告诉你如何使用 “for” 循环 首先 我要将 v 值设为 一个10行1列 的零向量 现在 我要写一个 “for" 循环 让 i 等于 1 到 10 写出来就是 i = 1:10 让我们来看看 我要设 v(i) 的值 等于 2 的 i 次方 循环最后 结束 (end) 这里的空格没关系 所以我就加一些空格 让缩进后的代码看起来结构更清晰 但是你要知道这里的空格没有意义 如果按我这样做 那么 向量 v 的值就是 这样一个集合 2的一次方 2的二次方 依此类推 于是这就是我的 i 等于 1 到 10 的语句结构 让 i 遍历 1 到 10 的值
>> v=zeros(10,1);
>> for i=1:10,
v(i) = 2^i;
end;
>> v
v =
2
4
8
16
32
64
128
256
512
1024
另外 你还可以通过 设置你的 indices (索引) 等于 1 一直到10 来做到这一点 这时 indices 就是一个从1到10的序列 你也可以写 i = indices 这实际上和我直接把 i 写到 1 到 10 是一样 你可以写 disp(i) 也能得到一样的结果
>> for i=indices,
disp(i);
end;
1
2
3
4
5
6
7
8
9
10
所以 这就是一个 “for” 循环 如果你对 “break” 和 “continue” 语句比较熟悉 Octave里也有 “break” 和 “continue” 语句 你也可以在 Octave环境里 使用那些循环语句
但是首先让我告诉你 一个 while 循环是如何工作的 这是我的 v 向量 让我们写个 while 循环 i = 1 ; while i <= 5 ; 让我们设置 v(i) 等于 100 然后 i 加 1 结束 (end) 所以这是什么意思呢 我让 i 取值从 1 开始 然后我要 让 v(i) 等于 100 再让 i 递增 1 直到 i 大于 5停止
>> i = 1;
>> while i <= 5,
v(i) = 100;
i = i+1;
end;
>> v
v =
100
100
100
100
100
64
128
256
512
1024
现在来看一下结果 原来的向量 v 是2的这些次方 我现在已经取出了 向量的前五个元素 把他们用100覆盖掉 这就是一个while循环的句法结构
现在我们来分析另外一个例子 i = 1; while true, 这里我将向你展示 如何使用break语句 比方说 v(i) = 999 然后让 i = i+1 当 i 等于6的时候 break (停止循环) 结束 (end) 当然这也是我们第一次 使用一个 if 语句 所以 我希望你们可以理解这个逻辑 让 i 等于1 然后开始下面的增量循环 while语句重复设置 v(i) 等于1 (此处口误 应为999 译者注) 不断让i增加 然后当 i 达到6 做一个 中止循环的命令 尽管有while循环 语句也就此中止 所以最后的效果是 取出向量 v 的前5个元素 并且把它们设置为999 然后运行 的确如此 我们用999覆盖了 v 的前五个元素 所以 这就是 if 语句和 while 语句的句法结构 并且要注意 要有end 这里是有两个 end 的 这里的 end 结束的是 if 语句 第二个 end 结束的是 while 语句
>> i=1;
>> while true,
v(i) = 999;
i = i+1;
if i == 6,
break;
end;
end;
>> v
v =
999
999
999
999
999
64
128
256
512
1024
现在让我告诉你使用 if-else 语句时 更一般的句法结构 举个例子 v(1) 等于999 假设我们 令 v(1) 等于2 所以 让我输入 if v(1) == 1, disp('The value is one'); 这里出现了一个else语句 或者更确切地说 这里是一个 elseif语句 elseif v(1) == 2, 这就是说 如果这种情况下命题为真 执行 disp('The value is two'); 否则(else) 执行 disp('The value is not one or two'); 好了 这就是一个if-else语句 if-else语句 记得最后有end 当然了 我们刚刚设置过 v(1)等于2 所以显然 显示的是 "The value is two"
>> v(1)
ans = 999
>> v(1) = 2;
>> if v(1)==1,
disp('The value is one');
elseif v(1) == 2,
disp('The value is two');
else
disp('The value is not one or two');
end;
最后 我觉得现在 提醒一件事 如果你需要退出 Octave 你可以键入 exit 命令然后 回车就会退出 Octave 或者命令 ‘quit’ 也可以
最后 让我们来说说 函数 (functions) 如何定义和调用函数 这是我的桌面 我在桌面上存了一个 预先定义的文件 名为 “squarethisnumber.m” 这就是在 Octave 环境下定义的函数 你需要创建一个文件 用你的函数名来命名 然后以 .m 的后缀结尾 当 Octave 发现这文件 它知道应该在什么位置 寻找 squareThisNumber.m 这个函数的定义 让我们打开这个文件 请注意 我使用的是 微软的写字板程序来打开这个文件 我只是想建议你 如果你也使用微软的 Windows 系统 那么可以使用写字板程序 而不是记事本 来打开这些文件 如果你有别的什么 文本编辑器 那也可以 但记事本有时会把代码的间距弄得很乱 如果你只有记事本程序 那也能用 但最好是 如果你有写字板的话 我建议你用写字板 或者其他可以编辑函数的文本编辑器 现在我们来说如何在 Octave 里定义函数 我们先来放大一点 这个文件只有三行 第一行写着 function y = squareThisNumber(x) 这就告诉 Octave 我想返回一个 y 值 我想返回一个值 并且返回的这个值 将被存放于 变量 y 里 另外 它告诉了 Octave 这个函数有一个参数 就是参数 x 还有定义的函数体 也就是 y 等于 x 的平方
function y = squareThisNumber(x)
y = x^2;
现在让我们尝试调用这个函数 SquareThisNumber(5) 这实际上 是行不通的 Octave 说这个方程未被定义 这是因为 Octave 不知道在哪里找这个文件 所以像之前一样 我们使用 pwd 现在不在我的目录下 因此我们把路径设为 "C:\User\ang\desktop" 这就是我的桌面的路径 噢 打错了 应该是 "Users" 现在如果我 键入SquareThisNumber(5) 返回值是25
cd 'C:\Users\ang\desktop'
pwd
还有一种更高级的功能 这只是对那些知道 “search path (搜索路径)” 这个术语的人使用的 所以如果你 想要修改 Octave 的搜索路径 你可以把下面这部分 作为一个进阶知识 或者选学材料 仅适用于那些 熟悉编程语言中 搜索路径概念的同学 你可以使用 addpath 命令添加路径 添加路径 “C:\Users\ang\desktop” 将该目录添加到 Octave 的搜索路径 这样即使你跑到 其他路径底下 Octave依然知道 会在 Users\ang\desktop 目录下寻找函数 这样 即使我现在 在不同的目录下 它仍然 知道在哪里可以找到 “SquareThisNumber” 这个函数 明白吗?
addpath('C:\Users\ang\desktop')
但是 如果你不熟悉 搜索路径的概念 不用担心 只要确保 在执行函数之前 先用 cd 命令 设置到你函数所在的目录下 实际上也是一样的效果
Octave 还有一个 其他许多编程语言都没有的概念 那就是它可以 允许你定义一个函数 使得返回值是多个值或多个参数 这是一个例子 定义一个函数叫 “SquareAndCubeThisNumber(x)” (x的平方以及x的立方) 这说的就是 函数返回值是两个 y1 和 y2 接下来就是 y1是被平方后的数 y2是被立方后的结果 这就是说 函数会真的返回2个值
function [y1, y2] = squareAndCubeThisNumber(x)
y1 = x^2;
y2 = x^3;
所以 有些同学可能会根据 你使用的编程语言 比如你们可能熟悉的C或C++ 通常情况下 认为作为函数返回值只能是一个值 但 Octave 的语法结构就不一样 可以返回多个值 现在回到 Octave 窗口 如果我键入 = SquareAndCubeThisNumber(5) 然后 a 就等于25 b 就等于 5的立方 125 所以说如果你需要定义一个函数 并且返回多个值 这一点常常会带来很多方便
最后 我来给大家演示一下 一个更复杂一点的函数的例子 比方说 我有一个数据集 像这样 数据点为, , 我想做的事是 定义一个 Octave 函数来计算代价函数 J(θ) 就是计算 不同 θ 值所对应的代价函数值 J 首先让我们把数据放到 Octave 里 我把我的矩阵设置为 X = ; 这就是我的设计矩阵 X 第一列表示x0项 矩阵的第一列 第二列表示 我的三个训练样本的 x 值 现在我再来 设置 y 值为 就像这样 是y轴对应值 现在我们设定 theta 为
>> x = [1 1; 1 2; 1 3];
>> y = [1; 2; 3];
>> theta = [0;1];
现在我的桌面上 已经有我预定义的代价函数 J 如果我打开函数 函数的定义应该是下面这样的 所以 函数J 就写成 J = costFunctionJ(X, y, theta) 这里有一些注释 主要用于解释输入变量 接下来几步 设定 m 为训练样本的数量 也就是 X 的行数 计算预测值 predictions 预测值等于 X 乘以 theta 这里是注释行 是上一个注释行拐过来的部分 下面就是计算平方误差 公式就是 预测值减去 y 值 然后取出来每一项进行平方 最后就可以 计算代价函数 J 并且 Octave 知道 J 是一个我想返回的值 因为 J 出现在了我函数的定义里
function J = costFunctionJ(X,y,theta)
m = size(X,1);
predictions = X*theta;
sqrErrors = (predictions-y).^2;
J = 1/(2*m) * sum(sqrErrors);
另外 你可以随时 暂停一下视频 如果你想 仔细看一下这个函数的定义 确保你明白了定义中的每一步 现在当我 在 Octave 里运行时 我键入 j = costFunctionJ(x, y, theta) 然后他就开始计算 噢 又打错了 这里应该是大写 X 它就计算出 j 等于0 这是因为 如果我的数据集 x 为 y 也为 然后设置 θ0 等于0 θ1 等于1 这给了我恰好45度的斜线 这条线是可以完美拟合我的数据集的
>> j = costFunctionJ(X,y,theta)
而相反地 如果我设置 theta 等于 那么这个假设就是 0是所有的预测值 和刚才一样 设置θ0 = 0 θ1 也等于0 然后我计算的代价函数 结果是2.333 实际上 他就等于1的平方 也就是第一个样本的平方误差 加上2的平方 加上3的平方 然后除以2m 也就是训练样本数的两倍 这就是2.33 因此这也反过来验证了 我们这里的函数 计算出了正确的代价函数 这些就是我们 用简单的训练样本 尝试的几次试验 这也可以作为我们对 定义的代价函数 J 进行了完整性检查 确实是可以计算出正确的代价函数的 至少基于这里的 X 和 y 是成立的 也就是我们 这几个简单的训练集 至少是成立的
好啦 现在你知道 如何在 Octave 环境下写出正确的控制语句 比如 for 循环、while 循环和 if 语句 以及如何定义和使用函数
在接下来的视频中 我会非常快的 介绍一下 如何在这门课里 完成和提交作业 如何使用我们的提交系统 在此之后 在最后的 Octave 教程视频里 我会讲解一下向量化 这是一种可以使你的 Octave 程序运行非常快的思想
Vectorization向量化
在这段视频中 我将介绍有关向量化的内容 无论你是用Octave 还是别的语言 比如MATLAB 或者你正在用Python NumPy 或 Java C C++ 所有这些语言都具有各种线性代数库 这些库文件都是内置的 容易阅读和获取 他们通常写得很好 已经经过高度优化 通常是数值计算方面的博士 或者专业人士开发的 而当你实现机器学习算法时 如果你能 好好利用这些 线性代数库或者说 数值线性代数库 并联合调用它们 而不是自己去做那些 函数库可以做的事情 如果是这样的话 那么 通常你会发现 首先 这样更有效 也就是说运行速度更快 并且更好地利用 你的计算机里可能有的一些并行硬件系统 等等 第二 这也意味着 你可以用更少的代码来实现你需要的功能 因此 实现的方式更简单 代码出现问题的有可能性也就越小
举个具体的例子 与其自己写代码 做矩阵乘法 如果你只在Octave中 输入 a乘以b 就是一个非常有效的 两个矩阵相乘的程序 有很多例子可以说明 如果你用合适的向量化方法来实现 你就会有一个简单得多 也有效得多的代码
让我们来看一些例子 这是一个常见的线性回归假设函数 如果 你想要计算 h(x) 注意到右边是求和 那么你可以 自己计算 j =0 到 j = n 的和 但换另一种方式来想想 是把 h(x) 看作 θ 转置乘以 x 那么 你就可以写成 两个向量的内积 其中 θ 就是 θ0 θ1 θ2 如果你有两个特征量 如果 n 等于2 并且如果 你把 x 看作 x0 x1 x2 这两种思考角度 会给你两种不同的实现方式
比如说 这是未向量化的代码实现方式 计算 h(x) 是未向量化的 我的意思是 没有被向量化 我们可能首先要初始化变量 prediction 的值为0.0 而这个 变量 prediction 的最终结果就是 h(x) 然后 我要用一个 for 循环 j 取值 0 到 n+1 变量prediction 每次就通过 自身加上 theta(j) 乘以 x(j) 更新值 这个就是算法的代码实现 顺便我要提醒一下 这里的向量 我用的下标是 0 所以我有 θ0 θ1 θ2 但因为 MATLAB 的下标从1开始 在 MATLAB 中 θ0 我们可能会 用 theta(1) 来表示 这第二个元素 最后就会变成 theta(2) 而第三个元素 最终可能就用 theta(3) 表示 因为 MATLAB 中的下标从1开始 即使我们实际的 θ 和 x 的下标从0开始 这就是为什么 这里我的 for 循环 j 取值从 1 直到 n+1 而不是 从 0 到 n 清楚了吗? 但这是一个 未向量化的代码实现方式 我们用一个 for 循环 对 n 个元素进行加和
作为比较 接下来是 向量化的代码实现 你把 x 和 θ 看做向量 而你只需要 令变量 prediction 等于 theta转置 乘以 x 你就可以这样计算 与其写所有这些 for 循环的代码 你只需要一行代码 这行代码 右边所做的 就是 利用 Octave 的高度优化的数值 线性代数算法来计算 两个向量的内积 θ 以及 x 这样向量化的实现不仅仅是更简单 它运行起来也将更加高效
这就是 Octave 所做的 而向量化的方法 在其他编程语言中同样可以实现 让我们来看一个 C++ 的例子 这就是未向量化的代码实现的样子 我们再次初始化变量 prediction 为 0.0 然后我们现在有一个完整的 从 j 等于 0 直到 n 变量 prediction += theta 乘以 x 再一次 你有这样的自己写的 for 循环 与此相反 使用一个比较好的 C++ 数值线性代数库 你就可以用这个方程 来写这个函数 与此相反 使用较好的 C++ 数值线性代数库 你可以写出 像这样的代码 因此取决于你的 数值线性代数库的内容 你可以有一个对象 (object) 像这个 C++ 对象 theta 和一个 C++ 对象 向量 x 你只需要用 theta.transpose ( ) 乘以 x 而这次是让 C++ 来实现运算 因此 你只需要在 C++ 中将两个向量相乘 根据你所使用的 数值和线性代数库的使用细节的不同 你最终使用的代码表达方式 可能会有些许不同 但是通过 一个库来做内积 你可以得到一段更简单 更有效的代码
现在 让我们来看一个更为复杂的例子 提醒一下 这是线性回归算法梯度下降的更新规则 所以 我们用这条规则对 j 等于 0 1 2 等等的所有值 更新 对象 θj 我只是 用 θ0 θ1 θ2 来写方程 那就是假设我们有两个特征量 所以 n等于2 这些都是我们需要对 θ0 θ1 θ2 进行更新 你可能还记得 在以前的视频中说过 这些都应该是同步更新
因此 让我们来看看 我们是否可以拿出一个 向量化的代码实现 这里是和之前相同的三个方程 只不过写得小一点而已 你可以想象 实现这三个方程的方式之一 就是用 一个 for 循环 就是让 j 等于0 等于 等于2 来更新 θj 但让我们 用向量化的方式来实现 看看我们是否能够有一个更简单的方法 基本上用三行代码 或者一个 for 循环 一次实现这三个方程 让我们来看看怎样能用这三步 并将它们压缩成 一行向量化的代码来实现 做法如下 我打算 把 θ 看做一个向量 然后我用 θ 减去 α 乘以 某个别的向量 δ 来更新 θ 这里的 δ 等于 m 分之 1 对 i=1 到 m 进行求和 然后这个表达式 对吧? 让我解释一下是怎么回事 在这里 我要把 θ 看作一个向量 有一个 n+1 维向量 我是说 θ 被更新 我们的 n+1 维向量 α 是一个实数 δ 在这里 是一个向量 所以这个减法运算是一个向量减法 没问题吧 ? 因为 α 乘以 δ 是一个向量 所以 θ 就是 θ 减去 α 乘以 δ 得到的向量 那么什么是向量 δ 呢 ? 嗯 向量 δ 是这样子的 这部分实际上 代表的就是 这部分内容 具体地说 δ 将成为 n+1 维向量 并且向量的第一个元素 就等于这个 所以我们的 δ 如果要写下标的话 就是从零开始 δ0 δ1 δ2 我想要的是 δ0 等于 这个 第一行绿色框起来的部分 事实上 你可能会 写出 δ0 是 m 分之 1 乘以 h(x(i)) 减去 y(i) 乘以 x(i)0 的求和 所以让我们 在同一页上 计算真正的 δ δ 就是 m 分之 1 乘以这个和 那这个和是什么 ? 嗯 这一项 是一个实数 这里的第二个项 是 x(i) 这一项是一个向量 对吧 ? 因为 x(i) 可能是一个向量 这将是 x(i)0 x(i)1 x(i)2 对吧 ? 那这个求和是什么 ? 嗯 这个求和就是 这里的式子 这里的这一项 等于 h(x(1)) - y(1) 乘以 x(1) 加上 h(x(2)) - y(2) 乘以 x(2) 依此类推 对吧 ? 因为这是对 i 的加和 所以 当 i 从 1 到 m 你就会得到这些不同的式子 然后作加和 每个式子的意思 很像 如果你还记得实际上 在以前的一个小测验 如果你要解这个方程 我们说过 为了向量化这段代码 我们会令 u = 2v +5w 因此 我们说 向量u 等于2乘以向量v 加上 5乘以向量 w 用这个例子说明 如何对不同的向量进行相加 这里的求和是同样的道理 这一部分 只是一个实数 就有点像数字 2 而这里是别的一些数字 来乘以向量x1 这就像是 2v 只不过用别的数字乘以 x1 然后加上 你知道 不是5w 而是用 别的实数乘以 一个别的向量 然后你 加上 其他的向量 这就是为什么 总体而言 在这里 这整个量 δ 就是一个向量 具体而言 对应这三个 δ 的元素 如果n等于2 δ 的三个元素一一对应 这个 第二个 以及这第三个 式子 这就是为什么 当您更新 θ 值时 根据 θ - αδ 这个式子 我们最终能得到 完全符合最上方更新规则的 同步更新 我知道 幻灯片上的内容很多
但是再次重申 请随时暂停视频 我也鼓励你 一步步对比这两者的差异 如果你不清楚刚才的内容 我希望你能一步一步读幻灯片的内容 以确保你理解 为什么这个式子 用 δ 的这个定理 定义的 好吗 ? 以及它为什么 和最上面的更新方式是等价的 为什么是这样子的 就是这里的式子 这就是向量 x 而我们只是用了 你知道 这三个计算式并且压缩 成一个步骤 用这个向量 δ 这就是为什么我们能够 向量化地实现 线性回归 所以 我希望 步骤是有逻辑的 请务必看视频 并且保证 你确实能理解它 如果你实在不能理解 它们数学上等价的原因 你就直接实现这个算法 也是能得到正确答案的 所以即使你没有 完全理解为何是等价的 如果只是实现这种算法 你仍然能实现线性回归算法 所以如果你能 弄清楚为什么这两个步骤是等价的 那我希望你可以对 向量化有一个更好的理解 以及 最后 如果你在实现线性回归的时候 使用一个或两个以上的特征量 有时我们使用 几十或几百个特征量 来计算线性归回 当你使用向量化地实现 线性回归 通常运行速度就会比你以前用 你的 for 循环快的多 也就是自己 写代码更新 θ0 θ1 θ2 因此使用向量化实现方式 你应该是能够得到 一个高效得多的线性回归算法 而当你向量化 我们将在之后的课程里面学到的算法 这会是一个很好的技巧 无论是对于 Octave 或者 一些其他的语言 如C++ Java 来让你的代码运行得更高效