线性模型形式如下:
综上,实际中往往采用的是梯度下降(Gradient Descent)方法:
因为代价函数对 θj θ j 求偏导得到:
同样地,将其转化为矩阵形式
注:这里 α α 是学习速率, θ θ 是(n+1, 1)的向量; X是(m, n+1)的矩阵; Y是(m, 1)的向量, hθ(X) h θ ( X ) 也是(m, 1)的向量:
这里导入的是高炉数据,高炉的含硅(Si)量预测可是个经典的问题
首先获取原始数据的DataFrame : data
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
# 这是我的环境需要,大多数人并不需要加以下两行代码
import sys
sys.path.append("C:\\python\\lib\\site-packages")
%matplotlib inline
data = pd.read_csv("raw/blast _furnance.csv")
print(data.describe())
得到结果:
Si | S | coal | wind | |
---|---|---|---|---|
count | 1000.000000 | 1000.000000 | 1000.000000 | 1000.000000 |
mean | 0.459410 | 0.023788 | 13.159400 | 1759.686810 |
std | 0.110965 | 0.009743 | 2.121651 | 80.083738 |
min | 0.210000 | 0.006000 | 0.120000 | 833.520000 |
25% | 0.380000 | 0.017000 | 12.447500 | 1733.732500 |
50% | 0.440000 | 0.023000 | 13.545000 | 1775.240000 |
75% | 0.520000 | 0.030000 | 14.370000 | 1802.390000 |
max | 1.260000 | 0.078000 | 17.700000 | 1897.230000 |
可以看到,该数据集分为3个特征: S(含硫量), coal(喷煤量), wind(风量)
且一共有1000条数据,不存在数据缺失
现在我们需要对数据预处理,即归一化(Normalization) 或 标准化(Standardization)
为什么要做归一化
1.提升模型的收敛(convergence)速度
如果没有做归一化,各特征尺度不一样的话,会造成目标函数整个图形特别狭长,梯度下降的过程会走很多”弯路”
2.有助于提升模型的精度
这在牵涉到距离计算的算法如SVM时,提升效果非常显著, 因为如果这个特征变化特别大,距离计算就主要取决于这个特征,不符合实际情况
常用归一化算法:
线性转换:
这里我们选择手动实现一个Z-Score的标准化:
def standard(X):
# np.array() 可将其它一些数据转化为numpy.ndarray类型的数据方便进行矩阵运算, 而np.ndarray()是将其它numpy.ndarray作为输入
X_norm = np.array(X)
# 创建一个1行,和X列宽等长的行向量
mu = np.zeros((1, X.shape[1]))
sigma = np.zeros((1, X.shape[1]))
mu = np.mean(X_norm, axis=0) # 求每一列的平均值,axis=0指定求的是列的均值
sigma = np.std(X_norm, axis=0)
for i in range(X.shape[1]): # 遍历每一列
X_norm[: ,i] = (X_norm[: ,i] - mu[i]) / sigma[i]
return X_norm, mu, sigma
data_norm, mu, sigma = standard(data)
print(data_norm)
得到结果:
array([[ 0.18564659, -0.38900304, -0.48071521, -1.441423 ],
[ 0.36597354, -1.41593821, -0.72592996, -1.33435646],
[-0.35533425, -1.41593821, -1.33896682, -0.70095235],
...,
[-0.1750073 , -1.21055117, -0.45242121, -1.71614797],
[ 0.00531965, 0.22715806, -0.99943871, -0.60125679],
[-0.71598815, -0.90247062, 0.4954281 , -1.00253766]])
接下来我们data_norm再分割为Samples(样本矩阵)和Labels(结果向量)
Samples = data_norm[:, 1:]
print(Samples)
结果如下
array([[-0.38900304, -0.48071521, -1.441423 ],
[-1.41593821, -0.72592996, -1.33435646],
[-1.41593821, -1.33896682, -0.70095235],
...,
[-1.21055117, -0.45242121, -1.71614797],
[ 0.22715806, -0.99943871, -0.60125679],
[-0.90247062, 0.4954281 , -1.00253766]])
再检查标记向量:
Labels = data_norm[:, 0:1]
print(Labels.shape)
结果如下:
(1000, 1)
"""
代价函数
X是样本矩阵(m, n+1), y是结果向量(m, 1), theta是参数向量(n+1, 1)
返回一个数,即代价值
"""
def get_cost(X, y, theta):
# 对numpy.ndarray来说,np.dot()才是真正的线性代数中的矩阵相乘
return np.dot((np.transpose(np.dot(X,theta) - y)) , (np.dot(X,theta) - y)) / (2*y.shape[0])
"""
计算梯度下降, 返回最终学得的theta向量和cost function的变化list
X: (m, n+1)样本矩阵
y: (m, 1)结果向量
theta: (n+1, 1)参数向量
rate: 学习速率
rounds: 训练轮次
intercept: 若为True,则需要给样本矩阵X增加常数列
"""
def gradient_descent(X, y, rate, rounds, intercept=True):
iX = X.copy() # 如果不用copy()函数,改变iX同样会改变X
if intercept == True:
iX = np.column_stack((np.ones(iX.shape[0]), iX)) # 给第一列全加上1
T = np.full(shape=(iX.shape[1], 1), fill_value=0.5, dtype=np.float)
L, costs = [], []
for i in range(rounds):
H = np.dot(iX, T) # 求内积,先计算出假设函数
T = T - (rate/y.shape[0]) * np.dot(np.transpose(iX), (H-y))
L.append(i)
costs.append(get_cost(iX, y, T).tolist()[0])
return T, costs
训练一个模型出来:
Theta, Costs = gradient_descent(Samples, Labels, 0.1, 40)
再绘出训练过程中代价函数变化曲线:
plt.plot(Costs)
from sklearn import linear_model
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(data)
data_norm = scaler.transform(data)
print(data_norm)
查看scaler缩放后的数据:
array([[ 0.18564659, -0.38900304, -0.48071521, -1.441423 ],
[ 0.36597354, -1.41593821, -0.72592996, -1.33435646],
[-0.35533425, -1.41593821, -1.33896682, -0.70095235],
...,
[-0.1750073 , -1.21055117, -0.45242121, -1.71614797],
[ 0.00531965, 0.22715806, -0.99943871, -0.60125679],
[-0.71598815, -0.90247062, 0.4954281 , -1.00253766]])
可以看到,这和我们自己写的缩放的效果是一摸一样的
再查看样本矩阵:
Samples = data_norm[:, 1:]
print(Samples)
结果:
array([[-0.38900304, -0.48071521, -1.441423 ],
[-1.41593821, -0.72592996, -1.33435646],
[-1.41593821, -1.33896682, -0.70095235],
...,
[-1.21055117, -0.45242121, -1.71614797],
[ 0.22715806, -0.99943871, -0.60125679],
[-0.90247062, 0.4954281 , -1.00253766]])
再检查结果向量:
Labels = data_norm[:, 0:1]
Labels.shape
(1000, 1)
接下来获取训练集与测试集的训练矩阵和结果向量:
trainX = Samples[:700]
trainY = Labels[:700]
testX = Samples[700:]
testY = Labels[700:]
训练一波模型:
model = linear_model.LinearRegression()
model.fit(trainX, trainY)
训练出来的是一个LinearRegression对象:
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)
接下来在测试集上预测一波,再查看预测值和真实值的均方误差(mean squared error):
from sklearn.metrics import mean_squared_error
result = model.predict(testX)
predict_y = scaler.inverse_transform(np.concatenate([result, testX], axis=1))[:, 0]
print(mean_squared_error(predict_y, testY))
1.06594865536
大功告成
1.从回归算法上:
2.扩展为分类
小小的线性模型,也能有这么多的内容。。。这只是革命第一步
注: 文中所有代码与数据下载地址:https://github.com/NileZhou/ML