1.KNN
一个小栗子
import numpy as np
import matplotlib.pyplot as plt
from sklearn.neighbors import KNeighborsClassifier
train_x=np.arange(1,32,2).reshape(-1,2)
train_y=np.array([0,0,0,0,1,1,1,1])
predict_x=np.array([23.6,13.6])
KNN_clf=KNeighborsClassifier(n_neighbors=4)#设置K值
KNN_clf.fit(train_x,train_y)#训练模型,即拟合
predict_y=KNN_clf.predict(predict_x.reshape(1,-1))#输入测试集
print(predict_y)
plt.scatter(train_x[train_y==0,0],train_x[train_y==0,1],color="r")
plt.scatter(train_x[train_y==1,0],train_x[train_y==1,1],color="b")
plt.scatter(predict_x[0],predict_x[1],color="g")
plt.show()
实现sklearn中的knn
class KNNClassifer:
def __init__(self,k):
assert k>=1,"k must be valid"
self.k=k
self._x_train=None
self._y_train=None
def fit(self,x_train,y_train):
#根据训练测试集,训练KNN分类器
assert x_train.shape[0]==y_train.shape[0],\
"the size of x_train must be equal to the size of y_train"
assert self.k<=x_train.shape[0],\
"the size of x_train must be at least k"
self._x_train=x_train
self._y_train=y_train
return self
def predict(self,x_predict):
assert self._x_train is not None and self._y_train is not None,\
"must fit before predict"
assert x_predict.shape[1]==self._x_train.shape[1],\
"the feature number of x_predict must be equal to x_train"
y_predict=[self._predict(x) for x in x_predict]
return np.array(y_predict)
def _predict(self,x):
assert x.shape[0]==self._x_train.shape[1],\
"the feature number of x must be equal to x_train"
distances=[sqrt(np.sum(one-x)**2) for one in self._x_train]
nearest=np.argsort(distances)
topK_y=[self._y_train[i] for i in nearest]
votes=Counter(topK_y)
return votes.most_common(1)[0][0]
将数据分为测试数据和训练数据
def train_test_split(x,y,test_ratio=0.2,seed=None):
assert x.shape[0]==y.shape[0],\
"the size of x should be equal to the size of y"
assert 0.0<=test_ratio<=1.0,\
"test ratio must be valid"
if seed:
np.random.seed(seed)
shuffle_index=np.random.permutation(len(x))
test_size=int(test_ratio*len(x))
test_index=shuffle_index[:test_size]
train_index=shuffle_index[test_size:]
X_train=x[train_index]
Y_train=y[train_index]
X_test=x[test_index]
Y_test=y[test_index]
return X_train,X_test,Y_train,Y_test
iris的分类:
from sklearn.neighbors import KNeighborsClassifier
knn_clf=KNeighborsClassifier(n_neighbors=3)
knn_clf.fit(X_train,Y_train)
Y_predict=knn_clf.predict(X_test)
knn_clf.score(X_test,Y_test)
数据归一化:
要将所有数据映射到同一尺度,这样有利于计算数据间的距离
最简单的方法:
1.最值归一化(normalization),将所有的数据映射到0~1之间
此种归一化方法适用于有明显边界的特征:例如考试分数,其边界为0~100
y=np.array(np.random.randint(0,100,size=(50,5)),dtype=float)
for i in range(len(y[0])):
y[:,i]=((y[:,i]-np.min(y[:,i]))/(np.max(y[:,i])-np.min(y[:,i])))
2.均值方差归一化(standardization):将所有数据归一到均值为0,方差为1的分布中
适用于没有明显分界的数据分布;但是可能有极端数据
z=np.array(np.random.randint(0,100,size=(50,5)),dtype=float)
for i in range(len(z[0])):
z[:,i]=(z[:,i]-np.mean(z[:,i]))/np.std(z[:,i])
众所周知,我们的数据集分为训练集和测试集,对于测试集的均值方差归一化,不能用测试集的均值和方差,而要用训练集的均值和方差,因为真实数据中很难得到其均值和方差。
sklearn中也封装了scaler类来进行数据归一化
2.回归
sklearn中的回归类的实现:
import numpy as np
import matplotlib.pyplot as plt
class SimpleLinearRegression1:
def __init__(self):
#初始化斜率a和截距b
self.a_=None
self.b_=None
def fit(self,x_train,y_train):
#根据训练数据集训练模型,即求出回归方程的a和b
assert x_train.ndim==1,\
"简单线性回归只能解决特征数为1的问题"
assert len(x_train)==len(y_train),\
"x_train与y_train的长度必须相同"
x_mean=np.mean(x_train)
y_mean=np.mean(y_train)
#numy与d分别为分子与分母
num=0.0
d=0.0
for x,y in zip(x_train,y_train):
num+=(x-x_mean)*(y-y_mean)
d+=(x-x_mean)**2
self.a_=num/d
self.b_=y_mean-self.a_*x_mean
return self
def predict(self,x_predict):
assert x_predict.ndim==1,\
"简单线性回归只能解决二维问题,请输入一维向量"
assert self.a_ is not None and self.b_ is not None,\
"请先调用fit函数训练回归模型"
return np.array([self.predict_(x) for x in x_predict])
def predict_(self,x):
#返回预测结果
return self.a_*x+self.b_
接着测试一下,先随机生成测试数据
%matplotlib inline
x_train=np.random.randint(1,10,10)
y_train=np.random.randint(1,10,10)
plt.scatter(x_train,y_train)
plt.axis([0,11,0,11])
plt.show()
然后训练模型
SLR=SimpleLinearRegression1()
SLR.fit(x_train,y_train)
x_test=np.array([1,10])
y_predict=SLR.predict(x_test)
plt.scatter(x_train,y_train)
plt.plot(x_test,y_predict,color="red")
plt.axis([0,11,0,11])
plt.show()
x_test,y_predict
向量化运算
由于for循环效率不高,考虑引入向量化运算
这里的乘法为逐项相乘,即点乘,两个向量点乘结果为一个数
向量即为
向量即为
import numpy as np
import matplotlib.pyplot as plt
class SimpleLinearRegression2:
def __init__(self):
#初始化斜率a和截距b
self.a_=None
self.b_=None
def fit(self,x_train,y_train):
#根据训练数据集训练模型,即求出回归方程的a和b
assert x_train.ndim==1,\
"简单线性回归只能解决特征数为1的问题"
assert len(x_train)==len(y_train),\
"x_train与y_train的长度必须相同"
x_mean=np.mean(x_train)
y_mean=np.mean(y_train)
#numy与d分别为分子与分母
num=(x_train-x_mean).dot(y_train-y_mean)
d=(x_train-x_mean).dot(x_train-x_mean)
self.a_=num/d
self.b_=y_mean-self.a_*x_mean
return self
def predict(self,x_predict):
assert x_predict.ndim==1,\
"简单线性回归只能解决二维问题,请输入一维向量"
assert self.a_ is not None and self.b_ is not None,\
"请先调用fit函数训练回归模型"
return np.array([self.predict_(x) for x in x_predict])
def predict_(self,x):
#返回预测结果
return self.a_*x+self.b_
def __repr__(self):
return "SimpleLinearRegression2()"
随机生成一组基本线性的测试数据
m=100000
big_x=np.random.random(size=m)
big_y=big_x*2.0+3.0+np.random.normal()#噪音
测试下效率
%%time
SLM=SimpleLinearRegression2()
SLM.fit(big_x,big_y)
运行时间为3.99ms,效率相当高
衡量线性回归准确度的指标
目标:找到a和b,使得尽可能小
衡量标准:,但是这个式子还有测试样本数m的干扰,于是就出现了均方误差MSE(Mean Square Error)
MSE:
mse_test=np.sum((y_predict-y_test)**2)/len(y_test)
均方根误差RMSE(Root Mean Square Error)
RMSE:
rmse_test=sqrt(mse_test)
平均绝对误差MAE(Mean absolute error)
MAE:
mae_test=np.sum(np.absolute(y_predict-y_test))/len(y_test)
一般来说,RMSE要比MAE要大一些
sklearn中也有封装MSE和MAE
调用方法如下
from sklearn.metrics import mean_squared_error as mse
from sklearn.metrics import mean_absolute_error as mae
mse(y_test,y_predict)
mae(y_test,y_predict)
除了这些指标,最好的衡量方法为
R Squared
首先必须小于等于1,并且越大越好,当完全不犯错误时等于1,当其小于0时,说明数据很可能不存在线性关系
将式子变形:
分子为mse,分母为方差var
所以其可以写成1-mse/var
from sklearn.metrics import mean_squared_error as mse
import numpy as np
def Rsquared(y_test,y_predict):
return 1-mse(y_test,y_predict)/np.var(y_test)
多元线性回归
变成向量化运算,即
回归的向量经过求导运算,得到正规方程解
其中
为截距,其他为特征的系数,一般来说我们将两者分开,Xb为特征向量前加一列1,代表截距的系数向量
代码实现:
class LinearRegression:
def __init__(self):
self.coef_ = None
self.intersection_ = None
self._theta = None
def r2_score(self,y_test,y_predict):
return 1-mse(y_test,y_predict)/np.var(y_test)
def fit_normal(self,X_train,y_train):
"""根据训练的特征向量X_train以及y_train训练模型"""
X_b=np.hstack([np.ones((len(X_train),1)),X_train])
self._theta=np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T).dot(y_train)
self.intersection_=self._theta[0]
self.coef_=self._theta[1:]
return self
def predict(self,X_predict):
"""根据X_train,返回X_predict结果向量"""
X_b=np.hstack([np.ones((len(X_predict),1)),X_predict])
return X_b.dot(self._theta)
def score(self,X_test,y_test):
"""测试当前模型的精准度"""
y_predict=self.predict(X_test)
return self.r2_score(y_test,y_predict)
def __repr__(self):
return "LinearRegression()"
用波士顿房价的数据测试一下多元回归算法:
from sklearn import datasets
import matplotlib.pyplot as plt
boston=datasets.load_boston()
X=boston.data
y=boston.target
X=X[y<50.0]
y=y[y<50.0]
X_train,X_test,y_train,y_test=train_test_split(X,y,seed=666)
reg = LinearRegression()
reg.fit_normal(X_train,y_train)
reg.score(X_test,y_test)
#0.8129802602658537
3.梯度下降法
最小化损失函数的方法,一种基于搜索的最优化算法。
对于一个图像J某一个点的导数,导数的符号代表J增大的方向。但是我们需要的是其减小的方向,所以一般对它乘一个,代表反向的一个步长。
这个参数我们称位学习率,它的取值影响获得最优解的速度,它取值不合适,就得不到最优解,并且它也是一个超参数。
但是并不是每一个函数都有极值点。 所以它有全局最优解和局部最优解两种。
但实际上并实际情况中一般不是一维的,,
其中,代表了所有的特征,即对每一个特征求偏导,求导的部分即为梯度,J为损失函数。
在线性回归中,我们的目标是使尽可能小,即使尽可能小,对每一个维度的未知量求偏导。由于求导后相当于对每一个特征求m次和,所以在J前乘以,乘了之后目标函数即为mse。
代码实现:
def J(theta,X_b,y):
try:
return np.sum((y-X_b.dot(theta))**2)/len(X_b)
except:
return float('inf')
def dJ(theta,X_b,y):
res=np.empty(len(theta))
res[0]=np.sum(X_b.dot(theta)-y)
for i in range(1,len(theta)):
res[i]=(X_b.dot(theta)-y).dot(X_b[:,i])
return res*2/len(X_b)
def gradient_descent(X_b,y,initial,eta,n_iters=1e4,epsilon=1e-8):
theta=initial_theta
i_iter=0
theta_history=[]
theta_history.append(initial_theta)
while i_iter<n_iters:
gradient=dJ(theta,X_b,y)
last_theta=theta
theta=theta-eta*gradient
theta_history.append(theta)
if(abs(J(theta,X_b,y)-J(last_theta,X_b,y))<epsilon):
break
i_iter+=1
return theta
将梯度下降法进行向量化
梯度里的式子可以看作下面两个矩阵的点乘
主成分分析法
主要作用是降维,目的是找到让样本间距最大的轴。
运用方差来定义样本间距,方差越大代码样本间距越稀疏。
首先,对所有样本demean,找到一个轴的方向,然后再对映射后的样本向量求方差,即最大,由于w为方向向量,所以模为1。
最终方差化为最大
使用梯度上升法,对方差(向量和的平方,由于其含有m个元素,所以为m个向量的向量和)的每一个维度进行求偏导
最终求得f的梯度:
def demean(X):
return X-np.mean(X,axis=0)
def f(w,X):
#计算方差
return np.sum((X.dot(w)**2))/len(X)
def df(w,X):
#计算目前的w的导数
return X.T.dot(X.dot(w))*2./len(X)
def df_debug(w,X,epsilon=0.0001):
res=np.empty(len(w))
for i in range(len(w)):
w_1=w.copy()
w_1[i]+=epsilon
w_2=w.copy()
w_2[i]-=epsilon
res[i]=(f(w_1,x)-f(w_2,x))/(2*epsilon)
return res
def direction(w):
return w/np.linalg.norm(w)
def gradient_ascent(df,x,initial_w,n_iters=1e4,epsilon=1e-8):
w=direction(initial_w)
cur_iter=0
while cur_iter<n_iter:
gradient=df(w,X)
last_w=w
w=w+eta*gradient
w=direction(w)
if(abs(f(w,X)-f(last_w,X))<epsilon):
break
cur_iter+=1
return w
initial_w=np.random.random(X.shape[1])#不能从0向量开始
eta=0.001
gradient_ascent(df_debug,X_demean,initial_w,eta)
gradient_ascent(df,X_demean,initial_w,eta)
df_debug与df得到的结果相同
逻辑回归算法
逻辑回归:解决分类问题
将样本的特征和样本发生的概率联系起来,在逻辑回归中,,
且一般用于解决二分类问题。
Sigmoid函数
def sigmoid(t):
return 1/(1+np.exp(-t))
这个函数的值域在0与1之间,将可以将转化为在0与1之间的,当t=0时,p=0.5
最终化为:
现在问题就是如何找到参数,可以最大程度获得样本X对应的分类y
损失函数
将这个分段函数合成:
损失函数:
带入Sigmoid函数
但是这个函数没有公式解,只能用梯度下降法求解。
梯度即对的每一个求偏导。
忽略推导过程,
逻辑回归实现:
import numpy as np
class LogisticRegression:
def __init__(self):
self.coef_ = None
self.intercept_ = None
self._theta = None
def _sigmoid(self,t):
return 1. /(1.+np.exp(-t))
def fit(self,X_train,y_train,eta=0.01,n_iters=1e4):
def J(theta,X_b,y):
y_hat=self._sigmoid(X_b.dot(theta))
try:
return - np.sum(y*np.log(y_hat)+(1-y)*np.log(1-y_hat))/len(y)
except:
return float('inf')
def dJ(theta,X_b,y):
return X_b.T.dot(self._sigmoid(X_b.dot(theta))-y)/len(X_b)
def gradient_descent(X_b,y,initial,eta,n_iters=1e4,epsilon=1e-8):
theta=initial_theta
cur_iter=0
while cur_iter<n_iters:
gradient=dJ(theta,X_b,y)
last_theta=theta
theta=theta-eta*gradient
if(abs(J(theta,X_b,y)-J(last_theta,X_b,y))<epsilon):
break
cur_iter+=1
return theta
X_b=np.hstack([np.ones((len(X_train),1)),X_train])
initial_theta=np.zeros(X_b.shape[1])
self._theta=gradient_descent(X_b,y_train,initial_theta,eta,n_iters)
self.intercept_=self._theta[0]
self.coef_=self._theta[1:]
return self
def predict_proba(self,X_predict):
"""根据X_train,返回X_predict结果概率向量"""
X_b=np.hstack([np.ones((len(X_predict),1)),X_predict])
return self._sigmoid(X_b.dot(self._theta))
def predict(self,X_predict):
"""根据X_train,返回X_predict结果向量"""
proba=self.predict_proba(X_predict)
return np.array(proba>=0.5,dtype='int')
def score(self,X_test,y_test):
"""测试当前模型的精准度"""
y_predict=self.predict(X_test)
return np.sum(y_test==y_predict)/len(y_test)
def __repr__(self):
return "LogisticRegression()"
决策边界
,当时,y=1,小于0时y=0。所以我们称为决策边界。
假如X有2个特征,即
那对于不规则的决策边界呢?
即将所有的同类的点划为一个类,这个类的边界即为决策边界。
将线性逻辑回归改为多项式逻辑回归,degree代表阶数
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler
def PolynomialLogisticRegression(degree):
return Pipeline([
('ploy',PolynomialFeatures(degree=degree)),
('std_scaler',StandardScaler()),
('log_reg',LogisticRegression())
])
多项式回归
例如对,可以将作为一个新的特征
x=np.random.uniform(-3,3,size=100)
X=x.reshape(-1,1)
y=0.5*x**2+x+2+np.random.normal(0,1,size=100)
X2=np.hstack([X,X**2])
from sklearn.linear_model import LinearRegression
lin_reg2=LinearRegression()
lin_reg2.fit(X2,y)
y_predict2=lin_reg2.predict(X2)
plt.scatter(x,y)
plt.plot(np.sort(x),y_predict2[np.argsort(x)],color='r')
plt.show()
sklearn中的多项式回归和Pipeline
from sklearn.preprocessing import PolynomialFeatures
poly=PolynomialFeatures(degree=2)
poly.fit(X)
X2=poly.transform(X)#转换为多项式特征
X2.shape
#(100,3),第一列全为1,代表常数项x的阶数
若有两个特征,degree=2
x1,x2->1,x1,x2,x12,x22,x1x2,即总共五项。
Pipeline可以一次实现三步:
1.多项式化
2.数据归一化
3.线性回归
管道函数传入一个列表,列表由三个元组构成,分别是poly,std_scaler和lin_reg
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
poly_reg=Pipeline([
("poly",PolynomialFeatures(degree=2)),
("std_scaler",StandardScaler()),
("lin_reg",LinearRegression())
])
poly_reg.fit(X,y)
y_predict=poly_reg.predict(X)
过拟合 欠拟合
对于degree的取值,虽然degree值越高,均方误差越低,但是实际显然并不是这样,它为了拟合所有的样本点变得太过复杂,我们称位过拟合。但是直接用直线来拟合,又太过简单了,我们称位欠拟合。
泛化能力
即模型面对新的数据的预测能力
所以训练集与测试集的分离是十分有必要的。
验证数据集与交叉验证
为了防止对测试数据集过拟合,我们将整个数据集分为训练数据,验证数据,测试数据。其中测试数据不参与模型的创建。其中验证数据集用于调整超参数。
但是这样也有问题,所以引入了交叉验证,将训练数据分为三份A、B、C,将BC训练,A验证,AC训练,B验证,AB训练,C验证,将这k个模型的均值作为结果。
偏差方差平衡
偏差与方差是有区别的,模型误差=偏差(Bias)+方差(Var)+不可避免的误差(噪音)
导致偏差的原因:对问题本身的假设不正确,即欠拟合
导致方差的原因:数据的一点点扰动就会影响模型,即过拟合
kNN就是天生的高方差算法(非参数学习),多项式回归是高偏差算法(参数学习)。但是偏差和方差是可以调整的,通过调整knn中的k,线性回归中的多项式回归等。
解决方差的通常手段:
1.降低模型复杂度
2.减少数据维度
3.增加样本数
4.使用验证集
模型正则化:限制参数的大小
加入模型正则化,使损失函数,让所有的尽可能小。其中是一个新的超参数
这样模型正则化的方式称为岭回归
from sklearn.linear_model import Ridge
def RidgeRegression(degree,alpha):
return Pipeline([
("poly",PolynomialFeatures(degree=degree)),
("std_scaler",StandardScaler()),
("ridge_reg",Ridge(alpha=alpha))
])
ridge_reg=RidgeRegression(8,0.000001)
ridge_reg.fit(X_train,y_train)
ridge_reg.predict(X_test)
在逻辑回归中使用正则化
sklearn中的使用方式:
PCA
将高维数据向低维数据映射
假如说原来的数据为MN个矩阵,将原来的m行映射后得到主成分分析后的m个特征,然后取前k个主成分。
即,最终变为mk的矩阵,含有m个样本,含有k个元素。
class PCA:
def __init__(self,n_components):
self.n_components=n_components
self.components_=None
def fit(self,X,eta=0.01,n_iters=1e4):
def demean(X):
return X-np.mean(X,axis=0)
def f(w,X):
return np.sum((X.dot(w)**2))/len(X)
def df(w,X):
return X.T.dot(X.dot(w))*2./len(X)
def direction(w):
return w/np.linalg.norm(w)
def first_component(X,initial_w,eta=0.01,n_iters=1e4,epsilon=1e-8):
w=direction(initial_w)
cur_iter=0
while cur_iter<n_iters:
gradient=df(w,X)
last_w=w
w=w+eta*gradient
w=direction(w)
if (abs(f(w,X)-f(last_w,X))<epsilon):
break
cur_iter+=1
return w
X_pca=demean(X)
self.components_=np.empty(shape=(self.n_components,X.shape[1]))
for i in range(self.n_components):
initial_w=np.random.random(X_pca.shape[1])
w=first_component(X_pca,initial_w,eta,n_iters)
self.components_[i,:]=w
X_pca=X_pca-X_pca.dot(w).reshape(-1,1)*w
return self
def transform(self,X):
"""将给定的X映射到各个主成分中"""
return X.dot(self.components_.T)
def inverse_transform(self,X):
"""将给定的X,反向映射回原来的空间"""
return X.dot(self.components_)
def __repr__(self):
return "PCA(n_components=%d)"% self.n_components
LASSO Regression
与岭回归不同,LASSO的损失函数加上的是的绝对值之和乘以,它趋向于使得一部分等于0,可以当作一种特征选择的方法。
L1 L2和弹性网络
范数:,所以岭回归相当于给损失函数加上L2正则项,LASSO回归相当于加上L1正则项。
弹性网:即结合了L1和L2的回归方法。
分类准确度的问题
对于极度偏斜(Skewed Data)的数据,只用分类准确度远远不够。
混淆矩阵Confusion Matrix
对于二分类问题,只需一个2*2的矩阵。
其中行代表预测值,列代表真实值。
0 - Negative
1 - Positive
0 | 1 | |
---|---|---|
0 | 预测Negative正确(TN) | 预测Positive错误(FP) |
1 | 预测Negative错误(FN) | 预测Positive正确(TP) |
精准率和召回率
精准率:
召回率:
F1 Score
兼顾精准率和召回率,使用精准率和召回率的调和平均值,使用调和平均值可以防止极端值出现。
def f1_score(precision,recall):
try:
return 2*precision*recall/(precision+recall)
except:
return 0.0
支撑向量机(support vector machine)
期望找到的决策边界的泛化能力尽可能好,要找到一条决策边界,使其离分类样本尽可能远。SVM算法要最大化margin,解决的是线性可分问题。
决策边界:
,其中为多维特征的不同参数。
其中
这两个不等式可以组合成,意思是margin里一个数据都没有。
对于任意支撑向量,要最大化,即,即,一般来说最小化的对象是,这样方便求导。这里的最优化问题是有条件的,条件为,要使用拉普拉斯算子。
决策边界:
对于线性不可分的数据,将条件改为,最小化方程变为:,相当于加入了L1正则,避免了过拟合。同样也有L2正则,即求和。
网友评论