大三的时候曾花两个星期学习了几个经典的机器学习算法,学习方法主要是白天参考《统计学习方法》推导公式,晚上利用公式编写实现。在参考GitHub上算法实现时,我发现其中大多数都比较繁杂冗长,很难体现出算法的核心思想。因此我特地找出了以前的机器学习算法实现,在修改整理后分享给大家(GitHub地址)。
所有算法的实现都没有使用其他机器学习库。希望可以帮助大家对机器学习算法及其本质原理有个基本的了解,但并不是提供最有效的实现。
目前已经实现的算法包括:
尚未更新的有:
- 梯度提升树
- 随机森林
- 条件随机场
- 隐马尔可夫模型
算法实现尽可能精简,因此大多数都只有几行,例如:
-
AdaBoost:
class AdaBoost: ... def fit(self, X: np.ndarray, Y: np.ndarray): weights = np.full([len(X)], 1 / len(X)) # 样本权重 for _ in range(self.n_estimators): estimator = WeakEstimator(lr=self.lr) error = estimator.fit(X, Y, weights) # 带权重训练弱分类器 if error < self.eps: # 误差达到下限,提前停止迭代 break alpha = np.log((1 - error) / error) / 2 # 更新弱分类器权重 weights *= np.exp(-alpha * Y * estimator(X)) # 更新样本权重 weights /= np.sum(weights) # 除以规范化因子 self.estimators += [(alpha, estimator)] # 添加此弱分类器 def __call__(self, X: np.ndarray): pred = sum((alpha * estimator(X) for alpha, estimator in self.estimators)) return np.where(pred > 0, 1, -1)
-
主成因分析:
class PCA: ... def __call__(self, X: np.ndarray): X_norm = X - X.mean(axis=0) # 去中心化 L, U = np.linalg.eig(X_norm.T @ X_norm) # 对协方差矩阵进行特征值分解 topk = np.argsort(L)[::-1][:self.k] # 找出前K大特征值对应的索引 return X_norm @ U[:, topk] # 将去中心化的X乘以前K大特征值对应的特征向量
-
K近邻:
class KNN: ... def __call__(self, X: np.ndarray): Y = np.zeros([len(X)], dtype=int) # X对应的类别 for i, x in enumerate(X): dist = LA.norm(self.X - x, axis=1) # 计算x与所有已知类别点的距离 topk = np.argsort(dist)[:self.k] # 取距离最小的k个点对应的索引 Y[i] = np.bincount(self.Y[topk]).argmax() # 取近邻点最多的类别作为x的类别 return Y
-
感知机:
class Perceptron: ... def fit(self, X: np.ndarray, Y: np.ndarray): for x, y in zip(self._pad(X), Y): if y * (x @ self.weights) <= 0: # 分类错误, y 与 wx + b 符号不同 neg_grad = x * y # 计算weights的负梯度 self.weights += self.lr * neg_grad # 沿负梯度方向更新weights def __call__(self, X: np.ndarray): pred = self._pad(X) @ self.weights return np.where(pred > 0, 1, -1)
-
逻辑斯蒂回归:
class LogisticRegression: ... def fit(self, X: np.ndarray, Y: np.ndarray): x_pad = self._pad(X) # 为X填充1作为偏置 pred = self._sigmoid(x_pad @ self.weights) # 计算预测值 grad = x_pad.T @ (pred - Y) / len(pred) # 计算梯度 self.weights -= self.lr * grad # 沿负梯度更新参数 def __call__(self, X: np.ndarray): x_pad = self._pad(X) # 为X填充1作为偏置 pred = self._sigmoid(x_pad @ self.weights) # 计算预测值 return np.where(pred > 0.5, 1, 0) # 将(0, 1)之间分布的概率转化为{0, 1}标签
除了最精简的实现之外,还对一些算法进行了可视化:
-
AdaBoost:
-
主成因分析:
-
线性判别分析:
-
感知机:
-
高斯混合模型
-
K-Means
-
K近邻
-
逻辑斯蒂回归
-
支持向量机