X = { x i ⃗ , i = 1 , 2 , . . . , m } X ⊆ R m × d X=\{\vec{x_i},i=1,2,...,m\} \quad X\subseteq \mathbb{R}^{m\times d} X={xi,i=1,2,...,m}X⊆Rm×d
x i ⃗ = ( x i 1 , x i 2 , . . . , x i d ) x i ⊆ R 1 × d \quad \vec{x_i}=(x_{i1},x_{i2},...,x_{id}) \quad x_i\subseteq \mathbb{R}^{1\times d} xi=(xi1,xi2,...,xid)xi⊆R1×d
Y = { y i , i = 1 , 2 , . . . , m } Y ⊆ R m × 1 Y=\{y_i,i=1,2,...,m\} \quad Y\subseteq \mathbb{R}^{m\times 1} Y={yi,i=1,2,...,m}Y⊆Rm×1
y i ⃗ ⊆ R \quad \vec{y_i}\subseteq \mathbb{R} yi⊆R
( W ⋆ , b ⋆ ) = a r g m i n ( w , b ) ∑ i = 1 m ( F ( x i ) − y i ) 2 (W^{\star},b^{\star})=\mathop{argmin}\limits_{(w,b)}\sum_{i=1}^{m}(F(x_i)-y_i)^2 (W⋆,b⋆)=(w,b)argmin∑i=1m(F(xi)−yi)2
∂ E ( w , b ) ∂ w = 2 ( ∑ i = 1 m x i w − ∑ i = 1 m x i ( y i − b ) ) \frac{\partial{E(w,b)}}{\partial{w}}=2(\sum_{i=1}^{m}x_{i}w-\sum_{i=1}^{m}x_{i}(y_i -b)) ∂w∂E(w,b)=2(∑i=1mxiw−∑i=1mxi(yi−b))
∂ E ( w , b ) ∂ b = 2 ( ∑ i = 1 m ( w x i − y i ) − m b ) \frac{\partial{E(w,b)}}{\partial{b}}=2(\sum_{i=1}^{m}(wx_i-y_i)-mb) ∂b∂E(w,b)=2(∑i=1m(wxi−yi)−mb)
d w = ∑ i = 1 m ( y i − b ) x i ∑ i = 1 m x i dw=\frac{\sum_{i=1}^{m}(y_i-b)x_i}{\sum_{i=1}^{m}x_i} dw=∑i=1mxi∑i=1m(yi−b)xi
d b = ∑ i = 1 m ( w x i − y i ) m db=\frac{\sum_{i=1}^{m}(wx_i-y_i)}{m} db=m∑i=1m(wxi−yi)
我们利用Numpy实现,数据存放为二维数组,在代码实现的时候需要看一下矩阵相乘合不合法。
首先分析有几个部分:LinearRegression作为一个类,应该满足喂入训练数据x和y,然后拟合好一个线性函数F,参数为W和b;此外根据这个线性函数可以预测我们的测试数据。
由此我们可以很快想到造一个训练器train、一个预测器predict和一个评价器evaluation。
下面我们按着思路设计。
initialize parameters w, b
for epoch in epochs:
caculate loss, dw, db
update w with dw
update b with db
return w, b
第一步是初始化参数,权重与特征维数一致,偏差是个常数。
第二步是求loss和偏导,我们可以通过一个函数完成。这个函数要求输入x_train,y_train和参数,返回loss,dw,db。
第三步就是利用返回的偏导更新参数,在这里可以加入早停,因为目标函数是个凸函数,只要发现loss变大就暂停。
接下来我们来实现训练器,我在训练后调整训练率、偏差初始值调整模型。
# 初始化
def initialize_params(self, dims):
W = np.zeros((dims,1))
b = 150
return W, b
# 求loss,dw,db
def loss(self, X, y, W, b):
# 对Numpy不太熟悉,点除直接写成一个函数
def dot_division(dW, dsum):
for i in range(len(dW)):
dW[i] /= dsum[i]
return dW
num_train = X.shape[0]
num_feature = X.shape[1]
# 求y_hat,loss,dw,db
y_hat = np.dot(X, W) + b
loss = np.sum((y - y_hat)**2) / num_train
db = np.sum(y - y_hat) / num_train
dsum = np.sum(X.T, axis=1)
dW = np.dot(X.T,(y - db))
dW = dot_division(dW, dsum)
return y_hat, loss, dW, db
def train(self, X, y, learning_rate, epochs):
# 初始化参数
dims = X.shape[1]
W, b = self.initialize_params(dims)
# 存放loss值,方便查看loss变化,并设定loss最小值,如果变大就停止
loss_list = []
loss_min = np.inf
for epoch in range(epochs):
y_hat, loss, dW, db = self.loss(X, y, W, b)
loss_list.append(loss)
if loss < loss_min:
loss_min = loss
else:
print("EarlyStop.")
break
if epoch % 10000 == 0:
print("epoch %d loss %f" %(epoch, loss))
W += learning_rate * dW
b += learning_rate * db
params = {
'W':W,
'b':b
}
grads = {
'dW':dW,
'db':db
}
return loss_list, loss, params, grads
input是存放的使loss最小的parameters和x_test,output是计算得到的y_predict。
这个比较简单,直接上代码。
def predict(self, X, params):
W = params['W']
b = params['b']
y_predict = np.dot(X, W) + b
return y_predict
input是预测得到的值y_predict和原值y_test,output是我们定义的均方误差,均方根误差,平均绝对误差,R平方值。
def evalution(self, y_predict, y):
m = y.shape[0]
mse = np.sum((y_predict - y)**2) / m
rmse = mse**0.5
mae = np.sum(np.absolute(y_predict - y)) / m
r2 = 1 - (mse / np.var(y))
return mse, rmse, mae, r2
到此我们的模块分析就完成了,接下来完整数据处理代码和其他代码。
我参照了公众号的代码,利用shuffle打乱数据,然后取10%的测试集,剩下的用来训练。
def pre_data_init(self):
data = load_diabetes().data
target = load_diabetes().target
X, y = shuffle(data, target, random_state=42)
X = X.astype(np.float32)
y = y.reshape((-1,1))
offset = int(X.shape[0] * 0.9)
x_train, y_train = X[:offset], y[:offset]
x_test, y_test = X[offset:], y[offset:]
y_train = y_train.reshape((-1,1))
y_test = y_test.reshape((-1,1))
return x_train, x_test, y_train, y_test
我加入sklearn的包来比较模型的效果,虽然效果不够好,但是已经具备初始形状了。
sklearn: mse: 3409.542078, rmse: 58.391284, mae: 47.374929, r2: 0.341719
ours: mse: 4637.266031, rmse: 68.097474, mae: 58.506708, r2: 0.104682
https://github.com/oneoyz/ml_algorithms/tree/master/linear_regression