史上最强:NumPy 实现全部机器学习算法,代码超3万行!
NumPy 作为 Python 生态中最受欢迎的科学计算包,很多读者已经非常熟悉它了。它为 Python 提供高效率的多维数组计算,并提供了一系列高等数学函数,我们可以快速搭建模型的整个计算流程。毫不负责任地说,NumPy 就是现代深度学习框架的「爸爸」。
尽管目前使用 NumPy 写模型已经不是主流,但这种方式依然不失为是理解底层架构和深度学习原理的好方法。最近,来自普林斯顿的一位博士后将 NumPy 实现的所有机器学习模型全部开源,超过 3 万行代码、30 多个模型,并提供了相应的论文和一些实现的测试效果。
项目地址:https://github.com/ddbourgin/numpy-ml
粗略估计,该项目大约有 30 个主要机器学习模型,此外还有 15 个用于预处理和计算的小工具,全部.py 文件数量有 62 个之多。平均每个模型的代码行数在 500 行以上,在神经网络模型的 layer.py 文件中,代码行数接近 4000。
这,应该是目前用 NumPy 手写机器学习模型的「最高境界」吧。
EM 训练
维特比解码 似然计算 通过 Baum-Welch/forward-backward 算法进行 MLE 参数估计
用变分 EM 进行 MLE 参数估计的标准模型 用 MCMC 进行 MAP 参数估计的平滑模型
Add Flatten Multiply Softmax 全连接/Dense 稀疏进化连接 LSTM Elman 风格的 RNN 最大+平均池化 点积注意力 受限玻尔兹曼机 (w. CD-n training) 2D 转置卷积 (w. padding 和 stride) 2D 卷积 (w. padding、dilation 和 stride) 1D 卷积 (w. padding、dilation、stride 和 causality)
双向 LSTM ResNet 风格的残差块(恒等变换和卷积) WaveNet 风格的残差块(带有扩张因果卷积) Transformer 风格的多头缩放点积注意力
Dropout 归一化 批归一化(时间上和空间上) 层归一化(时间上和空间上)
SGD w/ 动量 AdaGrad RMSProp Adam
常数 指数 Noam/Transformer Dlib 调度器
Glorot/Xavier uniform 和 normal He/Kaiming uniform 和 normal 标准和截断正态分布初始化
交叉熵 平方差 Bernoulli VAE 损失 带有梯度惩罚的 Wasserstein 损失
ReLU Tanh Affine Sigmoid Leaky ReLU
Bernoulli 变分自编码器 带有梯度惩罚的 Wasserstein GAN
col2im (MATLAB 端口) im2col (MATLAB 端口) conv1D conv2D deconv2D minibatch
决策树 (CART) [Bagging] 随机森林 [Boosting] 梯度提升决策树
岭回归 Logistic 回归 最小二乘法 贝叶斯线性回归 w/共轭先验
最大似然得分 Additive/Lidstone 平滑 简单 Good-Turing 平滑
使用交叉熵方法的智能体 首次访问 on-policy 蒙特卡罗智能体 加权增量重要采样蒙特卡罗智能体 Expected SARSA 智能体 TD-0 Q-learning 智能体 Dyna-Q / Dyna-Q+ 优先扫描
Nadaraya-Watson 核回归 k 最近邻分类与回归
离散傅立叶变换 (1D 信号) 双线性插值 (2D 信号) 最近邻插值 (1D 和 2D 信号) 自相关 (1D 信号) 信号窗口 文本分词 特征哈希 特征标准化 One-hot 编码/解码 Huffman 编码/解码 词频逆文档频率编码
相似度核 距离度量 优先级队列 Ball tree 数据结构
def __init__(self, scale=True, dropout_p=0, init='glorot_uniform', optimizer=None):
super().__init__(optimizer)
self.init = init
self.scale = scale
self.dropout_p = dropout_p
self.optimizer = self.optimizer
self._init_params()
def _fwd(self, Q, K, V):
scale = 1 / np.sqrt(Q.shape[-1]) if self.scale else 1
scores = Q @ K.swapaxes(-2, -1) * scale # attention scores
weights = self.softmax.forward(scores) # attention weights
Y = weights @ V
return Y, weights
def _bwd(self, dy, q, k, v, weights):
d_k = k.shape[-1]
scale = 1 / np.sqrt(d_k) if self.scale else 1
dV = weights.swapaxes(-2, -1) @ dy
dWeights = dy @ v.swapaxes(-2, -1)
dScores = self.softmax.backward(dWeights)
dQ = dScores @ k * scale
dK = dScores.swapaxes(-2, -1) @ q * scale
return dQ, dK, dV