红色石头的个人网站:红色石头的个人博客-机器学习、深度学习之路redstonewill.com
李航的《统计学习方法》可以说是机器学习的入门宝典,许多机器学习培训班、互联网企业的面试、笔试题目,很多都参考这本书。笔者近期发现了一个 GitHub 项目,不仅包含了该书的详细笔记,同时提供了各章节机器学习算法详细的 Python 代码。
该项目的作者是 Smirk,项目地址为:SmirkCao/Lihanggithub.com
前言
作者提到李航老师的这本书,真的很薄,但是几乎每句话都会带出很多点,值得反复研读。希望在反复研读的过程中,将整个这本书看厚,变薄。这个系列的所有的文档,以及代码,没有特殊说明的情况下"书中"这个描述指代的都是李航老师的《统计学习方法》。 其他参考文献中的内容如果引用会给出链接。
方便参考文献下载, 添加了 ref_downloader.sh,可以用来下载书中列举的参考文献。
该项目中还包含了 math_markdown.pdf,基本覆盖了书中用到的数学公式的 LaTeX 表达方式。方便读者对照理解。
课程笔记
关于《统计学习方法》的篇幅,其中 SVM 是大部头,占了很大的篇幅,另外DT,HMM,CRF 也占了相对较大的篇幅。如下图所示:
章节之间彼此又有联系,比如 NB 和 LR,DT 和 AdaBoost,Perceptron 和SVM,HMM 和 CRF 等等,如果有大章节遇到困难,可以回顾前面章节的内容,或查看具体章节的参考文献,一般都给出了对这个问题描述更详细的参考文献,可能会解释你卡住的地方。
这份详细笔记整理成 markdown 文件格式。大致提纲如下:
CH01 统计学习方法概论
统计学习方法三要素:模型
策略
算法
CH02 感知机感知机是二类分类的线性分类模型
感知机对应于特征空间中将实例划分为正负两类的分离超平面
CH03 k近邻法kNN是一种基本的分类与回归方法
k值的选择, 距离度量及分类决策规则是kNN的三个基本要素
CH04 朴素贝叶斯法朴素贝叶斯法是基于贝叶斯定理与特征条件独立假设的分类方法
x 的某种组合在先验中没有出现的情况, 会出现概率为0的情况, 对应平滑处理方案:
λ=0 对应极大似然估计,λ=1 对应拉普拉斯平滑朴素贝叶斯法实际上学习到生成数据的机制, 所以属于生成模型
CH05 决策树决策树是一种基本的分类与回归方法
CH06 逻辑斯谛回归与最大熵模型逻辑斯谛回归是统计学中的经典分类方法
最大熵是概率模型学习的一个准则, 将其推广到分类问题得到最大熵模型
CH07 支持向量机支持向量机是一种二分类模型。
基本模型是定义在特征空间上的间隔最大化的线性分类器, 间隔最大使他有别于感知机
这一章占了很大篇幅,因为 margin 这个思想几乎可以串起来整个分类问题。
CH08 提升方法提升方法是一种常用的统计学习方法, 应用广泛且有效
CH09 EM算法及其推广EM 算法是一种迭代算法, 用于含有隐变量的概率模型参数极大似然估计, 或者极大后验概率估计(这里的极大似然估计和极大后验概率估计是学习策略)
如果概率模型的变量都是观测变量, 那么给定数据, 可以直接用极大似然估计法, 或贝叶斯估计法估计模型参数(注意书上这个描述如果不理解,参考 CH04 中朴素贝叶斯法的参数估计部分)
这部分代码实现了 BMM 和 GMM, 值得看下
关于 EM, 这个章节写的不多, EM是十大算法之一, EM 和 Hinton 关系紧密, Hinton 在 2018 年 ICLR 上发表了 Capsule Network 的第二篇文章《Matrix Capsules with EM Routing》
CH10 隐马尔可夫模型隐马尔可夫模型是可用于标注问题的统计学习模型, 描述由隐藏的马尔可夫链随机生成观测序列的过程, 属于生成模型
隐马尔可夫模型是关于时序的概率模型, 描述由一个隐藏的马尔可夫链随机生成不可观测的状态的序列, 再由各个状态速记生成一个观测而产生观测的序列的过程
可用于标注(Tagging)问题, 状态对应标记
三个基本问题: 概率计算问题, 学习问题, 预测问题
CH11 条件随机场条件随机场是给定一组输入随机变量条件下另一组输出随机变量的条件概率分布模型, 其特点是假设输出随机变量构成马尔可夫随机场
概率无向图模型, 又称为马尔可夫随机场, 是一个可以由无向图表示的联合概率分布
三个基本问题: 概率计算问题, 学习问题, 预测问题
CH12 统计学习方法总结
这章就简单的几页, 可以考虑如下阅读套路和第一章一起看
在前面的学习中遇到不清楚的问题的时候,过一遍这个章节
将这一章看厚, 从这一章展开到其他十个章节
李老师这本书真的是每次刷都会有新的收获
随书 Python 代码
该项目除了有详细的讲解笔记之外,每一章还配备了完整可直接运行的 Python 代码 以及测试代码。下面以 感知机和k 近邻为例,演示代码。
感知机:
import numpy as np
import random
import argparse
import logging
class Perceptron(object):
def __init__(self,
max_iter=5000,
eta=0.00001,
verbose=True):
self.eta_ = eta
self.max_iter_ = max_iter
self.w = 0
self.verbose = verbose
def fit(self, X, y):
self.w = np.zeros(X.shape[1] + 1)
correct_count = 0
n_iter_ = 0
while n_iter_ < self.max_iter_:
index = random.randint(0, y.shape[0] - 1)
xx_ = np.hstack([X[index], 1])
yy_ = 2 * y[index] - 1
wx = np.dot(self.w, xx_)
if wx * yy_ > 0:
correct_count += 1
if correct_count > self.max_iter_:
break
continue
self.w += self.eta_ * yy_ * xx_
n_iter_ += 1
if self.verbose:
print(n_iter_)
def predict(self, X):
# for b
X = np.hstack([X, np.ones(X.shape[0]).reshape((-1, 1))])
# activation function for perceptron: sign
rst = np.array([1 if rst else -1 for rst in np.dot(X, self.w) > 0])
# np.sign(0) == 0
# rst = np.sign(np.dot(X, self.w))
return rst
if __name__ == '__main__':
logging.basicConfig(level=logging.INFO, format='%(asctime)s-%(name)s-%(levelname)s-%(message)s')
logger = logging.getLogger(__name__)
ap = argparse.ArgumentParser()
ap.add_argument("-p", "--path", required=False, help="path to input data file")
args = vars(ap.parse_args())
k 近邻:
from collections import namedtuple
from pprint import pformat
import numpy as np
class Node(namedtuple('Node', 'location left_child right_child')):
def __repr__(self):
return pformat(tuple(self))
class KNN(object):
def __init__(self,
k=1,
p=2):
""":param k: knn:param p:"""
self.k = k
self.p = p
self.kdtree = None
@staticmethod
def _fit(X, depth=0):
try:
k = X.shape[1]
except IndexError as e:
return None
# todo: 这里可以展开,通过方差选择
axis = depth % k
X = X[X[:, axis].argsort()]
median = X.shape[0] // 2
try:
X[median]
except IndexError:
return None
return Node(
location=X[median],
left_child=KNN._fit(X[:median], depth + 1),
right_child=KNN._fit(X[median + 1:], depth + 1)
)
def _distance(self, x, y):
return np.linalg.norm(x-y, ord=self.p)
def _search(self, point, tree=None, depth=0, best=None):
if tree is None:
return best
k = point.shape[0]
# update best
if best is None or self._distance(point, tree.location) < self._distance(best, tree.location):
next_best = tree.location
else:
next_best = best
# update branch
if point[depth%k] < tree.location[depth%k]:
next_branch = tree.left_child
else:
next_branch = tree.right_child
return self._search(point, tree=next_branch, depth=depth+1, best=next_best)
def fit(self, X):
self.kdtree = KNN._fit(X)
return self.kdtree
def predict(self, X):
rst = self._search(X, self.kdtree)
return rst
def predict_proba(self, X):
pass
if __name__ == '__main__':
pass
后记
整个这本书里面各章节也不是完全独立的,这部分希望整理章节之间的联系以及适用的数据集。算法到底实现到什么程度,能跑什么数据集也是一方面。
资源下载
该项目包含的完整笔记和代码都已经打包完毕,获取地址:
提取码:wiqa
文章首发于公众号【AI有道】!