转载:http://blog.csdn.net/and_w/article/details/52682905
作者:John Wittenauer
翻译:GreatX
源:Machine Learning Exercises In Python, Part 1
这篇文章是一系列 Andrew Ng 在 Coursera 上的机器学习课程的练习的一部分。这篇文章的原始代码,练习文本,数据文件可从这里获得。
Part 1 简单线性回归(Simple Linear Regression)
Part 2 多元线性回归(Multivariate Linear Regression)
Part 3 逻辑回归(Logistic Regression)
Part 4 多元逻辑回归(Multivariate Logistic Regression)
Part 5 神经网络(Neural Networks)
Part 6 支持向量机(Support Vector Machines)
Part 7 K-均值聚类与主成分分析(K-Means Clustering & PCA)
Part 8 异常检测与推荐(Anomaly Detection & Recommendation)
这些年来,我专业开发的一个关键时刻是当我发现 Coursera 的时候。我以前听过慕课现象,但我从来没有时间深入进去并上一门课。今年早些时候我终于“铤而走险”(pull the trigger)并注册了一门 Andrew Ng 的机器学习课程。我完成了从开始到结束所有的步骤,包括所有的编程练习。这次经历为我打开了新世界的大门,让我感到了这个教育平台的力量,我从此沉迷其中,不可自拔。
这篇文章将是一系列包含编程练习的 Andrew 课程的第一篇。
这个课程我不太在乎的一方面是用 Octave 做作业。
尽管 Octave/Matlab 是一个好的平台,但大多数现实世界的“数据科学家”不是用 R 就是用 Python(当然,也是其他的语言和工具正在被使用,但这两个毫无疑问排在首位)。自从我打算开发我的 Python 技能,我就决定通过这些练习从零开始学习 Python 。完整的源代码可以在我的 GitHub 上的IPython repo找到。
如果你有兴趣,你也可以在根目录的子文件夹下找到这些练习中用到的数据和原始练习 PDF 。
虽然随着时间的推移我可以解释一些练习中涉及的概念,但我不可能解释所有的你可能需要完全理解的信息。如果你真的被机器学习所吸引但还没有深入了解,我推荐你去看这门课(它是完全免费的,也不需要做任何承诺)。正因为如此,让我们开始吧!
在 练习 1 的第一部分,我们被要求用简单线性回归实现预测食物卡车的利润。假设你是一家连锁餐馆的 CEO 正在考虑在不同的城市开一家新店。连锁店在不同的城市已经有卡车了并且你有不同城市人口和利润的数据。你想找出一个新的食品卡车期望的利润,通过只给出它将被放置的城市的人口数。
让我们通过检查数据开始,数据在我的 repository 上的 data 目录下的 ex1data1.txt 文件中。首先,我需要导入一些库。
import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
现在,让我们开始吧。我们可以使用 pandas 将数据载入到数据框(DataFrame)并且用 head 函数显示前几行。
path = os.getcwd() + '\data\ex1data1.txt'
data = pd.read_csv(path, header=None, names=['Population', 'Profit'])
data.head()
Population | Profit | |
---|---|---|
0 | 6.1101 | 17.5920 |
1 | 5.5277 | 9.1302 |
2 | 8.5186 | 13.6620 |
3 | 7.0032 | 11.8540 |
4 | 5.8598 | 6.8233 |
pandas 提供的另一个开箱即用的函数是 describe,它能够对一个数据集进行基本的统计计算。这对于在一个项目的探索分析阶段对数据的整体“感觉”有帮助。
data.describe()
Population | Profit | |
---|---|---|
count | 97.000000 | 97.000000 |
mean | 8.159800 | 5.839135 |
std | 3.869884 | 5.510262 |
min | 5.026900 | -2.680700 |
25% | 5.707700 | 1.986900 |
50% | 6.589400 | 4.562300 |
75% | 8.578100 | 7.046700 |
max | 22.203000 | 24.147000 |
检查你数据的统计是有帮助的,但有时候你需要让它可视化。幸运的是这个数据集只有一个因变量,所以我们可以把它放在散点图( scatter plot)来看看它到底是什么样子的。我们可以使用 pandas 提供的 plot 函数来完成,它真的只是 matplotlib 的包装。
data.plot(kind='scatter', x='Population', y='Profit', figsize=(12,8))
它真的有助于实际上看看到底是什么样的,不是吗?我们可以清楚的看到较少人口城市值的集群,还有某种程度上增长的利润和城市规模增长的线性趋势。现在让我们来到有趣的部分——从零开始用 python 实现一种线性回归算法!
如果你不熟悉线性回归,它是一种建立一个因变量和一个或多个自变量(如果只有一个自变量那么它被称为简单线性回归,如果有多个自变量那么他被称为多元线性回归)之间关系的方法。有很多不同类型的线性回归并且差异很大,这超过这里讨论的范围,所以我不会去管,简单地说——我们试图创建一个关于数据 X 是什么(we could accurately predict what the outcome y would be without actually knowing what y is)。
在这个实现中,我们将要使用一种被称为梯度下降(gradient descent)的技术来找出参数 θ 。如果你熟悉线性代数,你可能会意识到对线性模型有另一种方法来找到最佳参数,它被称为正规方程(normal equation),它能基本上解决问题一旦用上了一系列矩阵计算。然而,这种方法的问题是它对大数据集的递增适应性不好。相反,我们可以用变种的梯度下降和其他优化方法去衡量无限大规模的数据集,所以对于机器学习问题这种方法更实用。
已经有足够的理论了。让我们来写一些代码吧。首先我们需要一个代价(cost)函数。代价函数评估我们模型的质量,通过(模型参数和实际数据点)计算我们模型对数据点的预测的误差(error)。例如,如果给定城市人口是 4 而我们预测值是 7,我们的误差就是 (7-4)^2=3^2=9(假设一个L2约束或“最小二乘”损失函数)。我们对每个 X 变量的数据点做计算并求和得到代价函数。以下是这个函数:
def computeCost(X, y, theta):
inner = np.power(((X * theta.T) - y), 2)
return np.sum(inner) / (2 * len(X))
注意这里没有循环。我们利用 numpy 的线性代数能力计算一系列矩阵运算的结果。这远比未优化的 for 循环计算效率高。
为了让代价函数能够无缝地应用在我们之前创建的 pandas 数据框上,我们需要做一些操作。首先,为了使矩阵运算正确(我不会详细的说明为什么这是必要的,但如果你感兴趣它在练习的文本中——基本上,它解释为在线性方程中的截距项( intercept term))我们需要在数据框的开头插入一列 1 。其次,我们需要将我们的数据分为自变量 X 。
# append a ones column to the front of the data set
data.insert(0, 'Ones', 1)
# set X (training data) and y (target variable)
cols = data.shape[1]
X = data.iloc[:,0:cols-1]
y = data.iloc[:,cols-1:cols]
最后,我们需要将我们的数据框转化为 numpy 矩阵并且实例化参数矩阵。
# convert from data frames to numpy matrices
X = np.matrix(X.values)
y = np.matrix(y.values)
theta = np.matrix(np.array([0,0]))
记住调试矩阵运算的一个有用的技巧是看你处理矩阵的 shape(译者注:形状,行列数)。它也有助于记住当在你脑海中进行矩阵乘法看起来像 (i x j) * (j x k) = (i x k)时,i,j,k 是矩阵相应维的 shape。
X.shape, theta.shape, y.shape
((97L, 2L), (1L, 2L), (97L, 1L))
现在,我们测试我们的代价函数了。记住参数被初始化为 θ 所以结果还并不是最优的,但我们能够知道它是否能正常运行。
computeCost(X, y, theta)
32.072733877455676
到目前为止运行良好。现在我们需要定义一个函数用定义在练习文本中的更新规则在参数 θ 上执行梯度下降。下面是梯度下降的函数:
def gradientDescent(X, y, theta, alpha, iters):
temp = np.matrix(np.zeros(theta.shape))
parameters = int(theta.ravel().shape[1])
cost = np.zeros(iters)
for i in range(iters):
error = (X * theta.T) - y
for j in range(parameters):
term = np.multiply(error, X[:,j])
temp[0,j] = theta[0,j] - ((alpha / len(X)) * np.sum(term))
theta = temp
cost[i] = computeCost(X, y, theta)
return theta, cost
梯度下降的思想是对于每次迭代,为了找出合适的方向来移动我们的参数向量我们计算出误差项(error term )的梯度。换句话说,我们为了减少误差正在计算参数需要的改变,从而使我们的结果更接近最佳的结果(即最合适)。
这是一个相当复杂的话题,我可以用一个整篇博客来讨论梯度下降。如果你对学习更多这方面的东西感兴趣的话,我建议从这篇文章开始,并从那里拓展出来。
我们又一次依靠 numpy 和线性代数获得解。你可能注意到我的实现并不是 100% 最优的。特别的,有方法能消除内循环并一次性更新所有参数。暂时,我将把它留给读者(我将在下一篇文章中讨论它)。
现在,我们已经有一种方法评估结果也有了一种方法找出好的结果,是时候将它们应用到我们的数据集了。
# initialize variables for learning rate and iterations
alpha = 0.01
iters = 1000
# perform gradient descent to "fit" the model parameters
g, cost = gradientDescent(X, y, theta, alpha, iters)
g
matrix([[-3.24140214, 1.1272942 ]])
注意,在这里我们已经初始化了一些新变量。如果你仔细看看梯度下降函数,它有参数叫alpha(α)和 iters。alpha 是学习率(learning rate)——它是一个在更新规则中的因素对于这些参数帮助确定算法多快能收敛到最优解。iters 是迭代次数。并没有明确规定怎么初始化这些参数,通常都是不断摸索(trial-and-error)出来的。
我们现在有一个参数向量描述对我们的数据集而言什么是我们认为的最优线性模型。一个快速的方法来评估我们的回归模型有多好是——看看我们的新解对数据集的总误差是多少:
computeCost(X, y, g)
4.5159555030789118
这确实比 32 好,但它并不是一个直观的方法。幸运的是,有一些其他的技术任我们使用。
我们现在将使用 matplotlib 来使我们的结果可视化。还记得之前的散点图吗?让我们放一条代表我们模型的线在散点图上来看看它有多适合。我们可以用 numpy 的 linspace 函数来创建均匀分布的点在我们数据范围内,然后用我们的模型评估这些点,来看看期望的利润是多少。然后,我们可以把它做成一个线图并绘制出来。
x = np.linspace(data.Population.min(), data.Population.max(), 100)
f = g[0, 0] + (g[0, 1] * x)
fig, ax = plt.subplots(figsize=(12,8))
ax.plot(x, f, 'r', label='Prediction')
ax.scatter(data.Population, data.Profit, label='Traning Data')
ax.legend(loc=2)
ax.set_xlabel('Population')
ax.set_ylabel('Profit')
ax.set_title('Predicted Profit vs. Population Size')
不错!我们的解对于这个数据集看起来像是最优线性模型。由于梯度下降函数也输出了一个向量和每次训练迭代的代价(cost),我们也可以绘制出来。
fig, ax = plt.subplots(figsize=(12,8))
ax.plot(np.arange(iters), cost, 'r')
ax.set_xlabel('Iterations')
ax.set_ylabel('Cost')
ax.set_title('Error vs. Training Epoch')
注意,代价总是降低——这是一个被称为凸优化问题(convex optimization problem)的一个例子。如果你对这个问题绘制了整个解空间(即对所有每可能的参数值绘制将代价当作一个模型参数的函数)你会看到它像一个“盆地”的“碗”形它代表最优解。
暂时就这些!在 第二部分 我们将通过拓展这个例子到多个变量来结束第一个练习。我也将展示上面的解如何用一个受欢迎的机器学习库 scikit-learn 来实现。