├── .gitignore ├── LICENSE ├── README.md ├── datasets ├── MNIST │ └── raw │ │ ├── load_data.py │ │ ├── t10k-images-idx3-ubyte │ │ ├── t10k-images-idx3-ubyte.gz │ │ ├── t10k-labels-idx1-ubyte │ │ ├── t10k-labels-idx1-ubyte.gz │ │ ├── train-images-idx3-ubyte │ │ ├── train-images-idx3-ubyte.gz │ │ ├── train-labels-idx1-ubyte │ │ └── train-labels-idx1-ubyte.gz └── README.ipynb ├── ml-with-numpy ├── AdaBoost │ ├── AdaBoost.py │ └── README.md ├── DecisionTree │ ├── DecisionTree.md │ └── DecisionTree.py ├── EM │ └── EM.md ├── HMM │ └── 10.HMM.ipynb ├── LinearRegression │ ├── BGD.png │ ├── LiR_np.py │ ├── LinearRegression.ipynb │ └── cross_valid.png ├── LogisticRegression │ ├── LogisticRegression.ipynb │ └── README.md ├── MLP │ ├── MLP_np.py │ ├── README.md │ └── assets │ │ └── img.png ├── SVM │ ├── README.md │ ├── SVM_np.ipynb │ ├── SVM_np.py │ └── assets │ │ ├── 1042406-20161125104106409-1177897648.png │ │ ├── 1042406-20161125104737206-364720074.png │ │ ├── 20180214224342909.png │ │ └── image-20210809104104109.png └── kNN │ ├── README.md │ └── kNN.py ├── ml-with-sklearn ├── .gitignore ├── 01-LinearRegression │ ├── LinearRegression.ipynb │ ├── assets │ │ ├── BGD.png │ │ ├── cross_valid.png │ │ └── 线性回归.jpg │ ├── 多项式回归.py │ ├── 线性回归.md │ ├── 线性回归.pdf │ └── 线性回归.py ├── 02-LogisticRegression │ ├── LogisticRegression.ipynb │ ├── LogisticRegression.py │ ├── 逻辑回归.md │ └── 逻辑回归.pdf ├── 03-DecisionTree │ ├── DecisionTree.ipynb │ ├── DecisionTree.md │ ├── DecisionTree.pdf │ ├── DecisionTree.py │ ├── DecisionTree_visualization.ipynb │ └── assets │ │ └── WeChat39e385eab473a12c016dd13b725eff9b.png ├── 04-MLP │ ├── MLP.assets │ │ └── WeChatb8270a9e865ca875dbbc163459504707.png │ ├── MLP.ipynb │ ├── MLP.md │ ├── MLP.pdf │ └── MLP.py ├── 05-SVM │ ├── SVM.ipynb │ ├── datasets.zip │ ├── svm.md │ ├── svm.pdf │ └── svm.py ├── 06-Bayes │ ├── NBayes.ipynb │ ├── NBayes.py │ ├── 贝叶斯.md │ ├── 贝叶斯.pdf │ └── 贝叶斯分类器.ipynb ├── 07-Random Forest │ ├── bagging与随机森林.ipynb │ ├── bagging和随机森林.ipynb │ ├── bagging和随机森林.md │ ├── bagging和随机森林.pdf │ └── bagging和随机森林.py ├── 08-AdaBoost │ ├── AdaBoost.ipynb │ ├── AdaBoost.py │ ├── AdaBoost算法.ipynb │ └── README.md ├── 09-K-means │ ├── k-means.ipynb │ ├── k-means.md │ ├── k-means.pdf │ ├── k-means.py │ └── res │ │ ├── k-means-1.png │ │ ├── k-means-2.webp │ │ ├── k-means-3.jpg │ │ └── k-means-4.png ├── 10-kNN │ ├── knn.ipynb │ ├── knn.md │ ├── knn.pdf │ ├── knn.py │ └── res │ │ ├── knn-1.png │ │ ├── knn-2.png │ │ └── knn-3.jpg ├── 11-PCA │ ├── PCA.ipynb │ ├── PCA.md │ ├── PCA.pdf │ ├── PCA.py │ ├── data │ │ └── cars.csv │ └── res │ │ ├── pca-0.png │ │ ├── pca-1.png │ │ ├── pca-2.png │ │ ├── pca-3.png │ │ └── pca-4.gif ├── 12-HMM │ ├── HMM.ipynb │ ├── HMM.md │ ├── HMM.pdf │ ├── HMM.py │ ├── data │ │ ├── HMMTrainSet.txt │ │ └── testArtical.txt │ └── res │ │ ├── hmm-1.svg │ │ ├── hmm-2.webp │ │ ├── hmm-3.webp │ │ ├── hmm-4.webp │ │ └── hmm-5.svg ├── 13-Visualization │ ├── asset │ │ ├── output_11_0.png │ │ ├── output_11_1.png │ │ ├── output_13_1.png │ │ ├── output_13_2.png │ │ ├── output_16_0.png │ │ ├── output_18_0.png │ │ ├── output_18_1.png │ │ ├── output_20_0.png │ │ ├── output_20_1.png │ │ ├── output_22_0.png │ │ ├── output_24_0.png │ │ ├── output_26_0.png │ │ ├── output_28_0.png │ │ ├── output_30_0.png │ │ ├── output_33_0.png │ │ ├── output_36_0.png │ │ └── output_38_0.png │ ├── visualization_report.ipynb │ ├── visualization_report.md │ ├── visualization_report.pdf │ └── visualization_report.py └── 组队学习 │ └── sklearn机器学习介绍.pptx ├── src ├── 1f9174e32387cd4dbc0212efbde06eada8173c937666927d4287155d8cc6f988.jpg ├── 80b58c8d6044a0f0a6c38b32de319994a570a5f40400a2bc30046f7fcb339fcb.jpg ├── 82a1f7c37889cc9d03a7ca97b56db6c796fed916d2e2576577d97586c52c80ed.jpg ├── 88dcdba0d98766b40896dfbf1809ed4456b85dc1e17a6e5b9ac952830760ab1d.jpg ├── b167b1042d0a59ae8a0c68c6fb9915e49f9e16f30e645adef405cca797607778.jpg ├── cd28bdb3dab6a33f137ceb4fde2527b14e4e6a41f8ef0bc8a6957dcd2324c840.jpg └── de17622c-0887-46d8-a32a-ee8da80a7c54.png ├── 天池金融风控.ipynb ├── 西瓜书代码实战.md └── 西瓜书代码实战.pdf /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | .git 4 | __pycache__ 5 | .ipynb_checkpoints -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 johnjim0816 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [西瓜书代码实战](https://github.com/datawhalechina/machine-learning-toy-code) 2 | 3 | 本项目以西瓜书以及[南瓜书](https://datawhalechina.github.io/pumpkin-book/#/)为主要参考,其他资料为辅助,来进行常见机器学习代码的实战。主要特色为力求数码结合,即数学公式与相关代码的形神对应,能够帮助读者加深对公式的理解以及代码的熟练。 4 | 5 | 详细教程请阅读 西瓜书代码实战.pdf 6 | 7 | ## 算法进度 8 | 9 | | 算法名称 | 相关材料 | 进度 | 备注 | 10 | | :----------: | :------: | :--: | ---- | 11 | | 01-LinearRegression |sklearn | OK | | 12 | | 02-LogisticRegression |sklearn | OK | | 13 | | 03-DecisionTree |sklearn | OK | | 14 | | 04-MLP |sklearn | OK | | 15 | | 05-SVM |sklearn | OK | | 16 | | 06-Bayes |sklearn | OK | | 17 | | 07-Random Forest |sklearn | OK | | 18 | | 08-AdaBoos |sklearn | OK | | 19 | | 09-K-means |sklearn | OK | | 20 | | 10-kNN |sklearn | OK | | 21 | | 11-PCA |sklearn | OK | | 22 | | 12-HMM |hmmlearn | OK | | 23 | | 13-Visualization |sklearn | OK | | 24 | 25 | ## 算法项目实战 26 | 27 | 学习完了西瓜书,手动实现相关的算法后,接下来就是到了实战的环节,datawhale开源的数据竞赛项目给大家施展自己coding的平台 28 | 29 | - [数据挖掘实践(二手车价格预测)](https://github.com/datawhalechina/team-learning-data-mining/tree/master/SecondHandCarPriceForecast) 30 | 31 | - [数据挖掘实践(金融风控)](https://github.com/datawhalechina/team-learning-data-mining/tree/master/FinancialRiskControl) 32 | - [数据挖掘实践(心跳信号分类)](https://github.com/datawhalechina/team-learning-data-mining/tree/master/HeartbeatClassification) 33 | 34 | 35 | ## 贡献者 36 | 37 | 38 | 39 | 40 | 47 | 52 | 57 | 62 | 67 | 68 | 69 |
41 | 42 | pic 43 |
44 | John Jim 45 |

算法实战
北京大学

46 |
48 | pic
49 | 牧小熊 50 |

chap 13
华中农业大学

51 |
53 | pic
54 | 梁家晖 55 |

chap 1-4
广州城市理工学院

56 |
58 | pic
59 | 张若萱 60 |

chap 5-8
天津理工大学

61 |
63 | pic
64 | 孙子涵 65 |

chap 9-12
太原理工大学

66 |
70 | 71 | 72 | 73 | 74 | ## Refs 75 | 76 | 77 | 统计学习方法、西瓜书、机器学习实战 78 | -------------------------------------------------------------------------------- /datasets/MNIST/raw/load_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | ''' 4 | Author: JiangJi 5 | Email: johnjim0816@gmail.com 6 | Date: 2023-01-30 09:31:34 7 | LastEditor: JiangJi 8 | LastEditTime: 2023-01-30 09:31:35 9 | Discription: 10 | ''' 11 | ''' 12 | 此脚本提供两种方法,一种为load_local_mnist,即将本地的.gz文件解码为数据, 13 | 一种是利用keras在线下载mnist 14 | ''' 15 | import numpy as np 16 | from struct import unpack 17 | import gzip 18 | import os 19 | 20 | 21 | def __read_image(path): 22 | with gzip.open(path, 'rb') as f: 23 | magic, num, rows, cols = unpack('>4I', f.read(16)) 24 | img = np.frombuffer(f.read(), dtype=np.uint8).reshape(num, 28*28) 25 | return img 26 | 27 | 28 | def __read_label(path): 29 | with gzip.open(path, 'rb') as f: 30 | magic, num = unpack('>2I', f.read(8)) 31 | lab = np.frombuffer(f.read(), dtype=np.uint8) 32 | # print(lab[1]) 33 | return lab 34 | 35 | 36 | def __normalize_image(image): 37 | '''__normalize_image 将image的像素值(0-255)归一化 38 | Args: 39 | image ([type]): [description] 40 | Returns: 41 | [type]: [description] 42 | ''' 43 | img = image.astype(np.float32) / 255.0 44 | return img 45 | 46 | 47 | def __one_hot_label(label): 48 | '''__one_hot_label 将label进行one-hot编码 49 | Args: 50 | label ([type]): 输入为0-9,表示数字标签 51 | Returns: 52 | [type]: 输出为二进制编码,比如[0,0,1,0,0,0,0,0,0,0]表示数字2 53 | ''' 54 | lab = np.zeros((label.size, 10)) 55 | for i, row in enumerate(lab): 56 | row[label[i]] = 1 57 | return lab 58 | 59 | 60 | def load_local_mnist(x_train_path=os.path.dirname(__file__)+'/train-images-idx3-ubyte.gz', y_train_path=os.path.dirname(__file__)+'/train-labels-idx1-ubyte.gz', x_test_path=os.path.dirname(__file__)+'/t10k-images-idx3-ubyte.gz', y_test_path=os.path.dirname(__file__)+'/t10k-labels-idx1-ubyte.gz', normalize=True, one_hot=True): 61 | '''load_mnist 读取.gz格式的MNIST数据集 62 | Args: 63 | x_train_path ([type]): [description] 64 | y_train_path ([type]): [description] 65 | x_test_path ([type]): [description] 66 | y_test_path ([type]): [description] 67 | normalize (bool, optional): [description]. Defaults to True. 68 | one_hot (bool, optional): one_hot为True的情况下,标签作为one-hot数组返回 69 | one-hot数组是指[0,0,1,0,0,0,0,0,0,0]这样的数组 70 | Returns: 71 | [type]: (训练图像, 训练标签), (测试图像, 测试标签) 72 | 训练集数量为60000,每行包含维度为784=28*28的向量 73 | ''' 74 | image = { 75 | 'train': __read_image(x_train_path), 76 | 'test': __read_image(x_test_path) 77 | } 78 | 79 | label = { 80 | 'train': __read_label(y_train_path), 81 | 'test': __read_label(y_test_path) 82 | } 83 | 84 | if normalize: 85 | for key in ('train', 'test'): 86 | image[key] = __normalize_image(image[key]) 87 | 88 | if one_hot: 89 | for key in ('train', 'test'): 90 | label[key] = __one_hot_label(label[key]) 91 | 92 | return (image['train'], label['train']), (image['test'], label['test']) 93 | 94 | 95 | 96 | def load_online_data(): # categorical_crossentropy 97 | from keras.datasets import mnist 98 | from keras.utils import np_utils 99 | import numpy as np 100 | (x_train, y_train), (x_test, y_test) = mnist.load_data() 101 | number = 10000 102 | x_train, y_train = x_train[0:number], y_train[0:number] 103 | x_train = x_train.reshape(number, 28 * 28) 104 | x_test = x_test.reshape(x_test.shape[0], 28 * 28) 105 | x_train = x_train.astype('float32') 106 | x_test = x_test.astype('float32') 107 | 108 | # convert class vectors to binary class matrices 109 | y_train = np_utils.to_categorical(y_train, 10) 110 | y_test = np_utils.to_categorical(y_test, 10) 111 | x_test = np.random.normal(x_test) # 加噪声 112 | 113 | x_train, x_test = x_train / 255, x_test / 255 114 | 115 | return (x_train, y_train), (x_test, y_test) 116 | 117 | 118 | if __name__ == "__main__": 119 | 120 | (x_train, y_train), (x_test, y_test) = load_local_mnist() -------------------------------------------------------------------------------- /datasets/MNIST/raw/t10k-images-idx3-ubyte: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/datasets/MNIST/raw/t10k-images-idx3-ubyte -------------------------------------------------------------------------------- /datasets/MNIST/raw/t10k-images-idx3-ubyte.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/datasets/MNIST/raw/t10k-images-idx3-ubyte.gz -------------------------------------------------------------------------------- /datasets/MNIST/raw/t10k-labels-idx1-ubyte: -------------------------------------------------------------------------------- 1 | '                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             -------------------------------------------------------------------------------- /datasets/MNIST/raw/t10k-labels-idx1-ubyte.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/datasets/MNIST/raw/t10k-labels-idx1-ubyte.gz -------------------------------------------------------------------------------- /datasets/MNIST/raw/train-images-idx3-ubyte: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/datasets/MNIST/raw/train-images-idx3-ubyte -------------------------------------------------------------------------------- /datasets/MNIST/raw/train-images-idx3-ubyte.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/datasets/MNIST/raw/train-images-idx3-ubyte.gz -------------------------------------------------------------------------------- /datasets/MNIST/raw/train-labels-idx1-ubyte: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/datasets/MNIST/raw/train-labels-idx1-ubyte -------------------------------------------------------------------------------- /datasets/MNIST/raw/train-labels-idx1-ubyte.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/datasets/MNIST/raw/train-labels-idx1-ubyte.gz -------------------------------------------------------------------------------- /ml-with-numpy/AdaBoost/AdaBoost.py: -------------------------------------------------------------------------------- 1 | ''' 2 | 数据集:Mnist 3 | 训练集数量:60000(实际使用:10000) 4 | 测试集数量:10000(实际使用:1000) 5 | 层数:40 6 | ------------------------------ 7 | 运行结果: 8 | 正确率:97% 9 | 运行时长:65m 10 | ''' 11 | 12 | import time 13 | import numpy as np 14 | 15 | def loadData(fileName): 16 | ''' 17 | 加载文件 18 | :param fileName:要加载的文件路径 19 | :return: 数据集和标签集 20 | ''' 21 | #存放数据及标记 22 | dataArr = []; labelArr = [] 23 | #读取文件 24 | fr = open(fileName) 25 | #遍历文件中的每一行 26 | for line in fr.readlines(): 27 | #获取当前行,并按“,”切割成字段放入列表中 28 | #strip:去掉每行字符串首尾指定的字符(默认空格或换行符) 29 | #split:按照指定的字符将字符串切割成每个字段,返回列表形式 30 | curLine = line.strip().split(',') 31 | #将每行中除标记外的数据放入数据集中(curLine[0]为标记信息) 32 | #在放入的同时将原先字符串形式的数据转换为整型 33 | #此外将数据进行了二值化处理,大于128的转换成1,小于的转换成0,方便后续计算 34 | dataArr.append([int(int(num) > 128) for num in curLine[1:]]) 35 | #将标记信息放入标记集中 36 | #放入的同时将标记转换为整型 37 | 38 | #转换成二分类任务 39 | #标签0设置为1,反之为-1 40 | if int(curLine[0]) == 0: 41 | labelArr.append(1) 42 | else: 43 | labelArr.append(-1) 44 | #返回数据集和标记 45 | return dataArr, labelArr 46 | 47 | def calc_e_Gx(trainDataArr, trainLabelArr, n, div, rule, D): 48 | ''' 49 | 计算分类错误率 50 | :param trainDataArr:训练数据集数字 51 | :param trainLabelArr: 训练标签集数组 52 | :param n: 要操作的特征 53 | :param div:划分点 54 | :param rule:正反例标签 55 | :param D:权值分布D 56 | :return:预测结果, 分类误差率 57 | ''' 58 | #初始化分类误差率为0 59 | e = 0 60 | #将训练数据矩阵中特征为n的那一列单独剥出来做成数组。因为其他元素我们并不需要, 61 | #直接对庞大的训练集进行操作的话会很慢 62 | x = trainDataArr[:, n] 63 | #同样将标签也转换成数组格式,x和y的转换只是单纯为了提高运行速度 64 | #测试过相对直接操作而言性能提升很大 65 | y = trainLabelArr 66 | predict = [] 67 | 68 | #依据小于和大于的标签依据实际情况会不同,在这里直接进行设置 69 | if rule == 'LisOne': L = 1; H = -1 70 | else: L = -1; H = 1 71 | 72 | #遍历所有样本的特征m 73 | for i in range(trainDataArr.shape[0]): 74 | if x[i] < div: 75 | #如果小于划分点,则预测为L 76 | #如果设置小于div为1,那么L就是1, 77 | #如果设置小于div为-1,L就是-1 78 | predict.append(L) 79 | #如果预测错误,分类错误率要加上该分错的样本的权值(8.1式) 80 | if y[i] != L: e += D[i] 81 | elif x[i] >= div: 82 | #与上面思想一样 83 | predict.append(H) 84 | if y[i] != H: e += D[i] 85 | #返回预测结果和分类错误率e 86 | #预测结果其实是为了后面做准备的,在算法8.1第四步式8.4中exp内部有个Gx,要用在那个地方 87 | #以此来更新新的D 88 | return np.array(predict), e 89 | 90 | def createSigleBoostingTree(trainDataArr, trainLabelArr, D): 91 | ''' 92 | 创建单层提升树 93 | :param trainDataArr:训练数据集数组 94 | :param trainLabelArr: 训练标签集数组 95 | :param D: 算法8.1中的D 96 | :return: 创建的单层提升树 97 | ''' 98 | 99 | #获得样本数目及特征数量 100 | m, n = np.shape(trainDataArr) 101 | #单层树的字典,用于存放当前层提升树的参数 102 | #也可以认为该字典代表了一层提升树 103 | sigleBoostTree = {} 104 | #初始化分类误差率,分类误差率在算法8.1步骤(2)(b)有提到 105 | #误差率最高也只能100%,因此初始化为1 106 | sigleBoostTree['e'] = 1 107 | 108 | #对每一个特征进行遍历,寻找用于划分的最合适的特征 109 | for i in range(n): 110 | #因为特征已经经过二值化,只能为0和1,因此分切分时分为-0.5, 0.5, 1.5三挡进行切割 111 | for div in [-0.5, 0.5, 1.5]: 112 | #在单个特征内对正反例进行划分时,有两种情况: 113 | #可能是小于某值的为1,大于某值得为-1,也可能小于某值得是-1,反之为1 114 | #因此在寻找最佳提升树的同时对于两种情况也需要遍历运行 115 | #LisOne:Low is one:小于某值得是1 116 | #HisOne:High is one:大于某值得是1 117 | for rule in ['LisOne', 'HisOne']: 118 | #按照第i个特征,以值div进行切割,进行当前设置得到的预测和分类错误率 119 | Gx, e = calc_e_Gx(trainDataArr, trainLabelArr, i, div, rule, D) 120 | #如果分类错误率e小于当前最小的e,那么将它作为最小的分类错误率保存 121 | if e < sigleBoostTree['e']: 122 | sigleBoostTree['e'] = e 123 | #同时也需要存储最优划分点、划分规则、预测结果、特征索引 124 | #以便进行D更新和后续预测使用 125 | sigleBoostTree['div'] = div 126 | sigleBoostTree['rule'] = rule 127 | sigleBoostTree['Gx'] = Gx 128 | sigleBoostTree['feature'] = i 129 | #返回单层的提升树 130 | return sigleBoostTree 131 | 132 | def createBosstingTree(trainDataList, trainLabelList, treeNum = 50): 133 | ''' 134 | 创建提升树 135 | 创建算法依据“8.1.2 AdaBoost算法” 算法8.1 136 | :param trainDataList:训练数据集 137 | :param trainLabelList: 训练测试集 138 | :param treeNum: 树的层数 139 | :return: 提升树 140 | ''' 141 | #将数据和标签转化为数组形式 142 | trainDataArr = np.array(trainDataList) 143 | trainLabelArr = np.array(trainLabelList) 144 | #没增加一层数后,当前最终预测结果列表 145 | finallpredict = [0] * len(trainLabelArr) 146 | #获得训练集数量以及特征个数 147 | m, n = np.shape(trainDataArr) 148 | 149 | #依据算法8.1步骤(1)初始化D为1/N 150 | D = [1 / m] * m 151 | #初始化提升树列表,每个位置为一层 152 | tree = [] 153 | #循环创建提升树 154 | for i in range(treeNum): 155 | #得到当前层的提升树 156 | curTree = createSigleBoostingTree(trainDataArr, trainLabelArr, D) 157 | #根据式8.2计算当前层的alpha 158 | alpha = 1/2 * np.log((1 - curTree['e']) / curTree['e']) 159 | #获得当前层的预测结果,用于下一步更新D 160 | Gx = curTree['Gx'] 161 | #依据式8.4更新D 162 | #考虑到该式每次只更新D中的一个w,要循环进行更新知道所有w更新结束会很复杂(其实 163 | #不是时间上的复杂,只是让人感觉每次单独更新一个很累),所以该式以向量相乘的形式, 164 | #一个式子将所有w全部更新完。 165 | #该式需要线性代数基础,如果不太熟练建议补充相关知识,当然了,单独更新w也一点问题 166 | #没有 167 | #np.multiply(trainLabelArr, Gx):exp中的y*Gm(x),结果是一个行向量,内部为yi*Gm(xi) 168 | #np.exp(-1 * alpha * np.multiply(trainLabelArr, Gx)):上面求出来的行向量内部全体 169 | #成员再乘以-αm,然后取对数,和书上式子一样,只不过书上式子内是一个数,这里是一个向量 170 | #D是一个行向量,取代了式中的wmi,然后D求和为Zm 171 | #书中的式子最后得出来一个数w,所有数w组合形成新的D 172 | #这里是直接得到一个向量,向量内元素是所有的w 173 | #本质上结果是相同的 174 | D = np.multiply(D, np.exp(-1 * alpha * np.multiply(trainLabelArr, Gx))) / sum(D) 175 | #在当前层参数中增加alpha参数,预测的时候需要用到 176 | curTree['alpha'] = alpha 177 | #将当前层添加到提升树索引中。 178 | tree.append(curTree) 179 | 180 | #-----以下代码用来辅助,可以去掉--------------- 181 | #根据8.6式将结果加上当前层乘以α,得到目前的最终输出预测 182 | finallpredict += alpha * Gx 183 | #计算当前最终预测输出与实际标签之间的误差 184 | error = sum([1 for i in range(len(trainDataList)) if np.sign(finallpredict[i]) != trainLabelArr[i]]) 185 | #计算当前最终误差率 186 | finallError = error / len(trainDataList) 187 | #如果误差为0,提前退出即可,因为没有必要再计算算了 188 | if finallError == 0: return tree 189 | #打印一些信息 190 | print('iter:%d:%d, sigle error:%.4f, finall error:%.4f'%(i, treeNum, curTree['e'], finallError )) 191 | #返回整个提升树 192 | return tree 193 | 194 | def predict(x, div, rule, feature): 195 | ''' 196 | 输出单独层预测结果 197 | :param x: 预测样本 198 | :param div: 划分点 199 | :param rule: 划分规则 200 | :param feature: 进行操作的特征 201 | :return: 202 | ''' 203 | #依据划分规则定义小于及大于划分点的标签 204 | if rule == 'LisOne': L = 1; H = -1 205 | else: L = -1; H = 1 206 | 207 | #判断预测结果 208 | if x[feature] < div: return L 209 | else: return H 210 | 211 | def model_test(testDataList, testLabelList, tree): 212 | ''' 213 | 测试 214 | :param testDataList:测试数据集 215 | :param testLabelList: 测试标签集 216 | :param tree: 提升树 217 | :return: 准确率 218 | ''' 219 | #错误率计数值 220 | errorCnt = 0 221 | #遍历每一个测试样本 222 | for i in range(len(testDataList)): 223 | #预测结果值,初始为0 224 | result = 0 225 | #依据算法8.1式8.6 226 | #预测式子是一个求和式,对于每一层的结果都要进行一次累加 227 | #遍历每层的树 228 | for curTree in tree: 229 | #获取该层参数 230 | div = curTree['div'] 231 | rule = curTree['rule'] 232 | feature = curTree['feature'] 233 | alpha = curTree['alpha'] 234 | #将当前层结果加入预测中 235 | result += alpha * predict(testDataList[i], div, rule, feature) 236 | #预测结果取sign值,如果大于0 sign为1,反之为0 237 | if np.sign(result) != testLabelList[i]: errorCnt += 1 238 | #返回准确率 239 | return 1 - errorCnt / len(testDataList) 240 | 241 | if __name__ == '__main__': 242 | #开始时间 243 | start = time.time() 244 | 245 | # 获取训练集 246 | print('start read transSet') 247 | trainDataList, trainLabelList = loadData('../Mnist/mnist_train.csv') 248 | 249 | # 获取测试集 250 | print('start read testSet') 251 | testDataList, testLabelList = loadData('../Mnist/mnist_test.csv') 252 | 253 | #创建提升树 254 | print('start init train') 255 | tree = createBosstingTree(trainDataList[:10000], trainLabelList[:10000], 40) 256 | 257 | #测试 258 | print('start to test') 259 | accuracy = model_test(testDataList[:1000], testLabelList[:1000], tree) 260 | print('the accuracy is:%d' % (accuracy * 100), '%') 261 | 262 | #结束时间 263 | end = time.time() 264 | print('time span:', end - start) -------------------------------------------------------------------------------- /ml-with-numpy/AdaBoost/README.md: -------------------------------------------------------------------------------- 1 | Adaboost[Freund and Schapire, 1997]是一种boosting算法。与boosting算法相似的有bagging,两者同属于ensemble learning。 2 | 3 | 把准确率不高但在50%以上(比如60%)的分类器称为弱分类器,弱分类器可以是一些基础的ML算法比如单层决策树等。对于同一数据可能有多种弱分类器,adaboost就是将这些弱分类器结合起来,称为基分类器,从而有效提高整体的准确率。但是要想获得好的结合或者集成,对基分类器的要求是“好而不同”,即有一定的准确率,而且弱分类器之间要有“多样性”,比如一个判断是否为男性的任务,弱分类器1侧重从鼻子、耳朵这些特征判断是否是男人,分类器2侧重脸和眼睛等等,把这些分类器结合起来就有了所有用来判断是否男性的特征,并且adaboost还可以给每个基分类器赋值不同的权重,比如从脸比鼻子更能判断是否为男性,就可以把分类器2的权重调高一些,这也是adaboost需要学习的内容。 4 | 5 | ## 数学推导 6 | 7 | AdaBoost可以表示为基分类器的线性组合: 8 | $$ 9 | H(\boldsymbol{x})=\sum_{i=1}^{N} \alpha_{i} h_{i}(\boldsymbol{x}) 10 | $$ 11 | 其中$h_i(x),i=1,2,...$表示基分类器,$\alpha_i$是每个基分类器对应的权重,表示如下: 12 | $$ 13 | \alpha_{i}=\frac{1}{2} \ln \left(\frac{1-\epsilon_{i}}{\epsilon_{i}}\right) 14 | $$ 15 | 其中$\epsilon_{i}$是每个弱分类器的错误率。 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ml-with-numpy/DecisionTree/DecisionTree.md: -------------------------------------------------------------------------------- 1 | ## 简介 2 | 3 | 决策树模型就是数据结构中的树,根据**特征选择依据**(信息熵)等划分特征,生成决策树,然后**剪枝**提高泛化能力,可分类可回归,代表算法有ID3,C4.5和CART 4 | 5 | ## 伪代码 6 | 7 | ## 优缺点 8 | 9 | 优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以处理不相关特征数据。 10 | 11 | 缺点:可能会产生过度匹配问题 12 | 13 | 适用数据类型:数值型和标称型 14 | 15 | ## 决策树生成算法 16 | 17 | | | 特征选择依据 | | 18 | | :--: | :----------: | ---- | 19 | | ID3 | 信息熵 | | 20 | | C4.5 | 信息增益比 | | 21 | | CART | 基尼系数 | | 22 | 23 | 24 | 25 | ## 想法 26 | 27 | 决策树的结构形式类似于二叉树 -------------------------------------------------------------------------------- /ml-with-numpy/DecisionTree/DecisionTree.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | ''' 4 | @Author: John 5 | @Email: johnjim0816@gmail.com 6 | @Date: 2020-06-15 09:41:47 7 | @LastEditor: John 8 | @LastEditTime: 2020-06-17 10:42:41 9 | @Discription: 10 | @Environment: python 3.7.7 11 | ''' 12 | import time 13 | import numpy as np 14 | # 导入处于不同目录下的Mnist.load_data 15 | import os 16 | import sys 17 | parent_path=os.path.dirname(os.path.dirname(sys.argv[0])) # 获取上级目录 18 | sys.path.append(parent_path) # 修改sys.path 19 | from Mnist.load_data import load_local_mnist 20 | 21 | 22 | class DecisionTree: 23 | def __init__(self, x_train, y_train, x_test, y_test): 24 | ''' 25 | Args: 26 | x_train [Array]: 训练集数据 27 | y_train [Array]: 训练集标签 28 | x_test [Array]: 测试集数据 29 | y_test [Array]: 测试集标签 30 | ''' 31 | self.x_train, self.y_train = x_train, y_train 32 | self.x_test, self.y_test = x_test, y_test 33 | # 将输入数据转为矩阵形式,方便运算 34 | self.x_train_mat, self.x_test_mat = np.mat( 35 | self.x_train), np.mat(self.x_test) 36 | self.y_train_mat, self.y_test_mat = np.mat( 37 | self.y_test).T, np.mat(self.y_test).T 38 | 39 | #设置epsilon阈值,ID3算法中需要将信息增益与阈值Epsilon比较,若小于则直接处理后返回T 40 | #该值的大小在设置上并未考虑太多,观察到信息增益前期在运行中为0.3左右,所以设置了0.1 41 | self.epsilon_threshhold = 0.1 42 | self.tree={} # 保存生成的树为字典 43 | 44 | def majorClass(labelArr): 45 | ''' 46 | 找到当前标签集中占数目最大的标签 47 | :param labelArr: 标签集 48 | :return: 最大的标签 49 | ''' 50 | #建立字典,用于不同类别的标签技术 51 | classDict = {} 52 | #遍历所有标签 53 | for i in range(len(labelArr)): 54 | #当第一次遇到A标签时,字典内还没有A标签,这时候直接幅值加1是错误的, 55 | #所以需要判断字典中是否有该键,没有则创建,有就直接自增 56 | if labelArr[i] in classDict.keys(): 57 | # 若在字典中存在该标签,则直接加1 58 | classDict[labelArr[i]] += 1 59 | else: 60 | #若无该标签,设初值为1,表示出现了1次了 61 | classDict[labelArr[i]] = 1 62 | #对字典依据值进行降序排序 63 | classSort = sorted(classDict.items(), key=lambda x: x[1], reverse=True) 64 | #返回最大一项的标签,即占数目最多的标签 65 | return classSort[0][0] 66 | 67 | def train(self): 68 | '''其实就是创建决策树的过程 69 | ''' 70 | #打印信息:开始一个子节点创建,打印当前特征向量数目及当前剩余样本数目 71 | print('start a node', len(self.x_train[0]), len(self.y_train)) 72 | #将标签放入一个字典中,当前样本有多少类,在字典中就会有多少项 73 | #也相当于去重,多次出现的标签就留一次。举个例子,假如处理结束后字典的长度为1,那说明所有的样本 74 | #都是同一个标签,那就可以直接返回该标签了,不需要再生成子节点了。 75 | classDict = {i for i in self.y_train} 76 | #如果训练数据中所有实例属于同一类Ck,则置T为单节点数,并将Ck作为该节点的类,返回T 77 | #即若所有样本的标签一致,也就不需要再分化,返回标记作为该节点的值,返回后这就是一个叶子节点 78 | if len(classDict) == 1: 79 | #因为所有样本都是一致的,在标签集中随便拿一个标签返回都行,这里用的第0个(因为你并不知道 80 | #当前标签集的长度是多少,但运行中所有标签只要有长度都会有第0位。 81 | return self.y_train[0] 82 | #如果A为空集,则置T为单节点数,并将D中实例数最大的类Ck作为该节点的类,返回T 83 | #即如果已经没有特征可以用来再分化了,就返回占大多数的类别 84 | if len(self.x_train[0]) == 0: 85 | #返回当前标签集中占数目最大的标签 86 | return majorClass(self.y_train) 87 | 88 | #否则,按式5.10计算A中个特征值的信息增益,选择信息增益最大的特征Ag 89 | Ag, EpsilonGet = calcBestFeature(self.x_train, self.y_train) 90 | 91 | #如果Ag的信息增益比小于阈值Epsilon,则置T为单节点树,并将D中实例数最大的类Ck 92 | #作为该节点的类,返回T 93 | if EpsilonGet < self.x_train: 94 | return majorClass(self.y_train) 95 | 96 | #否则,对Ag的每一可能值ai,依Ag=ai将D分割为若干非空子集Di,将Di中实例数最大的 97 | # 类作为标记,构建子节点,由节点及其子节点构成树T,返回T 98 | treeDict = {Ag:{}} 99 | #特征值为0时,进入0分支 100 | #getSubDataArr(self.x_train, self.y_train, Ag, 0):在当前数据集中切割当前feature,返回新的数据集和标签集 101 | treeDict[Ag][0] = createTree(getSubDataArr(self.x_train, self.y_train, Ag, 0)) 102 | treeDict[Ag][1] = createTree(getSubDataArr(self.x_train, self.y_train, Ag, 1)) 103 | 104 | return treeDict 105 | 106 | 107 | if __name__ == "__main__": 108 | #开始时间 109 | start = time.time() 110 | (x_train, y_train), (x_test, y_test) = load_local_mnist(one_hot=False) 111 | model=DecisionTree(x_train, y_train, x_test, y_test) 112 | -------------------------------------------------------------------------------- /ml-with-numpy/EM/EM.md: -------------------------------------------------------------------------------- 1 | E和M步不是指数目,E是求期望,M是最大化Q函数 2 | 3 | EM一般是收敛的,但是不一定达到全局最优,对初值的选择很敏感 -------------------------------------------------------------------------------- /ml-with-numpy/LinearRegression/BGD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-numpy/LinearRegression/BGD.png -------------------------------------------------------------------------------- /ml-with-numpy/LinearRegression/LiR_np.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | ''' 4 | Author: JiangJi 5 | Email: johnjim0816@gmail.com 6 | Date: 2021-08-27 11:50:51 7 | LastEditor: JiangJi 8 | LastEditTime: 2021-09-01 11:20:37 9 | Discription: 10 | Environment: 11 | ''' 12 | import numpy as np 13 | import matplotlib.pyplot as plt 14 | 15 | def true_fun(X): 16 | return 1.5*X + 0.2 17 | 18 | np.random.seed(0) # 随机种子 19 | n_samples = 30 20 | '''生成随机数据作为训练集''' 21 | X_train = np.sort(np.random.rand(n_samples)) 22 | y_train = (true_fun(X_train) + np.random.randn(n_samples) * 0.05).reshape(n_samples,1) 23 | data_X = [] 24 | for x in X_train: 25 | data_X.append([1,x]) 26 | data_X = np.array((data_X)) 27 | 28 | m,p = np.shape(data_X) # m, 数据量 p: 特征数 29 | max_iter = 1000 # 迭代数 30 | weights = np.ones((p,1)) 31 | alpha = 0.1 # 学习率 32 | for i in range(0,max_iter): 33 | error = np.dot(data_X,weights)- y_train 34 | gradient = data_X.transpose().dot(error)/m 35 | weights = weights - alpha * gradient 36 | print("输出参数w:",weights[1:][0]) # 输出模型参数w 37 | print("输出参数:b",weights[0]) # 输出参数b 38 | 39 | X_test = np.linspace(0, 1, 100) 40 | plt.plot(X_test, X_test*weights[1][0]+weights[0][0], label="Model") 41 | plt.plot(X_test, true_fun(X_test), label="True function") 42 | plt.scatter(X_train,y_train) # 画出训练集的点 43 | plt.legend(loc="best") 44 | plt.show() -------------------------------------------------------------------------------- /ml-with-numpy/LinearRegression/cross_valid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-numpy/LinearRegression/cross_valid.png -------------------------------------------------------------------------------- /ml-with-numpy/LogisticRegression/README.md: -------------------------------------------------------------------------------- 1 | # Logistic Regression 2 | 3 | 给定数据$X=\{x_1,x_2,...,\}$,$Y=\{y_1,y_2,...,\}$ 4 | 考虑二分类任务,即$y_i\in{\{0,1\}},i=1,2,...$, 5 | 6 | ## 假设函数(Hypothesis function) 7 | 假设函数就是其基本模型,如下: 8 | $$ 9 | h_{\theta}(x)=g(\theta^{T}x) 10 | $$ 11 | 其中$\theta^{T}x=w^Tx+b$, 而$g(z)=\frac{1}{1+e^{-z}}$为$sigmoid$函数,也称激活函数。 12 | 13 | ## 损失函数 14 | 15 | 损失函数又叫代价函数,**用于衡量模型的好坏**,这里可以用极大似然估计法来定义损失函数。 16 | 17 | 似然与概率的区别以及什么是极大似然估计,[一文搞懂极大似然估计](https://zhuanlan.zhihu.com/p/26614750) 18 | 19 | 代价函数可定义为极大似然估计,即$L(\theta)=\prod_{i=1}p(y_i=1|x_i)=h_\theta(x_1)(1-h_\theta(x_2))...$, 20 | 其中$x_1$对应的标签$y_1=1$,$x_2$对应的标签$y_2=0$,即设定正例的概率为$h_\theta(x_i)$: 21 | $$ 22 | p(y_i=1|x_i)=h_\theta(x_i) 23 | $$ 24 | $$ 25 | p(y_i=0|x_i)=1-h_\theta(x_i) 26 | $$ 27 | 28 | 根据极大似然估计原理,我们的目标是 29 | $$ 30 | \theta^* = \arg \max _{\theta} L(\theta) 31 | $$ 32 | 33 | 为了简化运算,两边加对数,得到 34 | $$ 35 | \theta^* = \arg \max _{\theta} L(\theta) \Rightarrow \theta^* = \arg \min _{\theta} -\ln(L(\theta)) 36 | $$ 37 | 38 | 化简可得(这里只为了写代码,具体推导参考西瓜书): 39 | $$ 40 | -\ln(L(\theta))=\ell(\boldsymbol{\theta})=\sum_{i=1}(-y_i\theta^Tx_i+\ln(1+e^{\theta^Tx_i})) 41 | $$ 42 | ## 求解:梯度下降 43 | 根据凸优化理论,该函数可以由梯度下降法,牛顿法得出最优解。 44 | 45 | 对于梯度下降来讲, 其中$\eta$为学习率: 46 | $$ 47 | \theta^{t+1}=\theta^{t}-\eta \frac{\partial \ell(\boldsymbol{\theta})}{\partial \boldsymbol{\theta}} 48 | $$ 49 | 其中 50 | $$ 51 | \frac{\partial \ell(\boldsymbol{\theta})}{\partial \boldsymbol{\theta}}=\sum_{i=1}(-y_ix_i+\frac{e^{\theta^Tx_i}x_i}{1+e^{\theta^Tx_i}})=\sum_{i=1}x_i(-y_i+h_\theta(x_i))=\sum_{i=1}x_i(-error) 52 | $$ 53 | 54 | 这里梯度上升更方便点: 55 | $$ 56 | \theta^{t+1}=\theta^{t}+\eta \frac{-\partial \ell(\boldsymbol{\theta})}{\partial \boldsymbol{\theta}} 57 | $$ 58 | 其中 59 | $$ 60 | \frac{-\partial \ell(\boldsymbol{\theta})}{\partial \boldsymbol{\theta}}=\sum_{i=1}(y_ix_i-\frac{e^{\theta^Tx_i}x_i}{1+e^{\theta^Tx_i}})=\sum_{i=1}x_i(y_i-h_\theta(x_i))=\sum_{i=1}x_i*error 61 | $$ 62 | 63 | ## 伪代码 64 | 65 | 训练算法如下: 66 | 67 | * 输入:训练数据$X=\{x_1,x_2,...,x_n\}$,训练标签$Y=\{y_1,y_2,...,\}$,注意均为矩阵形式 68 | 69 | * 输出: 训练好的模型参数$\theta$,或者$h_{\theta}(x)$ 70 | 71 | * 初始化模型参数$\theta$,迭代次数$n\_iters$,学习率$\eta$ 72 | 73 | * $\mathbf{FOR} \ i\_iter \ \mathrm{in \ range}(n\_iters)$ 74 | 75 | * $\mathbf{FOR} \ i \ \mathrm{in \ range}(n)$   $\rightarrow n=len(X)$ 76 | 77 | * $error=y_i-h_{\theta}(x_i)$ 78 | * $grad=error*x_i$ 79 | * $\theta \leftarrow \theta + \eta*grad$   $\rightarrow$梯度上升 80 | * $\mathbf{END \ FOR}$ 81 | 82 | * $\mathbf{END \ FOR}$ 83 | 84 | ## Refs 85 | 86 | 西瓜书 87 | 李宏毅笔记 -------------------------------------------------------------------------------- /ml-with-numpy/MLP/MLP_np.py: -------------------------------------------------------------------------------- 1 | import sys,os 2 | curr_path = os.path.dirname(os.path.abspath(__file__)) # 当前文件所在绝对路径 3 | parent_path = os.path.dirname(curr_path) # 父路径 4 | p_parent_path = os.path.dirname(parent_path) 5 | sys.path.append(p_parent_path) 6 | print(f"主目录为:{p_parent_path}") 7 | 8 | import numpy as np 9 | import scipy.io as sio 10 | import matplotlib.pyplot as plt 11 | from scipy.optimize import fmin_cg 12 | from torch.utils.data import DataLoader 13 | from torchvision import datasets, transforms 14 | 15 | def load_local_mnist(): 16 | ''' 使用Torch加载本地Mnist数据集 17 | ''' 18 | train_dataset = datasets.MNIST(root = p_parent_path+'/datasets/', train = True,transform = transforms.ToTensor(), download = False) 19 | test_dataset = datasets.MNIST(root = p_parent_path+'/datasets/', train = False, 20 | transform = transforms.ToTensor(), download = False) 21 | batch_size = len(train_dataset) 22 | train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True) 23 | test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=True) 24 | X_train, y_train = next(iter(train_loader)) 25 | X_test, y_test = next(iter(test_loader)) 26 | X_train, y_train = X_train.cpu().numpy(), y_train.cpu().numpy() # tensor 转为 array 形式) 27 | X_test, y_test = X_test.cpu().numpy(), y_test.cpu().numpy() # tensor 转为 array 形式) 28 | X_train = X_train.reshape(X_train.shape[0],784) 29 | X_test = X_test.reshape(X_test.shape[0],784) 30 | return X_train, X_test, y_train, y_test 31 | 32 | class MLP(): 33 | ''' 34 | 使用包含一个输入层,一个隐藏层,一个输出层的 MLP 完成手写数字识别任务 35 | ''' 36 | def __init__(self, X_train, y_train, lmb=1.0, input_size=784, hidden_size=64, output_size=10): 37 | ''' 38 | MLP 相关参数初始化 39 | Args: 40 | X_train: 训练样本取值 41 | y_train: 训练样本标签 42 | lmb: 神经网络正则化参数 43 | input_size: 输入层神经元个数 44 | hidden_size: 隐藏层神经元个数 45 | output_size: 输出层神经元个数 (== num_labels) 46 | ''' 47 | self.X_train = X_train 48 | self.y_train = y_train 49 | 50 | self.input_size = input_size 51 | self.hidden_size = hidden_size 52 | self.output_size = output_size 53 | 54 | self.lmb = lmb 55 | 56 | # 神经网络参数列表准备,后续进行随机初始化 (关于为什么 + 1,是因为我们在 MLP 中将偏置 b 视为哑神经元) 57 | self.nn_params = np.array([0.0] * (hidden_size * (input_size + 1) + output_size * (hidden_size + 1))) 58 | 59 | def random_initialize_weights(self, L_in, L_out): 60 | '''神经网络参数的随机初始化(逐层) 61 | ''' 62 | eps = np.sqrt(6) / np.sqrt(L_in + L_out) 63 | max_eps, min_eps = eps, -eps 64 | W = np.random.rand(L_out, 1 + L_in) * (max_eps - min_eps) + min_eps 65 | return W 66 | 67 | def param_initialization(self): 68 | '''神经网络全部权重的随机初始化 69 | ''' 70 | print('Initializing Neural Network Parameters ...') 71 | initial_Theta1 = self.random_initialize_weights(self.input_size, self.hidden_size) 72 | initial_Theta2 = self.random_initialize_weights(self.hidden_size, self.output_size) 73 | 74 | self.nn_params = np.hstack((initial_Theta1.flatten(), initial_Theta2.flatten())) 75 | pass 76 | 77 | def sigmoid(self, z): 78 | '''使用 Sigmoid 函数作为 MLP 激活函数 79 | ''' 80 | return 1.0 / (1.0 + np.exp(-np.asarray(z))) 81 | 82 | def sigmoid_gradient(self, z): 83 | '''Sigmoid 函数梯度计算 84 | ''' 85 | g = self.sigmoid(z) * (1 - self.sigmoid(z)) 86 | return g 87 | 88 | def nn_cost_function(self): 89 | '''神经网络损失计算(包含正则项) 90 | ''' 91 | Theta1 = self.nn_params[:self.hidden_size * (self.input_size + 1)] 92 | Theta1 = Theta1.reshape((self.hidden_size, self.input_size + 1)) 93 | Theta2 = self.nn_params[self.hidden_size * (self.input_size + 1):] 94 | Theta2 = Theta2.reshape((self.output_size, self.hidden_size + 1)) 95 | 96 | m = self.X_train.shape[0] 97 | 98 | # 对标签作 one-hot 编码 99 | one_hot_y = np.zeros((m, self.output_size)) 100 | for idx, label in enumerate(self.y_train): 101 | one_hot_y[idx, int(label)] = 1 102 | 103 | X = np.hstack([np.ones((m, 1)), self.X_train]) # [batch_size, input_layer_size + 1] 104 | X = self.sigmoid(np.matmul(X, Theta1.T)).reshape(-1, self.hidden_size) # [batch_size, hidden_layer_size] 105 | X = np.hstack([np.ones((m, 1)), X]) # [batch_size, hidden_layer_size + 1] 106 | h = self.sigmoid(np.matmul(X, Theta2.T)).reshape(-1, self.output_size) # [batch_size, num_labels] 107 | 108 | # 计算交叉熵损失项与正则化项 109 | ce = -one_hot_y * np.log(h) - (1 - one_hot_y) * np.log(1 - h) 110 | regular = np.sum(np.square(Theta1[:, 1:])) + np.sum(np.square(Theta2[:, 1:])) 111 | 112 | # finally, 含正则化项的损失表达 113 | J = np.sum(ce) / m + self.lmb * regular / (2 * m) 114 | return J 115 | 116 | def nn_grad_function(self): 117 | '''神经网络损失梯度计算 118 | ''' 119 | Theta1 = self.nn_params[:self.hidden_size * (self.input_size + 1)] 120 | Theta1 = Theta1.reshape((self.hidden_size, self.input_size + 1)) 121 | Theta2 = self.nn_params[self.hidden_size * (self.input_size + 1):] 122 | Theta2 = Theta2.reshape((self.output_size, self.hidden_size + 1)) 123 | 124 | m = self.X_train.shape[0] 125 | 126 | # 对标签作 one-hot 编码 127 | one_hot_y = np.zeros((m, self.output_size)) 128 | for idx, label in enumerate(self.y_train): 129 | one_hot_y[idx, int(label)] = 1 130 | 131 | a1 = np.hstack([np.ones((m, 1)), self.X_train]) # [batch_size, input_layer_size + 1] 132 | z2 = np.matmul(a1, Theta1.T) 133 | a2 = self.sigmoid(z2).reshape(-1, self.hidden_size) # [batch_size, hidden_layer_size] 134 | a2 = np.hstack([np.ones((m, 1)), a2]) # [batch_size, hidden_layer_size + 1] 135 | z3 = np.matmul(a2, Theta2.T) 136 | a3 = self.sigmoid(z3).reshape(-1, self.output_size) # [batch_size, num_labels] 137 | 138 | Theta1_grad = np.zeros_like(Theta1) 139 | Theta2_grad = np.zeros_like(Theta2) 140 | 141 | delta_output = a3 - one_hot_y # [batch_size, num_labels] 142 | delta_hidden = np.matmul(delta_output, Theta2[:, 1:]) * self.sigmoid_gradient(z2) 143 | 144 | Theta1_grad += np.matmul(delta_hidden.T, a1) / m 145 | Theta2_grad += np.matmul(delta_output.T, a2) / m 146 | 147 | # 包含正则化项 (注意:此时不应考虑偏置) 148 | Theta1_grad[:, 1:] += self.lmb * Theta1[:, 1:] / m 149 | Theta2_grad[:, 1:] ++ self.lmb * Theta2[:, 1:] / m 150 | 151 | grad = np.hstack((Theta1_grad.flatten(), Theta2_grad.flatten())) 152 | return grad 153 | 154 | def train(self, max_iter, learning_rate): 155 | '''使用梯度下降法训练 MLP 156 | ''' 157 | print("=" * 60) 158 | print("Start Training...") 159 | for i in range(max_iter): 160 | grad = self.nn_grad_function() 161 | self.nn_params -= learning_rate * grad 162 | if i % 10 == 0: 163 | loss = self.nn_cost_function() 164 | print('-' * 50) 165 | print(f"iteration {i}, loss: {loss}") 166 | print("=" * 60) 167 | pass 168 | 169 | def forward(self, X): 170 | '''前向传播 171 | ''' 172 | Theta1 = self.nn_params[:self.hidden_size * (self.input_size + 1)] 173 | Theta1 = Theta1.reshape((self.hidden_size, self.input_size + 1)) 174 | Theta2 = self.nn_params[self.hidden_size * (self.input_size + 1):] 175 | Theta2 = Theta2.reshape((self.output_size, self.hidden_size + 1)) 176 | 177 | m = X.shape[0] 178 | 179 | X = np.hstack([np.ones((m, 1)), X]) # [batch_size, input_layer_size + 1] 180 | X = self.sigmoid(np.matmul(X, Theta1.T)).reshape(-1, self.hidden_size) # [batch_size, hidden_layer_size] 181 | X = np.hstack([np.ones((m, 1)), X]) # [batch_size, hidden_layer_size + 1] 182 | h = self.sigmoid(np.matmul(X, Theta2.T)).reshape(-1, self.output_size) # [batch_size, num_labels] 183 | 184 | p = np.argmax(h, axis=1) 185 | return p 186 | 187 | if __name__ == '__main__': 188 | print('Loading data...') 189 | X_train, X_test, y_train, y_test = load_local_mnist() 190 | mlp = MLP(X_train=X_train[:2000], y_train=y_train[:2000]) 191 | # 初始化 MLP 权重 192 | mlp.param_initialization() 193 | # 模型训练 194 | mlp.train(max_iter=50, learning_rate=1.0) 195 | # 模型预测 196 | pred = mlp.forward(X=X_test[:200]) 197 | print('Test Set Accuracy:', np.mean(pred == y_test[:200]) * 100.0) -------------------------------------------------------------------------------- /ml-with-numpy/MLP/README.md: -------------------------------------------------------------------------------- 1 | ## 多层感知机(Multi Layer Perceptron, MLP) 2 | 3 | 多层感知机(Multilayer Perceptron, MLP)是一种前馈神经网络,由多个全连接层(fully connected layers)组成, 是最基本的神经网络架构之一。 4 | 5 | MLP 在机器学习和深度学习中被广泛用于处理结构化数据、分类、回归等任务。 6 | 7 | ### 结构 8 | 9 | - **输入层**:直接接收数据的输入。每个节点(neuron)对应一个输入特征,节点数与输入特征数相同。 10 | 11 | - **隐藏层**:位于输入层和输出层之间的层,MLP 至少包含一个隐藏层,每层的每个神经元都会接收前一层所有神经元的输入,通过加权求和和激活函数转换。 12 | 13 | - **输出层**:输出最终的预测结果,节点数一般与任务相关,比如分类问题中节点数等于类别数,回归问题中节点数等于目标值的数量。 14 | 15 | 在该目录中,我们实现了一个包含一个隐藏层的简单感知机,其基本结构如下图所示: 16 | 17 | ![img.png](assets/img.png) 18 | 19 | 在`MLP_np.py`中,完成了该感知机的初始化与训练,使其能够完成再 MNIST 数据集上的分类任务。 -------------------------------------------------------------------------------- /ml-with-numpy/MLP/assets/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-numpy/MLP/assets/img.png -------------------------------------------------------------------------------- /ml-with-numpy/SVM/README.md: -------------------------------------------------------------------------------- 1 | ## 支持向量机(Support Vector Machine, SVM) 2 | 3 | SVM是一种二元分类模型,它依托的原理是如果能找到一条线,能够划分样本,并且训练点到线的间隔尽可能远,也就是所有点到超平面的最小值要最大,这样的分割线(一般叫超平面, Hyperplane)就是最好的分类方式,间隔叫作Margin。因此SVM的两个关键点就是:完全分类正确,及所有点到超平面的最小值要最大。 4 | 5 |
6 | img 7 |
8 | 9 | 10 | ### 1. 线性SVM 11 | 12 | 设定超平面为$w^{T} x+b=0$, 在超平面上方我们定义$y=1$,下方定义为$y=-1$,则点$x$到超平面的**几何间隔**为: 13 | $$ 14 | \frac{1}{\|w\|}\left|w^{T} x+b\right| 15 | $$ 16 | 我们需要判断分类是否正确,可以通过观察$y$(实际值)和$w^{T} x+b$(预测值)是否同号($y$只能为$1$或$-1$),即我们可以定义**函数间隔**: 17 | $$ 18 | \gamma^{\prime}=y\left(w^{T} x+b\right) 19 | $$ 20 | 现在我们可以初步定义优化函数为: 21 | $$ 22 | \max \gamma=\frac{y\left(w^{T} x+b\right)}{\|w\|} \\ 23 | \text { s.t } y_{i}\left(w^{T} x_{i}+b\right)=\gamma^{\prime(i)} \geq \gamma^{\prime}(i=1,2, \ldots m) 24 | $$ 25 | 由于无论$w$和$b$怎么放缩,超平面的几何位置不变,所以我们可以设定最小的函数间隔$\gamma^{\prime}=1$,这样优化函数进一步简化为: 26 | $$ 27 | \max \frac{1}{\|w\|} \\ 28 | \text { s.t } y_{i}\left(w^{T} x_{i}+b\right) \geq 1(i=1,2, \ldots m) 29 | $$ 30 | 由于$\frac{1}{\|w\|}$等同于$\frac{1}{2}\|w\|^{2}$的最小化,所以最终的优化函数可以表达为: 31 | $$ 32 | \min \frac{1}{2}\|w\|^{2} \\ 33 | \text { s.t } y_{i}\left(w^{T} x_{i}+b\right) \geq 1(i=1,2, \ldots m) 34 | $$ 35 | 这样其实就是一个标准的二次规划(QP)问题,可以代入别的工具求解 36 | 37 | ### 2. 对偶SVM 38 | 39 | 我们通常会将线性SVM转化为对偶问题,主要解决在非线性转换之后维度变高二次规划问题求解困难的问题。在非线性分类问题中,我们一般作非线性转换$\mathbf{z}_{n}=\phi\left(\mathbf{x}_{n}\right)$,从而在$\mathbf{z}$空间里面进行线性分类,在非线性转换过程一般是一个升维的过程,将$\mathbf{z}$的维度一般设为$\tilde{d}$, 一般情况$\tilde{d}>>d$,(比如2维变5维,3维变19维等是一个非线性升高),这样在高维空间内部,在二次规划问题中的就会有$\tilde{d}+1$个变量,那么权值维度同样也为$\tilde{d}+1$维,以及$m$个约束,此外$Q$ 矩阵维度会十分大,达到$\tilde{d}^2$,因此,一旦$\tilde{d}$变大了, 二次规划问题求解就会变得很困难。 40 | 41 | 我们引入拉格朗日乘子来转化为对偶问题,优化函数变为: 42 | $$ 43 | L(w, b, \alpha)=\frac{1}{2}\|w\|^{2}-\sum_{i=1}^{m} \alpha_{i}\left[y_{i}\left(w^{T} x_{i}+b\right)-1\right] \text { 满足 } \alpha_{i} \geq 0 44 | $$ 45 | 优化目标为: 46 | $$ 47 | \underbrace{\min }_{w, b} \underbrace{\max }_{\alpha_{i} \geq 0} L(w, b, \alpha) 48 | $$ 49 | 由于这个优化函数满足KTT条件,因此通过拉格朗日对偶将优化目标转为: 50 | $$ 51 | \underbrace{\max }_{\alpha_{i} \geq 0} \underbrace{\min }_{w, b} L(w, b, \alpha) 52 | $$ 53 | 根据上式,我们可以先求出优化函数对于$w,b$的极小值,然后求拉格朗日乘子$\alpha$的最大值。 54 | 55 | 我们先求出优化函数对于$w,b$的极小值,即$\underbrace{\min }_{w, b} L(w, b, \alpha)$,只需要对$w,b$求偏导: 56 | $$ 57 | \begin{gathered} 58 | \frac{\partial L}{\partial w}=0 \Rightarrow w=\sum_{i=1}^{m} \alpha_{i} y_{i} x_{i} \\ 59 | \frac{\partial L}{\partial b}=0 \Rightarrow \sum_{i=1}^{m} \alpha_{i} y_{i}=0 60 | \end{gathered} 61 | $$ 62 | 这样我们就知道了$w$和$\alpha$的关系,然后代入$\underbrace{\min }_{w, b} L(w, b, \alpha)$就可以消去$w$,定义: 63 | $$ 64 | \psi(\alpha)=\underbrace{\min }_{w, b} L(w, b, \alpha) 65 | $$ 66 | 代入可得: 67 | $$ 68 | \begin{aligned} 69 | \psi(\alpha) &=\frac{1}{2}\|w\|^{2}-\sum_{i=1}^{m} \alpha_{i}\left[y_{i}\left(w^{T} x_{i}+b\right)-1\right] \\ 70 | &=\frac{1}{2} w^{T} w-\sum_{i=1}^{m} \alpha_{i} y_{i} w^{T} x_{i}-\sum_{i=1}^{m} \alpha_{i} y_{i} b+\sum_{i=1}^{m} \alpha_{i} \\ 71 | &=\frac{1}{2} w^{T} \sum_{i=1}^{m} \alpha_{i} y_{i} x_{i}-\sum_{i=1}^{m} \alpha_{i} y_{i} w^{T} x_{i}-\sum_{i=1}^{m} \alpha_{i} y_{i} b+\sum_{i=1}^{m} \alpha_{i} \\ 72 | &=\frac{1}{2} w^{T} \sum_{i=1}^{m} \alpha_{i} y_{i} x_{i}-w^{T} \sum_{i=1}^{m} \alpha_{i} y_{i} x_{i}-\sum_{i=1}^{m} \alpha_{i} y_{i} b+\sum_{i=1}^{m} \alpha_{i} \\ 73 | &=-\frac{1}{2} w^{T} \sum_{i=1}^{m} \alpha_{i} y_{i} x_{i}-\sum_{i=1}^{m} \alpha_{i} y_{i} b+\sum_{i=1}^{m} \alpha_{i} \\ 74 | &=-\frac{1}{2} w^{T} \sum_{i=1}^{m} \alpha_{i} y_{i} x_{i}-b \sum_{i=1}^{m} \alpha_{i} y_{i}+\sum_{i=1}^{m} \alpha_{i} \\ 75 | &=-\frac{1}{2}\left(\sum_{i=1}^{m} \alpha_{i} y_{i} x_{i}\right)^{T}\left(\sum_{i=1}^{m} \alpha_{i} y_{i} x_{i}\right)-b \sum_{i=1}^{m} \alpha_{i} y_{i}+\sum_{i=1}^{m} \alpha_{i} \\ 76 | &=-\frac{1}{2} \sum_{i=1}^{m} \alpha_{i} y_{i} x_{i}^{T} \sum_{i=1}^{m} \alpha_{i} y_{i} x_{i}-b \sum_{i=1}^{m} \alpha_{i} y_{i}+\sum_{i=1}^{m} \alpha_{i} \\ 77 | &=-\frac{1}{2} \sum_{i=1}^{m} \alpha_{i} y_{i} x_{i}^{T} \sum_{i=1}^{m} \alpha_{i} y_{i} x_{i}+\sum_{i=1}^{m} \alpha_{i} \\ 78 | &=-\frac{1}{2} \sum_{i=1, j=1}^{m} \alpha_{i} y_{i} x_{i}^{T} \alpha_{j} y_{j} x_{j}+\sum_{i=1}^{m} \alpha_{i} \\ 79 | &=\sum_{i=1}^{m} \alpha_{i}-\frac{1}{2} \sum_{i=1, j=1}^{m} \alpha_{i} \alpha_{j} y_{i} y_{j} x_{i}^{T} x_{j} 80 | \end{aligned} 81 | $$ 82 | 我们再代入到优化目标中: 83 | $$ 84 | \begin{gathered} 85 | \underbrace{\max }_{\alpha}-\frac{1}{2} \sum_{i=1}^{m} \sum_{j=1}^{m} \alpha_{i} \alpha_{j} y_{i} y_{j}\left(x_{i} \cdot x_{j}\right)+\sum_{i=1}^{m} \alpha_{i} \\ 86 | \text { s.t. } \sum_{i=1}^{m} \alpha_{i} y_{i}=0 \\ 87 | \alpha_{i} \geq 0 i=1,2, \ldots m 88 | \end{gathered} 89 | $$ 90 | 去掉负号转为最小值: 91 | $$ 92 | \begin{gathered} 93 | \underbrace{\min }_{\alpha} \frac{1}{2} \sum_{i=1}^{m} \sum_{j=1}^{m} \alpha_{i} \alpha_{j} y_{i} y_{j}\left(x_{i} \cdot x_{j}\right)-\sum_{i=1}^{m} \alpha_{i} \\ 94 | \text { s.t. } \sum_{i=1}^{m} \alpha_{i} y_{i}=0 \\ 95 | \alpha_{i} \geq 0 i=1,2, \ldots m 96 | \end{gathered} 97 | $$ 98 | 此时一般用SMO算法求解$\alpha$对应的极小值$\alpha^*$,求得之后,我们就可以根据$w=\sum_{i=1}^{m} \alpha_{i} y_{i} x_{i}$得出$w^*$。 99 | 100 | 求$b$则麻烦一些,但可根据对于任意的支持向量$\left(x_{x}, y_{s}\right)$,都有: 101 | $$ 102 | y_{s}\left(w^{T} x_{s}+b\right)=y_{s}\left(\sum_{i=1}^{m} \alpha_{i} y_{i} x_{i}^{T} x_{s}+b\right)=1 103 | $$ 104 | 即只需求出支持向量代入即可。 105 | 106 | ### 3. 软间隔SVM 107 | 108 | 前面讲到线性SVM一个关键点就是超平面必须完全分类所有点,但实际上一方面有些数据混入了异常点,导致本来线性可分变成了不可分,如下图一个橙色和蓝色的异常点导致我们无法用线性SVM。 109 | 110 |
111 | img 112 |
113 | 114 | 115 | 但还有一种情况是没有糟糕到那么不可分,如下图,本来如果不考虑异常点,SVM的超平面应该是如红色线所示,但由于有一个蓝色的异常点,导致我们学到的超平面是如粗虚线所示。 116 | 117 |
118 | img 119 |
120 | 121 | 122 | 为解决这个问题,SVM引入了软间隔的方法。 123 | 124 | 回顾硬间隔的优化函数: 125 | $$ 126 | \min \frac{1}{2}\|w\|^{2} \\ 127 | \text { s.t } y_{i}\left(w^{T} x_{i}+b\right) \geq 1(i=1,2, \ldots m) 128 | $$ 129 | 现在对每个样本引入一个**松弛变量**$\xi_{i} \geq 0$,使函数间隔加上松弛变量大于等于1,即: 130 | $$ 131 | y_{i}\left(w \bullet x_{i}+b\right) \geq 1-\xi_{i} 132 | $$ 133 | 134 | 135 | 可以看到我们对样本到超平面的函数距离的要求放松了,之前是一定要大于等于1,现在只需要加上一个大于等于0的松弛变量能大于等于1就可以了。当然,松弛变量不能白加,这是有成本的,每一个松弛变量$\xi_{i}$, 对应了一个代价$\xi_{i}$,因此优化函数变为: 136 | $$ 137 | \begin{gathered} 138 | \min \frac{1}{2}\|w\|^{2}+C \sum_{i=1}^{m} \xi_{i} \\ 139 | \text { s.t. } \quad y_{i}\left(w^{T} x_{i}+b\right) \geq 1-\xi_{i} \quad(i=1,2, \ldots m) \\ 140 | \xi_{i} \geq 0 \quad(i=1,2, \ldots m) 141 | \end{gathered} 142 | $$ 143 | 这里$C>0$为惩罚参数,$C$越大,对误分类的惩罚越大,$C$越小,对误分类的惩罚越小。 144 | 145 | 下面我们需要优化软间隔SVM优化函数,与线性SVM类似,引入拉格朗日乘子转为无约束问题: 146 | $$ 147 | L(w, b, \xi, \alpha, \mu)=\frac{1}{2}\|w\|_{2}^{2}+C \sum_{i=1}^{m} \xi_{i}-\sum_{i=1}^{m} \alpha_{i}\left[y_{i}\left(w^{T} x_{i}+b\right)-1+\xi_{i}\right]-\sum_{i=1}^{m} \mu_{i} \xi_{i} 148 | $$ 149 | 需要优化的目标函数,根据KKT条件再经过拉格朗日对偶转为: 150 | $$ 151 | \underbrace{\max }_{\alpha_{i} \geq 0, \mu_{i} \geq 0} \underbrace{\min }_{w, b, \xi} L(w, b, \alpha, \xi, \mu) 152 | $$ 153 | (以下推导略过),类似地首先求出$w, b, \xi$的极小值: 154 | $$ 155 | \begin{gathered} 156 | \frac{\partial L}{\partial w}=0 \Rightarrow w=\sum_{i=1}^{m} \alpha_{i} y_{i} x_{i} \\ 157 | \frac{\partial L}{\partial b}=0 \Rightarrow \sum_{i=1}^{m} \alpha_{i} y_{i}=0 \\ 158 | \frac{\partial L}{\partial \xi}=0 \Rightarrow C-\alpha_{i}-\mu_{i}=0 159 | \end{gathered} 160 | $$ 161 | 162 | 163 | 最后导出软间隔SVM的优化函数为: 164 | $$ 165 | \begin{gathered} 166 | \underbrace{\min }_{\alpha} \frac{1}{2} \sum_{i=1, j=1}^{m} \alpha_{i} \alpha_{j} y_{i} y_{j} x_{i}^{T} x_{j}-\sum_{i=1}^{m} \alpha_{i} \\ 167 | \text { s.t. } \sum_{i=1}^{m} \alpha_{i} y_{i}=0 \\ 168 | 0 \leq \alpha_{i} \leq C 169 | \end{gathered} 170 | $$ 171 | 可以看到与硬间隔SVM相比,只是多了一个约束条件$0 \leq \alpha_{i} \leq C$。 172 | 173 | ### 4. 核函数SVM 174 | 175 | 对于完全线性不可分的情况,我们可以将数据映射到高维,从而线性可分。回顾线性可分的SVM优化函数: 176 | $$ 177 | \begin{gathered}\underbrace{\min }_{\alpha} \frac{1}{2} \sum_{i=1}^{m} \sum_{j=1}^{m} \alpha_{i} \alpha_{j} y_{i} y_{j}\left(x_{i} \cdot x_{j}\right)-\sum_{i=1}^{m} \alpha_{i} \\\text { s.t. } \sum_{i=1}^{m} \alpha_{i} y_{i}=0 \\\alpha_{i} \geq 0 i=1,2, \ldots m\end{gathered} 178 | $$ 179 | 上式低维特征仅仅以内积$𝑥_𝑖∙𝑥_𝑗$的形式出现,如果我们定义一个低维特征空间到高维特征空间的映射$\phi$,将所有特征映射到一个更高的维度,让数据线性可分,从而按照前面的方法求出超平面,即: 180 | $$ 181 | \begin{gathered} 182 | \underbrace{\min }_{\alpha} \frac{1}{2} \sum_{i=1, j=1}^{m} \alpha_{i} \alpha_{j} y_{i} y_{j} \phi\left(x_{i}\right) \cdot \phi\left(x_{j}\right)-\sum_{i=1}^{m} \alpha_{i} \\ 183 | \text { s.t. } \sum_{i=1}^{m} \alpha_{i} y_{i}=0 \\ 184 | 0 \leq \alpha_{i} \leq C 185 | \end{gathered} 186 | $$ 187 | 但是这样我们需要求出内积$\phi\left(x_{i}\right) \cdot \phi\left(x_{j}\right)$,这样类似于二次规划那样会引入一个$\tilde{d}$维空间,从而发生维度爆炸影响计算速度。 188 | 189 | 为此我们可以引入核函数,设$\phi$是一个从低维的输入空间$\chi$(欧式空间的子集或者离散集合)到高维的希尔伯特空间的$\mathcal{H}$映射,如果存在函数$K(x, x')$,对于任意$x, x'\in\chi$,都有: 190 | $$ 191 | K(x, x')=\phi(x) \bullet \phi(x') 192 | $$ 193 | 咋看跟上面没什么区别,但实际上核函数计算都是在低维空间下进行的,例如对于 194 | $$ 195 | \Phi(\mathbf{x})=\left(1, x_{1}, x_{2}, \ldots, x_{d}, x_{1}^{2}, x_{1} x_{2}, \ldots, x_{1} x_{d}, x_{2} x_{1}, x_{2}^{2}, \ldots, x_{2} x_{d}, \ldots, x_{d}^{2}\right) 196 | $$ 197 | 我们得到: 198 | $$ 199 | K_{\Phi}\left(x, x^{\prime}\right)=1+\left(x^{T} x^{\prime}\right)+\left(x^{T} x^{\prime}\right)^{2} 200 | $$ 201 | 这样看只需要计算低维空间的内积就行了。 202 | 203 | 常见的核函数有四种: 204 | 205 | | 核函数 | 公式 | 备注 | 206 | | :-----------: | :---------------------------------------------: | --------------------------------------- | 207 | | 线性核函数 | $K(x, x')=x \bullet x'$ | 其实就是线性可分的SVM | 208 | | 多项式核函数 | $K(x, x')=(\gamma x \bullet x'+r)^{d}$ | 其中$\gamma,r,d$都需要自己调参定义 | 209 | | 高斯核函数 | $K(x, x')=\exp\left(-\gamma\|x-x'\|^{2}\right)$ | 最主流的核函数,在SVM中也叫径向基核函数 | 210 | | Sigmoid核函数 | $K(x, x')=\tanh (\gamma x \cdot x'+r)$ | 也是线性不可分SVM常用的核函数之一 | 211 | 212 | 下图是高斯核函数在不同参数下的分类效果: 213 |
214 | img 215 |
216 | 217 | 218 | 可以看到原来线性SVM下超平面是一条直线,映射到高维可以较自由地定义位置形状。 219 | 220 | ### 5. SMO算法 221 | 222 | 223 | 224 | ### Refs 225 | 226 | 西瓜书 227 | 228 | 台大林轩田机器学习技法 229 | 230 | [支持向量机原理(二) 线性支持向量机的软间隔最大化模型](https://www.cnblogs.com/pinard/p/6100722.html) 231 | 232 | [支持向量机原理(三)线性不可分支持向量机与核函数](https://www.cnblogs.com/pinard/p/6103615.html) 233 | 234 | [机器学习技法笔记(3)-对偶SVM](https://shomy.top/2017/02/17/svm-02-dual/) 235 | 236 | [林轩田机器学习笔记](https://wizardforcel.gitbooks.io/ntu-hsuantienlin-ml/content/21.html) -------------------------------------------------------------------------------- /ml-with-numpy/SVM/SVM_np.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "source": [ 7 | "import sys\n", 8 | "from pathlib import Path\n", 9 | "curr_path = str(Path().absolute()) # 当前文件所在绝对路径\n", 10 | "parent_path = str(Path().absolute().parent) # 父路径\n", 11 | "sys.path.append(parent_path) # 添加路径到系统路径\n", 12 | "\n", 13 | "import numpy as np" 14 | ], 15 | "outputs": [], 16 | "metadata": {} 17 | }, 18 | { 19 | "cell_type": "markdown", 20 | "source": [ 21 | "## 计算高斯核\n", 22 | "\n", 23 | "高斯核数学表达为:$K(x, x')=\\exp\\left(-\\gamma\\|x-x'\\|^{2}\\right)$ \n", 24 | "用矩阵表达则为:$K(i, j)=\\exp\\left(-\\gamma\\|x_i-x_j\\|^{2}\\right)$ \n", 25 | "其中数据集矩阵$x$的维度为$(m,)$,则高斯核$K$的维度为$(m,m)$\n" 26 | ], 27 | "metadata": {} 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": null, 32 | "source": [ 33 | "def calc_kernel(X_train):\n", 34 | " '''\n", 35 | " 计算核函数\n", 36 | " 使用的是高斯核 详见“7.3.3 常用核函数” 式7.90\n", 37 | " :return: 高斯核矩阵\n", 38 | " '''\n", 39 | " #初始化高斯核结果矩阵 大小 = 训练集长度m * 训练集长度m\n", 40 | " # k[i][j] = Xi * Xj\n", 41 | " m = X_train.shape[0]\n", 42 | " k = [[0 for i in range(m)] for j in range(m)]\n", 43 | " #大循环遍历Xi,Xi为式7.90中的x\n", 44 | " for i in range(m):\n", 45 | " #得到式7.90中的X\n", 46 | " X = X_train[i, :]\n", 47 | " #小循环遍历Xj,Xj为式7.90中的Z\n", 48 | " # 由于 Xi * Xj 等于 Xj * Xi,一次计算得到的结果可以\n", 49 | " # 同时放在k[i][j]和k[j][i]中,这样一个矩阵只需要计算一半即可\n", 50 | " #所以小循环直接从i开始\n", 51 | " for j in range(i, m):\n", 52 | " #获得Z\n", 53 | " Z = X_train[j, :]\n", 54 | " #先计算||X - Z||^2\n", 55 | " result = (X - Z) * (X - Z).T\n", 56 | " #分子除以分母后去指数,得到的即为高斯核结果\n", 57 | " result = np.exp(-1 * result / (2 * self.sigma**2))\n", 58 | " #将Xi*Xj的结果存放入k[i][j]和k[j][i]中\n", 59 | " k[i][j] = result\n", 60 | " k[j][i] = result\n", 61 | " #返回高斯核矩阵\n", 62 | " return k" 63 | ], 64 | "outputs": [], 65 | "metadata": {} 66 | } 67 | ], 68 | "metadata": { 69 | "orig_nbformat": 4, 70 | "language_info": { 71 | "name": "python" 72 | } 73 | }, 74 | "nbformat": 4, 75 | "nbformat_minor": 2 76 | } -------------------------------------------------------------------------------- /ml-with-numpy/SVM/assets/1042406-20161125104106409-1177897648.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-numpy/SVM/assets/1042406-20161125104106409-1177897648.png -------------------------------------------------------------------------------- /ml-with-numpy/SVM/assets/1042406-20161125104737206-364720074.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-numpy/SVM/assets/1042406-20161125104737206-364720074.png -------------------------------------------------------------------------------- /ml-with-numpy/SVM/assets/20180214224342909.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-numpy/SVM/assets/20180214224342909.png -------------------------------------------------------------------------------- /ml-with-numpy/SVM/assets/image-20210809104104109.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-numpy/SVM/assets/image-20210809104104109.png -------------------------------------------------------------------------------- /ml-with-numpy/kNN/README.md: -------------------------------------------------------------------------------- 1 | 2 | kNN(k-Nearest Neighbor)既可以分类,也可以用于回归。其工作机制比较简单:给定测试样本,找出训练集中与其距离最靠近的$k$个训练样本,称为Neighbor然后基于这$k$个Neighbor的信息来预测。通常在分类任务中,使用投票法,即选择这$k$个样本中出现最多的label作为预测结果;而在回归任务中,可使用平均法,即将这$k$个样本中的label值(在回归任务中应该是输出值,这里为了方便也叫label)平均作为预测结果,还可以基于距离远近进行加权平均投票,距离越近的权重越大。 3 | 4 | kNN中需要考虑两点: 5 | 6 | * $k$是一个重要参数,$k$不同分类结果会有显著不同。$k$值的选择可简单参考[锚点](#anchor)[KNN中的K怎么选择](https://zhuanlan.zhihu.com/p/30425907),如需严格深入须参考相关书籍或论文 7 | * 距离的计算方式,通常采用欧式距离,也有马氏距离等 8 | 9 | 10 | ## 伪代码 11 | 12 | > 抽空重写 13 | 14 | 对未知类别属性的数据集中的每个点依次执行以下操作: 15 | (1) 计算已知类别数据集中的点与当前点之间的距离; 16 | (2) 按照距离递增次序排序; 17 | (3) 选取与当前点距离最小的k个点(k<20); 18 | (4) 确定前k个点所在类别的出现频率; 19 | (5) 返回前k个点出现频率最高的类别作为当前点的预测分类。 20 | 21 | ## 优缺点 22 | 23 | 基本参考《机器学习实战》,其中称型目标变量的结果只在有限目标集中取值,如真与假、动物分类集合{ 爬行类、鱼类、哺乳类、两栖类} ;数值型目标变量则可以从无限的数值集合中取值,如0.100、42.001、1000.743 等。数值型目标变量主要用于回归分析。 24 | 25 | * 优点 26 | 27 | 精度高、对异常值不敏感、无数据输入假定 28 | 29 | * 缺点 30 | 31 | 计算复杂度高、空间复杂度高。 32 | 33 | * 适用数据 34 | 35 | 数值型和标称型 36 | 37 | ## $k$的选择 38 | 39 | 40 | 41 | ## Refs 42 | 43 | 西瓜书 44 | 45 | 机器学习实战 46 | 47 | [李航](https://github.com/SmirkCao/Lihang/tree/master/CH03) 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /ml-with-numpy/kNN/kNN.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | ''' 4 | @Author: John 5 | @Email: johnjim0816@gmail.com 6 | @Date: 2020-05-22 10:55:13 7 | @LastEditor: John 8 | LastEditTime: 2021-08-27 15:36:18 9 | @Discription: 10 | @Environment: python 3.7.7 11 | ''' 12 | # 参考https://github.com/Dod-o/Statistical-Learning-Method_Code/blob/master/KNN/KNN.py 13 | 14 | ''' 15 | 数据集:Mnist 16 | 训练集数量:60000 17 | 测试集数量:10000(实际使用:200) 18 | ------------------------------ 19 | 运行机器:CPU i7-9750H 20 | 超参数:k=25 21 | 运行结果: 22 | 向量距离使用算法——L2欧式距离 23 | 正确率:0.9698 24 | 运行时长:266.36s 25 | ''' 26 | 27 | import time 28 | import numpy as np 29 | import sys 30 | import os 31 | 32 | # 导入处于不同目录下的Mnist.load_data 33 | parent_path=os.path.dirname(os.path.dirname(sys.argv[0])) # 获取上级目录 34 | sys.path.append(parent_path) # 修改sys.path 35 | from Mnist.load_data import load_local_mnist 36 | 37 | 38 | class KNN: 39 | def __init__(self, x_train, y_train, x_test, y_test, k): 40 | ''' 41 | Args: 42 | x_train [Array]: 训练集数据 43 | y_train [Array]: 训练集标签 44 | x_test [Array]: 测试集数据 45 | y_test [Array]: 测试集标签 46 | k [int]: k of kNN 47 | ''' 48 | self.x_train, self.y_train = x_train, y_train 49 | self.x_test, self.y_test = x_test, y_test 50 | # 将输入数据转为矩阵形式,方便运算 51 | self.x_train_mat, self.x_test_mat = np.mat( 52 | self.x_train), np.mat(self.x_test) 53 | self.y_train_mat, self.y_test_mat = np.mat( 54 | self.y_test).T, np.mat(self.y_test).T 55 | self.k = k 56 | 57 | def _calc_dist(self, x1, x2): 58 | '''计算两个样本点向量之间的距离,使用的是欧氏距离 59 | :param x1:向量1 60 | :param x2:向量2 61 | :return: 向量之间的欧式距离 62 | ''' 63 | return np.sqrt(np.sum(np.square(x1 - x2))) 64 | 65 | def _get_k_nearest(self,x): 66 | ''' 67 | 预测样本x的标记。 68 | 获取方式通过找到与样本x最近的topK个点,并查看它们的标签。 69 | 查找里面占某类标签最多的那类标签 70 | :param trainDataMat:训练集数据集 71 | :param trainLabelMat:训练集标签集 72 | :param x:待预测的样本x 73 | :param topK:选择参考最邻近样本的数目(样本数目的选择关系到正确率,详看3.2.3 K值的选择) 74 | :return:预测的标记 75 | ''' 76 | # 初始化距离列表,dist_list[i]表示待预测样本x与训练集中第i个样本的距离 77 | dist_list=[0]* len(self.x_train_mat) 78 | 79 | # 遍历训练集中所有的样本点,计算与x的距离 80 | for i in range( len(self.x_train_mat)): 81 | # 获取训练集中当前样本的向量 82 | x0 = self.x_train_mat[i] 83 | # 计算向量x与训练集样本x0的距离 84 | dist_list[i] = self._calc_dist(x0, x) 85 | 86 | # 对距离列表排序并返回距离最近的k个训练样本的下标 87 | # ----------------优化点------------------- 88 | # 由于我们只取topK小的元素索引值,所以其实不需要对整个列表进行排序,而argsort是对整个 89 | # 列表进行排序的,存在时间上的浪费。字典有现成的方法可以只排序top大或top小,可以自行查阅 90 | # 对代码进行稍稍修改即可 91 | # 这里没有对其进行优化主要原因是KNN的时间耗费大头在计算向量与向量之间的距离上,由于向量高维 92 | # 所以计算时间需要很长,所以如果要提升时间,在这里优化的意义不大。 93 | k_nearest_index = np.argsort(np.array(dist_list))[:self.k] # 升序排序 94 | return k_nearest_index 95 | 96 | 97 | def _predict_y(self,k_nearest_index): 98 | # label_list[1]=3,表示label为1的样本数有3个,由于此处label为0-9,可以初始化长度为10的label_list 99 | label_list=[0] * 10 100 | for index in k_nearest_index: 101 | one_hot_label=self.y_train[index] 102 | number_label=np.argmax(one_hot_label) 103 | label_list[number_label] += 1 104 | # 采用投票法,即样本数最多的label就是预测的label 105 | y_predict=label_list.index(max(label_list)) 106 | return y_predict 107 | 108 | def test(self,n_test=200): 109 | ''' 110 | 测试正确率 111 | :param: n_test: 待测试的样本数 112 | :return: 正确率 113 | ''' 114 | print('start test') 115 | 116 | # 错误值计数 117 | error_count = 0 118 | # 遍历测试集,对每个测试集样本进行测试 119 | # 由于计算向量与向量之间的时间耗费太大,测试集有6000个样本,所以这里人为改成了 120 | # 测试200个样本点,若要全跑,更改n_test即可 121 | for i in range(n_test): 122 | # print('test %d:%d'%(i, len(trainDataArr))) 123 | print('test %d:%d' % (i, n_test)) 124 | # 读取测试集当前测试样本的向量 125 | x = self.x_test_mat[i] 126 | # 获取距离最近的训练样本序号 127 | k_nearest_index=self._get_k_nearest(x) 128 | # 预测输出y 129 | y=self._predict_y(k_nearest_index) 130 | # 如果预测label与实际label不符,错误值计数加1 131 | if y != np.argmax(self.y_test[i]): 132 | error_count += 1 133 | print("accuracy=",1 - (error_count /(i+1))) 134 | 135 | # 返回正确率 136 | return 1 - (error_count / n_test) 137 | 138 | 139 | if __name__ == "__main__": 140 | 141 | k=25 142 | start = time.time() 143 | (x_train, y_train), (x_test, y_test) = load_local_mnist() 144 | model=KNN( x_train, y_train, x_test, y_test,k) 145 | accur=model.test() 146 | end = time.time() 147 | print("total acc:",accur) 148 | print('time span:', end - start) -------------------------------------------------------------------------------- /ml-with-sklearn/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /ml-with-sklearn/01-LinearRegression/assets/BGD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-sklearn/01-LinearRegression/assets/BGD.png -------------------------------------------------------------------------------- /ml-with-sklearn/01-LinearRegression/assets/cross_valid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-sklearn/01-LinearRegression/assets/cross_valid.png -------------------------------------------------------------------------------- /ml-with-sklearn/01-LinearRegression/assets/线性回归.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-sklearn/01-LinearRegression/assets/线性回归.jpg -------------------------------------------------------------------------------- /ml-with-sklearn/01-LinearRegression/多项式回归.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | from sklearn.pipeline import Pipeline 4 | from sklearn.preprocessing import PolynomialFeatures # 导入能够计算多项式特征的类 5 | from sklearn.linear_model import LinearRegression 6 | from sklearn.model_selection import cross_val_score 7 | 8 | def true_fun(X): # 这是我们设定的真实函数,即ground truth的模型 9 | return np.cos(1.5 * np.pi * X) 10 | np.random.seed(0) 11 | n_samples = 30 # 设置随机种子 12 | 13 | X = np.sort(np.random.rand(n_samples)) 14 | y = true_fun(X) + np.random.randn(n_samples) * 0.1 15 | 16 | degrees = [1, 4, 15] # 多项式最高次 17 | plt.figure(figsize=(14, 5)) 18 | for i in range(len(degrees)): 19 | ax = plt.subplot(1, len(degrees), i + 1) 20 | plt.setp(ax, xticks=(), yticks=()) 21 | polynomial_features = PolynomialFeatures(degree=degrees[i], 22 | include_bias=False) 23 | linear_regression = LinearRegression() 24 | pipeline = Pipeline([("polynomial_features", polynomial_features), 25 | ("linear_regression", linear_regression)]) # 使用pipline串联模型 26 | pipeline.fit(X[:, np.newaxis], y) 27 | 28 | scores = cross_val_score(pipeline, X[:, np.newaxis], y,scoring="neg_mean_squared_error", cv=10) # 使用交叉验证 29 | X_test = np.linspace(0, 1, 100) 30 | plt.plot(X_test, pipeline.predict(X_test[:, np.newaxis]), label="Model") 31 | plt.plot(X_test, true_fun(X_test), label="True function") 32 | plt.scatter(X, y, edgecolor='b', s=20, label="Samples") 33 | plt.xlabel("x") 34 | plt.ylabel("y") 35 | plt.xlim((0, 1)) 36 | plt.ylim((-2, 2)) 37 | plt.legend(loc="best") 38 | plt.title("Degree {}\nMSE = {:.2e}(+/- {:.2e})".format( 39 | degrees[i], -scores.mean(), scores.std())) 40 | plt.show() -------------------------------------------------------------------------------- /ml-with-sklearn/01-LinearRegression/线性回归.md: -------------------------------------------------------------------------------- 1 | # 线性回归 2 | 3 | ​ 线性回归是比较简单的机器学习算法,通常作为机器学习入门第一课。线性回归属于线性模型的一部分。下面来看一下线性模型的定义: 4 | 5 | > **线性模型**:给定由d个属性描述的示例$\boldsymbol{x}=\left(x_{1} ; x_{2} ; \ldots ; x_{d}\right)$,其中$x_i$是$x$在第i个属性上的取值,线性模型(linear model) 试图学得一个通过属性的线性组合来进行预测的函数,即 6 | > $$ 7 | > f(\boldsymbol{x})=w_{1} x_{1}+w_{2} x_{2}+\ldots+w_{d} x_{d}+b 8 | > $$ 9 | > 10 | 11 | > 一般向量形式写成: 12 | > $$ 13 | > f(\boldsymbol{x})=\boldsymbol{w}^{\mathrm{T}} \boldsymbol{x}+b 14 | > $$ 15 | > 其中 16 | > $$ 17 | > \boldsymbol{w}=\left(w_{1} ; w_{2} ; \ldots ; w_{d}\right) 18 | > $$ 19 | > $w$和$b$学得后,模型就得以确定 20 | 21 | 22 | 23 | 简单地说就是找到一条线使得这些点都在这条直线上或者直线的附近。 24 | 25 | > ![机器学习之线性回归(linear regression)](assets/线性回归.jpg) 26 | 27 | 28 | 29 | 线性模型的形式简单,并且容易建模,但是其中蕴涵了机器学习的一些重要思想。许多功能更为强大的非线性模型可在线性模型的基础上通过引入层级结构或高维映射而得。 30 | 31 | 线性回归便是线性模型中最经典的模型之一 32 | 33 | 34 | 35 | **线性回归**:给定数据集$D=\left\{\left(\boldsymbol{x}_{1}, y_{1}\right),\left(\boldsymbol{x}_{2}, y_{2}\right), \ldots,\left(\boldsymbol{x}_{m}, y_{m}\right)\right\}$,其中$\boldsymbol{x}_{i}=\left(x_{i 1}\right.\left.x_{i 2} ; \ldots ; x_{i d}\right), y_{i} \in \mathbb{R}$ 36 | 37 | "线性回归" (linear regression) 试图学得一个线性模型以尽可能准确地预测实值输出标记. 38 | 39 | 按照线性模型的思想,设x是特征,y是真实值,我们需要找到能够一条尽可能让$f(x)=y$的曲线$f(x)=wx+b$,也就是找到对应的w和b。 40 | 41 | 关键在于衡量$f(x)$与$y$的差别 42 | 43 | 均方误差 (2.2) 是回归任务中最常用的性能度量,因此我们可试图让均 44 | 45 | 方误差最小化,即 46 | $$ 47 | \begin{aligned} 48 | \left(w^{*}, b^{*}\right) &=\underset{(w, b)}{\arg \min } \sum_{i=1}^{m}\left(f\left(x_{i}\right)-y_{i}\right)^{2} \\ 49 | &=\underset{(w, b)}{\arg \min } \sum_{i=1}^{m}\left(y_{i}-w x_{i}-b\right)^{2} 50 | \end{aligned} 51 | $$ 52 | 53 | ​ 均方误差有非常好的几何意义,它对应了常用的欧几里得距离。基于均方误差最小化来进行模型求解的方法称为“最小二乘法”。在线性回归中,最小二乘法就是试图找到一条直线,使所有样本到直线上的欧式距离之和最小。 54 | 55 | ​ 求解$w$和$b$使$E_{(w, b)}=\sum_{i=1}^{m}\left(y_{i}-w x_{i}-b\right)^{2}$最小化的过程,称为线性回归模型的最小二乘“参数估计”。我们可将$E_{(w,b)}$分别对$w$和$b$求导,得到: 56 | $$ 57 | \begin{aligned} 58 | &\frac{\partial E_{(w, b)}}{\partial w}=2\left(w \sum_{i=1}^{m} x_{i}^{2}-\sum_{i=1}^{m}\left(y_{i}-b\right) x_{i}\right) \\ 59 | &\frac{\partial E_{(w, b)}}{\partial b}=2\left(m b-\sum_{i=1}^{m}\left(y_{i}-w x_{i}\right)\right) 60 | \end{aligned} 61 | $$ 62 | ​ 然后令上两式为0可得到$w$和$b$最优解的闭式解: 63 | $$ 64 | w=\frac{\sum_{i=1}^{m} y_{i}\left(x_{i}-\bar{x}\right)}{\sum_{i=1}^{m} x_{i}^{2}-\frac{1}{m}\left(\sum_{i=1}^{m} x_{i}\right)^{2}} 65 | $$ 66 | 67 | $$ 68 | b=\frac{1}{m} \sum_{i=1}^{m}\left(y_{i}-w x_{i}\right) 69 | $$ 70 | 71 | 其中 72 | $$ 73 | \bar{x}=\frac{1}{m} \sum_{i=1}^{m} x_{i} 74 | $$ 75 | 为$x$的均值。 76 | 77 | ​ 更一般的情形是如本节开头的数据集D,样本由d个属性描述,此时我们试图学得 78 | $$ 79 | f\left(\boldsymbol{x}_{i}\right)=\boldsymbol{w}^{\mathrm{T}} \boldsymbol{x}_{i}+b, \text { 使得 } f\left(\boldsymbol{x}_{i}\right) \simeq y_{i} 80 | $$ 81 | 这就是多元线性回归 82 | 83 | -------------------------------------------------------------------------------- /ml-with-sklearn/01-LinearRegression/线性回归.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-sklearn/01-LinearRegression/线性回归.pdf -------------------------------------------------------------------------------- /ml-with-sklearn/01-LinearRegression/线性回归.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | from sklearn.linear_model import LinearRegression # 导入线性回归模型 4 | 5 | 6 | def true_fun(X): # 这是我们设定的真实函数,即ground truth的模型 7 | return 1.5*X + 0.2 8 | 9 | np.random.seed(0) # 设置随机种子 10 | n_samples = 30 # 设置采样数据点的个数 11 | 12 | '''生成随机数据作为训练集,并且加一些噪声''' 13 | X_train = np.sort(np.random.rand(n_samples)) 14 | y_train = (true_fun(X_train) + np.random.randn(n_samples) * 0.05).reshape(n_samples,1) 15 | 16 | 17 | model = LinearRegression() # 定义模型 18 | model.fit(X_train[:,np.newaxis], y_train) # 训练模型 19 | print("输出参数w:",model.coef_) # 输出模型参数w 20 | print("输出参数b:",model.intercept_) # 输出参数b 21 | 22 | 23 | X_test = np.linspace(0, 1, 100) 24 | plt.plot(X_test, model.predict(X_test[:, np.newaxis]), label="Model") 25 | plt.plot(X_test, true_fun(X_test), label="True function") 26 | plt.scatter(X_train,y_train) # 画出训练集的点 27 | plt.legend(loc="best") 28 | plt.show() 29 | 30 | 31 | -------------------------------------------------------------------------------- /ml-with-sklearn/02-LogisticRegression/LogisticRegression.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from sklearn.datasets import fetch_openml 3 | from sklearn.linear_model import LogisticRegression 4 | 5 | 6 | 7 | # 数据 8 | mnist = fetch_openml('mnist_784') 9 | X, y = mnist['data'], mnist['target'] 10 | X_train = np.array(X[:60000], dtype=float) 11 | y_train = np.array(y[:60000], dtype=float) 12 | X_test = np.array(X[60000:], dtype=float) 13 | y_test = np.array(y[60000:], dtype=float) 14 | 15 | print(X_train.shape) 16 | print(y_train.shape) 17 | print(X_test.shape) 18 | print(y_test.shape) 19 | 20 | 21 | clf = LogisticRegression(penalty="l1", solver="saga", tol=0.1) 22 | clf.fit(X_train, y_train) 23 | score = clf.score(X_test, y_test) 24 | print("Test score with L1 penalty: %.4f" % score) -------------------------------------------------------------------------------- /ml-with-sklearn/02-LogisticRegression/逻辑回归.md: -------------------------------------------------------------------------------- 1 | # Logistic Regression 2 | 3 | 给定数据 $X=\{x_1,x_2,...,\}$ $Y=\{y_1, y_2, ...,\}$ 4 | 考虑二分类任务,即 $y_i\in{\{0,1\}}, i = 1, 2, ...$, 5 | 6 | ## 假设函数(Hypothesis function) 7 | 假设函数就是其基本模型,如下: 8 | $$h_{\theta}(x)=g(\theta^{T}x)$$ 9 | 其中 $\theta^{T}x=w^Tx+b$, 而 $g(z)=\frac{1}{1+e^{-z}}$ 为 $sigmoid$ 函数,也称激活函数。 10 | 11 | ## 损失函数 12 | 13 | 损失函数又叫代价函数,**用于衡量模型的好坏**,这里可以用极大似然估计法来定义损失函数。 14 | 15 | 似然与概率的区别以及什么是极大似然估计,[一文搞懂极大似然估计](https://zhuanlan.zhihu.com/p/26614750) 16 | 17 | 代价函数可定义为极大似然估计,即 $L(\theta)=\prod_{i=1}p(y_i=1|x_i)=h_\theta(x_1)(1-h_\theta(x_2))...$, 18 | 其中 $x_1$ 对应的标签 $y_1=1$,$x_2$ 对应的标签 $y_2=0$,即设定正例的概率为 $h_\theta(x_i)$: 19 | $$p(y_i=1|x_i)=h_\theta(x_i)$$ 20 | $$p(y_i=0|x_i)=1-h_\theta(x_i)$$ 21 | 22 | 根据极大似然估计原理,我们的目标是 23 | $$\theta^* = \arg \max _{\theta} L(\theta)$$ 24 | 25 | 为了简化运算,两边加对数,得到 26 | $$\theta^* = \arg \max _{\theta} L(\theta) \Rightarrow \theta^* = \arg \min _{\theta} -\ln(L(\theta))$$ 27 | 28 | 化简可得(这里只为了写代码,具体推导参考西瓜书): 29 | $$-\ln(L(\theta))=\ell(\boldsymbol{\theta})=\sum_{i=1}(-y_i\theta^Tx_i+\ln(1+e^{\theta^Tx_i}))$$ 30 | ## 求解:梯度下降 31 | 根据凸优化理论,该函数可以由梯度下降法,牛顿法得出最优解。 32 | 33 | 对于梯度下降来讲,其中 $\eta$ 为学习率: 34 | $$\theta^{t+1}=\theta^{t}-\eta \frac{\partial \ell(\boldsymbol{\theta})}{\partial \boldsymbol{\theta}}$$ 35 | 其中 36 | $$\frac{\partial \ell(\boldsymbol{\theta})}{\partial \boldsymbol{\theta}}=\sum_{i=1}(-y_ix_i+\frac{e^{\theta^Tx_i}x_i}{1+e^{\theta^Tx_i}})=\sum_{i=1}x_i(-y_i+h_\theta(x_i))=\sum_{i=1}x_i(-error)$$ 37 | 38 | 这里梯度上升更方便点: 39 | $$\theta^{t+1}=\theta^{t}+\eta \frac{-\partial \ell(\boldsymbol{\theta})}{\partial \boldsymbol{\theta}}$$ 40 | 其中 41 | $$\frac{-\partial \ell(\boldsymbol{\theta})}{\partial \boldsymbol{\theta}}=\sum_{i=1}(y_ix_i-\frac{e^{\theta^Tx_i}x_i}{1+e^{\theta^Tx_i}})=\sum_{i=1}x_i(y_i-h_\theta(x_i))=\sum_{i=1}x_i*error$$ 42 | 43 | ## 伪代码 44 | 45 | 训练算法如下: 46 | 47 | * 输入:训练数据 $X=\{x_1,x_2,...,x_n\}$, 训练标签 $Y=\{y_1,y_2,...,\}$ 注意均为矩阵形式 48 | 49 | * 输出: 训练好的模型参数 $\theta$,或者 $h_{\theta}(x)$ 50 | 51 | * 初始化模型参数$\theta$,迭代次数$n\_iters$,学习率$\eta$ 52 | 53 | * $\mathbf{FOR} \ i\_iter \ \mathrm{in \ range}(n\_iters)$ 54 | 55 | * $\mathbf{FOR} \ i \ \mathrm{in \ range}(n)$   $\rightarrow n=len(X)$ 56 | 57 | * $error=y_i-h_{\theta}(x_i)$ 58 | * $grad=error*x_i$ 59 | * $\theta \leftarrow \theta + \eta*grad$   $\rightarrow$梯度上升 60 | * $\mathbf{END \ FOR}$ 61 | 62 | * $\mathbf{END \ FOR}$ 63 | 64 | ## Refs 65 | 66 | 西瓜书 67 | 68 | 李宏毅笔记 69 | -------------------------------------------------------------------------------- /ml-with-sklearn/02-LogisticRegression/逻辑回归.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-sklearn/02-LogisticRegression/逻辑回归.pdf -------------------------------------------------------------------------------- /ml-with-sklearn/03-DecisionTree/DecisionTree.md: -------------------------------------------------------------------------------- 1 | ## 简介 2 | 3 | > 决策树(decision tree) 是一类常见的机器学习方法.以二分类任务为例,我们希望从给定训练数据集学得一个模型用以对新示例进行分类,这个把样本分类的任务,可看作对"当前样本属于正类吗?"这个问题的"决策"或"判定"过程.顾名思义,决策树是基于树结构来进行决策的,这恰是人类在面临决策问题时一种很自然的处理机制.例如,我们要对"这是好瓜吗?"这样的问题 4 | > 5 | > 进行决策时,通常会进行一系列的判断或"子决策"我们先看"它是什么颜色?",如果是"青绿色",则我们再看"它的根蒂是什么形态?",如果是"蜷缩",我们再判断"它敲起来是什么音?",最后?我们得出最终决策:这是个好瓜.这个决策过程如图 4.1 所示. 6 | > 7 | > ![WeChat39e385eab473a12c016dd13b725eff9b](assets/WeChat39e385eab473a12c016dd13b725eff9b.png) 8 | 9 | 10 | 11 | 决策树模型会根据**特征选择依据**(信息熵)等划分特征,生成决策树,然后**剪枝**提高泛化能力,可分类可回归,代表算法有ID3,C4.5和CART 12 | 13 | ## 决策树生成算法 14 | 15 | | | 特征选择依据 | | 16 | | :--: | :----------: | ---- | 17 | | ID3 | 信息熵 | | 18 | | C4.5 | 信息增益比 | | 19 | | CART | 基尼系数 | | 20 | 21 | ## 信息增益 22 | 23 | 首先理解一个概念:信息熵 24 | 25 | **熵在化学中是表示分子的混乱程度,分子越混乱,它的熵就越大,而若分子越有序,熵值就越小**。 26 | 27 | 信息熵也是一样的,它能对信息的不确定性进行恒量,如果某个信息让我们的判断更加有序,清晰,则它信息熵越小,反之越大。 28 | $$ 29 | H(X)=-\sum_{i=1}^{n} p\left(x_{i}\right) \log p\left(x_{i}\right) 30 | $$ 31 | 其中$p(x_i)$代表时间$X$为$x_i$的概率,推导如下: 32 | 33 | 34 | 35 | 还有一个概念是条件熵,**在x取值一定的情况下随机变量y不确定性的度量** 36 | $$ 37 | H(Y \mid X)=\sum_{x \in X} p(x) H(Y \mid X=x) 38 | $$ 39 | 信息增益就是熵和特征条件熵的差,$g(D,A)=H(D)-H(D|A)$,对一个确定的数据集来说,H(D)是确定的,那H(D|A)在A特征一定的情况下,随机变量的不确定性越小,信息增益越大,这个特征的表现就越好 40 | 41 | ## 信息增益比 42 | 43 | 在信息增益的基础上除A特征的熵是因为信息增益偏向于选择取值较多的特征,容易过拟合。所以引进信息增益比 44 | $$ 45 | g_{R}(D, X)=\frac{g(D, X)}{H_{X}(D)} 46 | $$ 47 | 48 | 49 | ## 基尼指数 50 | 51 | 基尼指数的定义如下: 52 | $$ 53 | \operatorname{Gini}(D)=\sum_{k=1}^{K} \sum_{k^{\prime} \neq k} p_{k} p_{k^{\prime}}=1-\sum_{k=1}^{K} p_{k}^{2} 54 | $$ 55 | 其中$p_{k}=\frac{\left|C_{k}\right|}{|D|}$是数据集中 𝑘类样本的比例,所以有: 56 | $$ 57 | \operatorname{Gini}(D)=1-\sum_{k=1}^{K}\left(\frac{\left|C_{k}\right|}{|D|}\right)^{2} 58 | $$ 59 | 基尼指数就是从数据集D中随机抽取两个样本,其类别不同的概率,基尼指数越小,说明数据集D中的相同类别样本越多,纯度越高。 60 | 61 | 将基尼指数按属性a(特征)来划分,则其基尼指数为: 62 | $$ 63 | \operatorname{Gini}(D, a)=\sum_{v=1}^{V} \frac{\left|D^{v}\right|}{|D|} \operatorname{Gini}\left(D^{v}\right) 64 | $$ 65 | 66 | 67 | ## 优缺点 68 | 69 | 优点:计算复杂度不高,输出结果易于理解,对中间值的缺失不敏感,可以处理不相关特征数据。 70 | 71 | 缺点:可能会产生过度匹配问题 72 | 73 | 适用数据类型:数值型和标称型 -------------------------------------------------------------------------------- /ml-with-sklearn/03-DecisionTree/DecisionTree.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-sklearn/03-DecisionTree/DecisionTree.pdf -------------------------------------------------------------------------------- /ml-with-sklearn/03-DecisionTree/DecisionTree.py: -------------------------------------------------------------------------------- 1 | import seaborn as sns 2 | from pandas import plotting 3 | import pandas as pd 4 | import numpy as np 5 | import matplotlib.pyplot as plt 6 | from sklearn.tree import DecisionTreeClassifier 7 | from sklearn.datasets import load_iris 8 | from sklearn.model_selection import train_test_split 9 | from sklearn import tree 10 | 11 | # 加载数据集 12 | data = load_iris() 13 | # 转换成.DataFrame形式 14 | df = pd.DataFrame(data.data, columns = data.feature_names) 15 | # 添加品种列 16 | df['Species'] = data.target 17 | # 查看数据集信息 18 | print(f"数据集信息:\n{df.info()}") 19 | # 查看前5条数据 20 | print(f"前5条数据:\n{df.head()}") 21 | # 查看各特征列的摘要信息 22 | df.describe() 23 | 24 | 25 | # 设置颜色主题 26 | antV = ['#1890FF', '#2FC25B', '#FACC14', '#223273', '#8543E0', '#13C2C2', '#3436c7', '#F04864'] 27 | # 绘制violinplot 28 | f, axes = plt.subplots(2, 2, figsize=(8, 8), sharex=True) 29 | sns.despine(left=True) # 删除上方和右方坐标轴上不需要的边框,这在matplotlib中是无法通过参数实现的 30 | sns.violinplot(x='Species', y=df.columns[0], data=df, palette=antV, ax=axes[0, 0]) 31 | sns.violinplot(x='Species', y=df.columns[1], data=df, palette=antV, ax=axes[0, 1]) 32 | sns.violinplot(x='Species', y=df.columns[2], data=df, palette=antV, ax=axes[1, 0]) 33 | sns.violinplot(x='Species', y=df.columns[3], data=df, palette=antV, ax=axes[1, 1]) 34 | plt.show() 35 | # 绘制pointplot 36 | f, axes = plt.subplots(2, 2, figsize=(8, 6), sharex=True) 37 | sns.despine(left=True) 38 | sns.pointplot(x='Species', y=df.columns[0], data=df, color=antV[1], ax=axes[0, 0]) 39 | sns.pointplot(x='Species', y=df.columns[1], data=df, color=antV[1], ax=axes[0, 1]) 40 | sns.pointplot(x='Species', y=df.columns[2], data=df, color=antV[1], ax=axes[1, 0]) 41 | sns.pointplot(x='Species', y=df.columns[3], data=df, color=antV[1], ax=axes[1, 1]) 42 | plt.show() 43 | # g = sns.pairplot(data=df, palette=antV, hue= 'Species') 44 | # 安德鲁曲线 45 | plt.subplots(figsize = (8,6)) 46 | plotting.andrews_curves(df, 'Species', colormap='cool') 47 | 48 | plt.show() 49 | 50 | 51 | # 加载数据集 52 | data = load_iris() 53 | # 转换成.DataFrame形式 54 | df = pd.DataFrame(data.data, columns = data.feature_names) 55 | # 添加品种列 56 | df['Species'] = data.target 57 | 58 | # 用数值替代品种名作为标签 59 | target = np.unique(data.target) 60 | target_names = np.unique(data.target_names) 61 | targets = dict(zip(target, target_names)) 62 | df['Species'] = df['Species'].replace(targets) 63 | 64 | # 提取数据和标签 65 | X = df.drop(columns="Species") 66 | y = df["Species"] 67 | feature_names = X.columns 68 | labels = y.unique() 69 | 70 | X_train, test_x, y_train, test_lab = train_test_split(X,y, 71 | test_size = 0.4, 72 | random_state = 42) 73 | model = DecisionTreeClassifier(max_depth =3, random_state = 42) 74 | model.fit(X_train, y_train) 75 | # 以文字形式输出树 76 | text_representation = tree.export_text(model) 77 | print(text_representation) 78 | # 用图片画出 79 | plt.figure(figsize=(30,10), facecolor ='g') # 80 | a = tree.plot_tree(model, 81 | feature_names = feature_names, 82 | class_names = labels, 83 | rounded = True, 84 | filled = True, 85 | fontsize=14) 86 | plt.show() -------------------------------------------------------------------------------- /ml-with-sklearn/03-DecisionTree/assets/WeChat39e385eab473a12c016dd13b725eff9b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-sklearn/03-DecisionTree/assets/WeChat39e385eab473a12c016dd13b725eff9b.png -------------------------------------------------------------------------------- /ml-with-sklearn/04-MLP/MLP.assets/WeChatb8270a9e865ca875dbbc163459504707.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-sklearn/04-MLP/MLP.assets/WeChatb8270a9e865ca875dbbc163459504707.png -------------------------------------------------------------------------------- /ml-with-sklearn/04-MLP/MLP.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 2, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from sklearn.neural_network import MLPClassifier\n", 10 | "from sklearn.datasets import fetch_openml\n", 11 | "import numpy as np\n", 12 | "\n", 13 | "mnist = fetch_openml('mnist_784')\n", 14 | "X, y = mnist['data'], mnist['target']\n", 15 | "X_train = np.array(X[:60000], dtype=float)\n", 16 | "y_train = np.array(y[:60000], dtype=float)\n", 17 | "X_test = np.array(X[60000:], dtype=float)\n", 18 | "y_test = np.array(y[60000:], dtype=float)\n", 19 | "\n", 20 | "\n", 21 | "clf = MLPClassifier(alpha=1e-5,\n", 22 | " hidden_layer_sizes=(15,15), random_state=1)\n", 23 | "\n", 24 | "clf.fit(X_train, y_train)\n", 25 | "\n", 26 | "\n", 27 | "score = clf.score(X_test, y_test)\n", 28 | "\n" 29 | ] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": 3, 34 | "metadata": {}, 35 | "outputs": [ 36 | { 37 | "data": { 38 | "text/plain": [ 39 | "0.7791" 40 | ] 41 | }, 42 | "execution_count": 3, 43 | "metadata": {}, 44 | "output_type": "execute_result" 45 | } 46 | ], 47 | "source": [ 48 | "score" 49 | ] 50 | }, 51 | { 52 | "cell_type": "markdown", 53 | "metadata": {}, 54 | "source": [ 55 | "参数意义:\n", 56 | "\n", 57 | "hidden_layer_sizes :隐藏层大小,(50,50)表示有两层隐藏层,第一层隐藏层有50个神经元,第二层也有50个神经元。\n", 58 | "\n", 59 | "activation :激活函数,{‘identity’, ‘logistic’, ‘tanh’, ‘relu’}, 默认为relu\n", 60 | "\n", 61 | "solver: 权重优化器,{‘lbfgs’, ‘sgd’, ‘adam’}, 默认adam\n", 62 | "\n", 63 | "learning_rate_int:double,可选,默认0.001,初始学习率,控制更新权重的补偿,只有当solver=’sgd’ 或’adam’时使用。\n" 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": null, 69 | "metadata": {}, 70 | "outputs": [], 71 | "source": [ 72 | "import sys\n", 73 | "from pathlib import Path\n", 74 | "curr_path = str(Path().absolute()) \n", 75 | "parent_path = str(Path().absolute().parent) \n", 76 | "sys.path.append(parent_path) \n", 77 | "\n", 78 | "# 添加目录到系统路径方便导入模块,该项目的根目录为\".../machine-learning-toy-code\"\n", 79 | "from pathlib import Path\n", 80 | "from torch.utils.data import DataLoader\n", 81 | "from torchvision import datasets\n", 82 | "import torchvision\n", 83 | "import torchvision.transforms as transforms\n", 84 | "import matplotlib.pyplot as plt\n", 85 | "from sklearn.linear_model import LogisticRegression\n", 86 | "from sklearn.metrics import classification_report\n", 87 | "import numpy as np\n", 88 | "\n", 89 | "\n", 90 | "curr_path = str(Path().absolute())\n", 91 | "parent_path = str(Path().absolute().parent)\n", 92 | "p_parent_path = str(Path().absolute().parent.parent)\n", 93 | "sys.path.append(p_parent_path) \n", 94 | "print(f\"主目录为:{p_parent_path}\")\n", 95 | "\n", 96 | "\n", 97 | "train_dataset = datasets.MNIST(root = p_parent_path+'/datasets/', train = True,transform = transforms.ToTensor(), download = False)\n", 98 | "test_dataset = datasets.MNIST(root = p_parent_path+'/datasets/', train = False, \n", 99 | " transform = transforms.ToTensor(), download = False)\n", 100 | "\n", 101 | "batch_size = len(train_dataset)\n", 102 | "train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)\n", 103 | "test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=True)\n", 104 | "X_train,y_train = next(iter(train_loader))\n", 105 | "X_test,y_test = next(iter(test_loader))\n", 106 | "# 打印前100张图片\n", 107 | "images, labels= X_train[:100], y_train[:100] \n", 108 | "# 使用images生成宽度为10张图的网格大小\n", 109 | "img = torchvision.utils.make_grid(images, nrow=10)\n", 110 | "# cv2.imshow()的格式是(size1,size1,channels),而img的格式是(channels,size1,size1),\n", 111 | "# 所以需要使用.transpose()转换,将颜色通道数放至第三维\n", 112 | "img = img.numpy().transpose(1,2,0)\n", 113 | "print(images.shape)\n", 114 | "print(labels.reshape(10,10))\n", 115 | "print(img.shape)\n", 116 | "plt.imshow(img)\n", 117 | "plt.show()\n", 118 | "\n", 119 | "X_train,y_train = X_train.cpu().numpy(),y_train.cpu().numpy() # tensor转为array形式)\n", 120 | "X_test,y_test = X_test.cpu().numpy(),y_test.cpu().numpy() # tensor转为array形式)\n", 121 | "\n", 122 | "X_train = X_train.reshape(X_train.shape[0],784)\n", 123 | "X_test = X_test.reshape(X_test.shape[0],784)\n", 124 | "\n", 125 | "# solver:即使用的优化器,lbfgs:拟牛顿法, sag:随机梯度下降\n", 126 | "model = LogisticRegression(solver='lbfgs', max_iter=400) # lbfgs:拟牛顿法\n", 127 | "model.fit(X_train, y_train)\n", 128 | "y_pred = model.predict(X_test)\n", 129 | "print(classification_report(y_test, y_pred)) # 打印报告\n", 130 | "\n", 131 | "ones_col=[[1] for i in range(len(X_train))] # 生成全为1的二维嵌套列表,即[[1],[1],...,[1]]\n", 132 | "X_train = np.append(X_train,ones_col,axis=1)\n", 133 | "x_train = np.mat(X_train)\n", 134 | "X_test = np.append(X_test,ones_col,axis=1)\n", 135 | "x_test = np.mat(X_test)\n", 136 | "# Mnsit有0-9十个标记,由于是二分类任务,所以可以将标记0的作为1,其余为0用于识别是否为0的任务\n", 137 | "y_train=np.array([1 if y_train[i]==1 else 0 for i in range(len(y_train))])\n", 138 | "y_test=np.array([1 if y_test[i]==1 else 0 for i in range(len(y_test))])\n", 139 | "\n", 140 | "# solver:即使用的优化器,lbfgs:拟牛顿法, sag:随机梯度下降\n", 141 | "model = LogisticRegression(solver='lbfgs', max_iter=100) # lbfgs:拟牛顿法\n", 142 | "model.fit(X_train, y_train)\n", 143 | "y_pred = model.predict(X_test)\n", 144 | "print(classification_report(y_test, y_pred)) # 打印报告" 145 | ] 146 | } 147 | ], 148 | "metadata": { 149 | "interpreter": { 150 | "hash": "47dfafb046f9703cd15ca753999a7e7c95274099825c7bcc45b473d6496cd1b0" 151 | }, 152 | "kernelspec": { 153 | "display_name": "Python 3 (ipykernel)", 154 | "language": "python", 155 | "name": "python3" 156 | }, 157 | "language_info": { 158 | "codemirror_mode": { 159 | "name": "ipython", 160 | "version": 3 161 | }, 162 | "file_extension": ".py", 163 | "mimetype": "text/x-python", 164 | "name": "python", 165 | "nbconvert_exporter": "python", 166 | "pygments_lexer": "ipython3", 167 | "version": "3.8.12" 168 | } 169 | }, 170 | "nbformat": 4, 171 | "nbformat_minor": 4 172 | } 173 | -------------------------------------------------------------------------------- /ml-with-sklearn/04-MLP/MLP.md: -------------------------------------------------------------------------------- 1 | ## 神经元模型 2 | 3 | 神经网络中最基本的成分是神经元模型,即上述定义中的“简单单元”。在生物神经网络中,每个神经元与其他神经元相连,当它“兴奋”的时候,就会向相连的神经元发送化学物质,从而改变这些神经元内的电位;如果某神经元的电位超过了一个阈值,那么它就会被激活,即“兴奋”起来,向其他神经元发送化学物质。 4 | 5 | 6 | 7 | 8 | 9 | ![WeChatb8270a9e865ca875dbbc163459504707](MLP.assets/WeChatb8270a9e865ca875dbbc163459504707.png) 10 | 11 | 上图中的神经元接收到来自n个其他神经元传递过来的输入信号,这些信号通过带权重的连接进行传递,神经元接收到的总输入值将与神经元的阈值进行比较,然后通过激活函数处理以产生神经元的输出。 12 | 13 | 把多个神经元按一定的层次结构连接起来,就得到了神经网络 14 | 15 | 16 | 17 | # MLP实现方法 18 | 19 | ```python 20 | from sklearn.neural_network import MLPClassifier 21 | from sklearn.datasets import fetch_openml 22 | import numpy as np 23 | 24 | mnist = fetch_openml('mnist_784') 25 | X, y = mnist['data'], mnist['target'] 26 | X_train = np.array(X[:60000], dtype=float) 27 | y_train = np.array(y[:60000], dtype=float) 28 | X_test = np.array(X[60000:], dtype=float) 29 | y_test = np.array(y[60000:], dtype=float) 30 | 31 | 32 | clf = MLPClassifier(alpha=1e-5, 33 | hidden_layer_sizes=(15,15), random_state=1) 34 | 35 | clf.fit(X_train, y_train) 36 | 37 | 38 | score = clf.score(X_test, y_test) 39 | 40 | ``` 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /ml-with-sklearn/04-MLP/MLP.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-sklearn/04-MLP/MLP.pdf -------------------------------------------------------------------------------- /ml-with-sklearn/04-MLP/MLP.py: -------------------------------------------------------------------------------- 1 | from sklearn.neural_network import MLPClassifier 2 | from sklearn.datasets import fetch_openml 3 | import numpy as np 4 | 5 | mnist = fetch_openml('mnist_784') 6 | X, y = mnist['data'], mnist['target'] 7 | X_train = np.array(X[:60000], dtype=float) 8 | y_train = np.array(y[:60000], dtype=float) 9 | X_test = np.array(X[60000:], dtype=float) 10 | y_test = np.array(y[60000:], dtype=float) 11 | 12 | 13 | clf = MLPClassifier(alpha=1e-5, 14 | hidden_layer_sizes=(15,15), random_state=1) 15 | 16 | clf.fit(X_train, y_train) 17 | 18 | 19 | score = clf.score(X_test, y_test) 20 | 21 | 22 | -------------------------------------------------------------------------------- /ml-with-sklearn/05-SVM/datasets.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-sklearn/05-SVM/datasets.zip -------------------------------------------------------------------------------- /ml-with-sklearn/05-SVM/svm.md: -------------------------------------------------------------------------------- 1 | # SVM 2 | ## 间隔与支持向量 3 | 4 | 给定训练样本集$D={(x_i,y_1),(x_2,y_2),...,(x_m,y_m)},y_i∈{+1,-1}$ 5 | 6 | ,分类学习最基本的想法就是基于训练集D在样本空间中找到一个划分超平面,将不同类别的样本分开.但能将训练样本分开的划分超平面可能有很多 7 | 在样本空间中,划分超平面可通过如下线性方程来描述: 8 | $$ 9 | w^Tx+b=0 10 | $$ 11 | 其中$w=(w_1;w_2;..;w_d)$为法向量决定了超平面的方向; b为位移项,决定了超平面与原点之间的距离.显然,划分超平面可被法向量w和位移b确定,下面我们将其记为(w,b).样本空间中任意点x到超平面(w,b)的距离可写为 12 | $$ 13 | r=\frac{|w^Tx+b|}{||w||} 14 | $$ 15 | 假设超平面(w,b)能将样本正确分类,即对于$(x_i,y_i)∈D若y_i=+1,则有w^Tx_i+b>0,若y_i=-1,则有w^Tx_i+b<0$。令 16 | $$ 17 | 当w^Tx_i+b>1时,y_i=1;当w^Tx_i+b<-1时,y_i=-1 18 | $$ 19 | 距离超平面最近的这几个训练样本点使上式的等号成立,它们被称为“支持向量”(support vector), 两个异类支持向量到超平面的距离之和为 20 | $$ 21 | r=\frac{2}{||w||} 22 | $$ 23 | 欲找到具有“最大间隔”(maximum margin)的划分超平面,也就是要找到能满足式中约束的参数w和b,使得r最大,即 24 | $$ 25 | max_{w,b}\frac{2}{||w||} 26 | $$ 27 | 28 | $$ 29 | s.t. y_i(w^Tx_i+b)≥1,i=1,2,..,m 30 | $$ 31 | 32 | 可重写为 33 | $$ 34 | min_{w,b}\frac{1}{2}||w|| 35 | $$ 36 | 37 | $$ 38 | s.t. y_i(w^Tx_i+b)≥1,i=1,2,..,m 39 | $$ 40 | 41 | 42 | 43 | ## 对偶问题 44 | 上一个式子可以用拉格朗日乘子法,写出拉格朗日函数,对w,b求导后带入可得其对偶问题 45 | $$ 46 | \max\limits_{\alpha}\displaystyle\sum_{i=1}^m\alpha_i-\frac{1}{2}\displaystyle\sum_{i=1}^m\displaystyle\sum_{j=1}^m\alpha_i\alpha_jy_iy_jx_i^Tx_j 47 | $$ 48 | 49 | $$ 50 | s.t. \displaystyle\sum_{j=1}^m\alpha_iy_i=0 51 | $$ 52 | 53 | $$ 54 | \alpha_i>0,i=1,2,3...,m 55 | $$ 56 | 57 | 58 | 59 | ## 核函数 60 | 在前面我们假设训练样本是线性可分的,即存在一个划分超平面能将训练样本正确分类.然而在现实任务中,原始样本空间内也许并不存在一个能正确划分两类样本的超平面.对这样的问题,可将样本从原始空间映射到一个更高维的特征空间,使得样本在这个特征空间内线性可分,例如,若将原始的二维空间映射到一个合适的三维空间,就能找到一-个合适的划分超平面.幸运的是,如果原始空间是有限维,即属性数有限,那么一-定存在一个高维特征空间使样本可分. 61 | 令$\Phi$表示将x映射后的特征向量,于是,在特征空间中划分超平面所对应的模型可表示为 62 | $$ 63 | f(x)=w^T\Phi(x)+b=0 64 | $$ 65 | 其中w,b为模型参数,则有 66 | $$ 67 | min_{w,b}\frac{1}{2}||w|| 68 | $$ 69 | 70 | $$ 71 | s.t. y_i(w^T\Phi(x_i)+b)≥1,i=1,2,..,m 72 | $$ 73 | 74 | 其对偶问题是 75 | $$ 76 | \max\limits_{\alpha}\displaystyle\sum_{i=1}^m\alpha_i-\frac{1}{2}\displaystyle\sum_{i=1}^m\displaystyle\sum_{j=1}^m\alpha_i\alpha_jy_iy_j\Phi(x_i)^T\Phi(x_j) 77 | $$ 78 | 79 | $$ 80 | s.t. \displaystyle\sum_{j=1}^m\alpha_iy_i=0 81 | $$ 82 | 83 | $$ 84 | \alpha_i>0,i=1,2,3...,m 85 | $$ 86 | 87 | 求解$\Phi(x_i)^T\Phi(x_j)$,这是样本$x_i,x_j$映射到特征空间之后的内积.由于特征空间维数可能很高,甚至可能是无穷维,因此直接计算$\Phi(x_i)^T\Phi(x_j)$通常是困难的.为了避开这个障碍,可以设想这样一个函数: 88 | $$ 89 | k(x_i,x_j)=<\Phi(x_i),\Phi(x_j)>=\Phi(x_i)^T\Phi(x_j) 90 | $$ 91 | 于是,可写为 92 | $$ 93 | \max\limits_{\alpha}\displaystyle\sum_{i=1}^m\alpha_i-\frac{1}{2}\displaystyle\sum_{i=1}^m\displaystyle\sum_{j=1}^m\alpha_i\alpha_jy_iy_jk(x_i,x_j) 94 | $$ 95 | 96 | $$ 97 | s.t. \displaystyle\sum_{j=1}^m\alpha_iy_i=0 98 | $$ 99 | 100 | $$ 101 | \alpha_i>0,i=1,2,3...,m 102 | $$ 103 | 104 | 这里的函数k(;)就是核函数,常用核函数如下 105 | 线性核 $k(x_i,x_j)=x_i^Tx_j$ 106 | 多项式核 $k(x_i,x_j)=(x_i^Tx_j)^d,d≥1为多项式的次数$ 107 | 高斯核 $k(x_i,x_j)=exp(\frac{||x_i-x_j||^2}{2\sigma^2}),\sigma>0$ -------------------------------------------------------------------------------- /ml-with-sklearn/05-SVM/svm.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-sklearn/05-SVM/svm.pdf -------------------------------------------------------------------------------- /ml-with-sklearn/05-SVM/svm.py: -------------------------------------------------------------------------------- 1 | # 线性svm 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | from sklearn import svm 5 | 6 | data = np.array([ 7 | [0.1, 0.7], 8 | [0.3, 0.6], 9 | [0.4, 0.1], 10 | [0.5, 0.4], 11 | [0.8, 0.04], 12 | [0.42, 0.6], 13 | [0.9, 0.4], 14 | [0.6, 0.5], 15 | [0.7, 0.2], 16 | [0.7, 0.67], 17 | [0.27, 0.8], 18 | [0.5, 0.72] 19 | ])# 建立数据集 20 | label = [1] * 6 + [0] * 6 #前六个数据的label为1后六个为0 21 | x_min, x_max = data[:, 0].min() - 0.2, data[:, 0].max() + 0.2 22 | y_min, y_max = data[:, 1].min() - 0.2, data[:, 1].max() + 0.2 23 | xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.002), 24 | np.arange(y_min, y_max, 0.002)) # meshgrid如何生成网格 25 | print(xx) 26 | model_linear = svm.SVC(kernel='linear', C = 0.001)# 线性svm 27 | model_linear.fit(data, label) # 训练 28 | Z = model_linear.predict(np.c_[xx.ravel(), yy.ravel()]) # 预测 29 | Z = Z.reshape(xx.shape) 30 | plt.contourf(xx, yy, Z, cmap = plt.cm.ocean, alpha=0.6) 31 | plt.scatter(data[:6, 0], data[:6, 1], marker='o', color='r', s=100, lw=3) 32 | plt.scatter(data[6:, 0], data[6:, 1], marker='x', color='k', s=100, lw=3) 33 | plt.title('Linear SVM') 34 | plt.show() 35 | 36 | # 多项式核 37 | plt.figure(figsize=(16, 15)) 38 | 39 | for i, degree in enumerate([1, 3, 5, 7, 9, 12]): # 多项式次数选择了1,3,5,7,9,12 40 | # C: 惩罚系数,gamma: 高斯核的系数 41 | model_poly = svm.SVC(C=0.0001, kernel='poly', degree=degree) # 多项式核 42 | model_poly.fit(data, label) # 训练 43 | # ravel - flatten 44 | # c_ - vstack 45 | # 把后面两个压扁之后变成了x1和x2,然后进行判断,得到结果在压缩成一个矩形 46 | Z = model_poly.predict(np.c_[xx.ravel(), yy.ravel()]) # 预测 47 | Z = Z.reshape(xx.shape) 48 | 49 | plt.subplot(3, 2, i + 1) 50 | plt.subplots_adjust(wspace=0.4, hspace=0.4) 51 | plt.contourf(xx, yy, Z, cmap=plt.cm.ocean, alpha=0.6) 52 | 53 | # 画出训练点 54 | plt.scatter(data[:6, 0], data[:6, 1], marker='o', color='r', s=100, lw=3) 55 | plt.scatter(data[6:, 0], data[6:, 1], marker='x', color='k', s=100, lw=3) 56 | plt.title('Poly SVM with $\degree=$' + str(degree)) 57 | plt.show() 58 | 59 | 60 | # 高斯核svm 61 | 62 | plt.figure(figsize=(16, 15)) 63 | 64 | for i, gamma in enumerate([1, 5, 15, 35, 45, 55]): 65 | # C: 惩罚系数,gamma: 高斯核的系数 66 | model_rbf = svm.SVC(kernel='rbf', gamma=gamma, C=0.0001).fit(data, label) 67 | 68 | # ravel - flatten 69 | # c_ - vstack 70 | # 把后面两个压扁之后变成了x1和x2,然后进行判断,得到结果在压缩成一个矩形 71 | Z = model_rbf.predict(np.c_[xx.ravel(), yy.ravel()]) 72 | Z = Z.reshape(xx.shape) 73 | 74 | plt.subplot(3, 2, i + 1) 75 | plt.subplots_adjust(wspace=0.4, hspace=0.4) 76 | plt.contourf(xx, yy, Z, cmap=plt.cm.ocean, alpha=0.6) 77 | 78 | # 画出训练点 79 | plt.scatter(data[:6, 0], data[:6, 1], marker='o', color='r', s=100, lw=3) 80 | plt.scatter(data[6:, 0], data[6:, 1], marker='x', color='k', s=100, lw=3) 81 | plt.title('RBF SVM with $\gamma=$' + str(gamma)) 82 | plt.show() 83 | 84 | # 测试不同SVM在Mnist数据集上的分类情况 85 | # 添加目录到系统路径方便导入模块,该项目的根目录为".../machine-learning-toy-code" 86 | import sys 87 | from pathlib import Path 88 | 89 | 90 | from sklearn import svm 91 | import numpy as np 92 | from time import time 93 | from sklearn.metrics import accuracy_score 94 | from struct import unpack 95 | from sklearn.model_selection import GridSearchCV 96 | 97 | def readimage(path): 98 | with open(path, 'rb') as f: 99 | magic, num, rows, cols = unpack('>4I', f.read(16)) 100 | img = np.fromfile(f, dtype=np.uint8).reshape(num, 784) 101 | return img 102 | 103 | def readlabel(path): 104 | with open(path, 'rb') as f: 105 | magic, num = unpack('>2I', f.read(8)) 106 | lab = np.fromfile(f, dtype=np.uint8) 107 | return lab 108 | 109 | train_data = readimage("datasets/MNIST/raw/train-images-idx3-ubyte")#读取数据 110 | train_label = readlabel("datasets/MNIST/raw/train-labels-idx1-ubyte") 111 | test_data = readimage("datasets/MNIST/raw/t10k-images-idx3-ubyte") 112 | test_label = readlabel("datasets/MNIST/raw/t10k-labels-idx1-ubyte") 113 | print(train_data.shape) 114 | print(train_label.shape) 115 | #数据集中数据太多,为了节约时间,我们只使用前2000张进行训练 116 | train_data=train_data[:2000] 117 | train_label=train_label[:2000] 118 | test_data=test_data[:200] 119 | test_label=test_label[:200] 120 | svc=svm.SVC() 121 | parameters = {'kernel':['rbf'], 'C':[1]}#使用了高斯核 122 | print("Train...") 123 | clf=GridSearchCV(svc,parameters,n_jobs=-1) 124 | start = time() 125 | clf.fit(train_data, train_label) 126 | end = time() 127 | t = end - start 128 | print('Train:%dmin%.3fsec' % (t//60, t - 60 * (t//60)))#显示训练时间 129 | prediction = clf.predict(test_data)#对测试数据进行预测 130 | print("accuracy: ", accuracy_score(prediction, test_label)) 131 | accurate=[0]*10 132 | sumall=[0]*10 133 | i=0 134 | j=0 135 | while iY以最小化总体风险 9 | $$ 10 | R(h)=E_x[R(h(x)|x)] 11 | $$ 12 | 显然,对于每个样本x,若h能最小化条件风险R(h(x)|x)则总体风险R(h)也将被最小化,这就产生了贝叶斯判定准则:为最小化总体风险,只需在每个样本上选择那个能使条件风险R(c|x)最小的类别标记,即 13 | $$ 14 | h^*(x)= argmin_{c∈Y}R(c|x) 15 | $$ 16 | 此时,为贝叶斯最优分类器,与之对应的总体风险$R(h^*)$称为贝叶斯风险,$1-R(h^*)$反映了分类器所能达到的最好性能,即通过机器学习所能产生的模型精度的理论上限。 17 | 具体来说,若目标是最小化分类错误率,则误判损失$\lambda_{ij}$可以表示为,当i=j时,$\lambda_{ij}=0$,否则为1。 18 | 此时条件风险为* 19 | $$ 20 | R(c|x)=1-P(c|x) 21 | $$ 22 | *,于是,最小化分类错误率的贝叶斯最优分类器为 23 | $$ 24 | h^*(x)= argmax_{c∈Y}P(c|x) 25 | $$ 26 | 27 | 28 | 29 | # 朴素贝叶斯分类器 30 | 朴素贝叶斯分类器采用了“属性条件独立性假设”:对已知类别,假设所有属性相互独立,换言之,假设每个属性独立地对分类结果发生影响。基于属性条件独立性假 31 | $$ 32 | P(c|x)=\frac{P(c)P(x|c)}{P(x)}=\frac{P(c)}{P(x)}\displaystyle\prod_{i=1}^dP(x_i|c) 33 | $$ 34 | 由于对于所有类别来说P(x)相同,贝叶斯判定准则有 35 | $$ 36 | h_{nb}(x)=argmax_{c∈Y}P(c)\displaystyle\prod_{i=1}^dP(c|x) 37 | $$ 38 | -------------------------------------------------------------------------------- /ml-with-sklearn/06-Bayes/贝叶斯.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-sklearn/06-Bayes/贝叶斯.pdf -------------------------------------------------------------------------------- /ml-with-sklearn/06-Bayes/贝叶斯分类器.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "resident-number", 6 | "metadata": {}, 7 | "source": [ 8 | "# 贝叶斯分类器" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "handy-palestine", 14 | "metadata": {}, 15 | "source": [ 16 | "贝叶斯决策论是概率框架下实施决策的基本方法,对于分类任务来说,在所有相关概率都已知的理想情况下,贝叶斯决策论考虑如何基于这些概率和误判损失来选择最优的类别标记。" 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "id": "soviet-colon", 22 | "metadata": {}, 23 | "source": [ 24 | "假设有N种可能的类别标记,即$Y ={c_1,c_2,c_3...c_N},\\lambda_{ij} $是将一个真实标记为$ c_j $的样误分类为$ c_i $所产生的损失.基于后验概率P($c_i$|x)可以获得将样本x误分类成$c_i$所产生的的期望损失,记载样本x上的“条件风险”\n", 25 | "$$R(c_i|x)=\\displaystyle\\sum_{j=1}^N \\lambda_{ij}P(c_i|x)$$" 26 | ] 27 | }, 28 | { 29 | "cell_type": "markdown", 30 | "id": "oriented-radical", 31 | "metadata": {}, 32 | "source": [ 33 | "我们的任务是寻找一个判定准则h:X<->Y以最小化总体风险\n", 34 | "$$R(h)=E_x[R(h(x)|x)]$$" 35 | ] 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "id": "light-conspiracy", 40 | "metadata": {}, 41 | "source": [ 42 | "显然,对于每个样本x,若h能最小化条件风险R(h(x)|x)则总体风险R(h)也将被最小化,这就产生了贝叶斯判定准则:为最小化总体风险,只需在每个样本上选择那个能使条件风险R(c|x)最小的类别标记,即$$ h^*(x)= argmin_{c∈Y}R(c|x)$$" 43 | ] 44 | }, 45 | { 46 | "cell_type": "markdown", 47 | "id": "reserved-pendant", 48 | "metadata": {}, 49 | "source": [ 50 | "此时,为贝叶斯最优分类器,与之对应的总体风险$R(h^*)$称为贝叶斯风险,$1-R(h^*)$反映了分类器所能达到的最好性能,即通过机器学习所能产生的模型精度的理论上限。" 51 | ] 52 | }, 53 | { 54 | "cell_type": "markdown", 55 | "id": "adopted-pottery", 56 | "metadata": {}, 57 | "source": [ 58 | "具体来说,若目标是最小化分类错误率,则误判损失$\\lambda_{ij}$可以表示为,当i=j时,$\\lambda_{ij}=0$,否则为1。\n", 59 | "此时条件风险为$$R(c|x)=1-P(c|x)$$,于是,最小化分类错误率的贝叶斯最优分类器为$$h^*(x)= argmax_{c∈Y}P(c|x)$$" 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "id": "occupied-survey", 65 | "metadata": {}, 66 | "source": [ 67 | "# 朴素贝叶斯分类器" 68 | ] 69 | }, 70 | { 71 | "cell_type": "markdown", 72 | "id": "southwest-method", 73 | "metadata": {}, 74 | "source": [ 75 | "朴素贝叶斯分类器采用了“属性条件独立性假设”:对已知类别,假设所有属性相互独立,换言之,假设每个属性独立地对分类结果发生影响。基于属性条件独立性假$$P(c|x)=\\frac{P(c)P(x|c)}{P(x)}=\\frac{P(c)}{P(x)}\\displaystyle\\prod_{i=1}^dP(x_i|c)$$" 76 | ] 77 | }, 78 | { 79 | "cell_type": "markdown", 80 | "id": "acknowledged-violin", 81 | "metadata": {}, 82 | "source": [ 83 | "由于对于所有类别来说P(x)相同,贝叶斯判定准则有\n", 84 | "$$h_{nb}(x)=argmax_{c∈Y}P(c)\\displaystyle\\prod_{i=1}^dP(c|x)$$" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": null, 90 | "id": "smooth-industry", 91 | "metadata": {}, 92 | "outputs": [], 93 | "source": [] 94 | } 95 | ], 96 | "metadata": { 97 | "kernelspec": { 98 | "display_name": "Python 3", 99 | "language": "python", 100 | "name": "python3" 101 | }, 102 | "language_info": { 103 | "codemirror_mode": { 104 | "name": "ipython", 105 | "version": 3 106 | }, 107 | "file_extension": ".py", 108 | "mimetype": "text/x-python", 109 | "name": "python", 110 | "nbconvert_exporter": "python", 111 | "pygments_lexer": "ipython3", 112 | "version": "3.9.2" 113 | } 114 | }, 115 | "nbformat": 4, 116 | "nbformat_minor": 5 117 | } 118 | -------------------------------------------------------------------------------- /ml-with-sklearn/07-Random Forest/bagging与随机森林.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "identical-indie", 6 | "metadata": {}, 7 | "source": [ 8 | "# bagging与随机森林" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "id": "elementary-tours", 14 | "metadata": {}, 15 | "source": [ 16 | "欲得到泛化性能强的集成,集成中的个体学习器应尽可能相互独立;虽然“独立”在现实任务中无法做到,但可以设法使基学习器尽可能具有较大的差异.给定一个训练数据集,一种可能的做法是对训练样本进行采样,由于训练数据不同,我们获得的基学习器可望具有比较大的差异.然而,为获得好的集成,我们同时还希望个体学习器不能太差,如果采样出的每个子集都完全不同,则每个基学习器只用到了一小部分训练数据,甚至不足以进行有效学习,这显然无法确保产生出比较好的基学习器.为解决这个问题,我们可考虑使用相互有交叠的采样子集." 17 | ] 18 | }, 19 | { 20 | "cell_type": "markdown", 21 | "id": "consecutive-entity", 22 | "metadata": {}, 23 | "source": [ 24 | "## Bagging" 25 | ] 26 | }, 27 | { 28 | "cell_type": "markdown", 29 | "id": "perfect-museum", 30 | "metadata": {}, 31 | "source": [ 32 | "给定包含m个样本的数据集,我们先随机取出一个样本放入采样集中,再把该样本放回初始数据集,使得下次采样时该样本仍有可能被选中,这样,经过m\n", 33 | "次随机采样操作,我们得到含m个样本的采样集,初始训练集中有的样本在采样集里多次出现,有的则从未出现.初始训练集中约有63.2%,\n", 34 | "照这样,我们可采样出T个含m个训练样本的采样集,然后基于每个采样集训练出一个基学习器,再将这些基学习器进行结合.这就是Bagging的基本流程.在对预测输出进行结合时,Bagging通常对分类任务使用简单投票法,对回归任务使用简单平均法.若分类预测时出现两个类收到同样票数的情形,则最简单的做法是随机选择-一个,也可进一步考察学习器投票的置信度来确定最终胜者. " 35 | ] 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "id": "supreme-vitamin", 40 | "metadata": {}, 41 | "source": [ 42 | "算法描述如下:\n", 43 | "输入: 训练集$D={(x_1,y_1),(x_2,y_2),...,(x_m,y_m)}$\n", 44 | " 基学习算法$\\xi$\n", 45 | " 训练轮次T\n", 46 | "过程:\n", 47 | "1. for t=1,2,3..T do\n", 48 | "2. $h_t=\\xi(D,D_{bs})$\n", 49 | "3. end for\n", 50 | "\n", 51 | "输出:$H(x)=arg\\max\\limits_{y∈Y}\\displaystyle\\sum_{t=1}^TII(h_t(x)=y)$" 52 | ] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "id": "raised-round", 57 | "metadata": {}, 58 | "source": [ 59 | "## 随机森林" 60 | ] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "id": "outer-davis", 65 | "metadata": {}, 66 | "source": [ 67 | "随机森林(Random Forest,简称RF)是Bagging的一个扩展变体.RF在以决策树为基学习器构建Bagging集成的基础上,进一步在决策树的训练过程中引入了随机属性选择.具体来说,传统决策树在选择划分属性时是在当前结点的属性集合(假定有d个属性)中选择-一个最优属性;而在RF中,对基决策树的每个结点,先从该结点的属性集合中随机选择一个包含k个属性的子集,然后再从这个子集中选择一个最优属性用于划分.这里的参数k控制了随机性的引入程度:若令k=d,则基决策树的构建与传统决策树相同;若令k= 1,则是随机选择-一个属性用于划分;一般情况下,推荐值$k = log_2d$" 68 | ] 69 | } 70 | ], 71 | "metadata": { 72 | "kernelspec": { 73 | "display_name": "Python 3", 74 | "language": "python", 75 | "name": "python3" 76 | }, 77 | "language_info": { 78 | "codemirror_mode": { 79 | "name": "ipython", 80 | "version": 3 81 | }, 82 | "file_extension": ".py", 83 | "mimetype": "text/x-python", 84 | "name": "python", 85 | "nbconvert_exporter": "python", 86 | "pygments_lexer": "ipython3", 87 | "version": "3.9.2" 88 | } 89 | }, 90 | "nbformat": 4, 91 | "nbformat_minor": 5 92 | } 93 | -------------------------------------------------------------------------------- /ml-with-sklearn/07-Random Forest/bagging和随机森林.md: -------------------------------------------------------------------------------- 1 | # bagging与随机森林 2 | 欲得到泛化性能强的集成,集成中的个体学习器应尽可能相互独立;虽然“独立”在现实任务中无法做到,但可以设法使基学习器尽可能具有较大的差异.给定一个训练数据集,一种可能的做法是对训练样本进行采样,由于训练数据不同,我们获得的基学习器可望具有比较大的差异.然而,为获得好的集成,我们同时还希望个体学习器不能太差,如果采样出的每个子集都完全不同,则每个基学习器只用到了一小部分训练数据,甚至不足以进行有效学习,这显然无法确保产生出比较好的基学习器.为解决这个问题,我们可考虑使用相互有交叠的采样子集. 3 | ## Bagging 4 | 给定包含m个样本的数据集,我们先随机取出一个样本放入采样集中,再把该样本放回初始数据集,使得下次采样时该样本仍有可能被选中,这样,经过m 5 | 次随机采样操作,我们得到含m个样本的采样集,初始训练集中有的样本在采样集里多次出现,有的则从未出现.初始训练集中约有63.2%, 6 | 照这样,我们可采样出T个含m个训练样本的采样集,然后基于每个采样集训练出一个基学习器,再将这些基学习器进行结合.这就是Bagging的基本流程.在对预测输出进行结合时,Bagging通常对分类任务使用简单投票法,对回归任务使用简单平均法.若分类预测时出现两个类收到同样票数的情形,则最简单的做法是随机选择-一个,也可进一步考察学习器投票的置信度来确定最终胜者. 7 | 算法描述如下: 8 | 输入: 训练集$D={(x_1,y_1),(x_2,y_2),...,(x_m,y_m)}$ 9 | 基学习算法$\xi$ 10 | 训练轮次T 11 | 过程: 12 | 1. for t=1,2,3..T do 13 | 2. $h_t=\xi(D,D_{bs})$ 14 | 3. end for 15 | 16 | 输出: 17 | $$ 18 | H(x)=arg\max\limits_{y∈Y}\displaystyle\sum_{t=1}^TII(h_t(x)=y) 19 | $$ 20 | 21 | ## 随机森林 22 | 随机森林(Random Forest,简称RF)是Bagging的一个扩展变体.RF在以决策树为基学习器构建Bagging集成的基础上,进一步在决策树的训练过程中引入了随机属性选择.具体来说,传统决策树在选择划分属性时是在当前结点的属性集合(假定有d个属性)中选择-一个最优属性;而在RF中,对基决策树的每个结点,先从该结点的属性集合中随机选择一个包含k个属性的子集,然后再从这个子集中选择一个最优属性用于划分.这里的参数k控制了随机性的引入程度:若令k=d,则基决策树的构建与传统决策树相同;若令k= 1,则是随机选择-一个属性用于划分;一般情况下,推荐值$k = log_2d$ -------------------------------------------------------------------------------- /ml-with-sklearn/07-Random Forest/bagging和随机森林.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-sklearn/07-Random Forest/bagging和随机森林.pdf -------------------------------------------------------------------------------- /ml-with-sklearn/07-Random Forest/bagging和随机森林.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from sklearn.datasets import load_wine 3 | from sklearn.model_selection import train_test_split 4 | from sklearn.metrics import accuracy_score 5 | from sklearn.tree import DecisionTreeClassifier 6 | import matplotlib.pyplot as plt 7 | 8 | wine = load_wine()#使用葡萄酒数据集 9 | print(f"所有特征:{wine.feature_names}") 10 | X = pd.DataFrame(wine.data, columns=wine.feature_names) 11 | y = pd.Series(wine.target) 12 | X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=1) 13 | #构建并训练决策树分类器,这里特征选择标准使用基尼指数,树的最大深度为1 14 | base_model = DecisionTreeClassifier(max_depth=1, criterion='gini',random_state=1).fit(X_train, y_train) 15 | y_pred = base_model.predict(X_test)#对训练集进行预测 16 | print(f"决策树的准确率:{accuracy_score(y_test,y_pred):.3f}") 17 | 18 | ## bagging 19 | from sklearn.ensemble import BaggingClassifier 20 | # 建立AdaBoost分类器,每个基本分类模型为前面训练的决策树模型,最大的弱学习器的个数为50 21 | model = BaggingClassifier(base_estimator=base_model, 22 | n_estimators=50, 23 | random_state=1) 24 | model.fit(X_train, y_train)# 训练 25 | y_pred = model.predict(X_test)# 预测 26 | print(f"BaggingClassifier的准确率:{accuracy_score(y_test,y_pred):.3f}") 27 | 28 | 29 | # 测试估计器个数的影响 30 | x = list(range(2, 102, 2)) # 估计器个数即n_estimators,在这里我们取[2,102]的偶数 31 | y = [] 32 | 33 | for i in x: 34 | model = BaggingClassifier(base_estimator=base_model, 35 | n_estimators=i, 36 | 37 | random_state=1) 38 | 39 | model.fit(X_train, y_train) 40 | model_test_sc = accuracy_score(y_test, model.predict(X_test)) 41 | y.append(model_test_sc) 42 | 43 | plt.style.use('ggplot') 44 | plt.title("Effect of n_estimators", pad=20) 45 | plt.xlabel("Number of base estimators") 46 | plt.ylabel("Test accuracy of BaggingClassifier") 47 | plt.plot(x, y) 48 | plt.show() 49 | 50 | ## 随机森林 51 | 52 | from sklearn.ensemble import RandomForestClassifier 53 | model = RandomForestClassifier( 54 | n_estimators=50, 55 | random_state=1) 56 | model.fit(X_train, y_train)# 训练 57 | y_pred = model.predict(X_test)# 预测 58 | print(f"RandomForestClassifier的准确率:{accuracy_score(y_test,y_pred):.3f}") 59 | 60 | x = list(range(2, 102, 2)) # 估计器个数即n_estimators,在这里我们取[2,102]的偶数 61 | y = [] 62 | 63 | for i in x: 64 | model = RandomForestClassifier( 65 | n_estimators=i, 66 | 67 | random_state=1) 68 | 69 | model.fit(X_train, y_train) 70 | model_test_sc = accuracy_score(y_test, model.predict(X_test)) 71 | y.append(model_test_sc) 72 | 73 | plt.style.use('ggplot') 74 | plt.title("Effect of n_estimators", pad=20) 75 | plt.xlabel("Number of base estimators") 76 | plt.ylabel("Test accuracy of RandomForestClassifier") 77 | plt.plot(x, y) 78 | plt.show() -------------------------------------------------------------------------------- /ml-with-sklearn/08-AdaBoost/AdaBoost.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 14, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from sklearn.tree import DecisionTreeClassifier\n", 10 | "from sklearn.metrics import accuracy_score" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 19, 16 | "metadata": {}, 17 | "outputs": [ 18 | { 19 | "name": "stdout", 20 | "output_type": "stream", 21 | "text": [ 22 | "决策树的准确率:0.694\n" 23 | ] 24 | } 25 | ], 26 | "source": [ 27 | "#构建并训练决策树分类器,这里特征选择标准使用基尼指数,树的最大深度为1\n", 28 | "base_model = DecisionTreeClassifier(max_depth=1, criterion='gini',random_state=1).fit(X_train, y_train)\n", 29 | "y_pred = base_model.predict(X_test)#对训练集进行预测\n", 30 | "print(f\"决策树的准确率:{accuracy_score(y_test,y_pred):.3f}\")" 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": 23, 36 | "metadata": {}, 37 | "outputs": [ 38 | { 39 | "name": "stdout", 40 | "output_type": "stream", 41 | "text": [ 42 | "所有特征:['alcohol', 'malic_acid', 'ash', 'alcalinity_of_ash', 'magnesium', 'total_phenols', 'flavanoids', 'nonflavanoid_phenols', 'proanthocyanins', 'color_intensity', 'hue', 'od280/od315_of_diluted_wines', 'proline']\n", 43 | "训练数据量:142,测试数据量:36\n", 44 | "准确率:0.97\n" 45 | ] 46 | } 47 | ], 48 | "source": [ 49 | "from sklearn.ensemble import AdaBoostClassifier\n", 50 | "from sklearn.model_selection import GridSearchCV\n", 51 | "from sklearn.datasets import load_wine\n", 52 | "from sklearn.model_selection import train_test_split\n", 53 | "from sklearn import metrics\n", 54 | "import pandas as pd\n", 55 | "\n", 56 | "wine = load_wine()#使用葡萄酒数据集\n", 57 | "print(f\"所有特征:{wine.feature_names}\")\n", 58 | "X = pd.DataFrame(wine.data, columns=wine.feature_names)\n", 59 | "y = pd.Series(wine.target)\n", 60 | "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=1)\n", 61 | "\n", 62 | "\n", 63 | "print(f\"训练数据量:{len(X_train)},测试数据量:{len(X_test)}\")\n", 64 | "\n", 65 | "# 定义模型,这里最大分类器数量为50,学习率为1.5\n", 66 | "model = AdaBoostClassifier(base_estimator=base_model,n_estimators=50,learning_rate=0.8)\n", 67 | "# 训练\n", 68 | "model.fit(X_train, y_train) \n", 69 | "# 预测\n", 70 | "y_pred = model.predict(X_test) \n", 71 | "acc = metrics.accuracy_score(y_test, y_pred) # 准确率\n", 72 | "print(f\"准确率:{acc:.2}\")\n" 73 | ] 74 | }, 75 | { 76 | "cell_type": "markdown", 77 | "metadata": {}, 78 | "source": [ 79 | "## 使用GridSearchCV自动调参" 80 | ] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "execution_count": 24, 85 | "metadata": {}, 86 | "outputs": [ 87 | { 88 | "name": "stdout", 89 | "output_type": "stream", 90 | "text": [ 91 | "最优超参数: {'learning_rate': 0.8, 'n_estimators': 42}\n" 92 | ] 93 | } 94 | ], 95 | "source": [ 96 | "hyperparameter_space = {'n_estimators':list(range(2, 102, 2)), \n", 97 | " 'learning_rate':[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]}\n", 98 | "\n", 99 | "\n", 100 | "# 使用准确率为标准,将得到的准确率最高的参数输出,cv=5表示交叉验证参数,这里使用五折交叉验证,n_jobs=-1表示并行数和cpu一致\n", 101 | "gs = GridSearchCV(AdaBoostClassifier(\n", 102 | " algorithm='SAMME.R',\n", 103 | " random_state=1),\n", 104 | " param_grid=hyperparameter_space, \n", 105 | " scoring=\"accuracy\", n_jobs=-1, cv=5)\n", 106 | "\n", 107 | "gs.fit(X_train, y_train)\n", 108 | "print(\"最优超参数:\", gs.best_params_)" 109 | ] 110 | }, 111 | { 112 | "cell_type": "code", 113 | "execution_count": null, 114 | "metadata": {}, 115 | "outputs": [], 116 | "source": [] 117 | } 118 | ], 119 | "metadata": { 120 | "interpreter": { 121 | "hash": "47dfafb046f9703cd15ca753999a7e7c95274099825c7bcc45b473d6496cd1b0" 122 | }, 123 | "kernelspec": { 124 | "display_name": "Python 3", 125 | "language": "python", 126 | "name": "python3" 127 | }, 128 | "language_info": { 129 | "codemirror_mode": { 130 | "name": "ipython", 131 | "version": 3 132 | }, 133 | "file_extension": ".py", 134 | "mimetype": "text/x-python", 135 | "name": "python", 136 | "nbconvert_exporter": "python", 137 | "pygments_lexer": "ipython3", 138 | "version": "3.8.3" 139 | }, 140 | "toc": { 141 | "base_numbering": 1, 142 | "nav_menu": {}, 143 | "number_sections": true, 144 | "sideBar": true, 145 | "skip_h1_title": false, 146 | "title_cell": "Table of Contents", 147 | "title_sidebar": "Contents", 148 | "toc_cell": false, 149 | "toc_position": {}, 150 | "toc_section_display": true, 151 | "toc_window_display": false 152 | }, 153 | "varInspector": { 154 | "cols": { 155 | "lenName": 16, 156 | "lenType": 16, 157 | "lenVar": 40 158 | }, 159 | "kernels_config": { 160 | "python": { 161 | "delete_cmd_postfix": "", 162 | "delete_cmd_prefix": "del ", 163 | "library": "var_list.py", 164 | "varRefreshCmd": "print(var_dic_list())" 165 | }, 166 | "r": { 167 | "delete_cmd_postfix": ") ", 168 | "delete_cmd_prefix": "rm(", 169 | "library": "var_list.r", 170 | "varRefreshCmd": "cat(var_dic_list()) " 171 | } 172 | }, 173 | "types_to_exclude": [ 174 | "module", 175 | "function", 176 | "builtin_function_or_method", 177 | "instance", 178 | "_Feature" 179 | ], 180 | "window_display": false 181 | } 182 | }, 183 | "nbformat": 4, 184 | "nbformat_minor": 2 185 | } 186 | -------------------------------------------------------------------------------- /ml-with-sklearn/08-AdaBoost/AdaBoost.py: -------------------------------------------------------------------------------- 1 | from sklearn.ensemble import AdaBoostClassifier 2 | from sklearn import datasets 3 | from sklearn.model_selection import train_test_split 4 | from sklearn import metrics 5 | 6 | iris = datasets.load_iris() 7 | X = iris.data 8 | y = iris.target 9 | 10 | X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2) 11 | 12 | print("X_train:",len(X_train),"; X_test:",len(X_test),"; y_train:",len(y_train),"; y_test:",len(y_test)) 13 | 14 | # Create adaboost object 15 | Adbc = AdaBoostClassifier(n_estimators=50, 16 | learning_rate=1.5) 17 | # Train Adaboost 18 | model = Adbc.fit(X_train, y_train) 19 | 20 | #Predict the response for test dataset 21 | y_pred = model.predict(X_test) 22 | 23 | print("Accuracy:",metrics.accuracy_score(y_test, y_pred)) 24 | #('Accuracy:', 0.8888888888888888) -------------------------------------------------------------------------------- /ml-with-sklearn/08-AdaBoost/AdaBoost算法.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# AdaBoost" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "boosting是一族将弱学习器提升为强学习器的算法。这族算法的工作机制是:先从初试训练集训练出一个基学习器,再根据基学习器的表现对训练样本进行调整,使得先前基学习器做错的训练样本在后续受到更多关注,然后基于调整后的样本分布来训练下一个基分类器,如此重复进行,直至基学习器数目达到实现指定值T,最后将这T个基学习器进行加权组合" 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "boosting族算法最著名的代表室AdaBoost,AdaBoost算法有多种推导方式,比较容易理解的是基于“加性模型”,即基学习器的线性组合$$H(x)=\\displaystyle\\sum_{t=1}^T \\alpha_ih_t(x)$$" 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "metadata": {}, 27 | "source": [ 28 | "来最小化指数损失函数\n", 29 | " $$l_{exp}(H|D)=E_{X D}[e^{-f(x)H(x)}]$$" 30 | ] 31 | }, 32 | { 33 | "cell_type": "markdown", 34 | "metadata": {}, 35 | "source": [ 36 | "算法步骤:\n", 37 | "输入:训练集 $D={(x_1,y_1),(x_2,y_2),..,(x_m,y_m)}$,\n", 38 | "\n", 39 | " 基学习算法$\\xi$\n", 40 | " \n", 41 | " 训练轮次T\n", 42 | " \n", 43 | "过程:\n", 44 | "\n", 45 | "1.D_1(X)=1/m\n", 46 | "\n", 47 | "2.for t =1,2,3...,T do:\n", 48 | "\n", 49 | "3. $h_t=\\xi(D,D_t)$\n", 50 | "4. $\\epsilon_t = P_{X~D_t}(h_t(x) ≠ f(x))$\n", 51 | "5. if $\\epsilon_t>0.5 then break$\n", 52 | "6. $\\alpha_t = \\frac{1}{2}ln(\\frac{1-\\epsilon_t}{\\epsilon_t})$\n", 53 | "7. $D_{t+1}(x)=\\frac{D_t(x)exp(-\\alpha f(x)h_t(x))}{Z_t}$\n", 54 | "8.end for\n", 55 | "\n", 56 | "输出:$H(x) = sign(\\displaystyle\\sum_{t=1}^T\\alpha_ih_t(x))$" 57 | ] 58 | }, 59 | { 60 | "cell_type": "markdown", 61 | "metadata": {}, 62 | "source": [ 63 | "\n", 64 | "其中$\\epsilon_{i}$是每个弱分类器的错误率。$Z_t$是规范化因子,以确保$D_{t+1}$是一个分布\n", 65 | "\n" 66 | ] 67 | } 68 | ], 69 | "metadata": { 70 | "kernelspec": { 71 | "display_name": "Python 3", 72 | "language": "python", 73 | "name": "python3" 74 | }, 75 | "language_info": { 76 | "codemirror_mode": { 77 | "name": "ipython", 78 | "version": 3 79 | }, 80 | "file_extension": ".py", 81 | "mimetype": "text/x-python", 82 | "name": "python", 83 | "nbconvert_exporter": "python", 84 | "pygments_lexer": "ipython3", 85 | "version": "3.8.3" 86 | }, 87 | "toc": { 88 | "base_numbering": 1, 89 | "nav_menu": {}, 90 | "number_sections": true, 91 | "sideBar": true, 92 | "skip_h1_title": false, 93 | "title_cell": "Table of Contents", 94 | "title_sidebar": "Contents", 95 | "toc_cell": false, 96 | "toc_position": {}, 97 | "toc_section_display": true, 98 | "toc_window_display": false 99 | }, 100 | "varInspector": { 101 | "cols": { 102 | "lenName": 16, 103 | "lenType": 16, 104 | "lenVar": 40 105 | }, 106 | "kernels_config": { 107 | "python": { 108 | "delete_cmd_postfix": "", 109 | "delete_cmd_prefix": "del ", 110 | "library": "var_list.py", 111 | "varRefreshCmd": "print(var_dic_list())" 112 | }, 113 | "r": { 114 | "delete_cmd_postfix": ") ", 115 | "delete_cmd_prefix": "rm(", 116 | "library": "var_list.r", 117 | "varRefreshCmd": "cat(var_dic_list()) " 118 | } 119 | }, 120 | "types_to_exclude": [ 121 | "module", 122 | "function", 123 | "builtin_function_or_method", 124 | "instance", 125 | "_Feature" 126 | ], 127 | "window_display": false 128 | } 129 | }, 130 | "nbformat": 4, 131 | "nbformat_minor": 5 132 | } 133 | -------------------------------------------------------------------------------- /ml-with-sklearn/08-AdaBoost/README.md: -------------------------------------------------------------------------------- 1 | Adaboost[Freund and Schapire, 1997]是一种boosting算法。与boosting算法相似的有bagging,两者同属于ensemble learning。 2 | 3 | 把准确率不高但在50%以上(比如60%)的分类器称为弱分类器,弱分类器可以是一些基础的ML算法比如单层决策树等。对于同一数据可能有多种弱分类器,adaboost就是将这些弱分类器结合起来,称为基分类器,从而有效提高整体的准确率。但是要想获得好的结合或者集成,对基分类器的要求是“好而不同”,即有一定的准确率,而且弱分类器之间要有“多样性”,比如一个判断是否为男性的任务,弱分类器1侧重从鼻子、耳朵这些特征判断是否是男人,分类器2侧重脸和眼睛等等,把这些分类器结合起来就有了所有用来判断是否男性的特征,并且adaboost还可以给每个基分类器赋值不同的权重,比如从脸比鼻子更能判断是否为男性,就可以把分类器2的权重调高一些,这也是adaboost需要学习的内容。 4 | 5 | ## 数学推导 6 | 7 | AdaBoost可以表示为基分类器的线性组合: 8 | $$ 9 | H(\boldsymbol{x})=\sum_{i=1}^{N} \alpha_{i} h_{i}(\boldsymbol{x}) 10 | $$ 11 | 其中$h_i(x),i=1,2,...$表示基分类器,$\alpha_i$是每个基分类器对应的权重,表示如下: 12 | $$ 13 | \alpha_{i}=\frac{1}{2} \ln \left(\frac{1-\epsilon_{i}}{\epsilon_{i}}\right) 14 | $$ 15 | 其中$\epsilon_{i}$是每个弱分类器的错误率。 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ml-with-sklearn/09-K-means/k-means.md: -------------------------------------------------------------------------------- 1 | ### 1.聚类的概念 2 | 3 | 对于有标签的数据,我们进行有监督学习,常见的分类任务就是监督学习;而对于无标签的数据,我们希望发现无标签的数据中的潜在信息,这就是无监督学习。聚类,就是无监督学习的一种,它的概念是:将相似的对象归到同一个簇中,使得同一个簇内的数据对象的相似性尽可能大,同时不在同一个簇中的数据对象的差异性也尽可能地大。即聚类后同一类的数据尽可能聚集到一起,不同数据尽量分离。 4 | 5 | ### 2.聚类算法的分类 6 | 7 | 聚类算法有很多种分法,常见的分类方法有: 8 | 9 | 1. 基于划分的聚类:聚类目标是使得类内的点足够近,类间的点足够远,常见的如k-means及其衍生算法 10 | 2. 基于密度的聚类:当邻近区域的密度超过某个阈值,则继续聚类,如DBSCAN; OPTICS 11 | 3. 层次聚类:包括合并的层次聚类,分裂的层次聚类,实际上可以看作是二叉树的生成和分裂过程。 12 | 4. 基于图的聚类: 通过建图来进行聚类,这是聚类算法中的大头,很多较新的聚类算法都有图聚类的思想。 13 | 14 | 更多的分类可以参考[sklearn文档中关于聚类的划分](https://scikit-learn.org/stable/modules/clustering.html#k-means) 15 | 16 | ![Overview of clustering methods](./res/k-means-1.png) 17 | 18 | ### 3.性能度量 19 | 在机器学习中我们都需要对任务进行评价以便于进行下一步的优化,聚类的性能度量主要有一下两种。 20 | * 外部指标:是指把算法得到的划分结果跟某个外部的“参考模型”(如专家给出的划分结果)比较。其实质就是分析分错了和分对了的比例来衡量聚类效果 21 | * 内部指标:是指直接考察聚类结果,不利用任何参考模型的指标。例如轮廓系数:衡量了每个簇中的紧凑度,以及簇间的距离。当每个簇中距离越紧凑,每个簇间距离越远则认为聚类效果优秀。 22 | 23 | ### 4.距离计算 24 | 在机器学习和数据挖掘中,我们经常需要知道个体间差异的大小,进而评价个体的相似性和类别。 25 | * 欧式距离(2-norm距离):欧氏距离是最易于理解的一种距离计算方法,源自欧氏空间中两点间的距离公式。 26 | $$d(x,y)=\sqrt{\Sigma_{k=1}^n (x_k-y_k)^2}$$ 27 | * 曼哈顿距离(Manhattan distance, 1-norm距离):曼哈顿距离也称为街区距离,是数学中比较常用的距离度量之一,用以表示标准坐标系下两点之间的轴距和。计算公式如下: 28 | $$d(x,y)=\Sigma_{k=1}^n \left|x_k-y_k\right|$$ 29 | * 切比雪夫距离:切比雪夫距离和曼哈顿距离类似,但其采用的是两点之间的最大轴距。 30 | $$d(x,y) = \lim_{n\rightarrow \infty} (\Sigma_{k=1}^n (\left|x_k-y_k\right|)^r)^\dfrac{1}{r} = max_k (\left|x_k-y_k\right|)$$ 31 | * 闵可夫斯基距离 32 | $$d(x,y)=(\Sigma_{k=1}^n (\left|x_k-y_k\right|)^r)^\dfrac{1}{r}$$ 33 | 式中,r是一个可变参数,根据参数r取值的不同,闵可夫斯基距离可以表示一类距离 34 |   r = 1时,为曼哈顿距离 35 |   r = 2时,为欧式距离 36 |   r →∞时,为切比雪夫距离 37 | 闵可夫斯基距离包括欧式距离、曼哈顿距离、切比雪夫距离都假设数据各维属性的量纲和分布(期望、方差)相同,因此适用于度量独立同分布的数据对象。 38 | * 余弦相似性:余弦相似度公式定义如下: 39 | $$cos⁡(x,y)=\dfrac{xy}{\left|x\right|\left|y\right|} = \dfrac{\Sigma_{k=1}^n x_k y_k}{\sqrt{\Sigma_{k=1}^n x_k^2} \sqrt{\Sigma_{k=1}^n y_k^2}}$$ 40 | 余弦相似度实际上是向量x和y夹角的余弦度量,可用来衡量两个向量方向的差异。如果余弦相似度为1,则x和y之间夹角为0°,两向量除模外可认为是相同的;如果预先相似度为0,则x和y之间夹角为90°,则认为两向量完全不同。在计算余弦距离时,将向量均规范化成具有长度11,因此不用考虑两个数据对象的量值。 41 | 余弦相似度常用来度量文本之间的相似性。文档可以用向量表示,向量的每个属性代表一个特定的词或术语在文档中出现的频率,尽管文档具有大量的属性,但每个文档向量都是稀疏的,具有相对较少的非零属性值。 42 | * 马氏距离 43 | $$mahalanobis(x,y)=(x-y)\Sigma^{-1}(x-y)^T$$ 44 | 式中,Σ−1是数据协方差矩阵的逆。 45 | 前面的距离度量方法大都假设样本独立同分布、数据属性之间不相关。马氏距离考虑了数据属性之间的相关性,排除了属性间相关性的干扰,而且与量纲无关。若协方差矩阵是对角阵,则马氏距离变成了标准欧式距离;若协方差矩阵是单位矩阵,各个样本向量之间独立同分布,则变成欧式距离。 46 | 47 | ### 5.K-means 48 | **本次只介绍这一种算法,剩下的大家可以参考前面的sklearn文档实现。** 49 | 50 | 对于K-Means算法,首先要注意的是k值的选择,一般来说,我们会根据对数据的先验经验选择一个合适的k值,如果没有什么先验知识,则可以通 过交叉验证选择一个合适的k值。在确定了k的个数后,我们需要选择k个初始化的质心。由于我们是启发式方法,k个初始化的质心的位置选择对最后的聚 类结果和运行时间都有很大的影响,因此需要选择合适的k个质心,最好这些质心不能太近。 51 | 52 | 总结下传统的K-Means算法流程就是: 53 | 54 | - 输入是样本隻 $D=\left\{x_{1}, x_{2}, \ldots x_{m}\right\}$, 聚类的笶树 $\mathrm{k}$, 最大迭代次数 $\mathrm{N}$ 55 | - 输出是笶划分 $C=\left\{C_{1}, C_{2}, \ldots C_{k}\right\}$ 56 | 1. 从数据集 $\mathrm{D}$ 中随机选择 $\mathrm{k}$ 个样本作为初始的 $\mathrm{k}$ 个质心向量: $\left\{\mu_{1}, \mu_{2}, \ldots, \mu_{k}\right\}$ 57 | 2. 对于 $n=1,2, \ldots, \mathrm{N}$ 58 | 1. 将簇划分 $\mathrm{C}$ 初始化为 $C_{t}=\varnothing, t=1,2 \ldots k$ 59 | 2. 对于 $\mathrm{i}=1,2 \ldots \mathrm{m}$, 计算样本 $x_{i}$ 和各个质心向量 $\mu_{j}(j=1,2, \ldots k)$ 的距离: $d_{i j}=\left\|x_{i}-\mu_{j}\right\|_{2}^{2}$ , 将 $x_{i}$ 标记最小的为 $d_{i j}$ 所对应的类别 $\lambda_{i}$ 。此时更 新 $C_{\lambda_{i}}=C_{\lambda_{i}} \cup\left\{x_{i}\right\}$ 60 | 3. 对于 $\mathrm{j}=1,2, \ldots, \mathrm{k}$, 对 $C_{j}$ 中所有的样本点重新计算新的质心$\mu_{j}=\frac{1}{\left|C_{j}\right|} \sum_{x \in C_{j}} x$ 61 | 4. 如果所有的 $k$ 个质心向量都没有发生变化,则转到步骧3 62 | 3. 输出笶划分 $C=\left\{C_{1}, C_{2}, \ldots C_{k}\right\}$ 63 | 64 | **例如下图所示** 65 | 66 | ![](./res/k-means-2.webp) 67 | 68 | 69 | 下面是使用sklearn的一个简单demo 70 | 71 | ### 6.K-means的优缺点 72 | 73 | - **优点** 74 | - 原理比较简单,实现也是很容易,收敛速度快。 75 | - 聚类效果较优。 76 | - 算法的可解释度比较强。 77 | - 主要需要调参的参数仅仅是簇数k。 78 | 79 | - **缺点** 80 | - K值的选取不好把握 81 | - 最终结果和初始点的选择有关,容易陷入局部最优。 82 | - 对噪音和异常点比较的敏感。 83 | - 数据必须符合“数据之间的相似度可以使用欧式距离衡量”,这个是什么意思呢,看下图,这种数据的分布,样本点的距离不能简单地用欧式距离来衡量,否则分类效果会非常差。这里的距离衡量应该是“测地距离”,也就是样本沿着曲面到达另一个样本点的距离。如果在这种数据空间想要使用kmeans,必须先进行空间的转化 84 | 85 | ![](./res/k-means-3.jpg) 86 | 87 | k-means有一些改进算法,多是针对k-means会受异常点的影响这一点来改进的,这里就不详细赘述,只是简单提一下,起一个抛砖引玉的作用。 88 | | 缺点 | 改进 | 描述 | 89 | | -------------- | --------- | ------------------------------------------------------------ | 90 | | k值的确定 | ISODATA | 当属于某个簇的样本数过少时把这个簇去除,
当属于某个簇的样本数过多、分散程度较大时把这个簇分为两个子簇| 91 | | 对奇异点敏感 | k-median | 中位数代替平均值作为簇中心 | 92 | | 只能找到球状群 | GMM | 以高斯分布考虑簇内数据点的分布 | 93 | | 分群结果不稳定 | K-means++ | 初始的聚类中心之间的相互距离要尽可能的远 94 | 95 | 96 | 详细见:https://scikit-learn.org/stable/modules/clustering.html#overview-of-clustering-methods 97 | 98 | ![](./res/k-means-4.png) 99 | 100 | ### 6、代码实践 101 | 102 | - sklearn:[k-means.ipynb](./k-means.ipynb) 103 | - numpy:[k-means.py](k-means.py) 104 | 105 | ### 8.常见面试题 106 | 107 | **8.1 k值的选取** 108 | 109 | K-means算法要求事先知道数据集能分为几群,主要有两种方法定义k。 110 | 111 | - 手肘法:通过绘制k和损失函数的关系图,选拐点处的k值。 112 | 113 | - 经验选取人工据经验先定几个k,多次随机初始化中心选经验上最适合的。 114 | 115 | 通常都是以经验选取,因为实际操作中拐点不明显,且手肘法效率不高。 116 | 117 | **8.2 为什么在计算K-means之前要将数据点在各维度上归一化** 118 | 119 | 因为数据点各维度的量级不同。 120 | 举个例子,最近正好做完基于RFM模型的会员分群,每个会员分别有R(最近一次购买距今的时长)、F(来店消费的频率)和M(购买金额)。如果这是一家奢侈品商店,你会发现M的量级(可能几万元)远大于F(可能平均10次以下),如果不归一化就算K-means,相当于F这个特征完全无效。如果我希望能把常客与其他顾客区别开来,不归一化就做不到。 121 | 122 | **8.3 K-means不适用哪些数据** 123 | 124 | 1. 数据特征极强相关的数据集,因为会很难收敛(损失函数是非凸函数),一般要用kernal K-means,将数据点映射到更高维度再分群。 125 | 2. 数据集可分出来的簇密度不一,或有很多离群值(outliers),这时候考虑使用密度聚类。 126 | 127 | 128 | **8.4 K-means中空聚类的处理** 129 | 130 | 如果所有的点在指派步骤都未分配到某个簇,就会得到空簇。如果这种情况发生,则需要某种策略来选择一个替补质心,否则的话,平方误差将会偏大。一种方法是选择一个距离当前任何质心最远的点。这将消除当前对总平方误差影响最大的点。另一种方法是从具有最大SEE的簇中选择一个替补的质心。这将分裂簇并降低聚类的总SEE。如果有多个空簇,则该过程重复多次。另外编程实现时,要注意空簇可能导致的程序bug。 131 | 132 | **参考文献** 133 | - 西瓜书 134 | - 统计学习方法 135 | - 维基百科 136 | - https://www.zybuluo.com/rianusr/note/1199877 137 | - https://blog.csdn.net/zhouxianen1987/article/details/68945844 138 | - http://ddrv.cn/a/66611 139 | - https://zhuanlan.zhihu.com/p/29538307 140 | - https://github.com/Eajack/Statical-Learning-Method-LiHang_py_Eajack 141 | - https://github.com/datawhalechina/daily-interview 142 | - https://segmentfault.com/a/1190000041528708 -------------------------------------------------------------------------------- /ml-with-sklearn/09-K-means/k-means.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-sklearn/09-K-means/k-means.pdf -------------------------------------------------------------------------------- /ml-with-sklearn/09-K-means/k-means.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | #Author:haobo 3 | #Date:2022-4-23 4 | 5 | from sklearn.cluster import KMeans 6 | import matplotlib.pyplot as plt 7 | import numpy as np 8 | import matplotlib as mpl 9 | from sklearn import datasets 10 | 11 | 12 | # 聚类前 13 | X = np.random.rand(100, 2) 14 | plt.scatter(X[:, 0], X[:, 1], marker='o') 15 | 16 | # 初始化我们的质心,从原有的数据中选取K个座位质心 17 | def InitCentroids(X, k): 18 | index = np.random.randint(0,len(X)-1,k) 19 | return X[index] 20 | 21 | #聚类后,假设k=2 22 | kmeans = KMeans(n_clusters=2).fit(X) 23 | label_pred = kmeans.labels_ 24 | plt.scatter(X[:, 0], X[:, 1], c=label_pred) 25 | plt.show() -------------------------------------------------------------------------------- /ml-with-sklearn/09-K-means/res/k-means-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-sklearn/09-K-means/res/k-means-1.png -------------------------------------------------------------------------------- /ml-with-sklearn/09-K-means/res/k-means-2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-sklearn/09-K-means/res/k-means-2.webp -------------------------------------------------------------------------------- /ml-with-sklearn/09-K-means/res/k-means-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-sklearn/09-K-means/res/k-means-3.jpg -------------------------------------------------------------------------------- /ml-with-sklearn/09-K-means/res/k-means-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-sklearn/09-K-means/res/k-means-4.png -------------------------------------------------------------------------------- /ml-with-sklearn/10-kNN/knn.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "### 3.算法实践(sklearn)\n", 8 | "\n", 9 | "#### 3.1 KNeighborsClassifier 类\n", 10 | "\n", 11 | "sklearn 库的 neighbors 模块实现了KNN 相关算法,其中:\n", 12 | "- `KNeighborsClassifier` 类用于分类问题\n", 13 | "- `KNeighborsRegressor` 类用于回归问题\n", 14 | "\n", 15 | "这两个类的构造方法基本一致,这里我们主要介绍 KNeighborsClassifier 类,原型如下:\n", 16 | "\n", 17 | "```python\n", 18 | "KNeighborsClassifier(\n", 19 | "\tn_neighbors=5, \n", 20 | "\tweights='uniform', \n", 21 | "\talgorithm='auto', \n", 22 | "\tleaf_size=30, \n", 23 | "\tp=2, \n", 24 | "\tmetric='minkowski', \n", 25 | "\tmetric_params=None, \n", 26 | "\tn_jobs=None, \n", 27 | "\t**kwargs)\n", 28 | "```\n", 29 | "\n", 30 | "**来看下几个重要参数的含义:**\n", 31 | "- n_neighbors:即 KNN 中的 K 值,一般使用默认值 5。\n", 32 | "- weights:用于确定邻居的权重,有三种方式:\n", 33 | " - weights=uniform,表示所有邻居的权重相同。\n", 34 | " - weights=distance,表示权重是距离的倒数,即与距离成反比。\n", 35 | " - 自定义函数,可以自定义不同距离所对应的权重,一般不需要自己定义函数。\n", 36 | "- algorithm:用于设置计算邻居的算法,它有四种方式:\n", 37 | " - algorithm=auto,根据数据的情况自动选择适合的算法。\n", 38 | " - algorithm=kd_tree,使用 KD 树 算法。\n", 39 | " - KD 树是一种多维空间的数据结构,方便对数据进行检索。\n", 40 | " - KD 树适用于维度较少的情况,一般维数不超过 20,如果维数大于 20 之后,效率会下降。\n", 41 | " - algorithm=ball_tree,使用球树算法。\n", 42 | " - 与KD 树一样都是多维空间的数据结构。\n", 43 | " - 球树更适用于维度较大的情况。\n", 44 | " - algorithm=brute,称为暴力搜索。\n", 45 | " - 它和 KD 树相比,采用的是线性扫描,而不是通过构造树结构进行快速检索。\n", 46 | " - 缺点是,当训练集较大的时候,效率很低。\n", 47 | " - leaf_size:表示构造 KD 树或球树时的叶子节点数,默认是 30。\n", 48 | " 调整 leaf_size 会影响树的构造和搜索速度。\n" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": 2, 54 | "metadata": {}, 55 | "outputs": [], 56 | "source": [ 57 | "#加载数据集\n", 58 | "from sklearn.datasets import load_digits\n", 59 | "digits = load_digits()\n", 60 | "data = digits.data # 特征集\n", 61 | "target = digits.target # 目标集\n" 62 | ] 63 | }, 64 | { 65 | "cell_type": "code", 66 | "execution_count": 3, 67 | "metadata": {}, 68 | "outputs": [], 69 | "source": [ 70 | "#将数据集拆分为训练集(75%)和测试集(25%):\n", 71 | "from sklearn.model_selection import train_test_split\n", 72 | "train_x, test_x, train_y, test_y = train_test_split(\n", 73 | " data, target, test_size=0.25, random_state=33)" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": 4, 79 | "metadata": {}, 80 | "outputs": [], 81 | "source": [ 82 | "#构造KNN分类器:\n", 83 | "from sklearn.neighbors import KNeighborsClassifier\n", 84 | "# 采用默认参数\n", 85 | "knn = KNeighborsClassifier() " 86 | ] 87 | }, 88 | { 89 | "cell_type": "code", 90 | "execution_count": 7, 91 | "metadata": {}, 92 | "outputs": [], 93 | "source": [ 94 | "#拟合模型:\n", 95 | "knn.fit(train_x, train_y) \n", 96 | "\n", 97 | "#预测数据:\n", 98 | "predict_y = knn.predict(test_x) " 99 | ] 100 | }, 101 | { 102 | "cell_type": "code", 103 | "execution_count": 8, 104 | "metadata": {}, 105 | "outputs": [ 106 | { 107 | "data": { 108 | "text/plain": [ 109 | "0.9844444444444445" 110 | ] 111 | }, 112 | "execution_count": 8, 113 | "metadata": {}, 114 | "output_type": "execute_result" 115 | } 116 | ], 117 | "source": [ 118 | "#计算模型准确度\n", 119 | "from sklearn.metrics import accuracy_score\n", 120 | "score = accuracy_score(test_y, predict_y)\n", 121 | "score" 122 | ] 123 | } 124 | ], 125 | "metadata": { 126 | "interpreter": { 127 | "hash": "5f04dd1d6b72ff9d002f9c97d3bf130820120c0ac9ec7321437503cd785f0e6e" 128 | }, 129 | "kernelspec": { 130 | "display_name": "Python 3.9.7 ('base')", 131 | "language": "python", 132 | "name": "python3" 133 | }, 134 | "language_info": { 135 | "codemirror_mode": { 136 | "name": "ipython", 137 | "version": 3 138 | }, 139 | "file_extension": ".py", 140 | "mimetype": "text/x-python", 141 | "name": "python", 142 | "nbconvert_exporter": "python", 143 | "pygments_lexer": "ipython3", 144 | "version": "3.9.7" 145 | }, 146 | "orig_nbformat": 4 147 | }, 148 | "nbformat": 4, 149 | "nbformat_minor": 2 150 | } 151 | -------------------------------------------------------------------------------- /ml-with-sklearn/10-kNN/knn.md: -------------------------------------------------------------------------------- 1 | ### 1.KNN算法简介 2 | 3 | KNN的全称是K Nearest Neighbors,意思是K个最近的邻居,可以用于分类和回归,是一种监督学习算法。它的思路是这样,如果一个样本在特征空间中的K个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。也就是说,该方法在定类决策上只依据最邻近的一个或者几个样本的类别来决定待分样本所属的类别。 4 | 5 | 以下图为例 6 | 7 | ![](./res/knn-1.png) 8 | 9 | 图中绿色的点就是我们要预测的那个点,假设K=3。那么KNN算法就会找到与它距离最近的三个点(这里用圆圈把它圈起来了),看看哪种类别多一些,比如这个例子中是蓝色三角形多一些,新来的绿色点就归类到蓝三角了。 10 | 11 | ![](./res/knn-2.png) 12 | 13 | 但是,当K=5的时候,判定就变成不一样了。这次变成红圆多一些,所以新来的绿点被归类成红圆。从这个例子中,我们就能看得出K的取值是很重要的。 14 | 15 | ### 2.KNN算法介绍 16 | 17 | #### 2.1 KNN算法三要素 18 | 19 | **2.1.1 关于距离的衡量方法**:具体介绍参见K-means介绍 20 | 21 | KNN算法中要求数据的所有特征都可以做量化,若在数据特征中存在非数值类型,必须采用手段将其量化为数值。 22 | 23 | 在sklearn中,KNN分类器提供了四种距离 24 | - 欧式距离(euclidean) - 默认 25 | - 曼哈顿距离(manhatten) 26 | - 切比雪夫距离(chebyshev) 27 | - 闵可夫斯基距离(minkowski) 28 | 29 | **2.1.2 K值的选择问题** 30 | 31 | 在KNN分类中,K值的选择往往没有一个固定的经验,可以通过不停调整(例如交叉验证)到一个合适的K值。 32 | 1. K为1。如果K值被设定为1,那么训练集的正确率将达到100%(将训练集同时作为预测集),因为每个点只会找到它本身,但同时在预测集中的正确率不会太高(极度过拟合)。 33 | 2. K为较小的值。较小的邻域往往会带来更低的训练误差,但会导致过拟合的问题降低预测集的准确率。 34 | 3. K为较大的值。较大的邻域会增大训练误差,但能够有效减少过拟合的问题。(注意,这并不意味着预测集的准确率一定会增加) 35 | 4. K为训练集样本数量。当K极端到邻域覆盖整个样本时,就相当于不再分类而直接选择在训练集中出现最多的类。 36 | 37 | 在实际应用中,K 值一般选择一个较小的数值,通常采用交叉验证的方法来选择最优的 K 值。随着训练实例数目趋向于无穷和 K=1 时,误差率不会超过贝叶斯误差率的2倍,如果K也趋向于无穷,则误差率趋向于贝叶斯误差率。(贝叶斯误差可以理解为最小误差) 38 | 39 | 三种交叉验证方法: 40 | - Hold-Out: 随机从最初的样本中选出部分,形成交叉验证数据,而剩余的就当做训练数据。 一般来说,少于原本样本三分之一的数据被选做验证数据。常识来说,Holdout 验证并非一种交叉验证,因为数据并没有交叉使用。 41 | - K-foldcross-validation:K折交叉验证,初始采样分割成K个子样本,一个单独的子样本被保留作为验证模型的数据,其他K-1个样本用来训练。交叉验证重复K次,每个子样本验证一次,平均K次的结果或者使用其它结合方式,最终得到一个单一估测。这个方法的优势在于,同时重复运用随机产生的子样本进行训练和验证,每次的结果验证一次,10折交叉验证是最常用的。 42 | - Leave-One-Out Cross Validation:正如名称所建议, 留一验证(LOOCV)意指只使用原本样本中的一项来当做验证资料, 而剩余的则留下来当做训练资料。 这个步骤一直持续到每个样本都被当做一次验证资料。 事实上,这等同于 K-fold 交叉验证是一样的,其中K为原本样本个数。 43 | 44 | 45 | **2.1.3 分类决策的准则** 46 | 47 | 明确K个邻居中所有数据类别的个数,将测试数据划分给个数最多的那一类。即由输入实例的 K 个最临近的训练实例中的多数类决定输入实例的类别。 48 | 最常用的两种决策规则: 49 | - 多数表决法:多数表决法和我们日常生活中的投票表决是一样的,少数服从多数,是最常用的一种方法。 50 | - 加权表决法:有些情况下会使用到加权表决法,比如投票的时候裁判投票的权重更大,而一般人的权重较小。所以在数据之间有权重的情况下,一般采用加权表决法。 51 | 52 | 多数表决法图示说明(其中K=4): 53 | 54 | ![](./res/knn-3.jpg) 55 | 56 | #### 2.2 KNN算法的步骤 57 | 58 | 59 | 输入: 训练数据集 $T=\left\{\left(\mathrm{x}_{1}, \mathrm{y}_{1}\right),\left(\mathrm{x}_{2}, \mathrm{y}_{2}\right) \ldots . . .\left(\mathrm{x}_{\mathrm{N}}, \mathrm{y}_{\mathrm{N}}\right)\right\}, \mathrm{x}_{1}$ 为实例的特征向量, $\mathrm{yi}=\left\{\mathrm{c}_{1}, \mathrm{c}_{2}, \ldots \mathrm{c}_{k}\right\}$ 为实剅类别。 60 | 61 | 输出: 实例 $\mathrm{x}$ 所属的类别 $\mathrm{y}$ 。 62 | 63 | **步骤:** 64 | 1. 选择参数 $K$ 65 | 2. 计算末知实例与所有已知实例的距离 (可选择多种计算距离的方式) 66 | 3. 选择最近 $K$ 个已知实例 67 | 4. 根据少数服从多数的投眎法则(majority-voting), 让末知实例归类为 $\mathrm{K}$ 个最邻近样本中最多数的类别。加权表决法同理。 68 | 69 | 70 | #### 2.3 KNN算法的优缺点 71 | 72 | **优点** 73 | - KNN是一种较为成熟的算法,同时思路也比较简单,能够同时兼容回归与分类(KNN的回归将在日后的回归算法中提到)。 74 | - KNN时间复杂度为O(n)。因为是懒惰学习,在训练阶段速度比较快。 75 | - 可以用于非线性分类。 76 | - 未对数据进行任何假设,因此对异常点不敏感。 77 | - 通过近邻而不是通过类域判别,对类域交叉重叠“较多的样本具有较好的预测效果。 78 | 79 | **缺点** 80 | - 在特征较多的情况下,会有很大的计算量。 81 | - 需要存储所有的训练数据,对内存要求高。 82 | - 因为是懒惰学习(在测试样本阶段学习),预测阶段速度比较慢。 83 | - 在样本不平衡时,容易造成误判。 84 | - 对数据规模敏感。在大的训练集中有较高正确率,当规模小的时候正确率低。 85 | 86 | ### 3.算法实践 87 | 88 | - sklearn:[knn.ipynb](./knn.ipynb) 89 | - numpy:[knn.py](./knn.py) 90 | 91 | ### 4.常见面试题 92 | 93 | #### 4.1 为了解决KNN算法计算量过大的问题,可以使用分组的方式进行计算,简述一下该方式的原理。 94 | 95 | 先将样本按距离分解成组,获得质心,然后计算未知样本到各质心的距离,选出距离最近的一组或几组,再在这些组内引用KNN。 96 | 本质上就是事先对已知样本点进行剪辑,事先去除对分类作用不大的样本,该方法比较适用于样本容量比较大时的情况。 97 | 98 | #### 4.2 K-Means与KNN有什么区别 99 | - KNN 100 | + KNN是分类算法 101 | + 监督学习 102 | + 喂给它的数据集是带label的数据,已经是完全正确的数据 103 | + 没有明显的前期训练过程,属于memory-based learning 104 | + K的含义:来了一个样本x,要给它分类,即求出它的y,就从数据集中,在x附近找离它最近的K个数据点,这K个数据点,类别c占的个数最多,就把x的label设为c 105 | 106 | - K-Means 107 | + 1.K-Means是聚类算法 108 | + 2.非监督学习 109 | + 3.喂给它的数据集是无label的数据,是杂乱无章的,经过聚类后才变得有点顺序,先无序,后有序 110 | + 有明显的前期训练过程 111 | + K的含义:K是人工固定好的数字,假设数据集合可以分为K个簇,由于是依靠人工定好,需要一点先验知识 112 | 113 | - 相似点 114 | - 都包含这样的过程,给定一个点,在数据集中找离它最近的点。即二者都用到了NN(Nears Neighbor)算法,一般用KD树来实现NN。 115 | 116 | 117 | #### 4.3 讲解一下用于计算邻居的算法 118 | 119 | - KD 树 120 | - 参考:https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KDTree.html 121 | - 球树 122 | - 参考:https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.BallTree.html 123 | 124 | **参考文献** 125 | - 西瓜书 126 | - 统计学习方法 127 | - 维基百科 128 | - https://www.cnblogs.com/listenfwind/p/10311496.html 129 | - https://blog.csdn.net/weixin_43179522/article/details/105665528 130 | - https://jiang-hs.github.io/post/dIjHdNcg_/ 131 | - https://codeshellme.github.io/2020/11/ml-knn2/ 132 | - https://github.com/datawhalechina/daily-interview/blob/8b29d467997c9ccad1d4796bace87444b4c8c751/AI%E7%AE%97%E6%B3%95/machine-learning/kNN.md 133 | - https://github.com/fengdu78/lihang-code/blob/master/%E7%AC%AC03%E7%AB%A0%20k%E8%BF%91%E9%82%BB%E6%B3%95/3.KNearestNeighbors.ipynb -------------------------------------------------------------------------------- /ml-with-sklearn/10-kNN/knn.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-sklearn/10-kNN/knn.pdf -------------------------------------------------------------------------------- /ml-with-sklearn/10-kNN/knn.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | #Author:haobo 3 | #Date:2022-4-23 4 | 5 | from sklearn.datasets import load_digits 6 | from sklearn.model_selection import train_test_split 7 | from sklearn.neighbors import KNeighborsClassifier 8 | from sklearn.metrics import accuracy_score 9 | 10 | 11 | #加载数据集 12 | digits = load_digits() 13 | data = digits.data # 特征集 14 | target = digits.target # 目标集 15 | 16 | 17 | #将数据集拆分为训练集(75%)和测试集(25%): 18 | train_x, test_x, train_y, test_y = train_test_split( 19 | data, target, test_size=0.25, random_state=33) 20 | 21 | 22 | #构造KNN分类器:采用默认参数 23 | knn = KNeighborsClassifier() 24 | 25 | 26 | #拟合模型: 27 | knn.fit(train_x, train_y) 28 | #预测数据: 29 | predict_y = knn.predict(test_x) 30 | 31 | 32 | #计算模型准确度 33 | score = accuracy_score(test_y, predict_y) 34 | print(score) -------------------------------------------------------------------------------- /ml-with-sklearn/10-kNN/res/knn-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-sklearn/10-kNN/res/knn-1.png -------------------------------------------------------------------------------- /ml-with-sklearn/10-kNN/res/knn-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-sklearn/10-kNN/res/knn-2.png -------------------------------------------------------------------------------- /ml-with-sklearn/10-kNN/res/knn-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datawhalechina/machine-learning-toy-code/9e240b434aa7cf38737c2590527b4d860b2f067c/ml-with-sklearn/10-kNN/res/knn-3.jpg -------------------------------------------------------------------------------- /ml-with-sklearn/11-PCA/PCA.md: -------------------------------------------------------------------------------- 1 | ### 1.降维的基本介绍 2 | 3 | 机器学习领域中所谓的降维就是指采用某种映射方法,将原高维空间中的数据点映射到低维度的空间中。降维的本质是学习一个映射函数 f : x->y,其中x是原始数据点的表达,目前最多使用向量表达形式。 y是数据点映射后的低维向量表达,通常y的维度小于x的维度(当然提高维度也是可以的)。f可能是显式的或隐式的、线性的或非线性的。 4 | 5 | 6 | 关于维度灾难的一个理解角度: 7 | 8 | 假设存在下面这样一个球,D维,其半径为r=1,里面圆环的长度为$\varepsilon$ 9 | 10 | ![](./res/pca-0.png) 11 | 12 | 则我们可以计算得知 13 | 14 | $$ 15 | \begin{aligned} 16 | &V_{\text {外 }}=K \cdot 1^{D}=K\\ 17 | &V_{\text {环 }}=V_{\text {外 }}-V_{\text {内 }}\\ 18 | &=k-k \cdot(1-\varepsilon)^{D}\\ 19 | &\frac{V_{环}}{V_{外}}=\frac{k-k(1-\varepsilon)^{D}}{k}={1-(1-\varepsilon)^{D}} 20 | \end{aligned} 21 | $$ 22 | 23 | $K$为一个常数,由于0<$\varepsilon$<1,因此$\lim _{D^{\rightarrow \infty}}(1-\varepsilon)^{D}=0$,则$\lim _{D \rightarrow \infty} \frac{V_{环}}{V_{外}}=1$。这就是所谓的维度灾难,在高维数据中,主要样本都分布在立方体的边缘,所以数据集更加稀疏。 24 | 25 | 26 | 那从另外一个角度来看:为什么dimension reduction可能是有用的,如上图所示,假设你的data分布是这样的(在3D里面像螺旋的样子),但是用3D空间来描述这些data其实是很浪费的,其实你从资源就可以说:你把这个类似地毯卷起来的东西把它摊开就变成这样(右边的图)。所以你只需要在2D的空间就可以描述这个3D的information,你根本不需要把这个问题放到这个3D来解,这是把问题复杂化,其实你可以在2D就可以做这个task 27 | 28 | ![](../res/pca-1.png) 29 | 30 | 31 | #### 1.1常见降维算法比较 32 | 33 | **降维的算法分为:** 34 | 35 | 1. 直接降维,特征选择 36 | 2. 线性降维,PCA,MDS等 37 | 3. 分线性,流形包括 Isomap,LLE 等 38 | 39 | 40 | ![](./res/pca-2.png) 41 | 42 | 43 | 44 | #### 1.2sklearn中的降维算法 45 | 46 | ![](./res/pca-3.png) 47 | 48 | 49 | **本章节主要介绍一下PCA** 50 | 51 | ### 2. 主成分分析PCA 52 | 53 | 视频讲解参考: 54 | - [李宏毅老师视频](https://www.bilibili.com/video/BV1Ht411g7Ef?p=24):https://www.bilibili.com/video/BV1Ht411g7Ef?p=24 55 | - [白板机器学习](https://www.bilibili.com/video/BV1aE411o7qd?p=22):https://www.bilibili.com/video/BV1aE411o7qd?p=22 56 | 57 | 58 | #### 2.1损失函数 59 | 60 | 我们假设数据集为 61 | $$ 62 | X_{N\times p}=(x_{1},x_{2},\cdots,x_{N})^{T},x_{i}=(x_{i1},x_{i2},\cdots,x_{ip})^{T} 63 | $$ 64 | 这个记号表示有 $N$ 个样本,每个样本都是 $p$ 维向量。其中每个观测都是由 $p(x|\theta)$ 生成的。 65 | 66 | 为了方便,我们首先将协方差矩阵(数据集)写成中心化的形式: 67 | $$ 68 | \begin{aligned} 69 | S &=\frac{1}{N} \sum_{i=1}^{N}\left(x_{i}-\bar{x}\right)\left(x_{i}-\bar{x}\right)^{T} \\ 70 | &=\frac{1}{N}\left(x_{1}-\bar{x}, x_{2}-\bar{x}, \cdots, x_{N}-\bar{x}\right)\left(x_{1}-\bar{x}, x_{2}-\bar{x}, \cdots, x_{N}-\bar{x}\right)^{T} \\ 71 | &=\frac{1}{N}\left(X^{T}-\frac{1}{N} X^{T} \mathbb{I}_{N 1} \mathbb{I}_{N 1}^{T}\right)\left(X^{T}-\frac{1}{N} X^{T} \mathbb{I}_{N 1} \mathbb{I}_{N 1}^{T}\right)^{T} \\ 72 | &=\frac{1}{N} X^{T}\left(E_{N}-\frac{1}{N} \mathbb{I}_{N 1} \mathbb{I}_{1 N}\right)\left(E_{N}-\frac{1}{N} \mathbb{I}_{N 1} \mathbb{I}_{1 N}\right)^{T} X \\ 73 | &=\frac{1}{N} X^{T} H_{N} H_{N}^{T} X \\ 74 | &=\frac{1}{N} X^{T} H_{N} H_{N} X=\frac{1}{N} X^{T} H X 75 | \end{aligned} 76 | $$ 77 | 这个式子利用了中心矩阵 $ H$的对称性,这也是一个投影矩阵。 78 | 79 | 80 | 主成分分析中,我们的基本想法是将所有数据投影到一个字空间中,从而达到降维的目标,为了寻找这个子空间,我们基本想法是: 81 | 82 | 1. 所有数据在子空间中更为分散 83 | 2. 损失的信息最小,即:在补空间的分量少 84 | 85 | 原来的数据很有可能各个维度之间是相关的,于是我们希望找到一组 $p$ 个新的线性无关的单位基 $u_i$,降维就是取其中的 $q$ 个基。于是对于一个样本 $x_i$,经过这个坐标变换后: 86 | $$ 87 | \hat{x_i}=\sum\limits_{i=1}^p(u_i^Tx_i)u_i=\sum\limits_{i=1}^q(u_i^Tx_i)u_i+\sum\limits_{i=q+1}^p(u_i^Tx_i)u_i 88 | $$ 89 | 对于数据集来说,我们首先将其中心化然后再去上面的式子的第一项,并使用其系数的平方平均作为损失函数并最大化: 90 | $$ 91 | \begin{aligned} 92 | J &=\frac{1}{N} \sum_{i=1}^{N} \sum_{j=1}^{q}\left(\left(x_{i}-\bar{x}\right)^{T} u_{j}\right)^{2} \\ 93 | &=\sum_{j=1}^{q} u_{j}^{T} S u_{j}, \text { s.t. } u_{j}^{T} u_{j}=1 94 | \end{aligned} 95 | $$ 96 | 由于每个基都是线性无关的,于是每一个 $u_j$ 的求解可以分别进行,使用拉格朗日乘子法: 97 | $$ 98 | \mathop{argmax}_{u_j}L(u_j,\lambda)=\mathop{argmax}_{u_j}u_j^TSu_j+\lambda(1-u_j^Tu_j) 99 | $$ 100 | 于是: 101 | $$ 102 | Su_j=\lambda u_j 103 | $$ 104 | 可见,我们需要的基就是协方差矩阵的本征矢。损失函数最大取在本征值前 $q$ 个最大值。 105 | 106 | 下面看其损失的信息最少这个条件,同样适用系数的平方平均作为损失函数,并最小化: 107 | $$ 108 | \begin{aligned} 109 | J &=\frac{1}{N} \sum_{i=1}^{N} \sum_{j=q+1}^{p}\left(\left(x_{i}-\bar{x}\right)^{T} u_{j}\right)^{2} \\ 110 | &=\sum_{j=q+1}^{p} u_{j}^{T} S u_{j}, \text { s.t. } u_{j}^{T} u_{j}=1 111 | \end{aligned} 112 | $$ 113 | 同样的: 114 | $$ 115 | \mathop{argmin}_{u_j}L(u_j,\lambda)=\mathop{argmin}_{u_j}u_j^TSu_j+\lambda(1-u_j^Tu_j) 116 | $$ 117 | 损失函数最小取在本征值剩下的个最小的几个值。数据集的协方差矩阵可以写成 $S=U\Lambda U^T$,直接对这个表达式当然可以得到本征矢。 118 | 119 | ![](./res/pca-4.gif) 120 | 121 | #### 2.2SVD 与 PCoA 122 | 123 | 下面使用实际训练时常常使用的 SVD 直接求得这个 $q$ 个本征矢。 124 | 125 | 对中心化后的数据集进行奇异值分解: 126 | $$ 127 | HX=U\Sigma V^T,U^TU=E_N,V^TV=E_p,\Sigma:N\times p 128 | $$ 129 | 130 | 于是: 131 | $$ 132 | S=\frac{1}{N}X^THX=\frac{1}{N}X^TH^THX=\frac{1}{N}V\Sigma^T\Sigma V^T 133 | $$ 134 | 因此,我们直接对中心化后的数据集进行 SVD,就可以得到特征值和特征向量 $V$,在新坐标系中的坐标就是: 135 | $$ 136 | HX\cdot V 137 | $$ 138 | 由上面的推导,我们也可以得到另一种方法 PCoA 主坐标分析,定义并进行特征值分解: 139 | $$ 140 | T=HXX^TH=U\Sigma\Sigma^TU^T 141 | $$ 142 | 由于: 143 | $$ 144 | TU\Sigma=U\Sigma(\Sigma^T\Sigma) 145 | $$ 146 | 于是可以直接得到坐标。这两种方法都可以得到主成分,但是由于方差矩阵是 $p\times p$ 的,而 $T$ 是 $N\times N$ 的,所以对样本量较少的时候可以采用 PCoA的方法。 147 | 148 | **总结来说就是** 149 | 150 | 输入: 样本集 $D=\left\{\boldsymbol{x}_{1}, \boldsymbol{x}_{2}, \ldots, \boldsymbol{x}_{m}\right\}$; 151 | 低维空间维数 $d^{\prime}$. 152 | 153 | 过程: 154 | 155 | 1. 对所有样本进行中心化: $\boldsymbol{x}_{i} \leftarrow \boldsymbol{x}_{i}-\frac{1}{m} \sum_{i=1}^{m} \boldsymbol{x}_{i}$; 156 | 2. 计算样本的协方差矩阵 $\mathbf{X X} \mathbf{X}^{\mathrm{T}}$; 157 | 3. 对协方差矩阵 XXX 158 | 4. 取最大的 $d^{\prime}$ 个特征值所对应的特征向量 $\boldsymbol{w}_{1}, \boldsymbol{w}_{2}, \ldots, \boldsymbol{w}_{d^{\prime}}$. 159 | 5. 160 | 输出: 投影矩阵 $\mathbf{W}=\left(\boldsymbol{w}_{1}, \boldsymbol{w}_{2}, \ldots, \boldsymbol{w}_{d^{\prime}}\right)$. 161 | 162 | 163 | #### 2.3p-PCA 164 | 165 | 下面从概率的角度对 PCA 进行分析,概率方法也叫 p-PCA。我们使用线性模型,类似之前 LDA,我们选定一个方向,对原数据 $x\in\mathbb{R}^p$ ,降维后的数据为 $z\in\mathbb{R}^q,q