diff --git "a/11 \345\256\236\350\267\265\346\226\271\346\263\225\350\256\272.pdf" "b/11 \345\256\236\350\267\265\346\226\271\346\263\225\350\256\272.pdf" new file mode 100644 index 0000000..8ffcc49 Binary files /dev/null and "b/11 \345\256\236\350\267\265\346\226\271\346\263\225\350\256\272.pdf" differ diff --git "a/\347\272\277\346\200\247\344\273\243\346\225\260.pdf" "b/2 \347\272\277\346\200\247\344\273\243\346\225\260.pdf" similarity index 69% rename from "\347\272\277\346\200\247\344\273\243\346\225\260.pdf" rename to "2 \347\272\277\346\200\247\344\273\243\346\225\260.pdf" index 43f29d3..321687e 100644 Binary files "a/\347\272\277\346\200\247\344\273\243\346\225\260.pdf" and "b/2 \347\272\277\346\200\247\344\273\243\346\225\260.pdf" differ diff --git "a/\346\246\202\347\216\207\344\270\216\344\277\241\346\201\257\350\256\272.pdf" "b/3 \346\246\202\347\216\207\344\270\216\344\277\241\346\201\257\350\256\272.pdf" similarity index 88% rename from "\346\246\202\347\216\207\344\270\216\344\277\241\346\201\257\350\256\272.pdf" rename to "3 \346\246\202\347\216\207\344\270\216\344\277\241\346\201\257\350\256\272.pdf" index d056495..e2ead6a 100644 Binary files "a/\346\246\202\347\216\207\344\270\216\344\277\241\346\201\257\350\256\272.pdf" and "b/3 \346\246\202\347\216\207\344\270\216\344\277\241\346\201\257\350\256\272.pdf" differ diff --git "a/\346\225\260\345\200\274\350\256\241\347\256\227.pdf" "b/4 \346\225\260\345\200\274\350\256\241\347\256\227.pdf" similarity index 58% rename from "\346\225\260\345\200\274\350\256\241\347\256\227.pdf" rename to "4 \346\225\260\345\200\274\350\256\241\347\256\227.pdf" index 2107fb5..b48692b 100644 Binary files "a/\346\225\260\345\200\274\350\256\241\347\256\227.pdf" and "b/4 \346\225\260\345\200\274\350\256\241\347\256\227.pdf" differ diff --git "a/5 \346\234\272\345\231\250\345\255\246\344\271\240\345\237\272\347\241\200.pdf" "b/5 \346\234\272\345\231\250\345\255\246\344\271\240\345\237\272\347\241\200.pdf" new file mode 100644 index 0000000..f279056 Binary files /dev/null and "b/5 \346\234\272\345\231\250\345\255\246\344\271\240\345\237\272\347\241\200.pdf" differ diff --git "a/\346\267\261\345\272\246\345\211\215\351\246\210\347\275\221\347\273\234.pdf" "b/6 \346\267\261\345\272\246\345\211\215\351\246\210\347\275\221\347\273\234.pdf" similarity index 91% rename from "\346\267\261\345\272\246\345\211\215\351\246\210\347\275\221\347\273\234.pdf" rename to "6 \346\267\261\345\272\246\345\211\215\351\246\210\347\275\221\347\273\234.pdf" index e70f89e..e74c85d 100644 Binary files "a/\346\267\261\345\272\246\345\211\215\351\246\210\347\275\221\347\273\234.pdf" and "b/6 \346\267\261\345\272\246\345\211\215\351\246\210\347\275\221\347\273\234.pdf" differ diff --git "a/7 \346\267\261\345\272\246\345\255\246\344\271\240\344\270\255\347\232\204\346\255\243\345\210\231\345\214\226.pdf" "b/7 \346\267\261\345\272\246\345\255\246\344\271\240\344\270\255\347\232\204\346\255\243\345\210\231\345\214\226.pdf" new file mode 100644 index 0000000..864721e Binary files /dev/null and "b/7 \346\267\261\345\272\246\345\255\246\344\271\240\344\270\255\347\232\204\346\255\243\345\210\231\345\214\226.pdf" differ diff --git "a/8 \346\267\261\345\272\246\346\250\241\345\236\213\344\270\255\347\232\204\344\274\230\345\214\226.pdf" "b/8 \346\267\261\345\272\246\346\250\241\345\236\213\344\270\255\347\232\204\344\274\230\345\214\226.pdf" new file mode 100644 index 0000000..9264251 Binary files /dev/null and "b/8 \346\267\261\345\272\246\346\250\241\345\236\213\344\270\255\347\232\204\344\274\230\345\214\226.pdf" differ diff --git "a/\345\215\267\347\247\257\347\275\221\347\273\234.pdf" "b/9 \345\215\267\347\247\257\347\275\221\347\273\234.pdf" similarity index 81% rename from "\345\215\267\347\247\257\347\275\221\347\273\234.pdf" rename to "9 \345\215\267\347\247\257\347\275\221\347\273\234.pdf" index 5deed4e..32e7298 100644 Binary files "a/\345\215\267\347\247\257\347\275\221\347\273\234.pdf" and "b/9 \345\215\267\347\247\257\347\275\221\347\273\234.pdf" differ diff --git a/README.md b/README.md index 06cca62..1ab3767 100755 --- a/README.md +++ b/README.md @@ -2,18 +2,38 @@ 《**深度学习**》是深度学习领域唯一的综合性图书,全称也叫做**深度学习 AI圣经(Deep Learning)**,由三位全球知名专家IanGoodfellow、YoshuaBengio、AaronCourville编著,全书囊括了数学及相关概念的背景知识,包括线性代数、概率论、信息论、数值优化以及机器学习中的相关内容。同时,它还介绍了工业界中实践者用到的深度学习技术,包括深度前馈网络、正则化、优化算法、卷积网络、序列建模和实践方法等,并且调研了诸如自然语言处理、语音识别、计算机视觉、在线推荐系统、生物信息学以及视频游戏方面的应用。最后,深度学习全书还提供了一些研究方向,涵盖的理论主题包括线性因子模型、自编码器、表示学习、结构化概率模型、蒙特卡罗方法、配分函数、近似推断以及深度生成模型,适用于相关专业的大学生或研究生使用。 -深度学习封面 +深度学习封面 -可以下载《深度学习》的中文版[pdf](https://github.com/MingchaoZhu/DeepLearning/raw/master/DL%E4%B8%AD%E6%96%87.pdf)和英文版[pdf](https://github.com/MingchaoZhu/DeepLearning/raw/master/DL%E8%8B%B1%E6%96%87.pdf)直接阅读。 +可以下载《深度学习》的中文版 [pdf](https://github.com/MingchaoZhu/DeepLearning/raw/master/DL%E4%B8%AD%E6%96%87.pdf) 和英文版 [pdf](https://github.com/MingchaoZhu/DeepLearning/raw/master/DL%E8%8B%B1%E6%96%87.pdf) 直接阅读。 --- -《深度学习》可以说是深度学习与人工智能的入门宝典,许多算法爱好者、机器学习培训班、互联网企业的面试,很多都参考这本书。但本书晦涩,加上官方没有提供代码实现,因此某些地方较难理解。本站**基于数学推导和产生原理重新描述了书中的概念**,并用**Python** (numpy 库为主) 复现了书本内容(推导过程和代码实现均见**pdf文件**,重要部分的实现代码也放入**code文件夹**中)。 +《深度学习》可以说是深度学习与人工智能的入门宝典,许多算法爱好者、机器学习培训班、互联网企业的面试,很多都参考这本书。但本书晦涩,加上官方没有提供代码实现,因此某些地方较难理解。本站**基于数学推导和产生原理重新描述了书中的概念**,并用**Python** (numpy 库为主) 复现了书本内容 ( **源码级代码实现。推导过程和代码实现均放在了下载区的 pdf 文件中**,重要部分的实现代码也放入 **code 文件夹**中 )。 -然而我水平有限,但我真诚地希望这项工作可以帮助到更多人学习深度学习算法。我需要大家的建议和帮助。如果你在阅读中遇到有误或解释不清的地方,希望可以汇总你的建议,提issue (最好不要一个一个地提)。如果你也想加入这项工作书写中或有其他问题,可以联系我的邮箱:deityrayleigh@gmail.com。 +然而我水平有限,但我真诚地希望这项工作可以帮助到更多人学习深度学习算法。我需要大家的建议和帮助。如果你在阅读中遇到有误或解释不清的地方,希望可以汇总你的建议,提issue。如果你也想加入这项工作书写中或有其他问题,可以联系我的邮箱:deityrayleigh@gmail.com。 写的过程中参考了较多网上优秀的工作,所有参考资源保存在了`reference.txt`文件中。 +# 作者留言 + +最近收到了一些读者的催更邮件,感谢认可,但依旧想在此统一解释一下。每个章节的制作,从每一个概念的详细描述、原理推导、作图、代码实现到生成最终的 pdf 文件,需要时间。为了可以解释清楚,你在 pdf 文件中看到的所有的图几乎都是我自己画的。如果你在阅读过程中遇到有想要描述的概念点,可以发邮件告知我。这个项目的工作会一直更新完,不会咕。最后,如果你认可这份工作的话,希望可以 watch、star、fork 三连一下,或者在其他平台转发推广。非常感谢你的认可与推广,谢谢! ——朱明超 + +# 更新说明 + +2020/3/: + + 1. 修改第五章决策树部分,补充 ID3 和 CART 的原理,代码实现以 CART 为主。 + 2. 第七章添加 L1 和 L2 正则化最优解的推导 (即 L1稀疏解的原理)。 + 3. 第七章添加集成学习方法的推导与代码实现,包括 Bagging (随机森林)、Boosting (Adaboost、GBDT、XGBoost) + 4. 第八章添加牛顿法与拟牛顿法 (DFP、BFGS、L-BFGS) 的推导。 + 5. 第十一章节添加高斯过程回归 (GPR) 与贝叶斯优化的推导与代码实现。 + +后面每次的更新内容会统一放在 `update.txt` 文件中。 + +# 章节目录与文件下载 + +除了《深度学习》书中的概念点,**本项目也在各章节添加一些补充知识,例如第七章集成学习部分的 随机森林、Adaboost、GBDT、XGBoost 的原理剖析和代码实现等,又或者第十二章对当前一些主流方法的描述**。大的章节目录和 pdf 文件下载链接可以详见下表,而具体 pdf 文件中的实际目录请参考 `contents.txt`。 + | 中文章节 | 英文章节 | 下载
(含推导与代码实现) | | ------------ | ------------ | ------------ | | 第一章 前言 | 1 Introduction | | diff --git a/code/chapter 11.py b/code/chapter 11.py new file mode 100644 index 0000000..492b879 --- /dev/null +++ b/code/chapter 11.py @@ -0,0 +1,289 @@ +import pandas as pd +import numpy as np +import itertools +import time +import re +from scipy.stats import norm +import matplotlib.pyplot as plt + + +def cal_conf_matrix(labels, preds): + """ + 计算混淆矩阵。 + + 参数说明: + labels:样本标签 (真实结果) + preds:预测结果 + """ + n_sample = len(labels) + result = pd.DataFrame(index=range(0,n_sample),columns=('probability','label')) + result['label'] = np.array(labels) + result['probability'] = np.array(preds) + cm = np.arange(4).reshape(2,2) + cm[0,0] = len(result[result['label']==1][result['probability']>=0.5]) # TP,注意这里是以 0.5 为阈值 + cm[0,1] = len(result[result['label']==1][result['probability']<0.5]) # FN + cm[1,0] = len(result[result['label']==0][result['probability']>=0.5]) # FP + cm[1,1] = len(result[result['label']==0][result['probability']<0.5]) # TN + return cm + + +def cal_PRF1(labels, preds): + """ + 计算查准率P,查全率R,F1值。 + """ + cm = cal_conf_matrix(labels, preds) + P = cm[0,0]/(cm[0,0]+cm[1,0]) + R = cm[0,0]/(cm[0,0]+cm[0,1]) + F1 = 2*P*R/(P+R) + return P, R, F1 + + +def cal_PRcurve(labels, preds): + """ + 计算PR曲线上的值。 + """ + n_sample = len(labels) + result = pd.DataFrame(index=range(0,n_sample),columns=('probability','label')) + y_pred[y_pred>=0.5] = 1 + y_pred[y_pred<0.5] = 0 + result['label'] = np.array(labels) + result['probability'] = np.array(preds) + result.sort_values('probability',inplace=True,ascending=False) + PandR = pd.DataFrame(index=range(len(labels)),columns=('P','R')) + for j in range(len(result)): + # 以每一个概率为分类的阈值,统计此时正例和反例的数量 + result_j = result.head(n=j+1) + P = len(result_j[result_j['label']==1])/float(len(result_j)) # 当前实际为正的数量/当前预测为正的数量 + R = len(result_j[result_j['label']==1])/float(len(result[result['label']==1])) # 当前真正例的数量/实际为正的数量 + PandR.iloc[j] = [P,R] + return PandR + + +def cal_ROCcurve(labels, preds): + """ + 计算ROC曲线上的值。 + """ + n_sample = len(labels) + result = pd.DataFrame(index=range(0,n_sample),columns=('probability','label')) + y_pred[y_pred>=0.5] = 1 + y_pred[y_pred<0.5] = 0 + result['label'] = np.array(labels) + result['probability'] = np.array(preds) + # 计算 TPR,FPR + result.sort_values('probability',inplace=True,ascending=False) + TPRandFPR=pd.DataFrame(index=range(len(result)),columns=('TPR','FPR')) + for j in range(len(result)): + # 以每一个概率为分类的阈值,统计此时正例和反例的数量 + result_j=result.head(n=j+1) + TPR=len(result_j[result_j['label']==1])/float(len(result[result['label']==1])) # 当前真正例的数量/实际为正的数量 + FPR=len(result_j[result_j['label']==0])/float(len(result[result['label']==0])) # 当前假正例的数量/实际为负的数量 + TPRandFPR.iloc[j]=[TPR,FPR] + return TPRandFPR + + +def timeit(func): + """ + 装饰器,计算函数执行时间 + """ + def wrapper(*args, **kwargs): + time_start = time.time() + result = func(*args, **kwargs) + time_end = time.time() + exec_time = time_end - time_start + print("{function} exec time: {time}s".format(function=func.__name__,time=exec_time)) + return result + return wrapper + +@timeit +def area_auc(labels, preds): + """ + AUC值的梯度法计算 + """ + TPRandFPR = cal_ROCcurve(labels, preds) + # 计算AUC,计算小矩形的面积之和 + auc = 0. + prev_x = 0 + for x, y in zip(TPRandFPR.FPR,TPRandFPR.TPR): + if x != prev_x: + auc += (x - prev_x) * y + prev_x = x + return auc + +@timeit +def naive_auc(labels, preds): + """ + AUC值的概率法计算 + """ + n_pos = sum(labels) + n_neg = len(labels) - n_pos + total_pair = n_pos * n_neg # 总的正负样本对的数目 + labels_preds = zip(labels, preds) + labels_preds = sorted(labels_preds,key=lambda x:x[1]) # 对预测概率升序排序 + count_neg = 0 # 统计负样本出现的个数 + satisfied_pair = 0 # 统计满足条件的样本对的个数 + for i in range(len(labels_preds)): + if labels_preds[i][0] == 1: + satisfied_pair += count_neg # 表明在这个正样本下,有哪些负样本满足条件 + else: + count_neg += 1 + return satisfied_pair / float(total_pair) + + +#####----Bayesian Hyperparameter Optimization----#### +class KernelBase(ABC): + + def __init__(self): + super().__init__() + self.params = {} + self.hyperparams = {} + + @abstractmethod + def _kernel(self, X, Y): + raise NotImplementedError + + def __call__(self, X, Y=None): + return self._kernel(X, Y) + + def __str__(self): + P, H = self.params, self.hyperparams + p_str = ", ".join(["{}={}".format(k, v) for k, v in P.items()]) + return "{}({})".format(H["op"], p_str) + + def summary(self): + return { + "op": self.hyperparams["op"], + "params": self.params, + "hyperparams": self.hyperparams, + } + + +class RBFKernel(KernelBase): + + def __init__(self, sigma=None): + """ + RBF 核。 + """ + super().__init__() + self.hyperparams = {"op": "RBFKernel"} + self.params = {"sigma": sigma} # 如果 sigma 未赋值则默认为 np.sqrt(n_features/2),n_features 为特征数。 + + def _kernel(self, X, Y=None): + """ + 对 X 和 Y 的行的每一对计算 RBF 核。如果 Y 为空,则 Y=X。 + + 参数说明: + X:输入数组,为 (n_samples, n_features) + Y:输入数组,为 (m_samples, n_features) + """ + X = X.reshape(-1, 1) if X.ndim == 1 else X + Y = X if Y is None else Y + Y = Y.reshape(-1, 1) if Y.ndim == 1 else Y + assert X.ndim == 2 and Y.ndim == 2, "X and Y must have 2 dimensions" + sigma = np.sqrt(X.shape[1] / 2) if self.params["sigma"] is None else self.params["sigma"] + X, Y = X / sigma, Y / sigma + D = -2 * X @ Y.T + np.sum(Y**2, axis=1) + np.sum(X**2, axis=1)[:, np.newaxis] + D[D < 0] = 0 + return np.exp(-0.5 * D) + + +class KernelInitializer(object): + + def __init__(self, param=None): + self.param = param + + def __call__(self): + r = r"([a-zA-Z0-9]*)=([^,)]*)" + kr_str = self.param.lower() + kwargs = dict([(i, eval(j)) for (i, j) in re.findall(r, self.param)]) + if "rbf" in kr_str: + kernel = RBFKernel(**kwargs) + else: + raise NotImplementedError("{}".format(kr_str)) + return kernel + + +class GPRegression: + """ + 高斯过程回归 + """ + def __init__(self, kernel="RBFKernel", sigma=1e-10): + self.kernel = KernelInitializer(kernel)() + self.params = {"GP_mean": None, "GP_cov": None, "X": None} + self.hyperparams = {"kernel": str(self.kernel), "sigma": sigma} + + def fit(self, X, y): + """ + 用已有的样本集合得到 GP 先验。 + + 参数说明: + X:输入数组,为 (n_samples, n_features) + y:输入数组 X 的目标值,为 (n_samples) + """ + mu = np.zeros(X.shape[0]) + Cov = self.kernel(X, X) + self.params["X"] = X + self.params["y"] = y + self.params["GP_cov"] = Cov + self.params["GP_mean"] = mu + + def predict(self, X_star, conf_interval=0.95): + """ + 对新的样本 X 进行预测。 + + 参数说明: + X_star:输入数组,为 (n_samples, n_features) + conf_interval:置信区间,浮点型 (0, 1),default=0.95 + """ + X = self.params["X"] + y = self.params["y"] + K = self.params["GP_cov"] + sigma = self.hyperparams["sigma"] + K_star = self.kernel(X_star, X) + K_star_star = self.kernel(X_star, X_star) + sig = np.eye(K.shape[0]) * sigma + K_y_inv = np.linalg.pinv(K + sig) + mean = K_star @ K_y_inv @ y + cov = K_star_star - K_star @ K_y_inv @ K_star.T + percentile = norm.ppf(conf_interval) + conf = percentile * np.sqrt(np.diag(cov)) + return mean, conf, cov + + +class BayesianOptimization: + + def __init__(self): + self.model = GPRegression() + + def acquisition_function(self, Xsamples): + mu, _, cov = self.model.predict(Xsamples) + mu = mu if mu.ndim==1 else (mu.T)[0] + ysample = np.random.multivariate_normal(mu, cov) + return ysample + + def opt_acquisition(self, X, n_samples=20): + # 样本搜索策略,一般方法有随机搜索、基于网格的搜索,或局部搜索 + # 我们这里就用简单的随机搜索,这里也可以定义样本的范围 + Xsamples = np.random.randint(low=1,high=50,size=n_samples*X.shape[1]) + Xsamples = Xsamples.reshape(n_samples, X.shape[1]) + # 计算采集函数的值并取最大的值 + scores = self.acquisition_function(Xsamples) + ix = np.argmax(scores) + return Xsamples[ix, 0] + + def fit(self, f, X, y): + # 拟合 GPR 模型 + self.model.fit(X, y) + # 优化过程 + for i in range(15): + x_star = self.opt_acquisition(X) # 下一个采样点 + y_star = f(x_star) + mean, conf, cov = self.model.predict(np.array([[x_star]])) + # 添加当前数据到数据集合 + X = np.vstack((X, [[x_star]])) + y = np.vstack((y, [[y_star]])) + # 更新 GPR 模型 + self.model.fit(X, y) + ix = np.argmax(y) + print('Best Result: x=%.3f, y=%.3f' % (X[ix], y[ix])) + return X[ix], y[ix] + diff --git a/code/chapter5.py b/code/chapter5.py index c240311..01d597e 100644 --- a/code/chapter5.py +++ b/code/chapter5.py @@ -1,5 +1,6 @@ import numpy as np import cvxopt +import math ########-----NaiveBayes------######### @@ -56,6 +57,11 @@ def _calculate_probabilities(self, X): def predict(self, X): y_pred = [self._calculate_probabilities(sample) for sample in X] return y_pred + + def score(self, X, y): + y_pred = self.predict(X) + accuracy = np.sum(y == y_pred, axis=0) / len(y) + return accuracy ########-----LogisticRegression------######### @@ -88,6 +94,11 @@ def predict(self, X): y_pred = self.sigmoid(X.dot(self.param)) return y_pred + def score(self, X, y): + y_pred = self.predict(X) + accuracy = np.sum(y == y_pred, axis=0) / len(y) + return accuracy + ########-----SupportVectorMachine------######### # 隐藏cvxopt输出 @@ -190,7 +201,12 @@ def predict(self, X): y_pred.append(np.sign(prediction)) return np.array(y_pred) + def score(self, X, y): + y_pred = self.predict(X) + accuracy = np.sum(y == y_pred, axis=0) / len(y) + return accuracy + ########-----KNN------######### class KNN(): @@ -235,7 +251,12 @@ def predict(self, X): y_pred.append(np.sign(prediction)) return np.array(y_pred) + def score(self, X, y): + y_pred = self.predict(X) + accuracy = np.sum(y == y_pred, axis=0) / len(y) + return accuracy + ########-----DecisionTree------######### class DecisionNode(): @@ -366,6 +387,11 @@ def predict(self, X): y_pred = [self.predict_value(sample) for sample in X] return y_pred + def score(self, X, y): + y_pred = self.predict(X) + accuracy = np.sum(y == y_pred, axis=0) / len(y) + return accuracy + def print_tree(self, tree=None, indent=" "): """ 输出树 @@ -394,10 +420,31 @@ def calculate_entropy(y): return entropy +def calculate_gini(y): + unique_labels = np.unique(y) + var = 0 + for label in unique_labels: + count = len(y[y == label]) + p = count / len(y) + var += p ** 2 + return 1 - var + + class ClassificationTree(DecisionTree): """ - 分类树,在决策书节点选择计算信息增益,在叶子节点选择多数表决 + 分类树,在决策书节点选择计算信息增益/基尼指数,在叶子节点选择多数表决。 """ + def _calculate_gini_index(self, y, y1, y2): + """ + 计算基尼指数 + """ + p = len(y1) / len(y) + gini = calculate_gini(y) + gini_index = gini - p * \ + calculate_gini(y1) - (1 - p) * \ + calculate_gini(y2) + return gini_index + def _calculate_information_gain(self, y, y1, y2): """ @@ -408,7 +455,6 @@ def _calculate_information_gain(self, y, y1, y2): info_gain = entropy - p * \ calculate_entropy(y1) - (1 - p) * \ calculate_entropy(y2) - return info_gain def _majority_vote(self, y): @@ -425,40 +471,58 @@ def _majority_vote(self, y): return most_common def fit(self, X, y): - self._impurity_calculation = self._calculate_information_gain + self._impurity_calculation = self._calculate_gini_index self._leaf_value_calculation = self._majority_vote super(ClassificationTree, self).fit(X, y) - -def calculate_variance(X): - mean = np.ones(np.shape(X)) * X.mean(0) - n_samples = np.shape(X)[0] - variance = (1 / n_samples) * np.diag((X - mean).T.dot(X - mean)) + +def calculate_mse(y): + return np.mean((y - np.mean(y)) ** 2) + + +def calculate_variance(y): + n_samples = np.shape(y)[0] + variance = (1 / n_samples) * np.diag((y - np.mean(y)).T.dot(y - np.mean(y))) return variance class RegressionTree(DecisionTree): """ - 回归树,在决策书节点选择计算方差降低,在叶子节点选择均值 + 回归树,在决策书节点选择计算MSE/方差降低,在叶子节点选择均值。 """ + def _calculate_mse(self, y, y1, y2): + """ + 计算MSE降低 + """ + mse_tot = calculate_mse(y) + mse_1 = calculate_mse(y1) + mse_2 = calculate_mse(y2) + frac_1 = len(y1) / len(y) + frac_2 = len(y2) / len(y) + mse_reduction = mse_tot - (frac_1 * mse_1 + frac_2 * mse_2) + return mse_reduction def _calculate_variance_reduction(self, y, y1, y2): + """ + 计算方差降低 + """ var_tot = calculate_variance(y) var_1 = calculate_variance(y1) var_2 = calculate_variance(y2) frac_1 = len(y1) / len(y) frac_2 = len(y2) / len(y) - variance_reduction = var_tot - (frac_1 * var_1 + frac_2 * var_2) - return sum(variance_reduction) def _mean_of_y(self, y): + """ + 计算均值 + """ value = np.mean(y, axis=0) return value if len(value) > 1 else value[0] def fit(self, X, y): - self._impurity_calculation = self._calculate_variance_reduction + self._impurity_calculation = self._calculate_mse self._leaf_value_calculation = self._mean_of_y super(RegressionTree, self).fit(X, y) diff --git a/code/chapter7.py b/code/chapter7.py index 277de6a..3b6a909 100644 --- a/code/chapter7.py +++ b/code/chapter7.py @@ -1,7 +1,9 @@ from abc import ABC, abstractmethod import numpy as np +import math import re - +import progressbar +from chapter5 import RegressionTree, DecisionTree, ClassificationTree #########---Regularizer---###### class RegularizerBase(ABC): @@ -273,3 +275,556 @@ def hyperparams(self): else: hp["wrappers"] = [hpw] return hp + + +#####----Bagging----####### +# 进度条 +bar_widgets = [ + 'Training: ', progressbar.Percentage(), ' ', progressbar.Bar(marker="-", left="[", right="]"), + ' ', progressbar.ETA() +] + +def get_random_subsets(X, y, n_subsets, replacements=True): + """从训练数据中抽取数据子集 (默认可重复抽样)""" + n_samples = np.shape(X)[0] + # 将 X 和 y 拼接,并将元素随机排序 + Xy = np.concatenate((X, y.reshape((1, len(y))).T), axis=1) + np.random.shuffle(Xy) + subsets = [] + # 如果抽样时不重复抽样,可以只使用 50% 的训练数据;如果抽样时可重复抽样,使用全部的训练数据,默认可重复抽样 + subsample_size = int(n_samples // 2) + if replacements: + subsample_size = n_samples + for _ in range(n_subsets): + idx = np.random.choice( + range(n_samples), + size=np.shape(range(subsample_size)), + replace=replacements) + X = Xy[idx][:, :-1] + y = Xy[idx][:, -1] + subsets.append([X, y]) + return subsets + + +class Bagging(): + """ + Bagging分类器。使用一组分类树,这些分类树使用特征训练数据的随机子集。 + """ + def __init__(self, n_estimators=100, max_features=None, min_samples_split=2, + min_gain=0, max_depth=float("inf")): + self.n_estimators = n_estimators # 树的数目 + self.min_samples_split = min_samples_split # 分割所需的最小样本数 + self.min_gain = min_gain # 分割所需的最小纯度 (最小信息增益) + self.max_depth = max_depth # 树的最大深度 + self.progressbar = progressbar.ProgressBar(widgets=bar_widgets) + + # 初始化决策树 + self.trees = [] + for _ in range(n_estimators): + self.trees.append( + ClassificationTree( + min_samples_split=self.min_samples_split, + min_impurity=min_gain, + max_depth=self.max_depth)) + + def fit(self, X, y): + # 对每棵树选择数据集的随机子集 + subsets = get_random_subsets(X, y, self.n_estimators) + for i in self.progressbar(range(self.n_estimators)): + X_subset, y_subset = subsets[i] + # 用特征子集和真实值训练一棵子模型 (这里的数据也是训练数据集的随机子集) + self.trees[i].fit(X_subset, y_subset) + + def predict(self, X): + y_preds = np.empty((X.shape[0], len(self.trees))) + # 每棵决策树都在数据上预测 + for i, tree in enumerate(self.trees): + # 基于特征做出预测 + prediction = tree.predict(X) + y_preds[:, i] = prediction + + y_pred = [] + # 对每个样本,选择最常见的类别作为预测 + for sample_predictions in y_preds: + y_pred.append(np.bincount(sample_predictions.astype('int')).argmax()) + return y_pred + + def score(self, X, y): + y_pred = self.predict(X) + accuracy = np.sum(y == y_pred, axis=0) / len(y) + return accuracy + + +#####----RandomForest----####### +class RandomForest(): + """ + 随机森林分类器。使用一组分类树,这些分类树使用特征的随机子集训练数据的随机子集。 + """ + def __init__(self, n_estimators=100, max_features=None, min_samples_split=2, + min_gain=0, max_depth=float("inf")): + self.n_estimators = n_estimators # 树的数目 + self.max_features = max_features # 每棵树的最大使用特征数 + self.min_samples_split = min_samples_split # 分割所需的最小样本数 + self.min_gain = min_gain # 分割所需的最小纯度 (最小信息增益) + self.max_depth = max_depth # 树的最大深度 + self.progressbar = progressbar.ProgressBar(widgets=bar_widgets) + + # 初始化决策树 + self.trees = [] + for _ in range(n_estimators): + self.trees.append( + ClassificationTree( + min_samples_split=self.min_samples_split, + min_impurity=min_gain, + max_depth=self.max_depth)) + + def fit(self, X, y): + n_features = np.shape(X)[1] + # 如果 max_features 没有定义,取默认值 sqrt(n_features) + if not self.max_features: + self.max_features = int(math.sqrt(n_features)) + + # 对每棵树选择数据集的随机子集 + subsets = get_random_subsets(X, y, self.n_estimators) + + for i in self.progressbar(range(self.n_estimators)): + X_subset, y_subset = subsets[i] + # 选择特征的随机子集 + idx = np.random.choice(range(n_features), size=self.max_features, replace=True) + # 保存特征的索引用于预测 + self.trees[i].feature_indices = idx + # 选择索引对应的特征 + X_subset = X_subset[:, idx] + # 用特征子集和真实值训练一棵子模型 (这里的数据也是训练数据集的随机子集) + self.trees[i].fit(X_subset, y_subset) + + def predict(self, X): + y_preds = np.empty((X.shape[0], len(self.trees))) + # 每棵决策树都在数据上预测 + for i, tree in enumerate(self.trees): + # 使用该决策树训练使用的特征 + idx = tree.feature_indices + # 基于特征做出预测 + prediction = tree.predict(X[:, idx]) + y_preds[:, i] = prediction + + y_pred = [] + # 对每个样本,选择最常见的类别作为预测 + for sample_predictions in y_preds: + y_pred.append(np.bincount(sample_predictions.astype('int')).argmax()) + return y_pred + + def score(self, X, y): + y_pred = self.predict(X) + accuracy = np.sum(y == y_pred, axis=0) / len(y) + return accuracy + + +#####----Adaboost----####### +# 决策树桩,作为 Adaboost 算法的弱分类器 (基分类器) +class DecisionStump(): + + def __init__(self): + self.polarity = 1 # 表示决策树桩默认输出的类别为 1 或是 -1 + self.feature_index = None # 用于分类的特征索引 + self.threshold = None # 特征的阈值 + self.alpha = None # 表示分类器准确性的值 + +class Adaboost(): + """ + Adaboost 算法。 + """ + def __init__(self, n_estimators=5): + self.n_estimators = n_estimators # 将使用的弱分类器的数量 + self.progressbar = progressbar.ProgressBar(widgets=bar_widgets) + + def fit(self, X, y): + n_samples, n_features = np.shape(X) + # 初始化权重 (上文中的 D),均为 1/N + w = np.full(n_samples, (1 / n_samples)) + self.trees = [] + # 迭代过程 + for _ in self.progressbar(range(self.n_estimators)): + tree = DecisionStump() + min_error = float('inf') # 使用某一特征值的阈值预测样本的最小误差 + # 迭代遍历每个 (不重复的) 特征值,查找预测 y 的最佳阈值 + for feature_i in range(n_features): + feature_values = np.expand_dims(X[:, feature_i], axis=1) + unique_values = np.unique(feature_values) + # 将该特征的每个特征值作为阈值 + for threshold in unique_values: + p = 1 + # 将所有样本预测默认值可以设置为 1 + prediction = np.ones(np.shape(y)) + # 低于特征值阈值的预测改为 -1 + prediction[X[:, feature_i] < threshold] = -1 + # 计算错误率 + error = sum(w[y != prediction]) + # 如果错误率超过 50%,我们反转决策树桩默认输出的类别 + # 比如 error = 0.8 => (1 - error) = 0.2, + # 原来计算的是输出到类别 1 的概率,类别 1 作为默认类别。反转后类别 0 作为默认类别 + if error > 0.5: + error = 1 - error + p = -1 + # 如果这个阈值导致最小的错误率,则保存 + if error < min_error: + tree.polarity = p + tree.threshold = threshold + tree.feature_index = feature_i + min_error = error + + # 计算用于更新样本权值的 alpha 值,也是作为基分类器的系数。 + tree.alpha = 0.5 * math.log((1.0 - min_error) / (min_error + 1e-10)) + # 将所有样本预测默认值设置为 1 + predictions = np.ones(np.shape(y)) + # 如果特征值低于阈值,则修改预测结果,这里还需要考虑弱分类器的默认输出类别 + negative_idx = (tree.polarity * X[:, tree.feature_index] < tree.polarity * tree.threshold) + predictions[negative_idx] = -1 + # 计算新权值,未正确分类样本的权值增大,正确分类样本的权值减小 + w *= np.exp(-tree.alpha * y * predictions) + w /= np.sum(w) + # 保存分类器 + self.trees.append(tree) + + def predict(self, X): + n_samples = np.shape(X)[0] + y_pred = np.zeros((n_samples, 1)) + # 用每一个基分类器预测样本 + for tree in self.trees: + # 将所有样本预测默认值设置为 1 + predictions = np.ones(np.shape(y_pred)) + negative_idx = (tree.polarity * X[:, tree.feature_index] < tree.polarity * tree.threshold) + predictions[negative_idx] = -1 + # 对基分类器加权求和,权重 alpha + y_pred += tree.alpha * predictions + # 返回预测结果 1 或 -1 + y_pred = np.sign(y_pred).flatten() + return y_pred + + def score(self, X, y): + y_pred = self.predict(X) + accuracy = np.sum(y == y_pred, axis=0) / len(y) + return accuracy + + +#####----GBDT----####### +class Loss(ABC): + + def __init__(self): + super().__init__() + + @abstractmethod + def loss(self, y_true, y_pred): + return NotImplementedError() + + @abstractmethod + def grad(self, y, y_pred): + raise NotImplementedError() + +class SquareLoss(Loss): + + def __init__(self): + pass + + def loss(self, y, y_pred): + pass + + def grad(self, y, y_pred): + return -(y - y_pred) + + def hess(self, y, y_pred): + return 1 + +class CrossEntropyLoss(Loss): + + def __init__(self): + pass + + def loss(self, y, y_pred): + pass + + def grad(self, y, y_pred): + return - (y - y_pred) + + def hess(self, y, y_pred): + return y_pred * (1-y_pred) + + +def softmax(x): + e_x = np.exp(x - np.max(x, axis=-1, keepdims=True)) + return e_x / e_x.sum(axis=-1, keepdims=True) + + +def line_search(self, y, y_pred, h_pred): + Lp = 2 * np.sum((y - y_pred) * h_pred) + Lpp = np.sum(h_pred * h_pred) + return 1 if np.sum(Lpp) == 0 else Lp / Lpp + + +def to_categorical(x, n_classes=None): + """ + One-hot编码 + """ + if not n_classes: + n_classes = np.amax(x) + 1 + one_hot = np.zeros((x.shape[0], n_classes)) + one_hot[np.arange(x.shape[0]), x] = 1 + return one_hot + + +class GradientBoostingDecisionTree(object): + """ + GBDT 算法。用一组基学习器 (回归树) 学习损失函数的梯度。 + """ + def __init__(self, n_estimators, learning_rate=1, min_samples_split=2, + min_impurity=1e-7, max_depth=float("inf"), is_regression=False, line_search=False): + self.n_estimators = n_estimators # 迭代的次数 + self.learning_rate = learning_rate # 训练过程中沿着负梯度走的步长,也就是学习率 + self.min_samples_split = min_samples_split # 分割所需的最小样本数 + self.min_impurity = min_impurity # 分割所需的最小纯度 + self.max_depth = max_depth # 树的最大深度 + self.is_regression = is_regression # 分类问题或回归问题 + self.line_search = line_search # 是否使用 line search + self.progressbar = progressbar.ProgressBar(widgets=bar_widgets) + # 回归问题采用基础的平方损失,分类问题采用交叉熵损失 + self.loss = SquareLoss() + if not self.is_regression: + self.loss = CrossEntropyLoss() + + def fit(self, X, Y): + # 分类问题将 Y 转化为 one-hot 编码 + if not self.is_regression: + Y = to_categorical(Y.flatten()) + else: + Y = Y.reshape(-1, 1) if len(Y.shape) == 1 else Y + self.out_dims = Y.shape[1] + self.trees = np.empty((self.n_estimators, self.out_dims), dtype=object) + Y_pred = np.full(np.shape(Y), np.mean(Y, axis=0)) + self.weights = np.ones((self.n_estimators, self.out_dims)) + self.weights[1:, :] *= self.learning_rate + # 迭代过程 + for i in self.progressbar(range(self.n_estimators)): + for c in range(self.out_dims): + tree = RegressionTree( + min_samples_split=self.min_samples_split, + min_impurity=self.min_impurity, + max_depth=self.max_depth) + # 计算损失的梯度,并用梯度进行训练 + if not self.is_regression: + Y_hat = softmax(Y_pred) + y, y_pred = Y[:, c], Y_hat[:, c] + else: + y, y_pred = Y[:, c], Y_pred[:, c] + neg_grad = -1 * self.loss.grad(y, y_pred) + tree.fit(X, neg_grad) + # 用新的基学习器进行预测 + h_pred = tree.predict(X) + # line search + if self.line_search == True: + self.weights[i, c] *= line_search(y, y_pred, h_pred) + # 加法模型中添加基学习器的预测,得到最新迭代下的加法模型预测 + Y_pred[:, c] += np.multiply(self.weights[i, c], h_pred) + self.trees[i, c] = tree + + def predict(self, X): + Y_pred = np.zeros((X.shape[0], self.out_dims)) + # 生成预测 + for c in range(self.out_dims): + y_pred = np.array([]) + for i in range(self.n_estimators): + update = np.multiply(self.weights[i, c], self.trees[i, c].predict(X)) + y_pred = update if not y_pred.any() else y_pred + update + Y_pred[:, c] = y_pred + if not self.is_regression: + # 分类问题输出最可能类别 + Y_pred = Y_pred.argmax(axis=1) + return Y_pred + + def score(self, X, y): + y_pred = self.predict(X) + accuracy = np.sum(y == y_pred, axis=0) / len(y) + return accuracy + + +class GradientBoostingRegressor(GradientBoostingDecisionTree): + + def __init__(self, n_estimators=200, learning_rate=1, min_samples_split=2, + min_impurity=1e-7, max_depth=float("inf"), is_regression=True, line_search=False): + super(GradientBoostingRegressor, self).__init__(n_estimators=n_estimators, + learning_rate=learning_rate, + min_samples_split=min_samples_split, + min_impurity=min_impurity, + max_depth=max_depth, + is_regression=is_regression, + line_search=line_search) + + +class GradientBoostingClassifier(GradientBoostingDecisionTree): + + def __init__(self, n_estimators=200, learning_rate=1, min_samples_split=2, + min_impurity=1e-7, max_depth=float("inf"), is_regression=False, line_search=False): + super(GradientBoostingClassifier, self).__init__(n_estimators=n_estimators, + learning_rate=learning_rate, + min_samples_split=min_samples_split, + min_impurity=min_impurity, + max_depth=max_depth, + is_regression=is_regression, + line_search=line_search) + + +#####----XGBoost----####### +class XGBoostRegressionTree(DecisionTree): + """ + XGBoost 回归树。此处基于第五章介绍的决策树,故采用贪心算法找到特征上分裂点 (枚举特征上所有可能的分裂点)。 + """ + def __init__(self, min_samples_split=2, min_impurity=1e-7, + max_depth=float("inf"), loss=None, gamma=0., lambd=0.): + super(XGBoostRegressionTree, self).__init__(min_impurity=min_impurity, + min_samples_split=min_samples_split, + max_depth=max_depth) + self.gamma = gamma # 叶子节点的数目的惩罚系数 + self.lambd = lambd # 叶子节点的权重的惩罚系数 + self.loss = loss # 损失函数 + + def _split(self, y): + # y 包含 y_true 在左半列,y_pred 在右半列 + col = int(np.shape(y)[1]/2) + y, y_pred = y[:, :col], y[:, col:] + return y, y_pred + + def _gain(self, y, y_pred): + # 计算信息 + nominator = np.power((y * self.loss.grad(y, y_pred)).sum(), 2) + denominator = self.loss.hess(y, y_pred).sum() + return nominator / (denominator + self.lambd) + + def _gain_by_taylor(self, y, y1, y2): + # 分割为左子树和右子树 + y, y_pred = self._split(y) + y1, y1_pred = self._split(y1) + y2, y2_pred = self._split(y2) + true_gain = self._gain(y1, y1_pred) + false_gain = self._gain(y2, y2_pred) + gain = self._gain(y, y_pred) + # 计算信息增益 + return 0.5 * (true_gain + false_gain - gain) - self.gamma + + def _approximate_update(self, y): + y, y_pred = self._split(y) + # 计算叶节点权重 + gradient = self.loss.grad(y, y_pred).sum() + hessian = self.loss.hess(y, y_pred).sum() + leaf_approximation = -gradient / (hessian + self.lambd) + return leaf_approximation + + def fit(self, X, y): + self._impurity_calculation = self._gain_by_taylor + self._leaf_value_calculation = self._approximate_update + super(XGBoostRegressionTree, self).fit(X, y) + + +class XGBoost(object): + """ + XGBoost学习器。 + """ + def __init__(self, n_estimators=200, learning_rate=0.001, min_samples_split=2, + min_impurity=1e-7, max_depth=2, is_regression=False, gamma=0., lambd=0.): + self.n_estimators = n_estimators # 树的数目 + self.learning_rate = learning_rate # 训练过程中沿着负梯度走的步长,也就是学习率 + self.min_samples_split = min_samples_split # 分割所需的最小样本数 + self.min_impurity = min_impurity # 分割所需的最小纯度 + self.max_depth = max_depth # 树的最大深度 + self.gamma = gamma # 叶子节点的数目的惩罚系数 + self.lambd = lambd # 叶子节点的权重的惩罚系数 + self.is_regression = is_regression # 分类或回归问题 + self.progressbar = progressbar.ProgressBar(widgets=bar_widgets) + # 回归问题采用基础的平方损失,分类问题采用交叉熵损失 + self.loss = SquareLoss() + if not self.is_regression: + self.loss = CrossEntropyLoss() + + def fit(self, X, Y): + # 分类问题将 Y 转化为 one-hot 编码 + if not self.is_regression: + Y = to_categorical(Y.flatten()) + else: + Y = Y.reshape(-1, 1) if len(Y.shape) == 1 else Y + self.out_dims = Y.shape[1] + self.trees = np.empty((self.n_estimators, self.out_dims), dtype=object) + Y_pred = np.zeros(np.shape(Y)) + self.weights = np.ones((self.n_estimators, self.out_dims)) + self.weights[1:, :] *= self.learning_rate + # 迭代过程 + for i in self.progressbar(range(self.n_estimators)): + for c in range(self.out_dims): + tree = XGBoostRegressionTree( + min_samples_split=self.min_samples_split, + min_impurity=self.min_impurity, + max_depth=self.max_depth, + loss=self.loss, + gamma=self.gamma, + lambd=self.lambd) + # 计算损失的梯度,并用梯度进行训练 + if not self.is_regression: + Y_hat = softmax(Y_pred) + y, y_pred = Y[:, c], Y_hat[:, c] + else: + y, y_pred = Y[:, c], Y_pred[:, c] + + y, y_pred = y.reshape(-1, 1), y_pred.reshape(-1, 1) + y_and_ypred = np.concatenate((y, y_pred), axis=1) + tree.fit(X, y_and_ypred) + # 用新的基学习器进行预测 + h_pred = tree.predict(X) + # 加法模型中添加基学习器的预测,得到最新迭代下的加法模型预测 + Y_pred[:, c] += np.multiply(self.weights[i, c], h_pred) + self.trees[i, c] = tree + + def predict(self, X): + Y_pred = np.zeros((X.shape[0], self.out_dims)) + # 生成预测 + for c in range(self.out_dims): + y_pred = np.array([]) + for i in range(self.n_estimators): + update = np.multiply(self.weights[i, c], self.trees[i, c].predict(X)) + y_pred = update if not y_pred.any() else y_pred + update + Y_pred[:, c] = y_pred + if not self.is_regression: + # 分类问题输出最可能类别 + Y_pred = Y_pred.argmax(axis=1) + return Y_pred + + def score(self, X, y): + y_pred = self.predict(X) + accuracy = np.sum(y == y_pred, axis=0) / len(y) + return accuracy + + +class XGBRegressor(XGBoost): + + def __init__(self, n_estimators=200, learning_rate=1, min_samples_split=2, + min_impurity=1e-7, max_depth=float("inf"), is_regression=True, + gamma=0., lambd=0.): + super(XGBRegressor, self).__init__(n_estimators=n_estimators, + learning_rate=learning_rate, + min_samples_split=min_samples_split, + min_impurity=min_impurity, + max_depth=max_depth, + is_regression=is_regression, + gamma=gamma, + lambd=lambd) + + +class XGBClassifier(XGBoost): + + def __init__(self, n_estimators=200, learning_rate=1, min_samples_split=2, + min_impurity=1e-7, max_depth=float("inf"), is_regression=False, + gamma=0., lambd=0.): + super(XGBClassifier, self).__init__(n_estimators=n_estimators, + learning_rate=learning_rate, + min_samples_split=min_samples_split, + min_impurity=min_impurity, + max_depth=max_depth, + is_regression=is_regression, + gamma=gamma, + lambd=lambd) diff --git a/contents.txt b/contents.txt new file mode 100644 index 0000000..77ec27d --- /dev/null +++ b/contents.txt @@ -0,0 +1,205 @@ +注:目录是基于《深度学习》的目录起的。基于本项目的内容,目录其实可以分的更细致,这里就分到目录的第三级为止。 + +**目录**: + +- 第二章 线性代数 + - 1 标量, 向量, 矩阵, 张量 + - 2 矩阵转置 + - 3 矩阵加法 + - 4 矩阵乘法 + - 5 单位矩阵 + - 6 矩阵的逆 + - 7 范数 + - 8 特征值分解 + - 9 奇异值分解 + - 10 PCA (主成分分析) + + +- 第三章 概率与信息论 + - 1 概率 + - 1.1 概率与随机变量 + - 1.2 概率分布 + - 1.2.1 概率质量函数 + - 1.2.2 概率密度函数 + - 1.2.3 累积分布函数 + - 1.3 条件概率与条件独立 + - 1.4 随机变量的度量 + - 1.5 常用概率分布 + - 1.5.1 伯努利分布 (两点分布) + - 1.5.2 范畴分布 (分类分布) + - 1.5.3 高斯分布 (正态分布) + - 1.5.4 多元高斯分布 (多元正态分布) + - 1.5.5 指数分布 + - 1.5.6 拉普拉斯分布 + - 1.5.7 Dirac 分布 + - 1.6 常用函数的有用性质 + - 1.6.1 logistic sigmoid 函数 + - 1.6.2 softplus 函数 + - 2 信息论 + - 3 图模型 + - 3.1 有向图模型 + - 3.1.1 贝叶斯网的独立性 + - 3.2 无向图模型 + - 3.1.2 马尔可夫网的条件独立性 + + +- 第四章 数值计算 + - 1 上溢和下溢 + - 2 优化方法 + - 2.1 梯度下降法 + - 2.2 牛顿法 + - 2.3 约束优化 + + +- 第五章 机器学习基础 + - 1 学习算法 + - 1.1 举例:线性回归 + - 2 容量、过拟合、欠拟合 + - 2.1 泛化问题 + - 2.2 容量 + - 3 超参数与验证集 + - 4 偏差和方差 + - 4.1 偏差 + - 4.2 方差 + - 4.3 误差与偏差和方差的关系 + - 5 最大似然估计 + - 6 贝叶斯统计 + - 7 最大后验估计 + - 7.1 举例:线性回归 + - 8 监督学习方法 + - 8.1 概率监督学习 + - 8.2 支持向量机 + - 8.2.1 核技巧 + - 8.3 k-近邻 + - 8.4 决策树 + - 8.4.1 特征选择 + - 8.4.2 决策树生成 + - 8.4.3 决策树正则化 + - 9 无监督学习方法 + - 9.1 主成分分析法 + - 9.2 k-均值聚类 + + +- 第六章 深度前馈网络 + - 1 深度前馈网络 + - 2 DFN 相关设计 + - 2.1 隐藏单元 + - 2.2 输出单元 + - 2.3 代价函数 + - 2.4 架构设计 + - 3 反向传播算法 + - 3.1 单个神经元的训练 + - 3.2 多层神经网络的训练 + - 3.2.1 定义权重初始化方法 + - 3.2.2 定义激活函数 + - 3.2.3 定义优化方法 + - 3.2.4 定义网络层的框架 + - 3.2.5 定义代价函数 + - 3.2.6 定义深度前馈网络 + - 4 神经网络的万能近似定理 + - 5 实例:学习 XOR + + +- 第七章 深度学习中的正则化 + - 1 参数范数惩罚 + - 1.1 L2 正则化 + - 1.2 L1 正则化 + - 1.3 总结 (L2 正则化与L1 正则化的解) + - 1.4 作为约束的范数惩罚 + - 1.5 欠约束问题 + - 2 数据增强 + - 2.1 数据集增强 + - 2.2 噪声鲁棒性 + - 3 训练方案 + - 3.1 半监督学习 + - 3.2 多任务学习 + - 3.3 提前终止 + - 4 模型表示 + - 4.1 参数绑定与共享 + - 4.2 稀疏表示 + - 4.3 Bagging 及其他集成方法 + - 4.3.1 Bagging 方法 + - 4.3.2 随机森林 + - 4.3.3 方法解决过拟合 + - 4.4 Dropout + - 5 样本测试 + - 6 补充材料 + - 6.1 Boosting + - 6.1.1 前向分步加法模型 + - 6.1.2 AdaBoost 算法 + - 6.1.3 Boosting Tree 算法与 GBDT 算法 + - 6.1.4 XGBoost 算法 + + +- 第八章 深度模型中的优化 + - 1 基本优化算法 + - 1.1 梯度 + - 1.1.1 梯度下降 + - 1.1.2 随机梯度下降 + - 1.2 动量 + - 1.2.1 Momentum 算法 + - 1.2.2 NAG 算法 + - 1.3 自适应学习率 + - 1.3.1 AdaGrad 算法 + - 1.3.2 RMSProp 算法 + - 1.3.3 AdaDelta 算法 + - 1.3.4 Adam 算法 + - 1.4 二阶近似方法 + - 1.4.1 牛顿法 + - 1.4.2 拟牛顿法 + - 2 优化策略 + - 2.1 参数初始化 + - 3 批标准化 + - 4 坐标下降 + - 5 Polyak 平均 + - 6 监督预训练 + - 7 设计有助于优化的模型 + + +- 第九章 卷积网络 + - 1 卷积运算 + - 2 池化 + - 3 深度学习框架下的卷积 + - 3.1 多个并行卷积 + - 3.2 输入值与核 + - 3.3 填充 (Padding) + - 3.4 卷积步幅 (Stride) + - 4 更多的卷积策略 + - 4.1 深度可分离卷积 (Depthwise Separable Convolution) + - 4.2 分组卷积 (Group Convolution) + - 4.3 扩张卷积 (Dilated Convolution) + - 5 GEMM 转换 + - 6 卷积网络的训练 + - 6.1 卷积网络示意图 + - 6.2 单层卷积层/池化层 + - 6.2.1 卷积函数的导数及反向传播 + - 6.2.2 池化函数的导数及后向传播 + - 6.3 多层卷积层/池化层 + - 6.4 Flatten 层 & 全连接层 + - 7 平移等变 + - 8 代表性的卷积神经网络 + - 8.1 卷积神经网络 (LeNet) + + +- 第十一章 实践方法论 + - 1 实践方法论 + - 2 性能度量指标 + - 2.1 错误率与准确性 + - 2.2 查准率、查全率与 F1 值 + - 2.2.1 混淆矩阵 + - 2.2.2 查准率和查全率的定义与关联 + - 2.2.3 F1 值 + - 2.3 PR 曲线 + - 2.4 ROC 曲线与 AUC 值 + - 2.4.1 ROC 曲线 + - 2.4.2 AUC 值的计算方法 + - 2.5 覆盖 + - 2.6 指标性能的瓶颈 + - 3 默认基准模型 + - 4 确定是否收集更多数据 + - 5 选择超参数 + - 5.1 手动超参数调整 + - 5.2 自动超参数优化算法 + - 5.2.1 网格搜索 (Grid Search) + - 5.2.2 随机搜索 (Random Search) + - 5.2.3 基于模型的超参数优化 (Model-based Hyperparameter Optimization) diff --git a/reference.txt b/reference.txt index 535107c..9381c60 100644 --- a/reference.txt +++ b/reference.txt @@ -34,12 +34,20 @@ - https://www.zybuluo.com/songying/note/1400484 - https://zhuanlan.zhihu.com/p/37120298 - https://kevinzakka.github.io/2016/09/14/batch_normalization/ + - http://gitlinux.net/2018-10-29-xgboost/ + - https://medium.com/swlh/boosting-and-bagging-explained-with-examples-5353a36eb78d + - http://www.ccs.neu.edu/home/vip/teach/MLcourse/4_boosting/slides/gradient_boosting.pdf + - https://blog.csdn.net/liangjun_feng/article/details/79603705 + - https://blog.csdn.net/sinat_22594309/article/details/60957594 + - https://www.zybuluo.com/yxd/note/611571 + - http://freemind.pluskid.org/machine-learning/sparsity-and-some-basics-of-l1-regularization/#ed61992b37932e208ae114be75e42a3e6dc34cb3 - 深度模型中的优化 - https://zhuanlan.zhihu.com/p/32626442 - https://github.com/exacity/deeplearningbook-chinese - http://cthorey.github.io./backpropagation/ - http://www.ludoart.cn/2019/02/22/Optimization-Methods/ + - https://blog.csdn.net/itplus/article/details/21897715 - 卷积神经网络 - https://www.slideshare.net/kuwajima/cnnbp @@ -50,4 +58,14 @@ - https://zh.gluon.ai/chapter_convolutional-neural-networks/lenet.html - https://zhuanlan.zhihu.com/p/32702031 - https://blog.csdn.net/marsjhao/article/details/73088850 + +- 实践方法论 + - https://github.com/masakazu-ishihata/BayesianOptimization + - https://github.com/bjzhao143/MLwithPython + - https://medium.com/inveterate-learner/deep-learning-book-chapter-11-c6ad1d3c3c08 + - https://www.alexejgossmann.com/auc/ + - https://machinelearningmastery.com/roc-curves-and-precision-recall-curves-for-imbalanced-classification/ + - https://www.yuque.com/books/share/f4031f65-70c1-4909-ba01-c47c31398466/kqbfug + - http://bridg.land/posts/gaussian-processes-1 + - https://zhuanlan.zhihu.com/p/76269142 diff --git a/update.txt b/update.txt new file mode 100644 index 0000000..8e28d9f --- /dev/null +++ b/update.txt @@ -0,0 +1,10 @@ +**更新记录**: + +2020/3/: + + 1. 修改第五章决策树部分,补充 ID3 和 CART 的原理,代码实现以 CART 为主。 + 2. 第七章添加 L1 和 L2 正则化最优解的推导 (即 L1稀疏解的原理)。 + 3. 第七章添加集成学习方法的推导与代码实现,包括 Bagging (随机森林)、Boosting (Adaboost、GBDT、XGBoost) + 4. 第八章添加牛顿法与拟牛顿法 (DFP、BFGS、L-BFGS) 的推导。 + 5. 第十一章节添加高斯过程回归 (GPR) 与贝叶斯优化的推导与代码实现。 + diff --git "a/\346\234\272\345\231\250\345\255\246\344\271\240\345\237\272\347\241\200.pdf" "b/\346\234\272\345\231\250\345\255\246\344\271\240\345\237\272\347\241\200.pdf" deleted file mode 100644 index 99b59c2..0000000 Binary files "a/\346\234\272\345\231\250\345\255\246\344\271\240\345\237\272\347\241\200.pdf" and /dev/null differ diff --git "a/\346\267\261\345\272\246\345\255\246\344\271\240\344\270\255\347\232\204\346\255\243\345\210\231\345\214\226.pdf" "b/\346\267\261\345\272\246\345\255\246\344\271\240\344\270\255\347\232\204\346\255\243\345\210\231\345\214\226.pdf" deleted file mode 100644 index 4ffc7a8..0000000 Binary files "a/\346\267\261\345\272\246\345\255\246\344\271\240\344\270\255\347\232\204\346\255\243\345\210\231\345\214\226.pdf" and /dev/null differ diff --git "a/\346\267\261\345\272\246\346\250\241\345\236\213\344\270\255\347\232\204\344\274\230\345\214\226.pdf" "b/\346\267\261\345\272\246\346\250\241\345\236\213\344\270\255\347\232\204\344\274\230\345\214\226.pdf" deleted file mode 100644 index 5b2c75a..0000000 Binary files "a/\346\267\261\345\272\246\346\250\241\345\236\213\344\270\255\347\232\204\344\274\230\345\214\226.pdf" and /dev/null differ