由于最近时间比较紧,要学的东西也比较多,所以这篇文章会写得比较粗略,主要目的也是保存自己的代码,以及方便自己日后回忆。
首先我们将要定义一个 J J J 函数,意在表达当前函数与训练数据间的差异值, J J J 函数的值越大,表示在参数为 θ \theta θ 时当前函数 h θ ( x ) h_{\theta}(x) hθ(x) 与训练数据 y y y 的拟合程度越差。下面给出 J J J 函数的定义式:
J θ = 1 2 m ∗ ∑ i = 1 m ( h θ ( x ( i ) ) − y ( i ) ) J_{\theta} = \frac {1} {2m} * \sum_{i=1}^{m} {(h_{\theta}(x^{(i)})-y^{(i)})} Jθ=2m1∗i=1∑m(hθ(x(i))−y(i))
J J J 函数有一些喜人的性质,比如局部最优解等于全局最优解,正是这个性质使得我们可以放心地使用梯度下降算法来训练算法。
下面是 J J J 函数关于 θ \theta θ 值的 surf 图和 contour 图,图三中的 mark 表示经过数次学习后使 J J J 函数达到收敛的参数的所在位置。
可以被想象为一个小球处于某个初始状态,不断向山谷即极小值点滚动的过程。这里的山谷指的是 J J J 函数的值关于 θ \theta θ 的变化。
θ j = θ j − α 1 m ∑ i = 1 m ( h θ ( x ( i ) ) − y ( i ) ) x j ( i ) \theta_{j} = \theta_{j} - \alpha \frac {1} {m} \sum_{i=1}^m {(h_{\theta}(x^{(i)})-y^{(i)})} x^{(i)}_j θj=θj−αm1i=1∑m(hθ(x(i))−y(i))xj(i)
内置的向量库要比手写循环快很多,所以代码实现的时候尽可能用向量表示。
clear;
close all;
clc;
% ====================================================
fprintf('load the ex1data2.txt...\n');
data = load('ex1data2.txt');
X = data(:, 1:2);
y = data(:, 3);
m = length(y); % number of sample case
fprintf('load successful!\n\n\n');
fprintf('init the data...\n');
[X, mu, stdd] = init_data(X);
X = [ones(m,1), X];
fprintf('init successful!\n\n\n');
% ====================================================
theta = [0; 0; 0];
alpha = 0.2;
iteration = 50;
fprintf('init the parameter, successful!\n');
[theta, J_his] = gra_des(X, y, theta, alpha, iteration);
cur_J = J_his(size(J_his), 1);
fprintf('cur_J = %f\n',cur_J);
plot((1:50), J_his(:, 1));
query = [1650, 3];
query = [ones(1,1), (query - mu) ./ stdd * 3];
tmp = query * theta;
fprintf('Predicted price of a 1650 sq-ft, 3 br house is %f\n',tmp);
为了避免各个维度的数据之间相差过大,梯度下降时不收敛,把所有数据通过加减和缩放变为均值为 0,标准差为 1 的数据。并把缩放记录保存下来,在预测其他数据时需要使用。
function [res, mu, stdd] = init_data(X)
mu = mean(X); minn = min(X); maxx = max(X);
stdd = maxx - minn;
for i = 1:size(X, 2)
X(:, i) = X(:, i) - mu(1, i);
if maxx(1, i) ~= minn(1, i)
X(:, i) = X(:, i) / stdd(1, i) * 3;
end;
end;
res = X;
这个很简单,不多bb。
function J = calc_J(X, y, theta)
m = length(y);
J = 0;
J = 1/(2*m) * sum(((X*theta)-y).^2);
把 J J J 函数的变化曲线也保存下来,这样调 α \alpha α (学习速率) 的时候很方便。
function [theta, J_his] = gra_des(X, y, theta, alpha, iteration)
m = length(y);
J_his = zeros(m, 1);
for iter = 1:iteration
theta = theta - (alpha/m) * (X'*(X*theta-y));
J_his(iter, 1) = calc_J(X, y, theta);
end;
现在一看简单得一匹,我为什么当时实现了那么久?