Coursera-ML-AndrewNg-Notes/markdown/week2.md

1060 lines
66 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

第2周
=====
[TOC]
四、多变量线性回归(Linear Regression with Multiple Variables)
-------------------------------------------------------------
### 4.1 多维特征
参考视频: 4 - 1 - Multiple Features (8 min).mkv
目前为止,我们探讨了单变量/特征的回归模型,现在我们对房价模型增加更多的特征,例如房间数楼层等,构成一个含有多个变量的模型,模型中的特征为$\left( {x_{1}},{x_{2}},...,{x_{n}} \right)$。
![](../images/591785837c95bca369021efa14a8bb1c.png)
增添更多特征后,我们引入一系列新的注释:
$n$ 代表特征的数量
${x^{\left( i \right)}}$代表第 $i$ 个训练实例,是特征矩阵中的第$i$行,是一个**向量****vector**)。
比方说,上图的
${x}^{(2)}\text{=}\begin{bmatrix} 1416\\\ 3\\\ 2\\\ 40 \end{bmatrix}$
${x}_{j}^{\left( i \right)}$代表特征矩阵中第 $i$ 行的第 $j$ 个特征,也就是第 $i$ 个训练实例的第 $j$ 个特征。
如上图的$x_{2}^{\left( 2 \right)}=3,x_{3}^{\left( 2 \right)}=2$
支持多变量的假设 $h$ 表示为:$h_{\theta}\left( x \right)={\theta_{0}}+{\theta_{1}}{x_{1}}+{\theta_{2}}{x_{2}}+...+{\theta_{n}}{x_{n}}$
这个公式中有$n+1$个参数和$n$个变量,为了使得公式能够简化一些,引入$x_{0}=1$,则公式转化为:$h_{\theta} \left( x \right)={\theta_{0}}{x_{0}}+{\theta_{1}}{x_{1}}+{\theta_{2}}{x_{2}}+...+{\theta_{n}}{x_{n}}$
此时模型中的参数是一个$n+1$维的向量,任何一个训练实例也都是$n+1$维的向量,特征矩阵$X$的维度是 $m*(n+1)$。 因此公式可以简化为:$h_{\theta} \left( x \right)={\theta^{T}}X$,其中上标$T$代表矩阵转置。
### 4.2 多变量梯度下降
参考视频: 4 - 2 - Gradient Descent for Multiple Variables (5 min).mkv
与单变量线性回归类似,在多变量线性回归中,我们也构建一个代价函数,则这个代价函数是所有建模误差的平方和,即:$J\left( {\theta_{0}},{\theta_{1}}...{\theta_{n}} \right)=\frac{1}{2m}\sum\limits_{i=1}^{m}{{{\left( h_{\theta} \left({x}^{\left( i \right)} \right)-{y}^{\left( i \right)} \right)}^{2}}}$
其中:$h_{\theta}\left( x \right)=\theta^{T}X={\theta_{0}}+{\theta_{1}}{x_{1}}+{\theta_{2}}{x_{2}}+...+{\theta_{n}}{x_{n}}$
我们的目标和单变量线性回归问题中一样,是要找出使得代价函数最小的一系列参数。
多变量线性回归的批量梯度下降算法为:
![](../images/41797ceb7293b838a3125ba945624cf6.png)
即:
![](../images/6bdaff07783e37fcbb1f8765ca06b01b.png)
求导数后得到:
![](../images/dd33179ceccbd8b0b59a5ae698847049.png)
当$n>=1$时,
${{\theta }_{0}}:={{\theta }_{0}}-a\frac{1}{m}\sum\limits_{i=1}^{m}{({{h}_{\theta }}({{x}^{(i)}})-{{y}^{(i)}})}x_{0}^{(i)}$
${{\theta }_{1}}:={{\theta }_{1}}-a\frac{1}{m}\sum\limits_{i=1}^{m}{({{h}_{\theta }}({{x}^{(i)}})-{{y}^{(i)}})}x_{1}^{(i)}$
${{\theta }_{2}}:={{\theta }_{2}}-a\frac{1}{m}\sum\limits_{i=1}^{m}{({{h}_{\theta }}({{x}^{(i)}})-{{y}^{(i)}})}x_{2}^{(i)}$
我们开始随机选择一系列的参数值,计算所有的预测结果后,再给所有的参数一个新的值,如此循环直到收敛。
代码示例:
计算代价函数
$J\left( \theta \right)=\frac{1}{2m}\sum\limits_{i=1}^{m}{{{\left( {h_{\theta}}\left( {x^{(i)}} \right)-{y^{(i)}} \right)}^{2}}}$
其中:${h_{\theta}}\left( x \right)={\theta^{T}}X={\theta_{0}}{x_{0}}+{\theta_{1}}{x_{1}}+{\theta_{2}}{x_{2}}+...+{\theta_{n}}{x_{n}}$
**Python** 代码:
```python
def computeCost(X, y, theta):
inner = np.power(((X * theta.T) - y), 2)
return np.sum(inner) / (2 * len(X))
```
### 4.3 梯度下降法实践1-特征缩放
参考视频: 4 - 3 - Gradient Descent in Practice I - Feature Scaling (9 min).mkv
在我们面对多维特征问题的时候,我们要保证这些特征都具有相近的尺度,这将帮助梯度下降算法更快地收敛。
以房价问题为例,假设我们使用两个特征,房屋的尺寸和房间的数量,尺寸的值为 0-2000平方英尺而房间数量的值则是0-5以两个参数分别为横纵坐标绘制代价函数的等高线图能看出图像会显得很扁梯度下降算法需要非常多次的迭代才能收敛。
![](../images/966e5a9b00687678374b8221fdd33475.jpg)
解决的方法是尝试将所有特征的尺度都尽量缩放到-1到1之间。如图
![](../images/b8167ff0926046e112acf789dba98057.png)
最简单的方法是令:${{x}_{n}}=\frac{{{x}_{n}}-{{\mu}_{n}}}{{{s}_{n}}}$,其中 ${\mu_{n}}$是平均值,${s_{n}}$是标准差。
### 4.4 梯度下降法实践2-学习率
参考视频: 4 - 4 - Gradient Descent in Practice II - Learning Rate (9 min).mkv
梯度下降算法收敛所需要的迭代次数根据模型的不同而不同,我们不能提前预知,我们可以绘制迭代次数和代价函数的图表来观测算法在何时趋于收敛。
![](../images/cd4e3df45c34f6a8e2bb7cd3a2849e6c.jpg)
也有一些自动测试是否收敛的方法例如将代价函数的变化值与某个阀值例如0.001)进行比较,但通常看上面这样的图表更好。
梯度下降算法的每次迭代受到学习率的影响,如果学习率$a$过小,则达到收敛所需的迭代次数会非常高;如果学习率$a$过大,每次迭代可能不会减小代价函数,可能会越过局部最小值导致无法收敛。
通常可以考虑尝试些学习率:
$\alpha=0.010.030.10.31310$
### 4.5 特征和多项式回归
参考视频: 4 - 5 - Features and Polynomial Regression (8 min).mkv
如房价预测问题,
![](../images/8ffaa10ae1138f1873bc65e1e3657bd4.png)
$h_{\theta}\left( x \right)={\theta_{0}}+{\theta_{1}}\times{frontage}+{\theta_{2}}\times{depth}$
${x_{1}}=frontage$(临街宽度),${x_{2}}=depth$(纵向深度),$x=frontage*depth=area$(面积),则:${h_{\theta}}\left( x \right)={\theta_{0}}+{\theta_{1}}x$。
线性回归并不适用于所有数据,有时我们需要曲线来适应我们的数据,比如一个二次方模型:$h_{\theta}\left( x \right)={\theta_{0}}+{\theta_{1}}{x_{1}}+{\theta_{2}}{x_{2}^2}$
或者三次方模型: $h_{\theta}\left( x \right)={\theta_{0}}+{\theta_{1}}{x_{1}}+{\theta_{2}}{x_{2}^2}+{\theta_{3}}{x_{3}^3}$
![](../images/3a47e15258012b06b34d4e05fb3af2cf.jpg)
通常我们需要先观察数据然后再决定准备尝试怎样的模型。 另外,我们可以令:
${{x}_{2}}=x_{2}^{2},{{x}_{3}}=x_{3}^{3}$,从而将模型转化为线性回归模型。
根据函数图形特性,我们还可以使:
${{{h}}_{\theta}}(x)={{\theta }_{0}}\text{+}{{\theta }_{1}}(size)+{{\theta}_{2}}{{(size)}^{2}}$
或者:
${{{h}}_{\theta}}(x)={{\theta }_{0}}\text{+}{{\theta }_{1}}(size)+{{\theta }_{2}}\sqrt{size}$
注:如果我们采用多项式回归模型,在运行梯度下降算法前,特征缩放非常有必要。
### 4.6 正规方程
参考视频: 4 - 6 - Normal Equation (16 min).mkv
到目前为止,我们都在使用梯度下降算法,但是对于某些线性回归问题,正规方程方法是更好的解决方案。如:
![](../images/a47ec797d8a9c331e02ed90bca48a24b.png)
正规方程是通过求解下面的方程来找出使得代价函数最小的参数的:$\frac{\partial}{\partial{\theta_{j}}}J\left( {\theta_{j}} \right)=0$ 。
假设我们的训练集特征矩阵为 $X$(包含了 ${{x}_{0}}=1$)并且我们的训练集结果为向量 $y$,则利用正规方程解出向量 $\theta ={{\left( {X^T}X \right)}^{-1}}{X^{T}}y$ 。
上标**T**代表矩阵转置,上标-1 代表矩阵的逆。设矩阵$A={X^{T}}X$,则:${{\left( {X^T}X \right)}^{-1}}={A^{-1}}$
以下表示数据为例:
![](../images/261a11d6bce6690121f26ee369b9e9d1.png)
即:
![](../images/c8eedc42ed9feb21fac64e4de8d39a06.png)
运用正规方程方法求解参数:
![](../images/b62d24a1f709496a6d7c65f87464e911.jpg)
**Octave** 中,正规方程写作:
```
pinv(X'*X)*X'*y
```
注:对于那些不可逆的矩阵(通常是因为特征之间不独立,如同时包含英尺为单位的尺寸和米为单位的尺寸两个特征,也有可能是特征数量大于训练集的数量),正规方程方法是不能用的。
梯度下降与正规方程的比较:
| 梯度下降 | 正规方程 |
| ---------------- | ---------------------------------------- |
| 需要选择学习率$\alpha$ | 不需要 |
| 需要多次迭代 | 一次运算得出 |
| 当特征数量$n$大时也能较好适用 | 需要计算${{\left( {{X}^{T}}X \right)}^{-1}}$ 如果特征数量n较大则运算代价大因为矩阵逆的计算时间复杂度为$O\left( {{n}^{3}} \right)$,通常来说当$n$小于10000 时还是可以接受的 |
| 适用于各种类型的模型 | 只适用于线性模型,不适合逻辑回归模型等其他模型 |
总结一下,只要特征变量的数目并不大,标准方程是一个很好的计算参数$\theta $的替代方法。具体地说,只要特征变量数量小于一万,我通常使用标准方程法,而不使用梯度下降法。
随着我们要讲的学习算法越来越复杂,例如,当我们讲到分类算法,像逻辑回归算法,我们会看到,实际上对于那些算法,并不能使用标准方程法。对于那些更复杂的学习算法,我们将不得不仍然使用梯度下降法。因此,梯度下降法是一个非常有用的算法,可以用在有大量特征变量的线性回归问题。或者我们以后在课程中,会讲到的一些其他的算法,因为标准方程法不适合或者不能用在它们上。但对于这个特定的线性回归模型,标准方程法是一个比梯度下降法更快的替代算法。所以,根据具体的问题,以及你的特征变量的数量,这两种算法都是值得学习的。
正规方程的**python**实现:
```python
import numpy as np
def normalEqn(X, y):
theta = np.linalg.inv(X.T@X)@X.T@y #X.T@X等价于X.T.dot(X)
return theta
```
### 4.7 正规方程及不可逆性(可选)
参考视频: 4 - 7 - Normal Equation Noninvertibility (Optional) (6 min).mkv
在这段视频中谈谈正规方程 ( **normal equation** ),以及它们的不可逆性。
由于这是一种较为深入的概念,并且总有人问我有关这方面的问题,因此,我想在这里来讨论它,由于概念较为深入,所以对这段可选材料大家放轻松吧,也许你可能会深入地探索下去,并且会觉得理解以后会非常有用。但即使你没有理解正规方程和线性回归的关系,也没有关系。
我们要讲的问题如下:$\theta ={{\left( {X^{T}}X \right)}^{-1}}{X^{T}}y$
备注:本节最后我把推导过程写下。
有些同学曾经问过我,当计算 $\theta$=`inv(X'X ) X'y` ,那对于矩阵$X'X$的结果是不可逆的情况咋办呢?
如果你懂一点线性代数的知识,你或许会知道,有些矩阵可逆,而有些矩阵不可逆。我们称那些不可逆矩阵为奇异或退化矩阵。
问题的重点在于$X'X$的不可逆的问题很少发生,在**Octave**里,如果你用它来实现$\theta$的计算,你将会得到一个正常的解。在**Octave**里,有两个函数可以求解矩阵的逆,一个被称为`pinv()`,另一个是`inv()`,这两者之间的差异是些许计算过程上的,一个是所谓的伪逆,另一个被称为逆。使用`pinv()` 函数可以展现数学上的过程,这将计算出$\theta$的值,即便矩阵$X'X$是不可逆的。
在`pinv()` 和 `inv()` 之间,又有哪些具体区别呢 ?
其中`inv()` 引入了先进的数值计算的概念。例如,在预测住房价格时,如果${x_{1}}$是以英尺为尺寸规格计算的房子,${x_{2}}$是以平方米为尺寸规格计算的房子同时你也知道1米等于3.28英尺 ( 四舍五入到两位小数 ),这样,你的这两个特征值将始终满足约束:${x_{1}}={x_{2}}*{{\left( 3.28 \right)}^{2}}$。
实际上,你可以用这样的一个线性方程,来展示那两个相关联的特征值,矩阵$X'X$将是不可逆的。
第二个原因是,在你想用大量的特征值,尝试实践你的学习算法的时候,可能会导致矩阵$X'X$的结果是不可逆的。
具体地说,在$m$小于或等于n的时候例如有$m$等于10个的训练样本也有$n$等于100的特征数量。要找到适合的$(n +1)$ 维参数矢量$\theta$这将会变成一个101维的矢量尝试从10个训练样本中找到满足101个参数的值这工作可能会让你花上一阵子时间但这并不总是一个好主意。因为正如我们所看到你只有10个样本以适应这100或101个参数数据还是有些少。
稍后我们将看到如何使用小数据样本以得到这100或101个参数通常我们会使用一种叫做正则化的线性代数方法通过删除某些特征或者是使用某些技术来解决当$m$比$n$小的时候的问题。即使你有一个相对较小的训练集,也可使用很多的特征来找到很多合适的参数。
总之当你发现的矩阵$X'X$的结果是奇异矩阵,或者找到的其它矩阵是不可逆的,我会建议你这么做。
首先,看特征值里是否有一些多余的特征,像这些${x_{1}}$和${x_{2}}$是线性相关的,互为线性函数。同时,当有一些多余的特征时,可以删除这两个重复特征里的其中一个,无须两个特征同时保留,将解决不可逆性的问题。因此,首先应该通过观察所有特征检查是否有多余的特征,如果有多余的就删除掉,直到他们不再是多余的为止,如果特征数量实在太多,我会删除些 用较少的特征来反映尽可能多内容,否则我会考虑使用正规化方法。
如果矩阵$X'X$是不可逆的,(通常来说,不会出现这种情况),如果在**Octave**里,可以用伪逆函数`pinv()` 来实现。这种使用不同的线性代数库的方法被称为伪逆。即使$X'X$的结果是不可逆的,但算法执行的流程是正确的。总之,出现不可逆矩阵的情况极少发生,所以在大多数实现线性回归中,出现不可逆的问题不应该过多的关注${X^{T}}X$是不可逆的。
**增加内容:**
$\theta ={{\left( {X^{T}}X \right)}^{-1}}{X^{T}}y$ 的推导过程:
$J\left( \theta \right)=\frac{1}{2m}\sum\limits_{i=1}^{m}{{{\left( {h_{\theta}}\left( {x^{(i)}} \right)-{y^{(i)}} \right)}^{2}}}$
其中:${h_{\theta}}\left( x \right)={\theta^{T}}X={\theta_{0}}{x_{0}}+{\theta_{1}}{x_{1}}+{\theta_{2}}{x_{2}}+...+{\theta_{n}}{x_{n}}$
将向量表达形式转为矩阵表达形式,则有$J(\theta )=\frac{1}{2}{{\left( X\theta -y\right)}^{2}}$ ,其中$X$为$m$行$n$列的矩阵($m$为样本个数,$n$为特征个数),$\theta$为$n$行1列的矩阵$y$为$m$行1列的矩阵对$J(\theta )$进行如下变换
$J(\theta )=\frac{1}{2}{{\left( X\theta -y\right)}^{T}}\left( X\theta -y \right)$
$=\frac{1}{2}\left( {{\theta }^{T}}{{X}^{T}}-{{y}^{T}} \right)\left(X\theta -y \right)$
$=\frac{1}{2}\left( {{\theta }^{T}}{{X}^{T}}X\theta -{{\theta}^{T}}{{X}^{T}}y-{{y}^{T}}X\theta -{{y}^{T}}y \right)$
接下来对$J(\theta )$偏导,需要用到以下几个矩阵的求导法则:
$\frac{dAB}{dB}={{A}^{T}}$
$\frac{d{{X}^{T}}AX}{dX}=2AX$
所以有:
$\frac{\partial J\left( \theta \right)}{\partial \theta }=\frac{1}{2}\left(2{{X}^{T}}X\theta -{{X}^{T}}y -{}({{y}^{T}}X )^{T}-0 \right)$
$=\frac{1}{2}\left(2{{X}^{T}}X\theta -{{X}^{T}}y -{{X}^{T}}y -0 \right)$
$={{X}^{T}}X\theta -{{X}^{T}}y$
令$\frac{\partial J\left( \theta \right)}{\partial \theta }=0$,
则有$\theta ={{\left( {X^{T}}X \right)}^{-1}}{X^{T}}y$
五、Octave教程(Octave Tutorial)
-------------------------------
### 5.1 基本操作
参考视频: 5 - 1 - Basic Operations (14 min).mkv
在这段视频中,我将教你一种编程语言:**Octave**语言。你能够用它来非常迅速地实现这门课中我们已经学过的,或者将要学的机器学习算法。
过去我一直尝试用不同的编程语言来教授机器学习,包括**C++**、**Java**、**Python**、**Numpy**和**Octave**。我发现当使用像**Octave**这样的高级语言时,学生能够更快更好地学习并掌握这些算法。事实上,在硅谷,我经常看到进行大规模的机器学习项目的人,通常使用的程序语言就是**Octave**。(编者注:这是当时的情况,现在主要是用**Python**)
**Octave**是一种很好的原始语言(**prototyping language**),使用**Octave**你能快速地实现你的算法,剩下的事情,你只需要进行大规模的资源配置,你只用再花时间用**C++**或**Java**这些语言把算法重新实现就行了。开发项目的时间是很宝贵的,机器学习的时间也是很宝贵的。所以,如果你能让你的学习算法在**Octave**上快速的实现,基本的想法实现以后,再用**C++**或者**Java**去改写,这样你就能节省出大量的时间。
据我所见,人们使用最多的用于机器学习的原始语言是**Octave**、**MATLAB**、**Python**、**NumPy** 和**R**。
**Octave**很好,因为它是开源的。当然**MATLAB**也很好,但它不是每个人都买得起的。(貌似国内学生喜欢用收费的**matlab****matlab**功能要比**Octave**强大的多,网上有各种**D**版可以下载)。这次机器学习课的作业也是用**matlab**的。如果你能够使用**matlab**,你也可以在这门课里面使用。
如果你会**Python**、**NumPy**或者**R**语言,我也见过有人用 **R**的,据我所知,这些人不得不中途放弃了,因为这些语言在开发上比较慢,而且,因为这些语言如:**Python**、**NumPy**的语法相较于**Octave**来说,还是更麻烦一点。正因为这样,所以我强烈建议不要用**NumPy**或者**R**来完整这门课的作业,我建议在这门课中用**Octave**来写程序。
本视频将快速地介绍一系列的命令,目标是迅速地展示,通过这一系列**Octave**的命令,让你知道**Octave**能用来做什么。
启动**Octave**
现在打开**Octave**,这是**Octave**命令行。
![](../images/e2c2dcc31f19ac255566fa616799d496.png)
现在让我示范最基本的**Octave**代码:
输入5 + 6然后得到11。
输入3 2、5×8、1/2、2^6等等得到相应答案。
![](../images/6dcdf4a7c0d56787648d4a1902034150.png)
这些都是基本的数学运算。
![](../images/f8507899953ed2de68e6b2b83554f9ea.png)
你也可以做逻辑运算,例如 1==2计算结果为 **false** (**假**)这里的百分号命令表示注释1==2 计算结果为假这里用0表示。
请注意,不等于符号的写法是这个波浪线加上等于符号 ( \~= ),而不是等于感叹号加等号( != ),这是和其他一些编程语言中不太一样的地方。
![](../images/126b2a5c4b5bfb24e5c21cd080159530.png)
让我们看看逻辑运算 1 && 0使用双&符号表示逻辑与1 && 0判断为假1和0的或运算 1 \|\| 0其计算结果为真。
![](../images/fec0936e2a78c0fe8c9e3ed107614a31.png)
还有异或运算 如`XOR ( 1, 0 )`其返回值为1
从左向右写着 **Octave 324.x**版本,是默认的**Octave**提示,它显示了当前**Octave**的版本,以及相关的其它信息。
如果你不想看到那个提示,这里有一个隐藏的命令:
输入命令
![](../images/8d63fe546c12a7e9eb658118d76288f7.png)
现在命令提示已经变得简化了。
接下来,我们将谈到**Octave**的变量。
现在写一个变量,对变量$A$赋值为3并按下回车键显示变量$A$等于3。
![](../images/a537df35ccc9ff83a3c7518362e2f729.png)
如果你想分配一个变量,但不希望在屏幕上显示结果,你可以在命令后加一个分号,可以抑制打印输出,敲入回车后,不打印任何东西。
![](../images/c11786828c587189891a9ef02f041ab7.png)
其中这句命令不打印任何东西。
现在举一个字符串的例子:变量$b$等于"**hi**"。
![](../images/4b67374499c0d38ed8670ba74ff892d0.png)
$c$等于3大于等于1所以现在$c$变量的值是真。
![](../images/acedf91b6b39d551e62a89f2e0955628.png)
如果你想打印出变量,或显示一个变量,你可以像下面这么做:
设置$a$等于圆周率$π$,如果我要打印该值,那么只需键入`a`像这样 就打印出来了。
![](../images/2cdc09b8bf67e546df7284ba74601c66.png)
对于更复杂的屏幕输出,也可以用**DISP**命令显示:
![](../images/dfcd1d37526824726d85a655f8951249.png)
这是一种,旧风格的**C语言**语法,对于之前就学过**C语言**的同学来说,你可以使用这种基本的语法来将结果打印到屏幕。
例如 ^{T}命令的六个小数0.6%f ,a这应该打印$π$的6位小数形式。
也有一些控制输出长短格式的快捷命令:
![](../images/ba8f0c3d2d8f017e0f7a611aa5be75d6.png)
下面,让我们来看看向量和矩阵:
比方说 建立一个矩阵$A$
![](../images/693ebb444501838dd9b69520fff54be0.png)
对$A$矩阵进行赋值,考虑到这是一个三行两列的矩阵,你同样可以用向量。
建立向量$V$并赋值1 2 3$V$是一个行向量或者说是一个3 ( 列 )×1 ( 行 )的向量,或者说,一行三列的矩阵。
如果我想分配一个列向量我可以写“1;2;3”现在便有了一个3 行 1 列的向量,同时这是一个列向量。
下面是一些更为有用的符号,如:
```matlab
V=10.12
```
这个该如何理解呢:这个集合$v$是一组值从数值1开始增量或说是步长为0.1直到增加到2按照这样的方法对向量$V$操作可以得到一个行向量这是一个1行11列的矩阵其矩阵的元素是1
1.1 1.2 1.3依此类推直到数值2。
我也可以建立一个集合$v$并用命令“1:6”进行赋值这样$V$就被赋值了1至6的六个整数。
![](../images/1cdbd87db83a4184098cd6d5ee3c6a87.png)
这里还有一些其他的方法来生成矩阵
例如“`ones(2, 3)`”,也可以用来生成矩阵:
![](../images/5d2b25d4078a276091b9c00812674fa9.png)
元素都为2两行三列的矩阵就可以使用这个命令
![](../images/21985f8690965598d4a17e3a6e7fee94.png)
你可以把这个方法当成一个生成矩阵的快速方法。
$w$为一个一行三列的零矩阵,一行三列的$A$矩阵里的元素全部是零:
![](../images/846b48ec79c9fcee05b20767dcc89558.png)
还有很多的方式来生成矩阵。
如果我对$W$进行赋值,用**Rand**命令建立一个一行三列的矩阵,因为使用了**Rand**命令,则其一行三列的元素均为随机值,如“`rand(3,3)`”命令这就生成了一个3×3的矩阵并且其所有元素均为随机。
![](../images/0de6e7054e869a82060cefa9968cd56b.png)
数值介于0和1之间所以正是因为这一点我们可以得到数值均匀介于0和1之间的元素。
如果,你知道什么是高斯随机变量,或者,你知道什么是正态分布的随机变量,你可以设置集合$W$,使其等于一个一行三列的$N$矩阵并且来自三个值一个平均值为0的高斯分布方差或者等于1的标准偏差。
![](../images/048f3cac1c32e3dc56160849c4dd60b0.png)
还可以设置地更复杂:
并用**hist**命令绘制直方图。
![](../images/10c06cc39058da2c5eef696d75e65a2c.png)
绘制单位矩阵:
![](../images/08d11f870c5b30536f1965507fa7e7dc.png)
如果对命令不清楚,建议用**help**命令:
![](../images/79b55d71cf434126f3d8457a3a615d18.png)
以上讲解的内容都是**Octave**的基本操作。希望你能通过上面的讲解,自己练习一些矩阵、乘、加等操作,将这些操作在**Octave**中熟练运用。
在接下来的视频中,将会涉及更多复杂的命令,并使用它们在**Octave**中对数据进行更多的操作。
### 5.2 移动数据
参考视频: 5 - 2 - Moving Data Around (16 min).mkv
在这段关于 **Octave**的辅导课视频中,我将开始介绍如何在 **Octave** 中移动数据。
如果你有一个机器学习问题,你怎样把数据加载到 **Octave** 中?
怎样把数据存入一个矩阵?
如何对矩阵进行相乘?
如何保存计算结果?
如何移动这些数据并用数据进行操作?
进入我的 **Octave** 窗口,
我键入$A$,得到我们之前构建的矩阵 $A$,也就是用这个命令生成的:
`A = [1 2; 3 4; 5 6]`
这是一个3行2列的矩阵**Octave** 中的 `size()` 命令返回矩阵的尺寸。
所以 `size(A)` 命令返回3 2
![](../images/0f1fe8638058e229f1fc6c5b9cd4520c.png)
实际上,`size()` 命令返回的是一个 1×2 的矩阵,我们可以用 $sz$ 来存放。
设置 `sz = size(A)`
因此 $sz$ 就是一个1×2的矩阵第一个元素是3第二个元素是2。
所以如果键入 `size(sz)` 看看 $sz$ 的尺寸返回的是1 2表示是一个1×2的矩阵1 和 2分别表示矩阵$sz$的维度 。
你也可以键入 `size(A, 1)`将返回3这个命令会返回$A$矩阵的第一个元素,$A$矩阵的第一个维度的尺寸,也就是 $A$ 矩阵的行数。
同样,命令 `size(A, 2)`将返回2也就是 $A$ 矩阵的列数。
如果你有一个向量 $v$,假如 `v = [1 2 3 4]`,然后键入`length(v)`这个命令将返回最大维度的大小返回4。
你也可以键入`length(A)`,由于矩阵$A$是一个3×2的矩阵因此最大的维度应该是3因此该命令会返回3。
但通常我们还是对向量使用 $length$ 命令,而不是对矩阵使用 `length` 命令,比如
`length([1;2;3;4;5])`返回5。
如何在系统中加载数据和寻找数据:
当我们打开 **Octave** 时,我们通常已经在一个默认路径中,这个路径是 **Octave**的安装位置,`pwd` 命令可以显示出**Octave** 当前所处路径。
`cd`命令,意思是改变路径,我可以把路径改为**C:\\Users\\ang\\Desktop**,这样当前目录就变为了桌面。
如果键入 `ls`**ls** 来自于一个 **Unix** 或者 **Linux** 命令,**ls**命令将列出我桌面上的所有路径。
![](../images/0de527966203108b7efa1b6730bd966c.png)
事实上,我的桌面上有两个文件:**featuresX.dat** 和**priceY.dat**,是两个我想解决的机器学习问题。
**featuresX**文件如这个窗口所示是一个含有两列数据的文件其实就是我的房屋价格数据数据集中有47行第一个房子样本面积是2104平方英尺有3个卧室第二套房子面积为1600有3个卧室等等。
![](../images/e4080d69119a0e408581c81a66e133c8.png)
**priceY**这个文件就是训练集中的价格数据,所以 **featuresX** 和**priceY**就是两个存放数据的文档,那么应该怎样把数据读入 **Octave** 呢?我们只需要键入`featuresX.dat`,这样我将加载了 **featuresX** 文件。同样地我可以加载`priceY.dat`。其实有好多种办法可以完成,如果你把命令写成字符串的形式`load('featureX.dat')`,也是可以的,这跟刚才的命令效果是相同的,只不过是把文件名写成了一个字符串的形式,现在文件名被存在一个字符串中。**Octave**中使用引号来表示字符串。
另外 `who` 命令,能显示出 在我的 **Octave**工作空间中的所有变量
![](../images/7e85f313f721f53f3ae74664210a7a25.png)
所以我可以键入`featuresX` 回车,来显示 **featuresX**
![](../images/e49f56ceddd34dce986ae1dbdc399762.png)
这些就是存在里面的数据。
还可以键入 `size(featuresX)`,得出的结果是 47 2代表这是一个47×2的矩阵。
类似地,输入 `size(priceY)`,结果是 47
1表示这是一个47维的向量是一个列矩阵存放的是训练集中的所有价格$Y$ 的值。
`who` 函数能让你看到当前工作空间中的所有变量,同样还有另一个 `whos`命令,能更详细地进行查看。
![](../images/e8207c74976c4443d1ea25ec2a3b8477.png)
同样也列出我所有的变量,不仅如此,还列出了变量的维度。
**double** 意思是双精度浮点型,这也就是说,这些数都是实数,是浮点数。
如果你想删除某个变量,你可以使用 `clear` 命令,我们键入 `clear featuresX`,然后再输入 `whos` 命令,你会发现 **featuresX** 消失了。
另外,我们怎么储存数据呢?
我们设变量 `V= priceY(1:10)`
这表示的是将向量 $Y $的前10个元素存入 $V$中。
![](../images/a8c3b363f13820b4fc6463c7520ab58c.png)
假如我们想把它存入硬盘,那么用 `save hello.mat v` 命令,这个命令会将变量$V$存成一个叫 **hello.mat** 的文件,让我们回车,现在我的桌面上就出现了一个新文件,名为**hello.mat**。
由于我的电脑里同时安装了 **MATLAB**,所以这个图标上面有 **MATLAB**的标识,因为操作系统把文件识别为 **MATLAB**文件。如果在你的电脑上图标显示的不一样的话,也没有关系。
现在我们清除所有变量,直接键入`clear`,这样将删除工作空间中的所有变量,所以现在工作空间中啥都没了。
但如果我载入 **hello.mat** 文件,我又重新读取了变量 $v$,因为我之前把变量$v$存入了**hello.mat** 文件中,所以我们刚才用 `save`命令做了什么。这个命令把数据按照二进制形式储存,或者说是更压缩的二进制形式,因此,如果$v$是很大的数据,那么压缩幅度也更大,占用空间也更小。如果你想把数据存成一个人能看懂的形式,那么可以键入:
`save hello.txt v -ascii`
这样就会把数据存成一个文本文档,或者将数据的 **ascii 码**存成文本文档。
我键入了这个命令以后,我的桌面上就有了 **hello.txt**文件。如果打开它,我们可以发现这个文本文档存放着我们的数据。
这就是读取和储存数据的方法。
接下来我们再来讲讲操作数据的方法:
假如 $A$ 还是那个矩阵
![](../images/b39bf4e9212442464fe2f568dbe4fa0c.png)
跟刚才一样还是那个 3×2 的矩阵,现在我们加上索引值,比如键入 `A(3,2)`
这将索引到$A$ 矩阵的 (3,2) 元素。这就是我们通常书写矩阵的形式,写成 $A$ 323和2分别表示矩阵的第三行和第二列对应的元素因此也就对应 6。
我也可以键入`A(2,:)` 来返回第二行的所有元素,冒号表示该行或该列的所有元素。
类似地,如果我键入 `A(:,2)`,这将返回 $A$ 矩阵第二列的所有元素,这将得到 2 4 6。
这表示返回$A$ 矩阵的第二列的所有元素。
你也可以在运算中使用这些较为复杂的索引。
我再给你展示几个例子,可能你也不会经常使用,但我还是输入给你看 `A([1 3],:)`,这个命令意思是取 $A$ 矩阵第一个索引值为1或3的元素也就是说我取的是A矩阵的第一行和第三行的每一列冒号表示的是取这两行的每一列元素
![](../images/d1d551d0c540449d457e312a34434355.png)
可能这些比较复杂一点的索引操作你会经常用到。
我们还能做什么呢?依然是 $A$ 矩阵,`A(:,2)` 命令返回第二列。
你也可以为它赋值,我可以取 $A$ 矩阵的第二列然后将它赋值为10 11 12我实际上是取出了 $A$ 的第二列,然后把一个列向量[10;11;12]赋给了它,因此现在 $A$ 矩阵的第一列还是 1 3 5第二列就被替换为 10 11 12。
![](../images/f47bda45fd9beef6c600ebd48d163617.png)
接下来一个操作,让我们把 $A $设为`A = [A, [100, 101,102]]`,这样做的结果是在原矩阵的右边附加了一个新的列矩阵,就是把 $A$矩阵设置为原来的 $A$ 矩阵再在右边附上一个新添加的列矩阵。
![](../images/2e1c68a99a23d993674f08151e77dd44.png)
最后,还有一个小技巧,如果你就输入 `A(:)`,这是一个很特别的语法结构,意思是把 $A$中的所有元素放入一个单独的列向量,这样我们就得到了一个 9×1 的向量,这些元素都是$A$ 中的元素排列起来的。
再来几个例子:
我还是把 A 重新设为 [1 2; 3 4; 5 6],我再设一个 $B$为[11 12; 13 14; 15 16],我可以新建一个矩阵 $C$`C = [A B]`,这个意思就是把这两个矩阵直接连在一起,矩阵$A$ 在左边,矩阵$B$ 在右边,这样组成了 $C$矩阵,就是直接把$A$和 $B$ 合起来。
![](../images/e07db429512d4b3b5d641c52e606159d.png)
我还可以设`C = [A; B]`,这里的分号表示把分号后面的东西放到下面。所以,`[A;B]`的作用依然还是把两个矩阵放在一起,只不过现在是上下排列,所以现在 $A$ 在上面 $B$在下面,$C$ 就是一个 6×2 矩阵。
![](../images/7f11aa788b1b75c5a534d030b3ebc624.png)
简单地说,分号的意思就是换到下一行,所以 C 就包括上面的A然后换行到下面然后在下面放上一个 $B$。
另外顺便说一下,这个`[A B]`命令跟 `[A, B]` 是一样的,这两种写法的结果是相同的。
通过以上这些操作,希望你现在掌握了怎样构建矩阵,也希望我展示的这些命令能让你很快地学会怎样把矩阵放到一起,怎样取出矩阵,并且把它们放到一起,组成更大的矩阵。
通过几句简单的代码,**Octave**能够很方便地很快速地帮助我们组合复杂的矩阵以及对数据进行移动。这就是移动数据这一节课。
我认为对你来讲,最好的学习方法是,下课后复习一下我键入的这些代码好好地看一看,从课程的网上把代码的副本下载下来,重新好好看看这些副本,然后自己在**Octave** 中把这些命令重新输一遍,慢慢开始学会使用这些命令。
当然,没有必要把这些命令都记住,你也不可能记得住。你要做的就是,了解一下你可以用哪些命令,做哪些事。这样在你今后需要编写学习算法时,如果你要找到某个**Octave**中的命令,你可能回想起你之前在这里学到过,然后你就可以查找课程中提供的程序副本,这样就能很轻松地找到你想使用的命令了。
### 5.3 计算数据
参考视频: 5 - 3 - Computing on Data (13 min).mkv
现在,你已经学会了在**Octave**中如何加载或存储数据,如何把数据存入矩阵等等。在这段视频中,我将介绍如何对数据进行运算,稍后我们将使用这些运算操作来实现我们的学习算法。
这是我的 **Octave**窗口,我现在快速地初始化一些变量。比如设置$A$为一个3×2的矩阵设置$B$为一个3 ×2矩阵设置$C$为2 × 2矩阵。
我想算两个矩阵的乘积,比如说 $A × C$,我只需键入`A×C`,这是一个 3×2 矩阵乘以 2×2矩阵得到这样一个3×2矩阵。
![](../images/8ee5c7c05865e90f75feda99b9131319.png)
![](../images/38c956acb3bedf4362f40e6c5e8a692f.png)
你也可以对每一个元素,做运算 方法是做点乘运算`A.*B`这么做Octave将矩阵 $A$中的每一个元素与矩阵 $B$ 中的对应元素相乘:`A.*B`
这里第一个元素1乘以11得到11第二个元素2乘以12得到24这就是两个矩阵的元素位运算。通常来说在**Octave**中点号一般用来表示元素位运算。
![](../images/646de38bffd4f7f6601167d0c0686970.png)
这里是一个矩阵$A$,这里我输入`A.^2`,这将对矩阵$A$中每一个元素平方。
![](../images/d456e7501d7aaa9fa2ef8a89e89fa7e1.png)
我们设$V$为 [1; 2; 3] 是列向量,你也可以输入`1./V`,得到每一个元素的倒数,所以这样一来,就会分别算出 1/1 1/2 1/3。
矩阵也可以这样操作,`1./A` 得到$A$中每一个元素的倒数。
同样地,这里的点号还是表示对每一个元素进行操作。
我们还可以进行求对数运算,也就是对每个元素进行求对数运算。
![](../images/0c7c1d7726c09ffb45152cf153614003.png)
还有自然数$e$的幂次运算,就是以$e$为底,以这些元素为幂的运算。
![](../images/a36506049248948c82e598c8c254dc31.png)
我还可以用 **abs**来对 $v$ 的每一个元素求绝对值,当然这里 $v$都是正数。我们换成另一个这样对每个元素求绝对值,得到的结果就是这些非负的元素。还有$v$,给出$v$中每个元素的相反数,这等价于 -1 乘以 $v$,一般就直接用 $-v$
就好了,其实就等于 $-1*v$。
还有一个技巧,比如说我们想对$v$中的每个元素都加1那么我们可以这么做首先构造一个3行1列的1向量然后把这个1向量跟原来的向量相加因此$v$向量从[1 2 3] 增至 [2 3 4]。我用了一个,`length(v)`命令,因此这样一来,`ones(length(v) ,1)` 就相当于`ones(3,1)`,然后我做的是`v +ones(3,1)`,也就是将 $v$ 的各元素都加上这些1这样就将$v$ 的每个元素增加了1。
![](../images/9432bbcbfde53c7e0dcb1c7317b01c0c.png)
另一种更简单的方法是直接用 `v+1``v + 1` 也就等于把 $v$ 中的每一个元素都加上1。
![](../images/e02eec3ca4688ff1cb9126c8eb13bfed.png)
现在,让我们来谈谈更多的操作。
矩阵$A$ 如果你想要求它的转置那么方法是用A,将得出 A 的转置矩阵。当然,如果我写`(A')'`,也就是 $A$ 转置两次,那么我又重新得到矩阵 $A$。
还有一些有用的函数,比如: `a=[1 15 2 0.5]`这是一个1行4列矩阵`val=max(a)`,这将返回$A$矩阵中的最大值15。
我还可以写 `[val, ind] =max(a)`,这将返回$A$矩阵中的最大值存入$val$以及该值对应的索引元素15对应的索引值为2,存入$ind$,所以 $ind =2$。
特别注意一下,如果你用命令 `max(A)`$A$是一个矩阵的话,这样做就是对每一列求最大值。
我们还是用这个例子,这个 $a$ 矩阵`a=[1 15 2 0.5]`,如果输入`a<3`这将进行逐元素的运算所以元素小于3的返回1否则返回0
![](../images/fc04d42876c9d7d7bed51bade2077649.png)
因此返回[1 1 0 1]。也就是说$a$矩阵的每一个元素与3进行比较然后根据每一个元素与3的大小关系返回1和0表示真与假
如果我写 `find(a<3)`这将告诉我$a$ 中的哪些元素是小于3的
![](../images/ac9ad10f115d2d1cd15a0514c8ceeafa.png)
`A = magic(3)`**magic 函数**将返回一个矩阵称为魔方阵或幻方 (**magic squares**)它们具有以下这样的数学性质它们所有的行和列和对角线加起来都等于相同的值
当然据我所知这在机器学习里基本用不上但我可以用这个方法很方便地生成一个3行3列的矩阵而这个魔方矩阵这神奇的方形屏幕每一行每一列每一个对角线三个数字加起来都是等于同一个数
![](../images/f8c7f4f36183ef4b36bce427be2fce6f.png)
在其他有用的机器学习应用中这个矩阵其实没多大作用
如果我输入 `[r,c] = find(A>=7)`这将找出所有$A$矩阵中大于等于7的元素因此$r$ $c$分别表示行和列这就表示第一行第一列的元素大于等于7第三行第二列的元素大于等于7第二行第三列的元素大于等于7
![](../images/409a04487d1d7f039acfd61b3787f6aa.png)
顺便说一句其实我从来都不去刻意记住这个 **find 函数**到底是怎么用的我只需要会用**help函数**就可以了每当我在使用这个函数忘记怎么用的时候我就可以用 **help函数**键入 `help find` 来找到帮助文档
最后再讲两个内容一个是求和函数这是 $a$ 矩阵
![](../images/d7221c981ecc7730465710a0d8b49b34.png)
键入 `sum(a)`就把 a 中所有元素加起来了
如果我想把它们都乘起来键入 `prod(a)`**prod** 意思是**product(乘积)**它将返回这四个元素的乘积
`floor(a)` 是向下四舍五入因此对于 $a$ 中的元素0.5将被下舍入变成0
还有 `ceil(a)`表示向上四舍五入所以0.5将上舍入变为最接近的整数也就是1
键入 `type(3)`这通常得到一个3×3的矩阵如果键入 `max(rand(3),rand(3))`这样做的结果是返回两个3×3的随机矩阵并且逐元素比较取最大值
假如我输入`max(A,[],1)`这样做会得到每一列的最大值
所以第一列的最大值就是8第二列是9第三列的最大值是7这里的1表示取A矩阵第一个维度的最大值
相对地如果我键入`max(A,[],2)`这将得到每一行的最大值所以第一行的最大值是等于8第二行最大值是7第三行是9
![](../images/31bc524d1a19b4e9d8ea0974517a512e.png)
所以你可以用这个方法来求得每一行或每一列的最值另外你要知道默认情况下`max(A)`返回的是每一列的最大值如果你想要找出整个矩阵A的最大值你可以输入`max(max(A))`或者你可以将$A$ 矩阵转成一个向量然后键入 `max(A(:))`这样做就是把 $A$ 当做一个向量并返回 $A$向量中的最大值
最后让我们把 $A$设为一个9行9列的魔方阵魔方阵具有的特性是每行每列和对角线的求和都是相等的
这是一个9×9的魔方阵我们来求一个 `sum(A,1)`这样就得到每一列的总和这也验证了一个9×9的魔方阵确实每一列加起来都相等都为369
![](../images/0b9753a3e10bbdce0f26c3d44d61ae26.png)
现在我们来求每一行的和键入`sum(A,2)`这样就得到了$A$ 中每一行的和加起来还是369
现在我们来算$A $的对角线元素的和我们现在构造一个9×9 的单位矩阵键入 `eye(9)`,
然后我们要用 $A$逐点乘以这个单位矩阵除了对角线元素外其他元素都会得到0
键入`sum(sum(A.*eye(9))`
这实际上是求得了这个矩阵对角线元素的和确实是369
你也可以求另一条对角线的和也是是369
**flipup/flipud** 表示向上/向下翻转
同样地如果你想求这个矩阵的逆矩阵键入`pinv(A)`通常称为伪逆矩阵你就把它看成是矩阵 $A$ 求逆因此这就是 $A$矩阵的逆矩阵
`temp = pinv(A)`然后再用$temp$ 乘以$A$这实际上得到的就是单位矩阵对角线为1其他元素为0
如何对矩阵中的数字进行各种操作在运行完某个学习算法之后通常一件最有用的事情是看看你的结果或者说让你的结果可视化在接下来的视频中我会非常迅速地告诉你如何很快地画图如何只用一两行代码你就可以快速地可视化你的数据这样你就能更好地理解你使用的学习算法
### 5.4 绘图数据
参考视频: 5 - 4 - Plotting Data (10 min).mkv
当开发学习算法时往往几个简单的图可以让你更好地理解算法的内容并且可以完整地检查下算法是否正常运行是否达到了算法的目的
例如在之前的视频中我谈到了绘制成本函数$J(\theta)$可以帮助确认梯度下降算法是否收敛通常情况下绘制数据或学习算法所有输出也会启发你如何改进你的学习算法幸运的是**Octave**有非常简单的工具用来生成大量不同的图当我用学习算法时我发现绘制数据绘制学习算法等往往是我获得想法来改进算法的重要部分在这段视频中我想告诉你一些**Octave**的工具来绘制和可视化你的数据
我们先来快速生成一些数据用来绘图
![](../images/4514b422525aaac1e99add67e44882ee.png)
如果我想绘制正弦函数这是很容易的我只需要输入`plot(t,y1)`并回车就出现了这个图
![C:\\Users\\风度78\\AppData\\Roaming\\Tencent\\Users\\10822884\\QQ\\WinTemp\\RichOle\\8\~NJ7J5F0JE7}UOS%)(JN_G.png](../images/ff08dcc5b9718aa9c744e13fcc4fd607.png)
横轴是$t$变量纵轴是$y1$也就是我们刚刚所输出的正弦函数
让我们设置$y2$
![](../images/2d32a23ab895a8e765caf90a7679817e.png)
**Octave**将会消除之前的正弦图并且用这个余弦图来代替它这里纵轴$cos(x)$从1开始
![](../images/38969cc85853190ff3eac4f06398bc1b.png)
如果我要同时表示正弦和余弦曲线
我要做的就是输入`plot(t, y1)`得到正弦函数我使用函数**hold on****hold on**函数的功能是将新的图像绘制在旧的之上
我现在绘制$y2$输入`plot(t, y2)`。
我要以不同的颜色绘制余弦函数所以我在这里输入带引号的r绘制余弦函数$r$表示所使用的颜色`plot(t,y2,r)`再加上命令`xlabel('time')`
来标记X轴即水平轴输入`ylabel('value')`来标记垂直轴的值
![](../images/9eb4e496f34a801fd7ba5e85c4eec66b.png)
同时我也可以来标记我的两条函数曲线用这个命令 `legend('sin','cos')`将这个图例放在右上方表示这两条曲线表示的内容最后输入`title('myplot')`在图像的顶部显示这幅图的标题
![](../images/23594175efe66d5b9b1e687375a2dbda.png)
![](../images/c765aeee9c53e0e77d01d1e73cabd9b4.png)
如果你想保存这幅图像你输入`print dpng 'myplot.png'`**png**是一个图像文件格式如果你这样做了它可以让你保存为一个文件
**Octave**也可以保存为很多其他的格式你可以键入`help plot`。
最后如果你想删掉这个图像用命令**close**会让这个图像关掉
**Octave**也可以让你为图像标号
你键入`figure(1); plot(t, y1);`将显示第一张图绘制了变量$t$ $y1$。
键入`figure(2); plot(t, y2);` 将显示第一张图绘制了变量$t$ $y2$。
**subplot**命令我们要使用`subplot(1,2,1)`它将图像分为一个1\*2的格子也就是前两个参数然后它使用第一个格子也就是最后一个参数1的意思
![](../images/a786b8ed82ddd182f4595de2173cc84b.png)
我现在使用第一个格子如果键入`plot(t,y1)`现在这个图显示在第一个格子如果我键入`subplot(1,2,2)`那么我就要使用第二个格子键入`plot(t,y2)`现在y2显示在右边也就是第二个格子
最后一个命令你可以改变轴的刻度比如改成[0.5 1 -1 1]输入命令`axis([0.5 1 -1 1])`也就是设置了右边图的$x$轴和$y$轴的范围具体而言它将右图中的横轴的范围调整至0.5到1竖轴的范围为-1到1
![](../images/f13993f4784d01e7da769b4ec2545cd7.png)
你不需要记住所有这些命令如果你需要改变坐标轴或者需要知道**axis**命令你可以用**Octave**中用**help**命令了解细节
最后还有几个命令
`Clf`清除一幅图像)。
让我们设置A等于一个5×5的**magic**方阵
![](../images/82a990a5832ae2618d768551b90470dc.png)
我有时用一个巧妙的方法来可视化矩阵也就是`imagesc(A`)命令它将会绘制一个5\*5的矩阵一个5\*5的彩色格图不同的颜色对应A矩阵中的不同值
我还可以使用函数**colorbar**让我用一个更复杂的命令 `imagesc(A)colorbarcolormap gray`这实际上是在同一时间运行三个命令运行`imagesc`然后运行`colorbar`然后运行`colormap gray`。
它生成了一个颜色图像一个灰度分布图并在右边也加入一个颜色条所以这个颜色条显示不同深浅的颜色所对应的值
![](../images/881986ec5af9d86b6b14b260fb3b3618.png)
你可以看到在不同的方格它对应于一个不同的灰度
输入`imagesc(magic(15))colorbarcolormap gray`
![](../images/22a9a9536d4db17b6d64603fb54dce9e.png)
这将会是一幅15\*15的**magic**方阵值的图
最后总结一下这段视频你看到我所做的是使用逗号连接函数调用如果我键入$a=1$,$b=2$,$c=3$然后按**Enter**键,其实这是将这三个命令同时执行,或者是将三个命令一个接一个执行,它将输出所有这三个结果。
这很像$a=1$; $b=2$;$c=3$;如果我用分号来代替逗号,则没有输出出任何东西。
这里我们称之为逗号连接的命令或函数调用
![](../images/c0e8b7a19ced9a1dd006ed87d6323c9b.png)
用逗号连接是另一种**Octave**中更便捷的方式将多条命令例如`imagesc colorbar colormap`将这多条命令写在同一行中
现在你知道如何绘制**Octave**中不同的图像在下面的视频中我将告诉你怎样在Octave中写控制语句比如**if while for**语句并且定义和使用函数
### 5.5 控制语句forwhileif语句
参考视频: 5 - 5 - Control Statements\_ for, while, if statements (13 min).mkv
在这段视频中我想告诉你怎样为你的 **Octave** 程序写控制语句诸如"**for**" "**while**" "**if**" 这些语句并且如何定义和使用方程
我先告诉你如何使用 “**for**” 循环
首先我要将 $v$ 值设为一个10行1列的零向量
![](../images/26a912d550af9fd43a7ae62e3b610e97.png)
接着我要写一个 “**for**" 循环 $i$ 等于 1 10写出来就是 `i = 1:10`我要设$ v(i)$的值等于 2 $i$ 次方循环最后写上“**end**”。
向量$v$ 的值就是这样一个集合 2的一次方2的二次方依此类推这就是我的 $i$ 等于 1 10的语句结构 $i$ 遍历 1 10的值
![](../images/890f6ea8f857f22002f98c20d52b3bb8.png)
另外你还可以通过设置你的 indices (索引) 等于 1一直到10来做到这一点这时**indices** 就是一个从1到10的序列
你也可以写 `i = indices`这实际上和我直接把 i 写到 1 10 是一样你可以写 `disp(i)`也能得到一样的结果所以 这就是一个 “**for**” 循环
![](../images/fc956ae1291dc7d3b819e471d1962398.png)
如果你对 “**break**” “**continue**” 语句比较熟悉**Octave**里也有 “**break**” “**continue**”语句你也可以在 **Octave**环境里使用那些循环语句
但是首先让我告诉你一个 **while** 循环是如何工作的
![](../images/1b37f81896a59145576ae996c9dd4d16.png)
这是什么意思呢我让 $i$ 取值从 1 开始然后我要让 $v(i)$ 等于 100再让 $i$ 递增 1直到$i$ 大于 5停止
现在来看一下结果我现在已经取出了向量的前五个元素把他们用100覆盖掉这就是一个**while**循环的句法结构
现在我们来分析另外一个例子
![](../images/279cfbda6b4d9a2ced1332f086db4d9e.png)
这里我将向你展示如何使用**break**语句比方说 `v(i) = 999`然后让 `i = i+1` $i$ 等于6的时候 **break** (停止循环)结束 (**end**)。
当然这也是我们第一次使用一个 **if** 语句所以我希望你们可以理解这个逻辑 $i$ 等于1 然后开始下面的增量循环**while**语句重复设置 $v(i)$ 等于999不断让$i$增加然后当 $i$ 达到6做一个中止循环的命令尽管有**while**循环语句也就此中止所以最后的结果是取出向量 $v$ 的前5个元素并且把它们设置为999
所以这就是**if** 语句和 **while** 语句的句法结构并且要注意要有**end**上面的例子里第一个 **end** 结束的是 **if**
语句第二个 **end** 结束的是 **while** 语句
现在让我告诉你使用 **if-else** 语句
![](../images/3fce4367c960bb6fbc492a3b8f9ddc5d.png)
最后提醒一件事如果你需要退出 **Octave**你可以键入`exit`命令然后回车就会退出 **Octave**或者命令`quit`也可以
最后让我们来说说函数 (**functions**)如何定义和调用函数
我在桌面上存了一个预先定义的文件名为 “**squarethisnumber.m**”,这就是在 **Octave** 环境下定义的函数
让我们打开这个文件请注意我使用的是微软的写字板程序来打开这个文件我只是想建议你如果你也使用微软的**Windows**系统那么可以使用写字板程序而不是记事本来打开这些文件如果你有别的什么文本编辑器也可以记事本有时会把代码的间距弄得很乱如果你只有记事本程序那也能用我建议你用写字板或者其他可以编辑函数的文本编辑器
现在我们来说如何在 **Octave** 里定义函数
这个文件只有三行
![](../images/247f96f4e7ab7a259ac9ef1eebe0b503.png)
第一行写着 `function y = squareThisNumber(x)`这就告诉 **Octave**我想返回一个 y值我想返回一个值并且返回的这个值将被存放于变量 $y$ 另外它告诉了**Octave**这个函数有一个参数就是参数 $x$还有定义的函数体也就是 $y$ 等于 $x$ 的平方
还有一种更高级的功能这只是对那些知道“**search path** (**搜索路径**)”这个术语的人使用的所以如果你想要修改
**Octave**的搜索路径你可以把下面这部分作为一个进阶知识或者选学材料仅适用于那些熟悉编程语言中搜索路径概念的同学
你可以使用**addpath** 命令添加路径添加路径“**C:\\Users\\ang\\desktop**”将该目录添加到**Octav**e的搜索路径这样即使你跑到其他路径底下**Octave**依然知道会在 **Users\\ang\\desktop**目录下寻找函数。这样,即使我现在在不同的目录下,它仍然知道在哪里可以找到“**SquareThisNumber** 这个函数
但是如果你不熟悉搜索路径的概念不用担心只要确保在执行函数之前先用 `cd`命令设置到你函数所在的目录下实际上也是一样的效果
**Octave**还有一个其他许多编程语言都没有的概念那就是它可以允许你定义一个函数使得返回值是多个值或多个参数这里就是一个例子定义一个函数叫
“`SquareAndCubeThisNumber(x)`” ($x$的平方以及$x$的立方)
这说的就是函数返回值是两个 $y1$ $y2$接下来就是$y1$是被平方后的结果$y2$是被立方后的结果这就是说函数会真的返回2个值
有些同学可能会根据你使用的编程语言比如你们可能熟悉的**C****C++**通常情况下认为作为函数返回值只能是一个值**Octave** 的语法结构就不一样可以返回多个值
如果我键入 `[a,b] = SquareAndCubeThisNumber(5)`然后$a$就等于25$b$ 就等于5的立方125
所以说如果你需要定义一个函数并且返回多个值这一点常常会带来很多方便
最后我来给大家演示一下一个更复杂一点的函数的例子
比方说我有一个数据集像这样数据点为[1,1], [2,2],[3,3]我想做的事是定义一个 **Octave** 函数来计算代价函数 $J(\theta)$就是计算不同 $\theta$值所对应的代价函数值$J$。
首先让我们把数据放到 **Octave** 我把我的矩阵设置为`X = [1 1; 1 2; 1 3];`
![](../images/3c857152ef3f0d6b374e4863289d1c60.png)
请仔细看一下这个函数的定义确保你明白了定义中的每一步
![](../images/e01d6da07890e32d46d0616741a3fe64.png)
现在当我在 **Octave** 里运行时我键入 `J = costFunctionJ (X, y, theta)`它就计算出 $J$等于0这是因为如果我的数据集$x$ [1;2;3] $y$ 也为 [1;2;3] 然后设置 $\theta_0$ 等于0$\theta_1$等于1这给了我恰好45度的斜线这条线是可以完美拟合我的数据集的
而相反地如果我设置$\theta$ 等于[0;0]那么这个假设就是0是所有的预测值和刚才一样设置$\theta_0$ = 0$\theta_1$也等于0然后我计算的代价函数结果是2.333实际上他就等于1的平方也就是第一个样本的平方误差加上2的平方加上3的平方然后除以$2m$也就是训练样本数的两倍这就是2.33
![](../images/885b5d19c33f545292ccc1b69976c789.png)
因此这也反过来验证了我们这里的函数计算出了正确的代价函数这些就是我们用简单的训练样本尝试的几次试验这也可以作为我们对定义的代价函数$J$进行了完整性检查确实是可以计算出正确的代价函数的至少基于这里的 $x$ $y$是成立的也就是我们这几个简单的训练集至少是成立的
现在你知道如何在 **Octave** 环境下写出正确的控制语句比如 **for 循环**、**while 循环** **if语句**以及如何定义和使用函数
在接下来的**Octave** 教程视频里我会讲解一下向量化这是一种可以使你的 **Octave**程序运行非常快的思想
### 5.6 向量化
参考视频: 5 - 6 - Vectorization (14 min).mkv
在这段视频中我将介绍有关向量化的内容无论你是用**Octave**还是别的语言比如**MATLAB**或者你正在用**Python**、**NumPy** **Java C C++**所有这些语言都具有各种线性代数库这些库文件都是内置的容易阅读和获取他们通常写得很好已经经过高度优化通常是数值计算方面的博士或者专业人士开发的
而当你实现机器学习算法时如果你能好好利用这些线性代数库或者数值线性代数库并联合调用它们而不是自己去做那些函数库可以做的事情如果是这样的话那么通常你会发现首先这样更有效也就是说运行速度更快并且更好地利用你的计算机里可能有的一些并行硬件系统等等其次这也意味着你可以用更少的代码来实现你需要的功能因此实现的方式更简单代码出现问题的有可能性也就越小
举个具体的例子与其自己写代码做矩阵乘法如果你只在**Octave**中输入$a$乘以$b$就是一个非常有效的两个矩阵相乘的程序有很多例子可以说明如果你用合适的向量化方法来实现你就会有一个简单得多也有效得多的代码
让我们来看一些例子这是一个常见的线性回归假设函数${{h}_{\theta }}(x)=\sum\limits_{j=0}^{n}{{{\theta }_{j}}{{x}_{j}}}$
如果你想要计算$h_\theta(x)$ 注意到右边是求和那么你可以自己计算$j = 0$ $ j = n$ 的和但换另一种方式来想想 $h_\theta(x)$ 看作$\theta^Tx$那么你就可以写成两个向量的内积其中$\theta$就是$\theta_0$、$\theta_1$、$\theta_2$如果你有两个特征量如果 $n = 2$,并且如果你把 $x$ 看作$x_0$、$x_1$、$x_2$这两种思考角度会给你两种不同的实现方式
![](../images/7fefb92d8680e4a15f947cd2ca24a9ac.png)
比如说这是未向量化的代码实现方式
![](../images/125c07019cb39675085fe3b80b85fca5.png)
计算$h_\theta(x)$是未向量化的我们可能首先要初始化变量 $prediction$ 的值为0.0而这个变量$prediction$ 的最终结果就是$h_\theta(x)$然后我要用一个 **for** 循环$j$ 取值 0 $n+1$变量$prediction$ 每次就通过自身加上$ theta(j) $乘以 $x(j)$更新值这个就是算法的代码实现
顺便我要提醒一下这里的向量我用的下标是0所以我有$\theta_0$、$\theta_1$、$\theta_2$但因为**MATLAB**的下标从1开始 **MATLAB** $\theta_0$我们可能会用 $theta(1)$ 来表示这第二个元素最后就会变成$theta(2$) 而第三个元素最终可能就用$theta(3)$表示因为**MATLAB**中的下标从1开始这就是为什么这里我的 **for 循环**$j$取值从 1 直到$n+1$而不是从 0 $n$。这是一个未向量化的代码实现方式我们用一个 **for 循环** $n$ 个元素进行加和
作为比较接下来是向量化的代码实现
![](../images/829e153c119919f80c058d1bc703a08b.png)
你把x和$\theta$看做向量而你只需要令变量$prediction$等于$theta$转置乘以$x$你就可以这样计算与其写所有这些for循环的代码你只需要一行代码这行代码就是利用 **Octave** 的高度优化的数值线性代数算法来计算两个向量$\theta$以及$x$的内积这样向量化的实现更简单它运行起来也将更加高效这就是 **Octave** 所做的而向量化的方法在其他编程语言中同样可以实现
让我们来看一个**C++** 的例子
![](../images/987fc9372da3b167fd16d7a19722405b.png)
与此相反使用较好的**C++**数值线性代数库你可以写出像右边这样的代码因此取决于你的数值线性代数库的内容你只需要在**C++**中将两个向量相乘根据你所使用的数值和线性代数库的使用细节的不同你最终使用的代码表达方式可能会有些许不同但是通过一个库来做内积你可以得到一段更简单更有效的代码
现在让我们来看一个更为复杂的例子这是线性回归算法梯度下降的更新规则
![](../images/6ad266a30f955db5b905905670aabfc5.png)
我们用这条规则对$ j$ 等于 012等等的所有值更新对象$\theta_j$我只是用$\theta_0$、$\theta_1$、$\theta_2$来写方程假设我们有两个特征量所以$n$等于2这些都是我们需要对$\theta_0$、$\theta_1$、$\theta_2$进行更新这些都应该是同步更新我们用一个向量化的代码实现这里是和之前相同的三个方程只不过写得小一点而已
你可以想象实现这三个方程的方式之一就是用一个 **for 循环**就是让 $j$等于0等于1等于2来更新$\theta_j$。但让我们用向量化的方式来实现看看我们是否能够有一个更简单的方法基本上用三行代码或者一个**for 循环**一次实现这三个方程让我们来看看怎样能用这三步并将它们压缩成一行向量化的代码来实现做法如下
我打算把$\theta$看做一个向量然后我用$\theta$-$\alpha$ 乘以某个别的向量$\delta$ 来更新$\theta$。
这里的 $\delta$ 等于
![](../images/fa662ec6d5703d85c314f5e4792a7468.png)
让我解释一下是怎么回事我要把$\theta$看作一个向量有一个 $n+1$ 维向量$\alpha$ 是一个实数$\delta$在这里是一个向量
![](../images/20bc912bae44e66125f8bfcec6e720c7.png)
所以这个减法运算是一个向量减法因为 $\alpha$ 乘以 δ是一个向量所以$\theta$就是$\theta$ - $\alpha \delta$得到的向量
那么什么是向量 $\delta$ ?
![](../images/541b9f097a8e1357c2a75e4f64e53b54.png)
$X^{(i)}$是一个向量
![](../images/0a03d239f2f1d1af057d492bcce276f4.png)
你就会得到这些不同的式子然后作加和
![](../images/8d10103bf172a889090690a00037ffa1.png)
实际上在以前的一个小测验如果你要解这个方程我们说过为了向量化这段代码我们会令`u = 2v +5w`因此我们说向量$u$等于2乘以向量$v$加上5乘以向量$w$。用这个例子说明如何对不同的向量进行相加这里的求和是同样的道理
![](../images/c84012101afc6836a3396893695d9669.png)
这就是为什么我们能够向量化地实现线性回归
所以我希望步骤是有逻辑的请务必看视频并且保证你确实能理解它如果你实在不能理解它们数学上等价的原因你就直接实现这个算法也是能得到正确答案的所以即使你没有完全理解为何是等价的如果只是实现这种算法你仍然能实现线性回归算法如果你能弄清楚为什么这两个步骤是等价的那我希望你可以对向量化有一个更好的理解如果你在实现线性回归的时候使用一个或两个以上的特征量
有时我们使用几十或几百个特征量来计算线性归回当你使用向量化地实现线性回归通常运行速度就会比你以前用你的**for循环**快的多也就是自己写代码更新$\theta_0$、$\theta_1$、$\theta_2$。
因此使用向量化实现方式你应该是能够得到一个高效得多的线性回归算法而当你向量化我们将在之后的课程里面学到的算法这会是一个很好的技巧无论是对于Octave 或者一些其他的语言 如C++、Java 来让你的代码运行得更高效
### 5.7 工作和提交的编程练习
参考视频: 5 - 7 - Working on and Submitting Programming Exercises (4 min).mkv
在这段视频中我想很快地介绍一下这门课程做作业的流程以及如何使用作业提交系统这个提交系统可以即时检验你的机器学习程序答案是否正确
![](../images/feddafffd2937841222e6e8e2cfb9401.png)
'ml-class-ex1'目录中我们提供了大量的文件其中有一些需要由你自己来编辑因此第一个文件应该符合编程练习中pdf文件的要求其中一个我们要求你编写的文件是warmUpExercise.m这个文件这个文件只是为了确保你熟悉提交系统
你需要做的就是提交一个5×5的矩阵就是`A = eye(5)`这将修改该函数以产生5×5的单位矩阵现在`warmUpExercise()`这个方程就实现了返回5x5的单位矩阵将它保存一下所以我已经完成了作业的第一部分。
![](../images/d29dc95c23570991ae49d262ce351d7e.png)
现在回到我的 **Octave** 窗口现在来到我的目录**C:\\Users\\ang\\Desktop\\ml-class-ex1**如果我想确保我已经实现了程序 像这样输入`warmUpExercise()`好了它返回了我们用刚才写的代码创建的一个5x5的单位矩阵
![](../images/c1db5363de0286d223444d6a9225a393.png)
我现在可以按如下步骤提交代码我要在这里目录下键入`submit()`。我要提交第一部分 所以我选择输入'`1`'。这时它问我的电子邮件地址我们打开课程网站输入用户名密码
![](../images/3663f8b6d0612a40204450dcf0df2257.png)
按下回车键它连接到服务器并将其提交然后它就会立刻告诉你恭喜您已成功完成作业1第1部分这就确认了你已经做对了第一部分练习如果你提交的答案不正确那么它会给你一条消息说明你没有完全答对您还可以继续使用此提交密码也可以生成新密码你的密码是否会显示出来取决于你使用的操作系统
这就是提交作业的方法你完成家庭作业的时候我希望你都能答对