├── AdaBoost └── AdaBoost.py ├── Clustering ├── Hierachical_Clustering │ ├── Hierachical_Clustering.ipynb │ └── Hierachical_Clustering.py ├── K-means_Clustering │ ├── K-means_Clustering.ipynb │ └── K-means_Clustering.py └── iris.data ├── CodePic.png ├── DecisionTree └── DecisionTree.py ├── EM └── EM.py ├── HMM ├── HMM.py ├── HMMTrainSet.txt └── testArtical.txt ├── KNN └── KNN.py ├── LDA ├── LDA.ipynb ├── LDA.py └── bbc_text.csv ├── LSA ├── LSA.ipynb ├── LSA.py └── bbc_text.csv ├── Logistic_and_maximum_entropy_models ├── logisticRegression.py └── maxEntropy.py ├── Mnist ├── mnist_test.rar └── mnist_train.rar ├── NaiveBayes └── NaiveBayes.py ├── PCA ├── PCA.ipynb ├── PCA.py └── cars.csv ├── PLSA ├── PLSA.ipynb ├── PLSA.py └── bbc_text.csv ├── Page_Rank ├── Page_Rank.ipynb ├── Page_Rank.py └── directed_graph.png ├── README.md ├── SVM └── SVM.py ├── blogs ├── How Does Batch Normalizetion Help Optimization.pdf ├── K近邻原理剖析及实现.pdf ├── 决策树原理剖析及实现.pdf ├── 对话系统中的DST.pdf ├── 微软、头条、滴滴、爱奇艺NLP面试感想.pdf ├── 感知机原理剖析及实现.pdf ├── 支持向量机(SVM)原理剖析及实现.pdf ├── 最大熵原理剖析及实现.pdf ├── 朴素贝叶斯原理剖析及实现.pdf ├── 机器学习和NLP入门规划.pdf ├── 机器学习面试之最大熵模型.pdf ├── 记忆网络之Memory Networks.pdf └── 逻辑斯蒂原理剖析及实现.pdf ├── perceptron └── perceptron_dichotomy.py └── transMnist ├── Mnist ├── t10k-images.idx3-ubyte ├── t10k-labels.idx1-ubyte ├── train-images.idx3-ubyte └── train-labels.idx1-ubyte └── transMnist.py /AdaBoost/AdaBoost.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | #Author:Dodo 3 | #Date:2018-12-05 4 | #Email:lvtengchao@pku.edu.cn 5 | #Blog:www.pkudodo.com 6 | ''' 7 | 数据集:Mnist 8 | 训练集数量:60000(实际使用:10000) 9 | 测试集数量:10000(实际使用:1000) 10 | 层数:40 11 | ------------------------------ 12 | 运行结果: 13 | 正确率:97% 14 | 运行时长:65m 15 | ''' 16 | 17 | import time 18 | import numpy as np 19 | 20 | def loadData(fileName): 21 | ''' 22 | 加载文件 23 | :param fileName:要加载的文件路径 24 | :return: 数据集和标签集 25 | ''' 26 | #存放数据及标记 27 | dataArr = []; labelArr = [] 28 | #读取文件 29 | fr = open(fileName) 30 | #遍历文件中的每一行 31 | for line in fr.readlines(): 32 | #获取当前行,并按“,”切割成字段放入列表中 33 | #strip:去掉每行字符串首尾指定的字符(默认空格或换行符) 34 | #split:按照指定的字符将字符串切割成每个字段,返回列表形式 35 | curLine = line.strip().split(',') 36 | #将每行中除标记外的数据放入数据集中(curLine[0]为标记信息) 37 | #在放入的同时将原先字符串形式的数据转换为整型 38 | #此外将数据进行了二值化处理,大于128的转换成1,小于的转换成0,方便后续计算 39 | dataArr.append([int(int(num) > 128) for num in curLine[1:]]) 40 | #将标记信息放入标记集中 41 | #放入的同时将标记转换为整型 42 | 43 | #转换成二分类任务 44 | #标签0设置为1,反之为-1 45 | if int(curLine[0]) == 0: 46 | labelArr.append(1) 47 | else: 48 | labelArr.append(-1) 49 | #返回数据集和标记 50 | return dataArr, labelArr 51 | 52 | def calc_e_Gx(trainDataArr, trainLabelArr, n, div, rule, D): 53 | ''' 54 | 计算分类错误率 55 | :param trainDataArr:训练数据集数字 56 | :param trainLabelArr: 训练标签集数组 57 | :param n: 要操作的特征 58 | :param div:划分点 59 | :param rule:正反例标签 60 | :param D:权值分布D 61 | :return:预测结果, 分类误差率 62 | ''' 63 | #初始化分类误差率为0 64 | e = 0 65 | #将训练数据矩阵中特征为n的那一列单独剥出来做成数组。因为其他元素我们并不需要, 66 | #直接对庞大的训练集进行操作的话会很慢 67 | x = trainDataArr[:, n] 68 | #同样将标签也转换成数组格式,x和y的转换只是单纯为了提高运行速度 69 | #测试过相对直接操作而言性能提升很大 70 | y = trainLabelArr 71 | predict = [] 72 | 73 | #依据小于和大于的标签依据实际情况会不同,在这里直接进行设置 74 | if rule == 'LisOne': L = 1; H = -1 75 | else: L = -1; H = 1 76 | 77 | #遍历所有样本的特征m 78 | for i in range(trainDataArr.shape[0]): 79 | if x[i] < div: 80 | #如果小于划分点,则预测为L 81 | #如果设置小于div为1,那么L就是1, 82 | #如果设置小于div为-1,L就是-1 83 | predict.append(L) 84 | #如果预测错误,分类错误率要加上该分错的样本的权值(8.1式) 85 | if y[i] != L: e += D[i] 86 | elif x[i] >= div: 87 | #与上面思想一样 88 | predict.append(H) 89 | if y[i] != H: e += D[i] 90 | #返回预测结果和分类错误率e 91 | #预测结果其实是为了后面做准备的,在算法8.1第四步式8.4中exp内部有个Gx,要用在那个地方 92 | #以此来更新新的D 93 | return np.array(predict), e 94 | 95 | def createSigleBoostingTree(trainDataArr, trainLabelArr, D): 96 | ''' 97 | 创建单层提升树 98 | :param trainDataArr:训练数据集数组 99 | :param trainLabelArr: 训练标签集数组 100 | :param D: 算法8.1中的D 101 | :return: 创建的单层提升树 102 | ''' 103 | 104 | #获得样本数目及特征数量 105 | m, n = np.shape(trainDataArr) 106 | #单层树的字典,用于存放当前层提升树的参数 107 | #也可以认为该字典代表了一层提升树 108 | sigleBoostTree = {} 109 | #初始化分类误差率,分类误差率在算法8.1步骤(2)(b)有提到 110 | #误差率最高也只能100%,因此初始化为1 111 | sigleBoostTree['e'] = 1 112 | 113 | #对每一个特征进行遍历,寻找用于划分的最合适的特征 114 | for i in range(n): 115 | #因为特征已经经过二值化,只能为0和1,因此分切分时分为-0.5, 0.5, 1.5三挡进行切割 116 | for div in [-0.5, 0.5, 1.5]: 117 | #在单个特征内对正反例进行划分时,有两种情况: 118 | #可能是小于某值的为1,大于某值得为-1,也可能小于某值得是-1,反之为1 119 | #因此在寻找最佳提升树的同时对于两种情况也需要遍历运行 120 | #LisOne:Low is one:小于某值得是1 121 | #HisOne:High is one:大于某值得是1 122 | for rule in ['LisOne', 'HisOne']: 123 | #按照第i个特征,以值div进行切割,进行当前设置得到的预测和分类错误率 124 | Gx, e = calc_e_Gx(trainDataArr, trainLabelArr, i, div, rule, D) 125 | #如果分类错误率e小于当前最小的e,那么将它作为最小的分类错误率保存 126 | if e < sigleBoostTree['e']: 127 | sigleBoostTree['e'] = e 128 | #同时也需要存储最优划分点、划分规则、预测结果、特征索引 129 | #以便进行D更新和后续预测使用 130 | sigleBoostTree['div'] = div 131 | sigleBoostTree['rule'] = rule 132 | sigleBoostTree['Gx'] = Gx 133 | sigleBoostTree['feature'] = i 134 | #返回单层的提升树 135 | return sigleBoostTree 136 | 137 | def createBosstingTree(trainDataList, trainLabelList, treeNum = 50): 138 | ''' 139 | 创建提升树 140 | 创建算法依据“8.1.2 AdaBoost算法” 算法8.1 141 | :param trainDataList:训练数据集 142 | :param trainLabelList: 训练测试集 143 | :param treeNum: 树的层数 144 | :return: 提升树 145 | ''' 146 | #将数据和标签转化为数组形式 147 | trainDataArr = np.array(trainDataList) 148 | trainLabelArr = np.array(trainLabelList) 149 | #没增加一层数后,当前最终预测结果列表 150 | finallpredict = [0] * len(trainLabelArr) 151 | #获得训练集数量以及特征个数 152 | m, n = np.shape(trainDataArr) 153 | 154 | #依据算法8.1步骤(1)初始化D为1/N 155 | D = [1 / m] * m 156 | #初始化提升树列表,每个位置为一层 157 | tree = [] 158 | #循环创建提升树 159 | for i in range(treeNum): 160 | #得到当前层的提升树 161 | curTree = createSigleBoostingTree(trainDataArr, trainLabelArr, D) 162 | #根据式8.2计算当前层的alpha 163 | alpha = 1/2 * np.log((1 - curTree['e']) / curTree['e']) 164 | #获得当前层的预测结果,用于下一步更新D 165 | Gx = curTree['Gx'] 166 | #依据式8.4更新D 167 | #考虑到该式每次只更新D中的一个w,要循环进行更新知道所有w更新结束会很复杂(其实 168 | #不是时间上的复杂,只是让人感觉每次单独更新一个很累),所以该式以向量相乘的形式, 169 | #一个式子将所有w全部更新完。 170 | #该式需要线性代数基础,如果不太熟练建议补充相关知识,当然了,单独更新w也一点问题 171 | #没有 172 | #np.multiply(trainLabelArr, Gx):exp中的y*Gm(x),结果是一个行向量,内部为yi*Gm(xi) 173 | #np.exp(-1 * alpha * np.multiply(trainLabelArr, Gx)):上面求出来的行向量内部全体 174 | #成员再乘以-αm,然后取对数,和书上式子一样,只不过书上式子内是一个数,这里是一个向量 175 | #D是一个行向量,取代了式中的wmi,然后D求和为Zm 176 | #书中的式子最后得出来一个数w,所有数w组合形成新的D 177 | #这里是直接得到一个向量,向量内元素是所有的w 178 | #本质上结果是相同的 179 | D = np.multiply(D, np.exp(-1 * alpha * np.multiply(trainLabelArr, Gx))) / sum(D) 180 | #在当前层参数中增加alpha参数,预测的时候需要用到 181 | curTree['alpha'] = alpha 182 | #将当前层添加到提升树索引中。 183 | tree.append(curTree) 184 | 185 | #-----以下代码用来辅助,可以去掉--------------- 186 | #根据8.6式将结果加上当前层乘以α,得到目前的最终输出预测 187 | finallpredict += alpha * Gx 188 | #计算当前最终预测输出与实际标签之间的误差 189 | error = sum([1 for i in range(len(trainDataList)) if np.sign(finallpredict[i]) != trainLabelArr[i]]) 190 | #计算当前最终误差率 191 | finallError = error / len(trainDataList) 192 | #如果误差为0,提前退出即可,因为没有必要再计算算了 193 | if finallError == 0: return tree 194 | #打印一些信息 195 | print('iter:%d:%d, sigle error:%.4f, finall error:%.4f'%(i, treeNum, curTree['e'], finallError )) 196 | #返回整个提升树 197 | return tree 198 | 199 | def predict(x, div, rule, feature): 200 | ''' 201 | 输出单独层预测结果 202 | :param x: 预测样本 203 | :param div: 划分点 204 | :param rule: 划分规则 205 | :param feature: 进行操作的特征 206 | :return: 207 | ''' 208 | #依据划分规则定义小于及大于划分点的标签 209 | if rule == 'LisOne': L = 1; H = -1 210 | else: L = -1; H = 1 211 | 212 | #判断预测结果 213 | if x[feature] < div: return L 214 | else: return H 215 | 216 | def model_test(testDataList, testLabelList, tree): 217 | ''' 218 | 测试 219 | :param testDataList:测试数据集 220 | :param testLabelList: 测试标签集 221 | :param tree: 提升树 222 | :return: 准确率 223 | ''' 224 | #错误率计数值 225 | errorCnt = 0 226 | #遍历每一个测试样本 227 | for i in range(len(testDataList)): 228 | #预测结果值,初始为0 229 | result = 0 230 | #依据算法8.1式8.6 231 | #预测式子是一个求和式,对于每一层的结果都要进行一次累加 232 | #遍历每层的树 233 | for curTree in tree: 234 | #获取该层参数 235 | div = curTree['div'] 236 | rule = curTree['rule'] 237 | feature = curTree['feature'] 238 | alpha = curTree['alpha'] 239 | #将当前层结果加入预测中 240 | result += alpha * predict(testDataList[i], div, rule, feature) 241 | #预测结果取sign值,如果大于0 sign为1,反之为0 242 | if np.sign(result) != testLabelList[i]: errorCnt += 1 243 | #返回准确率 244 | return 1 - errorCnt / len(testDataList) 245 | 246 | if __name__ == '__main__': 247 | #开始时间 248 | start = time.time() 249 | 250 | # 获取训练集 251 | print('start read transSet') 252 | trainDataList, trainLabelList = loadData('../Mnist/mnist_train.csv') 253 | 254 | # 获取测试集 255 | print('start read testSet') 256 | testDataList, testLabelList = loadData('../Mnist/mnist_test.csv') 257 | 258 | #创建提升树 259 | print('start init train') 260 | tree = createBosstingTree(trainDataList[:10000], trainLabelList[:10000], 40) 261 | 262 | #测试 263 | print('start to test') 264 | accuracy = model_test(testDataList[:1000], testLabelList[:1000], tree) 265 | print('the accuracy is:%d' % (accuracy * 100), '%') 266 | 267 | #结束时间 268 | end = time.time() 269 | print('time span:', end - start) 270 | -------------------------------------------------------------------------------- /Clustering/Hierachical_Clustering/Hierachical_Clustering.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | #Author:Harold 3 | #Date:2021-1-27 4 | #Email:zenghr_zero@163.com 5 | 6 | ''' 7 | 数据集:iris 8 | 数据集数量:150 9 | ----------------------------- 10 | 运行结果: 11 | ARI:0.56 12 | 运行时长:177.0s 13 | ''' 14 | 15 | import numpy as np 16 | import math 17 | import time 18 | from scipy.special import comb 19 | 20 | 21 | #定义加载数据的函数 22 | def load_data(file): 23 | ''' 24 | INPUT: 25 | file - (str) 数据文件的路径 26 | 27 | OUTPUT: 28 | Xarray - (array) 特征数据数组 29 | Ylist - (list) 类别标签列表 30 | 31 | ''' 32 | Xlist = [] #定义一个列表用来保存每条数据 33 | Ylist = [] #定义一个列表用来保存每条数据的类别标签 34 | fr = open(file) 35 | for line in fr.readlines(): #逐行读取数据,鸢尾花数据集每一行表示一个鸢尾花的特征和类别标签,用逗号分隔 36 | cur = line.split(',') 37 | label = cur[-1] 38 | X = [float(x) for x in cur[:-1]] #用列表来表示一条特征数据 39 | Xlist.append(X) 40 | Ylist.append(label) 41 | Xarray = np.array(Xlist) #将特征数据转换为数组类型,方便之后的操作 42 | print('Data shape:', Xarray.shape) 43 | print('Length of labels:', len(Ylist)) 44 | return Xarray, Ylist 45 | 46 | 47 | #定义标准化函数,对每一列特征进行min-max标准化,将数据缩放到0-1之间 48 | #标准化处理对于计算距离的机器学习方法是非常重要的,因为特征的尺度不同会导致计算出来的距离倾向于尺度大的特征,为保证距离对每一列特征都是公平的,必须将所有特征缩放到同一尺度范围内 49 | def Normalize(Xarray): 50 | ''' 51 | INPUT: 52 | Xarray - (array) 特征数据数组 53 | 54 | OUTPUT: 55 | Xarray - (array) 标准化处理后的特征数据数组 56 | 57 | ''' 58 | for f in range(Xarray.shape[1]): 59 | maxf = np.max(Xarray[:, f]) 60 | minf = np.min(Xarray[:, f]) 61 | for n in range(Xarray.shape[0]): 62 | Xarray[n][f] = (Xarray[n][f]-minf) / (maxf-minf) 63 | return Xarray 64 | 65 | 66 | #定义计算两条数据间的距离的函数,这里计算的是欧式距离 67 | def cal_distance(xi, xj): 68 | ''' 69 | INPUT: 70 | Xi - (array) 第i条特征数据 71 | Xj - (array) 第j条特征数据 72 | 73 | OUTPUT: 74 | dist - (float) 两条数据的欧式距离 75 | 76 | ''' 77 | dist = 0 78 | for col in range(len(xi)): 79 | dist += (xi[col]-xj[col]) ** 2 80 | dist = math.sqrt(dist) 81 | return dist 82 | 83 | 84 | #定义计算所有特征数据两两之间距离的函数 85 | def Distances(Xarray): 86 | ''' 87 | INPUT: 88 | Xarray - (array) 特征数据数组 89 | 90 | OUTPUT: 91 | dists - (array) 两两数据的欧式距离数组 92 | 93 | ''' 94 | dists = np.zeros((Xarray.shape[0], Xarray.shape[0])) #定义一个数组用来保存两两数据的距离 95 | for n1 in range(Xarray.shape[0]): 96 | for n2 in range(n1): 97 | dists[n1][n2] = cal_distance(Xarray[n1], Xarray[n2]) 98 | dists[n2][n1] = dists[n1][n2] 99 | return dists 100 | 101 | 102 | #定义计算两类的类间距离的函数,这里计算的是最短距离 103 | def cal_groupdist(g1, g2, group_dict, dists): 104 | ''' 105 | INPUT: 106 | g1 - (int) 类别1的标签 107 | g2 - (int) 类别2的标签 108 | group_dict - (dict) 类别字典 109 | dists - (array) 两两数据的欧式距离数组 110 | 111 | OUTPUT: 112 | (int) 类间最短距离 113 | 114 | ''' 115 | d = [] 116 | #循环计算两类之间两两数据的距离 117 | for xi in group_dict[g1]: 118 | for xj in group_dict[g2]: 119 | if xi != xj: 120 | d.append(dists[xi][xj]) 121 | return min(d) 122 | 123 | 124 | #定义层次聚类函数 125 | def Clustering(Xarray, k, dists): 126 | ''' 127 | INPUT: 128 | Xarray - (array) 特征数据数组 129 | k - (int) 设定的类别数 130 | dists - (array) 两两数据的欧式距离数组 131 | 132 | OUTPUT: 133 | group_dict - (dict) 类别字典 134 | 135 | ''' 136 | group_dict = {} #定义一个空字典,用于保存聚类所产生的所有类别 137 | for n in range(Xarray.shape[0]): #层次聚类是一种聚合聚类方法,首先将每条数据都分到不同的类,数据的类别标签为0-(N-1),其中N为数据条数 138 | group_dict[n] = [n] 139 | newgroup = Xarray.shape[0] #newgroup表示新的类别标签,此时下一个类别标签为N 140 | while len(group_dict.keys()) > k: #当类别数大于我们所设定的类别数k时,不断循环进行聚类 141 | print('Number of groups:', len(group_dict.keys())) 142 | group_dists = {} #定义一个空字典,用于保存两两类之间的间距,其中字典的值为元组(g1, g2),表示两个类别标签,字典的键为这两个类别的间距 143 | #循环计算group_dict中两两类别之间的间距,保存到group_dists中 144 | for g1 in group_dict.keys(): 145 | for g2 in group_dict.keys(): 146 | if g1 != g2: 147 | if (g1, g2) not in group_dists.values(): 148 | d = cal_groupdist(g1, g2, group_dict, dists) 149 | group_dists[d] = (g1, g2) 150 | group_mindist = min(list(group_dists.keys())) #取类别之间的最小间距 151 | mingroups = group_dists[group_mindist] #取间距最小的两个类别 152 | new = [] #定义一个列表,用于保存所产生的新类中包含的数据,这里用之前对每条数据给的类别标签0-(N-1)来表示 153 | for g in mingroups: 154 | new.extend(group_dict[g]) #将间距最小的两类中包含的数据保存在new列表中 155 | del group_dict[g] #然后在group_dict中移去这两类 156 | print(newgroup, new) 157 | group_dict[newgroup] = new #此时聚类所产生的新类中包含的数据即为以上两类的中包含的数据的聚合,给新类贴上类别标签为newgroup,保存到group_dict中 158 | newgroup += 1 #产生下一个类别标签 159 | return group_dict 160 | 161 | 162 | #定义计算调整兰德系数(ARI)的函数,调整兰德系数是一种聚类方法的常用评估方法 163 | def Adjusted_Rand_Index(group_dict, Ylist, k): 164 | ''' 165 | INPUT: 166 | group_dict - (dict) 类别字典 167 | Ylist - (list) 类别标签列表 168 | k - (int) 设定的类别数 169 | 170 | OUTPUT: 171 | (int) 调整兰德系数 172 | 173 | ''' 174 | group_array = np.zeros((k, k)) #定义一个数组,用来保存聚类所产生的类别标签与给定的外部标签各类别之间共同包含的数据数量 175 | y_dict = {} #定义一个空字典,用来保存外部标签中各类所包含的数据,结构与group_dict相同 176 | for i in range(len(Ylist)): 177 | if Ylist[i] not in y_dict: 178 | y_dict[Ylist[i]] = [i] 179 | else: 180 | y_dict[Ylist[i]].append(i) 181 | #循环计算group_array的值 182 | for i in range(k): 183 | for j in range(k): 184 | for n in range(len(Ylist)): 185 | if n in group_dict[list(group_dict.keys())[i]] and n in y_dict[list(y_dict.keys())[j]]: 186 | group_array[i][j] += 1 #如果数据n同时在group_dict的类别i和y_dict的类别j中,group_array[i][j]的数值加一 187 | RI = 0 #定义兰德系数(RI) 188 | sum_i = np.zeros(k) #定义一个数组,用于保存聚类结果group_dict中每一类的个数 189 | sum_j = np.zeros(k) #定义一个数组,用于保存外部标签y_dict中每一类的个数 190 | for i in range(k): 191 | for j in range(k): 192 | sum_i[i] += group_array[i][j] 193 | sum_j[j] += group_array[i][j] 194 | if group_array[i][j] >= 2: 195 | RI += comb(group_array[i][j], 2) #comb用于计算group_array[i][j]中两两组合的组合数 196 | ci = 0 #ci保存聚类结果中同一类中的两两组合数之和 197 | cj = 0 #cj保存外部标签中同一类中的两两组合数之和 198 | for i in range(k): 199 | if sum_i[i] >= 2: 200 | ci += comb(sum_i[i], 2) 201 | for j in range(k): 202 | if sum_j[j] >= 2: 203 | cj += comb(sum_j[j], 2) 204 | E_RI = ci * cj / comb(len(Ylist), 2) #计算RI的期望 205 | max_RI = (ci + cj) / 2 #计算RI的最大值 206 | return (RI-E_RI) / (max_RI-E_RI) #返回调整兰德系数的值 207 | 208 | 209 | if __name__ == "__main__": 210 | Xarray, Ylist = load_data('..\iris.data') #加载数据 211 | start = time.time() #保存开始时间 212 | Xarray = Normalize(Xarray) #对特征数据进行标准化处理 213 | k = 3 #设定聚类数为3 214 | dists = Distances(Xarray) #计算特征数据的距离数组 215 | print(dists) 216 | group_dict = Clustering(Xarray, k, dists) #进行层次聚类 217 | end = time.time() #保存结束时间 218 | print(group_dict) 219 | ARI = Adjusted_Rand_Index(group_dict, Ylist, k) #计算ARI用来评估聚类结果 220 | print('Adjusted Rand Index:', ARI) 221 | print('Time:', end-start) 222 | -------------------------------------------------------------------------------- /Clustering/K-means_Clustering/K-means_Clustering.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 6, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "Data shape: (150, 4)\n", 13 | "Length of labels: 150\n", 14 | "1/2\n", 15 | "{0: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 57, 60, 67, 69, 79, 80, 81, 93, 98], 1: [50, 51, 52, 54, 58, 62, 63, 65, 68, 71, 72, 73, 74, 75, 76, 77, 83, 86, 87, 91, 92, 97, 102, 103, 104, 105, 107, 108, 109, 110, 111, 112, 116, 117, 118, 119, 120, 122, 123, 124, 125, 126, 128, 129, 130, 131, 132, 133, 134, 135, 137, 139, 140, 141, 143, 144, 145, 146, 147], 2: [53, 55, 56, 59, 61, 64, 66, 70, 78, 82, 84, 85, 88, 89, 90, 94, 95, 96, 99, 100, 101, 106, 113, 114, 115, 121, 127, 136, 138, 142, 148, 149]}\n", 16 | "2/2\n", 17 | "{0: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49], 1: [50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149], 2: []}\n", 18 | "Time: 0.0059719085693359375\n" 19 | ] 20 | }, 21 | { 22 | "data": { 23 | "image/png": "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\n", 24 | "text/plain": [ 25 | "
" 26 | ] 27 | }, 28 | "metadata": { 29 | "needs_background": "light" 30 | }, 31 | "output_type": "display_data" 32 | } 33 | ], 34 | "source": [ 35 | "#导入所需的库\n", 36 | "import numpy as np\n", 37 | "import time\n", 38 | "import random \n", 39 | "from scipy.special import comb\n", 40 | "import matplotlib.pyplot as plt\n", 41 | "%matplotlib inline\n", 42 | "\n", 43 | "\n", 44 | "#定义加载数据的函数,这里以鸢尾花数据集为例\n", 45 | "def load_data(file):\n", 46 | " '''\n", 47 | " INPUT:\n", 48 | " file - (str) 数据文件的路径\n", 49 | " \n", 50 | " OUTPUT:\n", 51 | " Xarray - (array) 特征数据数组\n", 52 | " Ylist - (list) 类别标签列表\n", 53 | " \n", 54 | " '''\n", 55 | " Xlist = [] #定义一个列表用来保存每条数据\n", 56 | " Ylist = [] #定义一个列表用来保存每条数据的类别标签\n", 57 | " fr = open(file)\n", 58 | " for line in fr.readlines(): #逐行读取数据,鸢尾花数据集每一行表示一个鸢尾花的特征和类别标签,用逗号分隔\n", 59 | " cur = line.split(',')\n", 60 | " label = cur[-1]\n", 61 | " X = [float(x) for x in cur[:-1]] #用列表来表示一条特征数据\n", 62 | " Xlist.append(X)\n", 63 | " Ylist.append(label)\n", 64 | " Xarray = np.array(Xlist) #将特征数据转换为数组类型,方便之后的操作\n", 65 | " print('Data shape:', Xarray.shape)\n", 66 | " print('Length of labels:', len(Ylist))\n", 67 | " return Xarray, Ylist\n", 68 | "\n", 69 | "\n", 70 | "#定义标准化函数,对每一列特征进行min-max标准化,将数据缩放到0-1之间\n", 71 | "#标准化处理对于计算距离的机器学习方法是非常重要的,因为特征的尺度不同会导致计算出来的距离倾向于尺度大的特征,为保证距离对每一列特征都是公平的,必须将所有特征缩放到同一尺度范围内\n", 72 | "def Normalize(Xarray):\n", 73 | " '''\n", 74 | " INPUT:\n", 75 | " Xarray - (array) 特征数据数组\n", 76 | " \n", 77 | " OUTPUT:\n", 78 | " Xarray - (array) 标准化处理后的特征数据数组\n", 79 | " \n", 80 | " '''\n", 81 | " for f in range(Xarray.shape[1]):\n", 82 | " maxf = np.max(Xarray[:, f])\n", 83 | " minf = np.min(Xarray[:, f])\n", 84 | " for n in range(Xarray.shape[0]):\n", 85 | " Xarray[n][f] = (Xarray[n][f]-minf) / (maxf-minf) \n", 86 | " return Xarray\n", 87 | "\n", 88 | "\n", 89 | "#定义计算两条数据间的距离的函数,这里计算的是欧式距离\n", 90 | "def cal_distance(xi, xj):\n", 91 | " '''\n", 92 | " INPUT:\n", 93 | " Xi - (array) 第i条特征数据\n", 94 | " Xj - (array) 第j条特征数据\n", 95 | " \n", 96 | " OUTPUT:\n", 97 | " dist - (float) 两条数据的欧式距离\n", 98 | " \n", 99 | " '''\n", 100 | " dist = 0\n", 101 | " for col in range(len(xi)):\n", 102 | " dist += (xi[col]-xj[col]) ** 2\n", 103 | " return dist\n", 104 | "\n", 105 | "\n", 106 | "#定义计算类中心的函数,以当前类中所包含数据的各个特征均值作为新的新的类中心\n", 107 | "def cal_groupcenter(group, Xarray):\n", 108 | " '''\n", 109 | " INPUT:\n", 110 | " group - (list) 类所包含的数据列表\n", 111 | " Xarray - (array) 特征数据数组\n", 112 | " \n", 113 | " OUTPUT:\n", 114 | " center - (array) 新的类中心\n", 115 | " \n", 116 | " '''\n", 117 | " center = np.zeros(Xarray.shape[1])\n", 118 | " for i in range(Xarray.shape[1]):\n", 119 | " for n in group:\n", 120 | " center[i] += Xarray[n][i] #计算当前类中第i个特征的数据之和\n", 121 | " center = center / Xarray.shape[0] #计算各个特征的均值\n", 122 | " return center\n", 123 | "\n", 124 | "\n", 125 | "#定义计算调整兰德系数(ARI)的函数,调整兰德系数是一种聚类方法的常用评估方法\n", 126 | "def Adjusted_Rand_Index(group_dict, Ylist, k):\n", 127 | " '''\n", 128 | " INPUT:\n", 129 | " group_dict - (dict) 类别字典\n", 130 | " Ylist - (list) 类别标签列表\n", 131 | " k - (int) 设定的类别数\n", 132 | " \n", 133 | " OUTPUT:\n", 134 | " (int) 调整兰德系数\n", 135 | " \n", 136 | " '''\n", 137 | " group_array = np.zeros((k, k)) #定义一个数组,用来保存聚类所产生的类别标签与给定的外部标签各类别之间共同包含的数据数量\n", 138 | " ylabel = list(set(Ylist)) #Ylist保存的标签为字符串,用ylabel来保存各个标签,在y_dict中类别以标签在ylabel列表中的索引值来表示类\n", 139 | " y_dict = {i:[] for i in range(k)} #定义一个空字典,用来保存外部标签中各类所包含的数据,结构与group_dict相同\n", 140 | " for i in range(len(Ylist)):\n", 141 | " y_dict[ylabel.index(Ylist[i])].append(i)\n", 142 | " #循环计算group_array的值\n", 143 | " for i in range(k):\n", 144 | " for j in range(k):\n", 145 | " for n in range(len(Ylist)):\n", 146 | " if n in group_dict[i] and n in y_dict[j]:\n", 147 | " group_array[i][j] += 1 #如果数据n同时在group_dict的类别i和y_dict的类别j中,group_array[i][j]的数值加一\n", 148 | " RI = 0 #定义兰德系数(RI)\n", 149 | " sum_i = np.zeros(3) #定义一个数组,用于保存聚类结果group_dict中每一类的个数\n", 150 | " sum_j = np.zeros(3) #定义一个数组,用于保存外部标签y_dict中每一类的个数\n", 151 | " for i in range(k):\n", 152 | " for j in range(k):\n", 153 | " sum_i[i] += group_array[i][j]\n", 154 | " sum_j[j] += group_array[i][j]\n", 155 | " if group_array[i][j] >= 2:\n", 156 | " RI += comb(group_array[i][j], 2) #comb用于计算group_array[i][j]中两两组合的组合数\n", 157 | " ci = 0 #ci保存聚类结果中同一类中的两两组合数之和\n", 158 | " cj = 0 #cj保存外部标签中同一类中的两两组合数之和\n", 159 | " for i in range(k):\n", 160 | " if sum_i[i] >= 2:\n", 161 | " ci += comb(sum_i[i], 2)\n", 162 | " for j in range(k):\n", 163 | " if sum_j[j] >= 2:\n", 164 | " cj += comb(sum_j[j], 2)\n", 165 | " E_RI = ci * cj / comb(len(Ylist), 2) #计算RI的期望\n", 166 | " max_RI = (ci + cj) / 2 #计算RI的最大值\n", 167 | " return (RI-E_RI) / (max_RI-E_RI) #返回调整兰德系数的值\n", 168 | "\n", 169 | "\n", 170 | "#定义k均值聚类函数\n", 171 | "def Kmeans(Xarray, k, iters):\n", 172 | " '''\n", 173 | " INPUT:\n", 174 | " Xarray - (array) 特征数据数组\n", 175 | " k - (int) 设定的类别数\n", 176 | " iters - (int) 设定的迭代次数\n", 177 | " \n", 178 | " OUTPUT:\n", 179 | " group_dict - (dict) 类别字典\n", 180 | " scores - (int) 每次迭代的ARI得分列表\n", 181 | " \n", 182 | " '''\n", 183 | " center_inds = random.sample(range(Xarray.shape[0]), k) #从特征数据中随机抽取k个数据索引\n", 184 | " centers = [Xarray[ci] for ci in center_inds] #将这k个数据索引所对应的特征数据作为初始的k个聚类中心\n", 185 | " scores = [] #定义一个空列表用来保存每次迭代的ARI得分\n", 186 | " for i in range(iters):\n", 187 | " group_dict = {i:[] for i in range(k)} #定义一个空字典,用于保存聚类所产生的所有类别,其中字典的键为类别标签,值为类别所包含的数据列表,以索引表示每条数据\n", 188 | " print('{}/{}'.format(i+1, iters))\n", 189 | " #循环计算每条数据到各个聚类中心的距离\n", 190 | " for n in range(Xarray.shape[0]):\n", 191 | " dists = [] #保存第n条数据到各个聚类中心的距离\n", 192 | " for ci in range(k):\n", 193 | " dist = cal_distance(Xarray[n], centers[ci])\n", 194 | " dists.append(dist)\n", 195 | " g = dists.index(min(dists)) #取距离最近的中心所在的类\n", 196 | " group_dict[g].append(n) #将第n条数据的索引n保存到g类\n", 197 | " print(group_dict)\n", 198 | " for i in range(k):\n", 199 | " centers[i] = cal_groupcenter(group_dict[i], Xarray) #根据每一类所包含的数据重新计算类中心\n", 200 | " scores.append(Adjusted_Rand_Index(group_dict, Ylist, k)) #将该轮迭代的ARI得分保存到scores列表\n", 201 | " return group_dict, scores\n", 202 | "\n", 203 | "\n", 204 | "if __name__ == \"__main__\":\n", 205 | " Xarray, Ylist = load_data('..\\iris.data') #加载数据\n", 206 | " start = time.time() #保存开始时间\n", 207 | " Xarray = Normalize(Xarray) #对特征数据进行标准化处理\n", 208 | " k = 3 #设定聚类数为3\n", 209 | " iters = 2 #设定迭代次数为2\n", 210 | " group_dict, scores = Kmeans(Xarray, k, iters) #进行k均值聚类\n", 211 | " end = time.time() #保存结束时间\n", 212 | " print('Time:', end-start)\n", 213 | " plt.plot(range(iters), scores) #绘制ARI得分折线图" 214 | ] 215 | }, 216 | { 217 | "cell_type": "code", 218 | "execution_count": null, 219 | "metadata": {}, 220 | "outputs": [], 221 | "source": [] 222 | } 223 | ], 224 | "metadata": { 225 | "kernelspec": { 226 | "display_name": "Python 3", 227 | "language": "python", 228 | "name": "python3" 229 | }, 230 | "language_info": { 231 | "codemirror_mode": { 232 | "name": "ipython", 233 | "version": 3 234 | }, 235 | "file_extension": ".py", 236 | "mimetype": "text/x-python", 237 | "name": "python", 238 | "nbconvert_exporter": "python", 239 | "pygments_lexer": "ipython3", 240 | "version": "3.7.3" 241 | } 242 | }, 243 | "nbformat": 4, 244 | "nbformat_minor": 2 245 | } 246 | -------------------------------------------------------------------------------- /Clustering/K-means_Clustering/K-means_Clustering.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | #Author:Harold 3 | #Date:2021-1-27 4 | #Email:zenghr_zero@163.com 5 | 6 | ''' 7 | 数据集:iris 8 | 数据集数量:150 9 | ----------------------------- 10 | 运行结果: 11 | ARI:0.57 12 | 运行时长:0.0060s 13 | ''' 14 | 15 | import numpy as np 16 | import time 17 | import random 18 | from scipy.special import comb 19 | import matplotlib.pyplot as plt 20 | %matplotlib inline 21 | 22 | 23 | #定义加载数据的函数,这里以鸢尾花数据集为例 24 | def load_data(file): 25 | ''' 26 | INPUT: 27 | file - (str) 数据文件的路径 28 | 29 | OUTPUT: 30 | Xarray - (array) 特征数据数组 31 | Ylist - (list) 类别标签列表 32 | 33 | ''' 34 | Xlist = [] #定义一个列表用来保存每条数据 35 | Ylist = [] #定义一个列表用来保存每条数据的类别标签 36 | fr = open(file) 37 | for line in fr.readlines(): #逐行读取数据,鸢尾花数据集每一行表示一个鸢尾花的特征和类别标签,用逗号分隔 38 | cur = line.split(',') 39 | label = cur[-1] 40 | X = [float(x) for x in cur[:-1]] #用列表来表示一条特征数据 41 | Xlist.append(X) 42 | Ylist.append(label) 43 | Xarray = np.array(Xlist) #将特征数据转换为数组类型,方便之后的操作 44 | print('Data shape:', Xarray.shape) 45 | print('Length of labels:', len(Ylist)) 46 | return Xarray, Ylist 47 | 48 | 49 | #定义标准化函数,对每一列特征进行min-max标准化,将数据缩放到0-1之间 50 | #标准化处理对于计算距离的机器学习方法是非常重要的,因为特征的尺度不同会导致计算出来的距离倾向于尺度大的特征,为保证距离对每一列特征都是公平的,必须将所有特征缩放到同一尺度范围内 51 | def Normalize(Xarray): 52 | ''' 53 | INPUT: 54 | Xarray - (array) 特征数据数组 55 | 56 | OUTPUT: 57 | Xarray - (array) 标准化处理后的特征数据数组 58 | 59 | ''' 60 | for f in range(Xarray.shape[1]): 61 | maxf = np.max(Xarray[:, f]) 62 | minf = np.min(Xarray[:, f]) 63 | for n in range(Xarray.shape[0]): 64 | Xarray[n][f] = (Xarray[n][f]-minf) / (maxf-minf) 65 | return Xarray 66 | 67 | 68 | #定义计算两条数据间的距离的函数,这里计算的是欧式距离 69 | def cal_distance(xi, xj): 70 | ''' 71 | INPUT: 72 | Xi - (array) 第i条特征数据 73 | Xj - (array) 第j条特征数据 74 | 75 | OUTPUT: 76 | dist - (float) 两条数据的欧式距离 77 | 78 | ''' 79 | dist = 0 80 | for col in range(len(xi)): 81 | dist += (xi[col]-xj[col]) ** 2 82 | dist = math.sqrt(dist) 83 | return dist 84 | 85 | 86 | #定义计算类中心的函数,以当前类中所包含数据的各个特征均值作为新的新的类中心 87 | def cal_groupcenter(group, Xarray): 88 | ''' 89 | INPUT: 90 | group - (list) 类所包含的数据列表 91 | Xarray - (array) 特征数据数组 92 | 93 | OUTPUT: 94 | center - (array) 新的类中心 95 | 96 | ''' 97 | center = np.zeros(Xarray.shape[1]) 98 | for i in range(Xarray.shape[1]): 99 | for n in group: 100 | center[i] += Xarray[n][i] #计算当前类中第i个特征的数据之和 101 | center = center / Xarray.shape[0] #计算各个特征的均值 102 | return center 103 | 104 | 105 | #定义计算调整兰德系数(ARI)的函数,调整兰德系数是一种聚类方法的常用评估方法 106 | def Adjusted_Rand_Index(group_dict, Ylist, k): 107 | ''' 108 | INPUT: 109 | group_dict - (dict) 类别字典 110 | Ylist - (list) 类别标签列表 111 | k - (int) 设定的类别数 112 | 113 | OUTPUT: 114 | (int) 调整兰德系数 115 | 116 | ''' 117 | group_array = np.zeros((k, k)) #定义一个数组,用来保存聚类所产生的类别标签与给定的外部标签各类别之间共同包含的数据数量 118 | ylabel = list(set(Ylist)) #Ylist保存的标签为字符串,用ylabel来保存各个标签,在y_dict中类别以标签在ylabel列表中的索引值来表示类 119 | y_dict = {i:[] for i in range(k)} #定义一个空字典,用来保存外部标签中各类所包含的数据,结构与group_dict相同 120 | for i in range(len(Ylist)): 121 | y_dict[ylabel.index(Ylist[i])].append(i) 122 | #循环计算group_array的值 123 | for i in range(k): 124 | for j in range(k): 125 | for n in range(len(Ylist)): 126 | if n in group_dict[i] and n in y_dict[j]: 127 | group_array[i][j] += 1 #如果数据n同时在group_dict的类别i和y_dict的类别j中,group_array[i][j]的数值加一 128 | RI = 0 #定义兰德系数(RI) 129 | sum_i = np.zeros(k) #定义一个数组,用于保存聚类结果group_dict中每一类的个数 130 | sum_j = np.zeros(k) #定义一个数组,用于保存外部标签y_dict中每一类的个数 131 | for i in range(k): 132 | for j in range(k): 133 | sum_i[i] += group_array[i][j] 134 | sum_j[j] += group_array[i][j] 135 | if group_array[i][j] >= 2: 136 | RI += comb(group_array[i][j], 2) #comb用于计算group_array[i][j]中两两组合的组合数 137 | ci = 0 #ci保存聚类结果中同一类中的两两组合数之和 138 | cj = 0 #cj保存外部标签中同一类中的两两组合数之和 139 | for i in range(k): 140 | if sum_i[i] >= 2: 141 | ci += comb(sum_i[i], 2) 142 | for j in range(k): 143 | if sum_j[j] >= 2: 144 | cj += comb(sum_j[j], 2) 145 | E_RI = ci * cj / comb(len(Ylist), 2) #计算RI的期望 146 | max_RI = (ci + cj) / 2 #计算RI的最大值 147 | return (RI-E_RI) / (max_RI-E_RI) #返回调整兰德系数的值 148 | 149 | 150 | #定义k均值聚类函数 151 | def Kmeans(Xarray, k, iters): 152 | ''' 153 | INPUT: 154 | Xarray - (array) 特征数据数组 155 | k - (int) 设定的类别数 156 | iters - (int) 设定的迭代次数 157 | 158 | OUTPUT: 159 | group_dict - (dict) 类别字典 160 | scores - (int) 每次迭代的ARI得分列表 161 | 162 | ''' 163 | center_inds = random.sample(range(Xarray.shape[0]), k) #从特征数据中随机抽取k个数据索引 164 | centers = [Xarray[ci] for ci in center_inds] #将这k个数据索引所对应的特征数据作为初始的k个聚类中心 165 | scores = [] #定义一个空列表用来保存每次迭代的ARI得分 166 | for i in range(iters): 167 | group_dict = {i:[] for i in range(k)} #定义一个空字典,用于保存聚类所产生的所有类别,其中字典的键为类别标签,值为类别所包含的数据列表,以索引表示每条数据 168 | print('{}/{}'.format(i+1, iters)) 169 | #循环计算每条数据到各个聚类中心的距离 170 | for n in range(Xarray.shape[0]): 171 | dists = [] #保存第n条数据到各个聚类中心的距离 172 | for ci in range(k): 173 | dist = cal_distance(Xarray[n], centers[ci]) 174 | dists.append(dist) 175 | g = dists.index(min(dists)) #取距离最近的中心所在的类 176 | group_dict[g].append(n) #将第n条数据的索引n保存到g类 177 | print(group_dict) 178 | for i in range(k): 179 | centers[i] = cal_groupcenter(group_dict[i], Xarray) #根据每一类所包含的数据重新计算类中心 180 | scores.append(Adjusted_Rand_Index(group_dict, Ylist, k)) #将该轮迭代的ARI得分保存到scores列表 181 | return group_dict, scores 182 | 183 | 184 | if __name__ == "__main__": 185 | Xarray, Ylist = load_data('..\iris.data') #加载数据 186 | start = time.time() #保存开始时间 187 | Xarray = Normalize(Xarray) #对特征数据进行标准化处理 188 | k = 3 #设定聚类数为3 189 | iters = 2 #设定迭代次数为2 190 | group_dict, scores = Kmeans(Xarray, k, iters) #进行k均值聚类 191 | end = time.time() #保存结束时间 192 | print('Time:', end-start) 193 | plt.plot(range(iters), scores) #绘制ARI得分折线图 194 | -------------------------------------------------------------------------------- /Clustering/iris.data: -------------------------------------------------------------------------------- 1 | 5.1,3.5,1.4,0.2,Iris-setosa 2 | 4.9,3.0,1.4,0.2,Iris-setosa 3 | 4.7,3.2,1.3,0.2,Iris-setosa 4 | 4.6,3.1,1.5,0.2,Iris-setosa 5 | 5.0,3.6,1.4,0.2,Iris-setosa 6 | 5.4,3.9,1.7,0.4,Iris-setosa 7 | 4.6,3.4,1.4,0.3,Iris-setosa 8 | 5.0,3.4,1.5,0.2,Iris-setosa 9 | 4.4,2.9,1.4,0.2,Iris-setosa 10 | 4.9,3.1,1.5,0.1,Iris-setosa 11 | 5.4,3.7,1.5,0.2,Iris-setosa 12 | 4.8,3.4,1.6,0.2,Iris-setosa 13 | 4.8,3.0,1.4,0.1,Iris-setosa 14 | 4.3,3.0,1.1,0.1,Iris-setosa 15 | 5.8,4.0,1.2,0.2,Iris-setosa 16 | 5.7,4.4,1.5,0.4,Iris-setosa 17 | 5.4,3.9,1.3,0.4,Iris-setosa 18 | 5.1,3.5,1.4,0.3,Iris-setosa 19 | 5.7,3.8,1.7,0.3,Iris-setosa 20 | 5.1,3.8,1.5,0.3,Iris-setosa 21 | 5.4,3.4,1.7,0.2,Iris-setosa 22 | 5.1,3.7,1.5,0.4,Iris-setosa 23 | 4.6,3.6,1.0,0.2,Iris-setosa 24 | 5.1,3.3,1.7,0.5,Iris-setosa 25 | 4.8,3.4,1.9,0.2,Iris-setosa 26 | 5.0,3.0,1.6,0.2,Iris-setosa 27 | 5.0,3.4,1.6,0.4,Iris-setosa 28 | 5.2,3.5,1.5,0.2,Iris-setosa 29 | 5.2,3.4,1.4,0.2,Iris-setosa 30 | 4.7,3.2,1.6,0.2,Iris-setosa 31 | 4.8,3.1,1.6,0.2,Iris-setosa 32 | 5.4,3.4,1.5,0.4,Iris-setosa 33 | 5.2,4.1,1.5,0.1,Iris-setosa 34 | 5.5,4.2,1.4,0.2,Iris-setosa 35 | 4.9,3.1,1.5,0.1,Iris-setosa 36 | 5.0,3.2,1.2,0.2,Iris-setosa 37 | 5.5,3.5,1.3,0.2,Iris-setosa 38 | 4.9,3.1,1.5,0.1,Iris-setosa 39 | 4.4,3.0,1.3,0.2,Iris-setosa 40 | 5.1,3.4,1.5,0.2,Iris-setosa 41 | 5.0,3.5,1.3,0.3,Iris-setosa 42 | 4.5,2.3,1.3,0.3,Iris-setosa 43 | 4.4,3.2,1.3,0.2,Iris-setosa 44 | 5.0,3.5,1.6,0.6,Iris-setosa 45 | 5.1,3.8,1.9,0.4,Iris-setosa 46 | 4.8,3.0,1.4,0.3,Iris-setosa 47 | 5.1,3.8,1.6,0.2,Iris-setosa 48 | 4.6,3.2,1.4,0.2,Iris-setosa 49 | 5.3,3.7,1.5,0.2,Iris-setosa 50 | 5.0,3.3,1.4,0.2,Iris-setosa 51 | 7.0,3.2,4.7,1.4,Iris-versicolor 52 | 6.4,3.2,4.5,1.5,Iris-versicolor 53 | 6.9,3.1,4.9,1.5,Iris-versicolor 54 | 5.5,2.3,4.0,1.3,Iris-versicolor 55 | 6.5,2.8,4.6,1.5,Iris-versicolor 56 | 5.7,2.8,4.5,1.3,Iris-versicolor 57 | 6.3,3.3,4.7,1.6,Iris-versicolor 58 | 4.9,2.4,3.3,1.0,Iris-versicolor 59 | 6.6,2.9,4.6,1.3,Iris-versicolor 60 | 5.2,2.7,3.9,1.4,Iris-versicolor 61 | 5.0,2.0,3.5,1.0,Iris-versicolor 62 | 5.9,3.0,4.2,1.5,Iris-versicolor 63 | 6.0,2.2,4.0,1.0,Iris-versicolor 64 | 6.1,2.9,4.7,1.4,Iris-versicolor 65 | 5.6,2.9,3.6,1.3,Iris-versicolor 66 | 6.7,3.1,4.4,1.4,Iris-versicolor 67 | 5.6,3.0,4.5,1.5,Iris-versicolor 68 | 5.8,2.7,4.1,1.0,Iris-versicolor 69 | 6.2,2.2,4.5,1.5,Iris-versicolor 70 | 5.6,2.5,3.9,1.1,Iris-versicolor 71 | 5.9,3.2,4.8,1.8,Iris-versicolor 72 | 6.1,2.8,4.0,1.3,Iris-versicolor 73 | 6.3,2.5,4.9,1.5,Iris-versicolor 74 | 6.1,2.8,4.7,1.2,Iris-versicolor 75 | 6.4,2.9,4.3,1.3,Iris-versicolor 76 | 6.6,3.0,4.4,1.4,Iris-versicolor 77 | 6.8,2.8,4.8,1.4,Iris-versicolor 78 | 6.7,3.0,5.0,1.7,Iris-versicolor 79 | 6.0,2.9,4.5,1.5,Iris-versicolor 80 | 5.7,2.6,3.5,1.0,Iris-versicolor 81 | 5.5,2.4,3.8,1.1,Iris-versicolor 82 | 5.5,2.4,3.7,1.0,Iris-versicolor 83 | 5.8,2.7,3.9,1.2,Iris-versicolor 84 | 6.0,2.7,5.1,1.6,Iris-versicolor 85 | 5.4,3.0,4.5,1.5,Iris-versicolor 86 | 6.0,3.4,4.5,1.6,Iris-versicolor 87 | 6.7,3.1,4.7,1.5,Iris-versicolor 88 | 6.3,2.3,4.4,1.3,Iris-versicolor 89 | 5.6,3.0,4.1,1.3,Iris-versicolor 90 | 5.5,2.5,4.0,1.3,Iris-versicolor 91 | 5.5,2.6,4.4,1.2,Iris-versicolor 92 | 6.1,3.0,4.6,1.4,Iris-versicolor 93 | 5.8,2.6,4.0,1.2,Iris-versicolor 94 | 5.0,2.3,3.3,1.0,Iris-versicolor 95 | 5.6,2.7,4.2,1.3,Iris-versicolor 96 | 5.7,3.0,4.2,1.2,Iris-versicolor 97 | 5.7,2.9,4.2,1.3,Iris-versicolor 98 | 6.2,2.9,4.3,1.3,Iris-versicolor 99 | 5.1,2.5,3.0,1.1,Iris-versicolor 100 | 5.7,2.8,4.1,1.3,Iris-versicolor 101 | 6.3,3.3,6.0,2.5,Iris-virginica 102 | 5.8,2.7,5.1,1.9,Iris-virginica 103 | 7.1,3.0,5.9,2.1,Iris-virginica 104 | 6.3,2.9,5.6,1.8,Iris-virginica 105 | 6.5,3.0,5.8,2.2,Iris-virginica 106 | 7.6,3.0,6.6,2.1,Iris-virginica 107 | 4.9,2.5,4.5,1.7,Iris-virginica 108 | 7.3,2.9,6.3,1.8,Iris-virginica 109 | 6.7,2.5,5.8,1.8,Iris-virginica 110 | 7.2,3.6,6.1,2.5,Iris-virginica 111 | 6.5,3.2,5.1,2.0,Iris-virginica 112 | 6.4,2.7,5.3,1.9,Iris-virginica 113 | 6.8,3.0,5.5,2.1,Iris-virginica 114 | 5.7,2.5,5.0,2.0,Iris-virginica 115 | 5.8,2.8,5.1,2.4,Iris-virginica 116 | 6.4,3.2,5.3,2.3,Iris-virginica 117 | 6.5,3.0,5.5,1.8,Iris-virginica 118 | 7.7,3.8,6.7,2.2,Iris-virginica 119 | 7.7,2.6,6.9,2.3,Iris-virginica 120 | 6.0,2.2,5.0,1.5,Iris-virginica 121 | 6.9,3.2,5.7,2.3,Iris-virginica 122 | 5.6,2.8,4.9,2.0,Iris-virginica 123 | 7.7,2.8,6.7,2.0,Iris-virginica 124 | 6.3,2.7,4.9,1.8,Iris-virginica 125 | 6.7,3.3,5.7,2.1,Iris-virginica 126 | 7.2,3.2,6.0,1.8,Iris-virginica 127 | 6.2,2.8,4.8,1.8,Iris-virginica 128 | 6.1,3.0,4.9,1.8,Iris-virginica 129 | 6.4,2.8,5.6,2.1,Iris-virginica 130 | 7.2,3.0,5.8,1.6,Iris-virginica 131 | 7.4,2.8,6.1,1.9,Iris-virginica 132 | 7.9,3.8,6.4,2.0,Iris-virginica 133 | 6.4,2.8,5.6,2.2,Iris-virginica 134 | 6.3,2.8,5.1,1.5,Iris-virginica 135 | 6.1,2.6,5.6,1.4,Iris-virginica 136 | 7.7,3.0,6.1,2.3,Iris-virginica 137 | 6.3,3.4,5.6,2.4,Iris-virginica 138 | 6.4,3.1,5.5,1.8,Iris-virginica 139 | 6.0,3.0,4.8,1.8,Iris-virginica 140 | 6.9,3.1,5.4,2.1,Iris-virginica 141 | 6.7,3.1,5.6,2.4,Iris-virginica 142 | 6.9,3.1,5.1,2.3,Iris-virginica 143 | 5.8,2.7,5.1,1.9,Iris-virginica 144 | 6.8,3.2,5.9,2.3,Iris-virginica 145 | 6.7,3.3,5.7,2.5,Iris-virginica 146 | 6.7,3.0,5.2,2.3,Iris-virginica 147 | 6.3,2.5,5.0,1.9,Iris-virginica 148 | 6.5,3.0,5.2,2.0,Iris-virginica 149 | 6.2,3.4,5.4,2.3,Iris-virginica 150 | 5.9,3.0,5.1,1.8,Iris-virginica 151 | -------------------------------------------------------------------------------- /CodePic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dod-o/Statistical-Learning-Method_Code/cd1d28337d223bc164e4949c167958634f409939/CodePic.png -------------------------------------------------------------------------------- /DecisionTree/DecisionTree.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | #Author:Dodo 3 | #Date:2018-11-21 4 | #Email:lvtengchao@pku.edu.cn 5 | 6 | ''' 7 | 数据集:Mnist 8 | 训练集数量:60000 9 | 测试集数量:10000 10 | ------------------------------ 11 | 运行结果:ID3(未剪枝) 12 | 正确率:85.9% 13 | 运行时长:356s 14 | ''' 15 | 16 | import time 17 | import numpy as np 18 | 19 | def loadData(fileName): 20 | ''' 21 | 加载文件 22 | :param fileName:要加载的文件路径 23 | :return: 数据集和标签集 24 | ''' 25 | #存放数据及标记 26 | dataArr = []; labelArr = [] 27 | #读取文件 28 | fr = open(fileName) 29 | #遍历文件中的每一行 30 | for line in fr.readlines(): 31 | #获取当前行,并按“,”切割成字段放入列表中 32 | #strip:去掉每行字符串首尾指定的字符(默认空格或换行符) 33 | #split:按照指定的字符将字符串切割成每个字段,返回列表形式 34 | curLine = line.strip().split(',') 35 | #将每行中除标记外的数据放入数据集中(curLine[0]为标记信息) 36 | #在放入的同时将原先字符串形式的数据转换为整型 37 | #此外将数据进行了二值化处理,大于128的转换成1,小于的转换成0,方便后续计算 38 | dataArr.append([int(int(num) > 128) for num in curLine[1:]]) 39 | #将标记信息放入标记集中 40 | #放入的同时将标记转换为整型 41 | labelArr.append(int(curLine[0])) 42 | #返回数据集和标记 43 | return dataArr, labelArr 44 | 45 | def majorClass(labelArr): 46 | ''' 47 | 找到当前标签集中占数目最大的标签 48 | :param labelArr: 标签集 49 | :return: 最大的标签 50 | ''' 51 | #建立字典,用于不同类别的标签技术 52 | classDict = {} 53 | #遍历所有标签 54 | for i in range(len(labelArr)): 55 | #当第一次遇到A标签时,字典内还没有A标签,这时候直接幅值加1是错误的, 56 | #所以需要判断字典中是否有该键,没有则创建,有就直接自增 57 | if labelArr[i] in classDict.keys(): 58 | # 若在字典中存在该标签,则直接加1 59 | classDict[labelArr[i]] += 1 60 | else: 61 | #若无该标签,设初值为1,表示出现了1次了 62 | classDict[labelArr[i]] = 1 63 | #对字典依据值进行降序排序 64 | classSort = sorted(classDict.items(), key=lambda x: x[1], reverse=True) 65 | #返回最大一项的标签,即占数目最多的标签 66 | return classSort[0][0] 67 | 68 | def calc_H_D(trainLabelArr): 69 | ''' 70 | 计算数据集D的经验熵,参考公式5.7 经验熵的计算 71 | :param trainLabelArr:当前数据集的标签集 72 | :return: 经验熵 73 | ''' 74 | #初始化为0 75 | H_D = 0 76 | #将当前所有标签放入集合中,这样只要有的标签都会在集合中出现,且出现一次。 77 | #遍历该集合就可以遍历所有出现过的标记并计算其Ck 78 | #这么做有一个很重要的原因:首先假设一个背景,当前标签集中有一些标记已经没有了,比如说标签集中 79 | #没有0(这是很正常的,说明当前分支不存在这个标签)。 式5.7中有一项Ck,那按照式中的针对不同标签k 80 | #计算Cl和D并求和时,由于没有0,那么C0=0,此时C0/D0=0,log2(C0/D0) = log2(0),事实上0并不在log的 81 | #定义区间内,出现了问题 82 | #所以使用集合的方式先知道当前标签中都出现了那些标签,随后对每个标签进行计算,如果没出现的标签那一项就 83 | #不在经验熵中出现(未参与,对经验熵无影响),保证log的计算能一直有定义 84 | trainLabelSet = set([label for label in trainLabelArr]) 85 | #遍历每一个出现过的标签 86 | for i in trainLabelSet: 87 | #计算|Ck|/|D| 88 | #trainLabelArr == i:当前标签集中为该标签的的位置 89 | #例如a = [1, 0, 0, 1], c = (a == 1): c == [True, false, false, True] 90 | #trainLabelArr[trainLabelArr == i]:获得为指定标签的样本 91 | #trainLabelArr[trainLabelArr == i].size:获得为指定标签的样本的大小,即标签为i的样本 92 | #数量,就是|Ck| 93 | #trainLabelArr.size:整个标签集的数量(也就是样本集的数量),即|D| 94 | p = trainLabelArr[trainLabelArr == i].size / trainLabelArr.size 95 | #对经验熵的每一项累加求和 96 | H_D += -1 * p * np.log2(p) 97 | 98 | #返回经验熵 99 | return H_D 100 | 101 | def calcH_D_A(trainDataArr_DevFeature, trainLabelArr): 102 | ''' 103 | 计算经验条件熵 104 | :param trainDataArr_DevFeature:切割后只有feature那列数据的数组 105 | :param trainLabelArr: 标签集数组 106 | :return: 经验条件熵 107 | ''' 108 | #初始为0 109 | H_D_A = 0 110 | #在featue那列放入集合中,是为了根据集合中的数目知道该feature目前可取值数目是多少 111 | trainDataSet = set([label for label in trainDataArr_DevFeature]) 112 | 113 | #对于每一个特征取值遍历计算条件经验熵的每一项 114 | for i in trainDataSet: 115 | #计算H(D|A) 116 | #trainDataArr_DevFeature[trainDataArr_DevFeature == i].size / trainDataArr_DevFeature.size:|Di| / |D| 117 | #calc_H_D(trainLabelArr[trainDataArr_DevFeature == i]):H(Di) 118 | H_D_A += trainDataArr_DevFeature[trainDataArr_DevFeature == i].size / trainDataArr_DevFeature.size \ 119 | * calc_H_D(trainLabelArr[trainDataArr_DevFeature == i]) 120 | #返回得出的条件经验熵 121 | return H_D_A 122 | 123 | def calcBestFeature(trainDataList, trainLabelList): 124 | ''' 125 | 计算信息增益最大的特征 126 | :param trainDataList: 当前数据集 127 | :param trainLabelList: 当前标签集 128 | :return: 信息增益最大的特征及最大信息增益值 129 | ''' 130 | #将数据集和标签集转换为数组形式 131 | trainDataArr = np.array(trainDataList) 132 | trainLabelArr = np.array(trainLabelList) 133 | 134 | #获取当前特征数目,也就是数据集的横轴大小 135 | featureNum = trainDataArr.shape[1] 136 | 137 | #初始化最大信息增益 138 | maxG_D_A = -1 139 | #初始化最大信息增益的特征 140 | maxFeature = -1 141 | 142 | #“5.2.2 信息增益”中“算法5.1(信息增益的算法)”第一步: 143 | #1.计算数据集D的经验熵H(D) 144 | H_D = calc_H_D(trainLabelArr) 145 | #对每一个特征进行遍历计算 146 | for feature in range(featureNum): 147 | #2.计算条件经验熵H(D|A) 148 | #由于条件经验熵的计算过程中只涉及到标签以及当前特征,为了提高运算速度(全部样本 149 | #做成的矩阵运算速度太慢,需要剔除不需要的部分),将数据集矩阵进行切割 150 | #数据集在初始时刻是一个Arr = 60000*784的矩阵,针对当前要计算的feature,在训练集中切割下 151 | #Arr[:, feature]这么一条来,因为后续计算中数据集中只用到这个(没明白的跟着算一遍例5.2) 152 | #trainDataArr[:, feature]:在数据集中切割下这么一条 153 | #trainDataArr[:, feature].flat:将这么一条转换成竖着的列表 154 | #np.array(trainDataArr[:, feature].flat):再转换成一条竖着的矩阵,大小为60000*1(只是初始是 155 | #这么大,运行过程中是依据当前数据集大小动态变的) 156 | trainDataArr_DevideByFeature = np.array(trainDataArr[:, feature].flat) 157 | #3.计算信息增益G(D|A) G(D|A) = H(D) - H(D | A) 158 | G_D_A = H_D - calcH_D_A(trainDataArr_DevideByFeature, trainLabelArr) 159 | #不断更新最大的信息增益以及对应的feature 160 | if G_D_A > maxG_D_A: 161 | maxG_D_A = G_D_A 162 | maxFeature = feature 163 | return maxFeature, maxG_D_A 164 | 165 | 166 | def getSubDataArr(trainDataArr, trainLabelArr, A, a): 167 | ''' 168 | 更新数据集和标签集 169 | :param trainDataArr:要更新的数据集 170 | :param trainLabelArr: 要更新的标签集 171 | :param A: 要去除的特征索引 172 | :param a: 当data[A]== a时,说明该行样本时要保留的 173 | :return: 新的数据集和标签集 174 | ''' 175 | #返回的数据集 176 | retDataArr = [] 177 | #返回的标签集 178 | retLabelArr = [] 179 | #对当前数据的每一个样本进行遍历 180 | for i in range(len(trainDataArr)): 181 | #如果当前样本的特征为指定特征值a 182 | if trainDataArr[i][A] == a: 183 | #那么将该样本的第A个特征切割掉,放入返回的数据集中 184 | retDataArr.append(trainDataArr[i][0:A] + trainDataArr[i][A+1:]) 185 | #将该样本的标签放入返回标签集中 186 | retLabelArr.append(trainLabelArr[i]) 187 | #返回新的数据集和标签集 188 | return retDataArr, retLabelArr 189 | 190 | def createTree(*dataSet): 191 | ''' 192 | 递归创建决策树 193 | :param dataSet:(trainDataList, trainLabelList) <<-- 元祖形式 194 | :return:新的子节点或该叶子节点的值 195 | ''' 196 | #设置Epsilon,“5.3.1 ID3算法”第4步提到需要将信息增益与阈值Epsilon比较,若小于则 197 | #直接处理后返回T 198 | #该值的大小在设置上并未考虑太多,观察到信息增益前期在运行中为0.3左右,所以设置了0.1 199 | Epsilon = 0.1 200 | #从参数中获取trainDataList和trainLabelList 201 | #之所以使用元祖作为参数,是由于后续递归调用时直数据集需要对某个特征进行切割,在函数递归 202 | #调用上直接将切割函数的返回值放入递归调用中,而函数的返回值形式是元祖的,等看到这个函数 203 | #的底部就会明白了,这样子的用处就是写程序的时候简洁一点,方便一点 204 | trainDataList = dataSet[0][0] 205 | trainLabelList = dataSet[0][1] 206 | #打印信息:开始一个子节点创建,打印当前特征向量数目及当前剩余样本数目 207 | print('start a node', len(trainDataList[0]), len(trainLabelList)) 208 | 209 | #将标签放入一个字典中,当前样本有多少类,在字典中就会有多少项 210 | #也相当于去重,多次出现的标签就留一次。举个例子,假如处理结束后字典的长度为1,那说明所有的样本 211 | #都是同一个标签,那就可以直接返回该标签了,不需要再生成子节点了。 212 | classDict = {i for i in trainLabelList} 213 | #如果D中所有实例属于同一类Ck,则置T为单节点数,并将Ck作为该节点的类,返回T 214 | #即若所有样本的标签一致,也就不需要再分化,返回标记作为该节点的值,返回后这就是一个叶子节点 215 | if len(classDict) == 1: 216 | #因为所有样本都是一致的,在标签集中随便拿一个标签返回都行,这里用的第0个(因为你并不知道 217 | #当前标签集的长度是多少,但运行中所有标签只要有长度都会有第0位。 218 | return trainLabelList[0] 219 | 220 | #如果A为空集,则置T为单节点数,并将D中实例数最大的类Ck作为该节点的类,返回T 221 | #即如果已经没有特征可以用来再分化了,就返回占大多数的类别 222 | if len(trainDataList[0]) == 0: 223 | #返回当前标签集中占数目最大的标签 224 | return majorClass(trainLabelList) 225 | 226 | #否则,按式5.10计算A中个特征值的信息增益,选择信息增益最大的特征Ag 227 | Ag, EpsilonGet = calcBestFeature(trainDataList, trainLabelList) 228 | 229 | #如果Ag的信息增益比小于阈值Epsilon,则置T为单节点树,并将D中实例数最大的类Ck 230 | #作为该节点的类,返回T 231 | if EpsilonGet < Epsilon: 232 | return majorClass(trainLabelList) 233 | 234 | #否则,对Ag的每一可能值ai,依Ag=ai将D分割为若干非空子集Di,将Di中实例数最大的 235 | # 类作为标记,构建子节点,由节点及其子节点构成树T,返回T 236 | treeDict = {Ag:{}} 237 | #特征值为0时,进入0分支 238 | #getSubDataArr(trainDataList, trainLabelList, Ag, 0):在当前数据集中切割当前feature,返回新的数据集和标签集 239 | treeDict[Ag][0] = createTree(getSubDataArr(trainDataList, trainLabelList, Ag, 0)) 240 | treeDict[Ag][1] = createTree(getSubDataArr(trainDataList, trainLabelList, Ag, 1)) 241 | 242 | return treeDict 243 | 244 | def predict(testDataList, tree): 245 | ''' 246 | 预测标签 247 | :param testDataList:样本 248 | :param tree: 决策树 249 | :return: 预测结果 250 | ''' 251 | # treeDict = copy.deepcopy(tree) 252 | 253 | #死循环,直到找到一个有效地分类 254 | while True: 255 | #因为有时候当前字典只有一个节点 256 | #例如{73: {0: {74:6}}}看起来节点很多,但是对于字典的最顶层来说,只有73一个key,其余都是value 257 | #若还是采用for来读取的话不太合适,所以使用下行这种方式读取key和value 258 | (key, value), = tree.items() 259 | #如果当前的value是字典,说明还需要遍历下去 260 | if type(tree[key]).__name__ == 'dict': 261 | #获取目前所在节点的feature值,需要在样本中删除该feature 262 | #因为在创建树的过程中,feature的索引值永远是对于当时剩余的feature来设置的 263 | #所以需要不断地删除已经用掉的特征,保证索引相对位置的一致性 264 | dataVal = testDataList[key] 265 | del testDataList[key] 266 | #将tree更新为其子节点的字典 267 | tree = value[dataVal] 268 | #如果当前节点的子节点的值是int,就直接返回该int值 269 | #例如{403: {0: 7, 1: {297:7}},dataVal=0 270 | #此时上一行tree = value[dataVal],将tree定位到了7,而7不再是一个字典了, 271 | #这里就可以直接返回7了,如果tree = value[1],那就是一个新的子节点,需要继续遍历下去 272 | if type(tree).__name__ == 'int': 273 | #返回该节点值,也就是分类值 274 | return tree 275 | else: 276 | #如果当前value不是字典,那就返回分类值 277 | return value 278 | 279 | def model_test(testDataList, testLabelList, tree): 280 | ''' 281 | 测试准确率 282 | :param testDataList:待测试数据集 283 | :param testLabelList: 待测试标签集 284 | :param tree: 训练集生成的树 285 | :return: 准确率 286 | ''' 287 | #错误次数计数 288 | errorCnt = 0 289 | #遍历测试集中每一个测试样本 290 | for i in range(len(testDataList)): 291 | #判断预测与标签中结果是否一致 292 | if testLabelList[i] != predict(testDataList[i], tree): 293 | errorCnt += 1 294 | #返回准确率 295 | return 1 - errorCnt / len(testDataList) 296 | 297 | if __name__ == '__main__': 298 | #开始时间 299 | start = time.time() 300 | 301 | # 获取训练集 302 | trainDataList, trainLabelList = loadData('../Mnist/mnist_train.csv') 303 | # 获取测试集 304 | testDataList, testLabelList = loadData('../Mnist/mnist_test.csv') 305 | 306 | #创建决策树 307 | print('start create tree') 308 | tree = createTree((trainDataList, trainLabelList)) 309 | print('tree is:', tree) 310 | 311 | #测试准确率 312 | print('start test') 313 | accur = model_test(testDataList, testLabelList, tree) 314 | print('the accur is:', accur) 315 | 316 | #结束时间 317 | end = time.time() 318 | print('time span:', end - start) 319 | -------------------------------------------------------------------------------- /EM/EM.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # Author:Dodo 3 | # Date:2018-12-8 4 | # Email:lvtengchao@pku.edu.cn 5 | # Blog:www.pkudodo.com 6 | 7 | ''' 8 | 数据集:伪造数据集(两个高斯分布混合) 9 | 数据集长度:1000 10 | ------------------------------ 11 | 运行结果: 12 | ---------------------------- 13 | the Parameters set is: 14 | alpha0:0.3, mu0:0.7, sigmod0:-2.0, alpha1:0.5, mu1:0.5, sigmod1:1.0 15 | ---------------------------- 16 | the Parameters predict is: 17 | alpha0:0.4, mu0:0.6, sigmod0:-1.7, alpha1:0.7, mu1:0.7, sigmod1:0.9 18 | ---------------------------- 19 | ''' 20 | 21 | import numpy as np 22 | import random 23 | import math 24 | import time 25 | 26 | def loadData(mu0, sigma0, mu1, sigma1, alpha0, alpha1): 27 | ''' 28 | 初始化数据集 29 | 这里通过服从高斯分布的随机函数来伪造数据集 30 | :param mu0: 高斯0的均值 31 | :param sigma0: 高斯0的方差 32 | :param mu1: 高斯1的均值 33 | :param sigma1: 高斯1的方差 34 | :param alpha0: 高斯0的系数 35 | :param alpha1: 高斯1的系数 36 | :return: 混合了两个高斯分布的数据 37 | ''' 38 | #定义数据集长度为1000 39 | length = 1000 40 | 41 | #初始化第一个高斯分布,生成数据,数据长度为length * alpha系数,以此来 42 | #满足alpha的作用 43 | data0 = np.random.normal(mu0, sigma0, int(length * alpha0)) 44 | #第二个高斯分布的数据 45 | data1 = np.random.normal(mu1, sigma1, int(length * alpha1)) 46 | 47 | #初始化总数据集 48 | #两个高斯分布的数据混合后会放在该数据集中返回 49 | dataSet = [] 50 | #将第一个数据集的内容添加进去 51 | dataSet.extend(data0) 52 | #添加第二个数据集的数据 53 | dataSet.extend(data1) 54 | #对总的数据集进行打乱(其实不打乱也没事,只不过打乱一下直观上让人感觉已经混合了 55 | # 读者可以将下面这句话屏蔽以后看看效果是否有差别) 56 | random.shuffle(dataSet) 57 | 58 | #返回伪造好的数据集 59 | return dataSet 60 | 61 | def calcGauss(dataSetArr, mu, sigmod): 62 | ''' 63 | 根据高斯密度函数计算值 64 | 依据:“9.3.1 高斯混合模型” 式9.25 65 | 注:在公式中y是一个实数,但是在EM算法中(见算法9.2的E步),需要对每个j 66 | 都求一次yjk,在本实例中有1000个可观测数据,因此需要计算1000次。考虑到 67 | 在E步时进行1000次高斯计算,程序上比较不简洁,因此这里的y是向量,在numpy 68 | 的exp中如果exp内部值为向量,则对向量中每个值进行exp,输出仍是向量的形式。 69 | 所以使用向量的形式1次计算即可将所有计算结果得出,程序上较为简洁 70 | :param dataSetArr: 可观测数据集 71 | :param mu: 均值 72 | :param sigmod: 方差 73 | :return: 整个可观测数据集的高斯分布密度(向量形式) 74 | ''' 75 | #计算过程就是依据式9.25写的,没有别的花样 76 | result = (1 / (math.sqrt(2 * math.pi) * sigmod)) * \ 77 | np.exp(-1 * (dataSetArr - mu) * (dataSetArr - mu) / (2 * sigmod**2)) 78 | #返回结果 79 | return result 80 | 81 | 82 | def E_step(dataSetArr, alpha0, mu0, sigmod0, alpha1, mu1, sigmod1): 83 | ''' 84 | EM算法中的E步 85 | 依据当前模型参数,计算分模型k对观数据y的响应度 86 | :param dataSetArr: 可观测数据y 87 | :param alpha0: 高斯模型0的系数 88 | :param mu0: 高斯模型0的均值 89 | :param sigmod0: 高斯模型0的方差 90 | :param alpha1: 高斯模型1的系数 91 | :param mu1: 高斯模型1的均值 92 | :param sigmod1: 高斯模型1的方差 93 | :return: 两个模型各自的响应度 94 | ''' 95 | #计算y0的响应度 96 | #先计算模型0的响应度的分子 97 | gamma0 = alpha0 * calcGauss(dataSetArr, mu0, sigmod0) 98 | #模型1响应度的分子 99 | gamma1 = alpha1 * calcGauss(dataSetArr, mu1, sigmod1) 100 | 101 | #两者相加为E步中的分布 102 | sum = gamma0 + gamma1 103 | #各自相除,得到两个模型的响应度 104 | gamma0 = gamma0 / sum 105 | gamma1 = gamma1 / sum 106 | 107 | #返回两个模型响应度 108 | return gamma0, gamma1 109 | 110 | def M_step(muo, mu1, gamma0, gamma1, dataSetArr): 111 | #依据算法9.2计算各个值 112 | #这里没什么花样,对照书本公式看看这里就好了 113 | mu0_new = np.dot(gamma0, dataSetArr) / np.sum(gamma0) 114 | mu1_new = np.dot(gamma1, dataSetArr) / np.sum(gamma1) 115 | 116 | sigmod0_new = math.sqrt(np.dot(gamma0, (dataSetArr - muo)**2) / np.sum(gamma0)) 117 | sigmod1_new = math.sqrt(np.dot(gamma1, (dataSetArr - mu1)**2) / np.sum(gamma1)) 118 | 119 | alpha0_new = np.sum(gamma0) / len(gamma0) 120 | alpha1_new = np.sum(gamma1) / len(gamma1) 121 | 122 | #将更新的值返回 123 | return mu0_new, mu1_new, sigmod0_new, sigmod1_new, alpha0_new, alpha1_new 124 | 125 | 126 | def EM_Train(dataSetList, iter = 500): 127 | ''' 128 | 根据EM算法进行参数估计 129 | 算法依据“9.3.2 高斯混合模型参数估计的EM算法” 算法9.2 130 | :param dataSetList:数据集(可观测数据) 131 | :param iter: 迭代次数 132 | :return: 估计的参数 133 | ''' 134 | #将可观测数据y转换为数组形式,主要是为了方便后续运算 135 | dataSetArr = np.array(dataSetList) 136 | 137 | #步骤1:对参数取初值,开始迭代 138 | alpha0 = 0.5; mu0 = 0; sigmod0 = 1 139 | alpha1 = 0.5; mu1 = 1; sigmod1 = 1 140 | 141 | #开始迭代 142 | step = 0 143 | while (step < iter): 144 | #每次进入一次迭代后迭代次数加1 145 | step += 1 146 | #步骤2:E步:依据当前模型参数,计算分模型k对观测数据y的响应度 147 | gamma0, gamma1 = E_step(dataSetArr, alpha0, mu0, sigmod0, alpha1, mu1, sigmod1) 148 | #步骤3:M步 149 | mu0, mu1, sigmod0, sigmod1, alpha0, alpha1 = \ 150 | M_step(mu0, mu1, gamma0, gamma1, dataSetArr) 151 | 152 | #迭代结束后将更新后的各参数返回 153 | return alpha0, mu0, sigmod0, alpha1, mu1, sigmod1 154 | 155 | if __name__ == '__main__': 156 | start = time.time() 157 | 158 | #设置两个高斯模型进行混合,这里是初始化两个模型各自的参数 159 | #见“9.3 EM算法在高斯混合模型学习中的应用” 160 | # alpha是“9.3.1 高斯混合模型” 定义9.2中的系数α 161 | # mu0是均值μ 162 | # sigmod是方差σ 163 | #在设置上两个alpha的和必须为1,其他没有什么具体要求,符合高斯定义就可以 164 | alpha0 = 0.3; mu0 = -2; sigmod0 = 0.5 165 | alpha1 = 0.7; mu1 = 0.5; sigmod1 = 1 166 | 167 | #初始化数据集 168 | dataSetList = loadData(mu0, sigmod0, mu1, sigmod1, alpha0, alpha1) 169 | 170 | #打印设置的参数 171 | print('---------------------------') 172 | print('the Parameters set is:') 173 | print('alpha0:%.1f, mu0:%.1f, sigmod0:%.1f, alpha1:%.1f, mu1:%.1f, sigmod1:%.1f'%( 174 | alpha0, mu0, sigmod0, alpha1, mu1, sigmod1 175 | )) 176 | 177 | #开始EM算法,进行参数估计 178 | alpha0, mu0, sigmod0, alpha1, mu1, sigmod1 = EM_Train(dataSetList) 179 | 180 | #打印参数预测结果 181 | print('----------------------------') 182 | print('the Parameters predict is:') 183 | print('alpha0:%.1f, mu0:%.1f, sigmod0:%.1f, alpha1:%.1f, mu1:%.1f, sigmod1:%.1f' % ( 184 | alpha0, mu0, sigmod0, alpha1, mu1, sigmod1 185 | )) 186 | 187 | #打印时间 188 | print('----------------------------') 189 | print('time span:', time.time() - start) 190 | -------------------------------------------------------------------------------- /HMM/HMM.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | #Author:Dodo 3 | #Date:2018-12-10 4 | #Email:lvtengchao@pku.edu.cn 5 | #Blog:www.pkudodo.com 6 | ''' 7 | 数据集:人民日报1998年中文标注语料库 8 | ------------------------------ 9 | 运行结果: 10 | -------------------原文---------------------- 11 | 深圳有个打工者阅览室 12 | 去年12月,我在广东深圳市出差,听说南山区工商分局为打工者建了个免费图书阅览室,这件新鲜事引起了我的兴趣。 13 | 12月18日下午,我来到了这个阅览室。阅览室位于桂庙,临南油大道,是一间轻体房,面积约有40平方米,内部装修得整洁干净,四周的书架上摆满了书,并按政治、哲学、法律法规、文化教育、经济、科技、艺术、中国文学、外国文学等分类,屋中央有两排书架,上面也摆满了图书和杂志。一些打工青年或站或蹲,认真地阅读,不时有人到借阅台前办理借书或还书手续。南山区在深圳市西边,地处城乡结合部,外来打工者较多。去年2月,南山区工商分局局长王安全发现分局对面的公园里常有不少打工者业余时间闲逛,有时还滋扰生事。为了给这些打工者提供一个充实自己的场所,他提议由全分局工作人员捐款,兴建一个免费阅览室。领导带头,群众响应,大家捐款1.4万元,购买了近千册图书。3月6日,建在南头繁华的南新路和金鸡路交叉口的阅览室开放了。从此,这里每天都吸引了众多借书、看书的人们,其中不仅有打工者,还有机关干部、公司职员和个体户。到了夏天,由于阅览室所在地被工程征用,南山区工商分局便把阅览室迁到了桂庙。阅览室的管理人员是两名青年,男的叫张攀,女的叫赵阳。张攀自己就是湖北来的打工者,听说南山区工商分局办免费阅览室,便主动应聘来服务。阅览室每天从早9时开到晚10时,夜里张攀就住在这里。他谈起阅览室里的图书,翻着一本本的借阅名册,如数家珍,对图书和工作的挚爱之情溢于言表。我在这里碰到南山区华英大厦一位叫聂煜的女青年,她说她也是个打工者,由于春节探家回来后就要去市内工作,很留恋这里的这个免费阅览室,想抓紧时间多看些书,她还把自己买的几本杂志捐给了阅览室。在阅览室的捐书登记簿上,记录着这样的数字:工商系统内部捐书3550册,社会各界捐书250册。我在阅览室读到了这样几封感谢信:深圳瑞兴光学厂的王志明写道:“我们这些年轻人远离了家乡,来到繁华紧张的都市打工,辛劳之余,能有机会看书读报,感到特别充实。”深圳文光灯泡厂的江虹说:“南山区工商分局的干部职工捐款、捐书,给我们打工者提供良好的学习环境,鼓励我们求知上进,真是办了一件大好事,他们是我们打工者的知音。”(本报记者罗华) 14 | -------------------分词后---------------------- 15 | 深圳|有个|打|工者|阅览室 16 | 去年|12月|,|我|在|广东|深圳|市出|差|,|听|说|南|山区|工商|分局|为|打|工者|建了|个|免费|图书|阅览室|,|这件|新|鲜事|引起|了|我|的|兴趣|。 17 | 12月|18日|下午|,|我来|到|了|这个|阅览室|。|阅览|室位|于|桂庙|,|临南油|大道|,|是|一间|轻|体房|,|面积|约|有|40|平方|米|,|内|部装|修得|整洁|干净|,|四|周|的|书架|上|摆满|了|书|,|并|按|政治|、|哲学|、|法律|法规|、|文化|教育|、|经济|、|科技|、|艺术|、|中国|文学|、|外国|文学|等|分类|,|屋|中央|有|两排|书架|,|上面|也|摆满|了|图书|和|杂志|。|一些|打工|青年|或站|或|蹲|,|认真|地阅|读|,|不时|有|人到|借阅|台前|办理|借书|或|还书|手续|。|南|山区|在|深圳|市|西边|,|地处|城乡|结合部|,|外来|打|工者|较|多|。|去年|2月|,|南|山区|工商|分局|局长|王|安全|发现|分局|对面|的|公园|里|常有|不少|打|工者|业余|时间|闲逛|,|有时|还|滋扰|生事|。|为|了|给|这些|打|工者|提供|一个|充实|自己|的|场所|,|他|提议|由全|分局|工作|人员|捐款|,|兴建|一个|免费|阅览室|。|领导|带头|,|群众|响应|,|大家|捐款|1.4万|元|,|购买|了|近|千册|图书|。|3月|6日|,|建在|南头|繁华|的|南|新路|和|金鸡|路交|叉口|的|阅览室|开放|了|。|从此|,|这里|每天|都|吸引|了|众多|借书|、|看书|的|人们|,|其中|不仅|有|打|工者|,|还|有|机关|干部|、|公司|职员|和|个|体户|。|到|了|夏天|,|由于|阅览室|所|在|地|被|工程|征用|,|南|山区|工商|分局|便|把|阅览|室迁|到|了|桂庙|。|阅览室|的|管理|人员|是|两|名|青年|,|男|的|叫|张|攀|,|女|的|叫|赵阳|。|张|攀|自己|就|是|湖北|来|的|打|工者|,|听|说|南|山区|工商|分局|办|免费|阅览室|,|便|主动|应|聘来|服务|。|阅览室|每天|从|早9时|开到|晚|10|时|,|夜里|张|攀|就|住|在|这里|。|他谈|起|阅览|室里|的|图书|,|翻着|一|本本|的|借阅|名册|,|如数|家珍|,|对|图书|和|工作|的|挚爱|之|情溢|于|言表|。|我|在|这里|碰到|南|山区|华英|大厦|一位|叫|聂|煜|的|女|青年|,|她|说|她|也|是|个|打|工者|,|由于|春节|探家|回来|后|就|要|去市|内|工作|,|很|留恋|这里|的|这个|免费|阅览室|,|想|抓紧|时间|多|看些|书|,|她|还|把|自己|买|的|几本|杂志|捐给|了|阅览室|。|在|阅览室|的|捐书|登|记簿|上|,|记录|着|这样|的|数字|:|工商|系统|内部|捐书|3550册|,|社会|各界|捐书|250册|。|我|在|阅览室|读到|了|这样|几|封感|谢信|:|深圳|瑞兴|光学|厂|的|王|志明|写道|:|“|我们|这些|年|轻人|远离|了|家乡|,|来到|繁华|紧张|的|都|市|打工|,|辛劳|之余|,|能|有|机会|看书|读报|,|感到|特别|充实|。|”|深圳|文光|灯|泡厂|的|江虹|说|:|“|南|山区|工商|分局|的|干部|职工|捐款|、|捐书|,|给|我们|打|工者|提供|良好|的|学习|环境|,|鼓励|我们|求知|上进|,|真是|办|了|一件|大好|事|,|他们|是|我们|打|工者|的|知音|。|”|(|本报|记者|罗华|) 18 | 19 | 运行时长:3.6s 20 | ''' 21 | import numpy as np 22 | import time 23 | 24 | def trainParameter(fileName): 25 | ''' 26 | 依据训练文本统计PI、A、B 27 | :param fileName: 训练文本 28 | :return: 三个参数 29 | ''' 30 | #定义一个查询字典,用于映射四种标记在数组中对应的位置,方便查询 31 | # B:词语的开头 32 | # M:一个词语的中间词 33 | # E:一个词语的结果 34 | # S:非词语,单个词 35 | statuDict = {'B':0, 'M':1, 'E':2, 'S':3} 36 | 37 | #每个字只有四种状态,所以下方的各类初始化中大小的参数均为4 38 | #初始化PI的一维数组,因为对应四种状态,大小为4 39 | PI = np.zeros(4) 40 | #初始化状态转移矩阵A,涉及到四种状态各自到四种状态的转移,因为大小为4x4 41 | A = np.zeros((4, 4)) 42 | #初始化观测概率矩阵,分别为四种状态到每个字的发射概率 43 | #因为是中文分词,使用ord(汉字)即可找到其对应编码,这里用一个65536的空间来保证对于所有的汉字都能 44 | #找到对应的位置来存储 45 | B = np.zeros((4, 65536)) 46 | #去读训练文本 47 | fr = open(fileName, encoding='utf-8') 48 | 49 | #文本中的每一行认为是一个训练样本 50 | #在统计上,三个参数依据“10.3.2” Baum-Welch算法内描述的统计 51 | #PI依据式10.35 52 | #A依据10.37 53 | #B依据10.38 54 | #注:并没有使用Baum-Welch算法,只是借助了其内部的三个参数生成公式,其实 55 | #公式并不是Baum-Welch特有的,只是在那一节正好有描述 56 | for line in fr.readlines(): 57 | #---------------------训练集单行样例-------------------- 58 | #深圳 有 个 打工者 阅览室 59 | #------------------------------------------------------ 60 | #可以看到训练样本已经分词完毕,词语之间空格隔开,因此我们在生成统计时主要借助以下思路: 61 | # 1.先将句子按照空格隔开,例如例句中5个词语,隔开后变成一个长度为5的列表,每个元素为一个词语 62 | # 2.对每个词语长度进行判断: 63 | # 如果为1认为该词语是S,即单个字 64 | # 如果为2则第一个是B,表开头,第二个为E,表结束 65 | # 如果大于2,则第一个为B,最后一个为E,中间全部标为M,表中间词 66 | # 3.统计PI:该句第一个字的词性对应的PI中位置加1 67 | # 例如:PI = [0, 0, 0, 0],当本行第一个字是B,即表示开头时,PI中B对应位置为0, 68 | # 则PI = [1, 0, 0, 0],全部统计结束后,按照计数值再除以总数得到概率 69 | # 统计A:对状态链中位置t和t-1的状态进行统计,在矩阵中相应位置加1,全部结束后生成概率 70 | # 统计B:对于每个字的状态以及字内容,生成状态到字的发射计数,全部结束后生成概率 71 | # 注:可以看一下“10.1.1 隐马尔可夫模型的定义”一节中三个参数的定义,会有更清晰一点的认识 72 | #------------------------------------------------------- 73 | #对单行句子按空格进行切割 74 | curLine = line.strip().split() 75 | #对词性的标记放在该列表中 76 | wordLabel = [] 77 | #对每一个单词进行遍历 78 | for i in range(len(curLine)): 79 | #如果长度为1,则直接将该字标记为S,即单个词 80 | if len(curLine[i]) == 1: 81 | label = 'S' 82 | else: 83 | #如果长度不为1,开头为B,最后为E,中间添加长度-2个M 84 | #如果长度刚好为2,长度-2=0也就不添加了,反之添加对应个数的M 85 | label = 'B' + 'M' * (len(curLine[i]) - 2) + 'E' 86 | 87 | #如果是单行开头第一个字,PI中对应位置加1, 88 | if i == 0: PI[statuDict[label[0]]] += 1 89 | 90 | #对于该单词中的每一个字,在生成的状态链中统计B 91 | for j in range(len(label)): 92 | #遍历状态链中每一个状态,并找到对应的中文汉字,在B中 93 | #对应位置加1 94 | B[statuDict[label[j]]][ord(curLine[i][j])] += 1 95 | 96 | #在整行的状态链中添加该单词的状态链 97 | #注意:extend表直接在原先元素的后方添加, 98 | #可以百度一下extend和append的区别 99 | wordLabel.extend(label) 100 | 101 | #单行所有单词都结束后,统计A信息 102 | #因为A涉及到前一个状态,因此需要等整条状态链都生成了才能开始统计 103 | for i in range(1, len(wordLabel)): 104 | #统计t时刻状态和t-1时刻状态的所有状态组合的出现次数 105 | A[statuDict[wordLabel[i - 1]]][statuDict[wordLabel[i]]] += 1 106 | 107 | #上面代码在统计上全部是统计的次数,实际运算需要使用概率, 108 | #下方代码是将三个参数的次数转换为概率 109 | #---------------------------------------- 110 | #对PI求和,概率生成中的分母 111 | sum = np.sum(PI) 112 | #遍历PI中每一个元素,元素出现的次数/总次数即为概率 113 | for i in range(len(PI)): 114 | #如果某元素没有出现过,该位置为0,在后续的计算中这是不被允许的 115 | #比如说某个汉字在训练集中没有出现过,那在后续不同概率相乘中只要有 116 | #一项为0,其他都是0了,此外整条链很长的情况下,太多0-1的概率相乘 117 | #不管怎样最后的结果都会很小,很容易下溢出 118 | #所以在概率上我们习惯将其转换为log对数形式,这在书上是没有讲的 119 | #x大的时候,log也大,x小的时候,log也相应小,我们最后比较的是不同 120 | #概率的大小,所以使用log没有问题 121 | 122 | #那么当单向概率为0的时候,log没有定义,因此需要单独判断 123 | #如果该项为0,则手动赋予一个极小值 124 | if PI[i] == 0: PI[i] = -3.14e+100 125 | #如果不为0,则计算概率,再对概率求log 126 | else: PI[i] = np.log(PI[i] / sum) 127 | 128 | #与上方PI思路一样,求得A的概率对数 129 | for i in range(len(A)): 130 | sum = np.sum(A[i]) 131 | for j in range(len(A[i])): 132 | if A[i][j] == 0: A[i][j] = -3.14e+100 133 | else: A[i][j] = np.log(A[i][j] / sum) 134 | 135 | #与上方PI思路一样,求得B的概率对数 136 | for i in range(len(B)): 137 | sum = np.sum(B[i]) 138 | for j in range(len(B[i])): 139 | if B[i][j] == 0: B[i][j] = -3.14e+100 140 | else:B[i][j] = np.log(B[i][j] / sum) 141 | 142 | #返回统计得到的三个参数 143 | return PI, A, B 144 | 145 | def loadArticle(fileName): 146 | ''' 147 | 加载文章 148 | :param fileName:文件路径 149 | :return: 文章内容 150 | ''' 151 | #初始化文章列表 152 | artical = [] 153 | #打开文件 154 | fr = open(fileName, encoding='utf-8') 155 | #按行读取文件 156 | for line in fr.readlines(): 157 | #读到的每行最后都有一个\n,使用strip将最后的回车符去掉 158 | line = line.strip() 159 | #将该行放入文章列表中 160 | artical.append(line) 161 | #将文章返回 162 | return artical 163 | 164 | def participle(artical, PI, A, B): 165 | ''' 166 | 分词 167 | 算法依据“10.4.2 维特比算法” 168 | :param artical:要分词的文章 169 | :param PI: 初始状态概率向量PI 170 | :param A: 状态转移矩阵 171 | :param B: 观测概率矩阵 172 | :return: 分词后的文章 173 | ''' 174 | #初始化分词后的文章列表 175 | retArtical = [] 176 | 177 | #对文章按行读取 178 | for line in artical: 179 | #初始化δ,δ存放四种状态的概率值,因为状态链中每个状态都有 180 | #四种概率值,因此长度时该行的长度 181 | delta = [[0 for i in range(4)] for i in range(len(line))] 182 | #依据算法10.5 第一步:初始化 183 | for i in range(4): 184 | #初始化δ状态链中第一个状态的四种状态概率 185 | delta[0][i] = PI[i] + B[i][ord(line[0])] 186 | #初始化ψ,初始时为0 187 | psi = [[0 for i in range(4)] for i in range(len(line))] 188 | 189 | #算法10.5中的第二步:递推 190 | #for循环的符号与书中公式一致,可以对比着看来理解 191 | #依次处理整条链 192 | for t in range(1, len(line)): 193 | #对于链中的米格状态,求四种状态概率 194 | for i in range(4): 195 | #初始化一个临时列表,用于存放四种概率 196 | tmpDelta = [0] * 4 197 | for j in range(4): 198 | # 计算第二步中的δ,该部分只计算max内部,不涉及后面的bi(o) 199 | # 计算得到四个结果以后,再去求那个max即可 200 | # 注:bi(Ot)并不在max的式子中,是求出max以后再乘b的 201 | # 此外读者可能注意到书中的乘法在这里变成了加法,这是由于原先是概率 202 | # 直接相乘,但我们在求得概率时,同时取了log,取完log以后,概率的乘法 203 | # 也就转换为加法了,同时也简化了运算 204 | # 所以log优点还是很多的对不? 205 | tmpDelta[j] = delta[t - 1][j] + A[j][i] 206 | 207 | #找到最大的那个δ * a, 208 | maxDelta = max(tmpDelta) 209 | #记录最大值对应的状态 210 | maxDeltaIndex = tmpDelta.index(maxDelta) 211 | 212 | #将找到的最大值乘以b放入, 213 | #注意:这里同样因为log变成了加法 214 | delta[t][i] = maxDelta + B[i][ord(line[t])] 215 | #在ψ中记录对应的最大状态索引 216 | psi[t][i] = maxDeltaIndex 217 | 218 | #建立一个状态链列表,开始生成状态链 219 | sequence = [] 220 | #算法10.5 第三步:终止 221 | #在上面for循环全部结束后,很明显就到了第三步了 222 | #获取最后一个状态的最大状态概率对应的索引 223 | i_opt = delta[len(line) - 1].index(max(delta[len(line) - 1])) 224 | #在状态链中添加索引 225 | #注:状态链应该是B、M、E、S,这里图方便用了0、1、2、3,其实一样的 226 | sequence.append(i_opt) 227 | #算法10.5 第四步:最优路径回溯 228 | #从后往前遍历整条链 229 | for t in range(len(line) - 1, 0, -1): 230 | #不断地从当前时刻t的ψ列表中读取到t-1的最优状态 231 | i_opt = psi[t][i_opt] 232 | #将状态放入列表中 233 | sequence.append(i_opt) 234 | #因为是从后往前将状态放入的列表,所以这里需要翻转一下,变成了从前往后 235 | sequence.reverse() 236 | 237 | #开始对该行分词 238 | curLine = '' 239 | #遍历该行每一个字 240 | for i in range(len(line)): 241 | #在列表中放入该字 242 | curLine += line[i] 243 | #如果该字是3:S->单个词 或 2:E->结尾词 ,则在该字后面加上分隔符 | 244 | #此外如果改行的最后一个字了,也就不需要加 | 245 | if (sequence[i] == 3 or sequence[i] == 2) and i != (len(line) - 1): 246 | curLine += '|' 247 | #在返回列表中添加分词后的该行 248 | retArtical.append(curLine) 249 | #返回分词后的文章 250 | return retArtical 251 | 252 | if __name__ == '__main__': 253 | # 开始时间 254 | start = time.time() 255 | 256 | #依据现有训练集统计PI、A、B 257 | PI, A, B = trainParameter('HMMTrainSet.txt') 258 | 259 | #读取测试文章 260 | artical = loadArticle('testArtical.txt') 261 | 262 | #打印原文 263 | print('-------------------原文----------------------') 264 | for line in artical: 265 | print(line) 266 | 267 | #进行分词 268 | partiArtical = participle(artical, PI, A, B) 269 | 270 | #打印分词结果 271 | print('-------------------分词后----------------------') 272 | for line in partiArtical: 273 | print(line) 274 | 275 | #结束时间 276 | print('time span:', time.time() - start) 277 | -------------------------------------------------------------------------------- /HMM/testArtical.txt: -------------------------------------------------------------------------------- 1 | 深圳有个打工者阅览室 2 | 去年12月,我在广东深圳市出差,听说南山区工商分局为打工者建了个免费图书阅览室,这件新鲜事引起了我的兴趣。 3 | 12月18日下午,我来到了这个阅览室。阅览室位于桂庙,临南油大道,是一间轻体房,面积约有40平方米,内部装修得整洁干净,四周的书架上摆满了书,并按政治、哲学、法律法规、文化教育、经济、科技、艺术、中国文学、外国文学等分类,屋中央有两排书架,上面也摆满了图书和杂志。一些打工青年或站或蹲,认真地阅读,不时有人到借阅台前办理借书或还书手续。南山区在深圳市西边,地处城乡结合部,外来打工者较多。去年2月,南山区工商分局局长王安全发现分局对面的公园里常有不少打工者业余时间闲逛,有时还滋扰生事。为了给这些打工者提供一个充实自己的场所,他提议由全分局工作人员捐款,兴建一个免费阅览室。领导带头,群众响应,大家捐款1.4万元,购买了近千册图书。3月6日,建在南头繁华的南新路和金鸡路交叉口的阅览室开放了。从此,这里每天都吸引了众多借书、看书的人们,其中不仅有打工者,还有机关干部、公司职员和个体户。到了夏天,由于阅览室所在地被工程征用,南山区工商分局便把阅览室迁到了桂庙。阅览室的管理人员是两名青年,男的叫张攀,女的叫赵阳。张攀自己就是湖北来的打工者,听说南山区工商分局办免费阅览室,便主动应聘来服务。阅览室每天从早9时开到晚10时,夜里张攀就住在这里。他谈起阅览室里的图书,翻着一本本的借阅名册,如数家珍,对图书和工作的挚爱之情溢于言表。我在这里碰到南山区华英大厦一位叫聂煜的女青年,她说她也是个打工者,由于春节探家回来后就要去市内工作,很留恋这里的这个免费阅览室,想抓紧时间多看些书,她还把自己买的几本杂志捐给了阅览室。在阅览室的捐书登记簿上,记录着这样的数字:工商系统内部捐书3550册,社会各界捐书250册。我在阅览室读到了这样几封感谢信:深圳瑞兴光学厂的王志明写道:“我们这些年轻人远离了家乡,来到繁华紧张的都市打工,辛劳之余,能有机会看书读报,感到特别充实。”深圳文光灯泡厂的江虹说:“南山区工商分局的干部职工捐款、捐书,给我们打工者提供良好的学习环境,鼓励我们求知上进,真是办了一件大好事,他们是我们打工者的知音。”(本报记者罗华) -------------------------------------------------------------------------------- /KNN/KNN.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | #Author:Dodo 3 | #Date:2018-11-16 4 | #Email:lvtengchao@pku.edu.cn 5 | 6 | ''' 7 | 数据集:Mnist 8 | 训练集数量:60000 9 | 测试集数量:10000(实际使用:200) 10 | ------------------------------ 11 | 运行结果:(邻近k数量:25) 12 | 向量距离使用算法——欧式距离 13 | 正确率:97% 14 | 运行时长:308s 15 | 向量距离使用算法——曼哈顿距离 16 | 正确率:14% 17 | 运行时长:246s 18 | ''' 19 | 20 | import numpy as np 21 | import time 22 | 23 | def loadData(fileName): 24 | ''' 25 | 加载文件 26 | :param fileName:要加载的文件路径 27 | :return: 数据集和标签集 28 | ''' 29 | print('start read file') 30 | #存放数据及标记 31 | dataArr = []; labelArr = [] 32 | #读取文件 33 | fr = open(fileName) 34 | #遍历文件中的每一行 35 | for line in fr.readlines(): 36 | #获取当前行,并按“,”切割成字段放入列表中 37 | #strip:去掉每行字符串首尾指定的字符(默认空格或换行符) 38 | #split:按照指定的字符将字符串切割成每个字段,返回列表形式 39 | curLine = line.strip().split(',') 40 | #将每行中除标记外的数据放入数据集中(curLine[0]为标记信息) 41 | #在放入的同时将原先字符串形式的数据转换为整型 42 | dataArr.append([int(num) for num in curLine[1:]]) 43 | #将标记信息放入标记集中 44 | #放入的同时将标记转换为整型 45 | labelArr.append(int(curLine[0])) 46 | #返回数据集和标记 47 | return dataArr, labelArr 48 | 49 | def calcDist(x1, x2): 50 | ''' 51 | 计算两个样本点向量之间的距离 52 | 使用的是欧氏距离,即 样本点每个元素相减的平方 再求和 再开方 53 | 欧式举例公式这里不方便写,可以百度或谷歌欧式距离(也称欧几里得距离) 54 | :param x1:向量1 55 | :param x2:向量2 56 | :return:向量之间的欧式距离 57 | ''' 58 | return np.sqrt(np.sum(np.square(x1 - x2))) 59 | 60 | #马哈顿距离计算公式 61 | # return np.sum(x1 - x2) 62 | 63 | 64 | 65 | 66 | def getClosest(trainDataMat, trainLabelMat, x, topK): 67 | ''' 68 | 预测样本x的标记。 69 | 获取方式通过找到与样本x最近的topK个点,并查看它们的标签。 70 | 查找里面占某类标签最多的那类标签 71 | (书中3.1 3.2节) 72 | :param trainDataMat:训练集数据集 73 | :param trainLabelMat:训练集标签集 74 | :param x:要预测的样本x 75 | :param topK:选择参考最邻近样本的数目(样本数目的选择关系到正确率,详看3.2.3 K值的选择) 76 | :return:预测的标记 77 | ''' 78 | #建立一个存放向量x与每个训练集中样本距离的列表 79 | #列表的长度为训练集的长度,distList[i]表示x与训练集中第 80 | ## i个样本的距离 81 | distList = [0] * len(trainLabelMat) 82 | #遍历训练集中所有的样本点,计算与x的距离 83 | for i in range(len(trainDataMat)): 84 | #获取训练集中当前样本的向量 85 | x1 = trainDataMat[i] 86 | #计算向量x与训练集样本x的距离 87 | curDist = calcDist(x1, x) 88 | #将距离放入对应的列表位置中 89 | distList[i] = curDist 90 | 91 | #对距离列表进行排序 92 | #argsort:函数将数组的值从小到大排序后,并按照其相对应的索引值输出 93 | #例如: 94 | # >>> x = np.array([3, 1, 2]) 95 | # >>> np.argsort(x) 96 | # array([1, 2, 0]) 97 | #返回的是列表中从小到大的元素索引值,对于我们这种需要查找最小距离的情况来说很合适 98 | #array返回的是整个索引值列表,我们通过[:topK]取列表中前topL个放入list中。 99 | #----------------优化点------------------- 100 | #由于我们只取topK小的元素索引值,所以其实不需要对整个列表进行排序,而argsort是对整个 101 | #列表进行排序的,存在时间上的浪费。字典有现成的方法可以只排序top大或top小,可以自行查阅 102 | #对代码进行稍稍修改即可 103 | #这里没有对其进行优化主要原因是KNN的时间耗费大头在计算向量与向量之间的距离上,由于向量高维 104 | #所以计算时间需要很长,所以如果要提升时间,在这里优化的意义不大。(当然不是说就可以不优化了, 105 | #主要是我太懒了) 106 | topKList = np.argsort(np.array(distList))[:topK] #升序排序 107 | #建立一个长度时的列表,用于选择数量最多的标记 108 | #3.2.4提到了分类决策使用的是投票表决,topK个标记每人有一票,在数组中每个标记代表的位置中投入 109 | #自己对应的地方,随后进行唱票选择最高票的标记 110 | labelList = [0] * 10 111 | #对topK个索引进行遍历 112 | for index in topKList: 113 | #trainLabelMat[index]:在训练集标签中寻找topK元素索引对应的标记 114 | #int(trainLabelMat[index]):将标记转换为int(实际上已经是int了,但是不int的话,报错) 115 | #labelList[int(trainLabelMat[index])]:找到标记在labelList中对应的位置 116 | #最后加1,表示投了一票 117 | labelList[int(trainLabelMat[index])] += 1 118 | #max(labelList):找到选票箱中票数最多的票数值 119 | #labelList.index(max(labelList)):再根据最大值在列表中找到该值对应的索引,等同于预测的标记 120 | return labelList.index(max(labelList)) 121 | 122 | 123 | def model_test(trainDataArr, trainLabelArr, testDataArr, testLabelArr, topK): 124 | ''' 125 | 测试正确率 126 | :param trainDataArr:训练集数据集 127 | :param trainLabelArr: 训练集标记 128 | :param testDataArr: 测试集数据集 129 | :param testLabelArr: 测试集标记 130 | :param topK: 选择多少个邻近点参考 131 | :return: 正确率 132 | ''' 133 | print('start test') 134 | #将所有列表转换为矩阵形式,方便运算 135 | trainDataMat = np.mat(trainDataArr); trainLabelMat = np.mat(trainLabelArr).T 136 | testDataMat = np.mat(testDataArr); testLabelMat = np.mat(testLabelArr).T 137 | 138 | #错误值技术 139 | errorCnt = 0 140 | #遍历测试集,对每个测试集样本进行测试 141 | #由于计算向量与向量之间的时间耗费太大,测试集有6000个样本,所以这里人为改成了 142 | #测试200个样本点,如果要全跑,将行注释取消,再下一行for注释即可,同时下面的print 143 | #和return也要相应的更换注释行 144 | # for i in range(len(testDataMat)): 145 | for i in range(200): 146 | # print('test %d:%d'%(i, len(trainDataArr))) 147 | print('test %d:%d' % (i, 200)) 148 | #读取测试集当前测试样本的向量 149 | x = testDataMat[i] 150 | #获取预测的标记 151 | y = getClosest(trainDataMat, trainLabelMat, x, topK) 152 | #如果预测标记与实际标记不符,错误值计数加1 153 | if y != testLabelMat[i]: errorCnt += 1 154 | 155 | #返回正确率 156 | # return 1 - (errorCnt / len(testDataMat)) 157 | return 1 - (errorCnt / 200) 158 | 159 | 160 | 161 | if __name__ == "__main__": 162 | start = time.time() 163 | 164 | #获取训练集 165 | trainDataArr, trainLabelArr = loadData('../Mnist/mnist_train.csv') 166 | #获取测试集 167 | testDataArr, testLabelArr = loadData('../Mnist/mnist_test.csv') 168 | #计算测试集正确率 169 | accur = model_test(trainDataArr, trainLabelArr, testDataArr, testLabelArr, 25) 170 | #打印正确率 171 | print('accur is:%d'%(accur * 100), '%') 172 | 173 | end = time.time() 174 | #显示花费时间 175 | print('time span:', end - start) 176 | 177 | 178 | -------------------------------------------------------------------------------- /LDA/LDA.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 37, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "Original Topics:\n", 13 | "['tech', 'business', 'sport', 'entertainment', 'politics']\n", 14 | "1/10\n", 15 | "2/10\n", 16 | "3/10\n", 17 | "4/10\n", 18 | "5/10\n", 19 | "6/10\n", 20 | "7/10\n", 21 | "8/10\n", 22 | "9/10\n", 23 | "10/10\n", 24 | "Topic 1: said game england would time first back play last good\n", 25 | "Topic 2: said year would economy growth also economic bank government could\n", 26 | "Topic 3: said year games sales company also market last firm 2004\n", 27 | "Topic 4: film said music best also people year show number digital\n", 28 | "Topic 5: said would people government labour election party blair could also\n", 29 | "Time: 7620.509902954102\n" 30 | ] 31 | } 32 | ], 33 | "source": [ 34 | "import numpy as np\n", 35 | "import pandas as pd\n", 36 | "import string\n", 37 | "from nltk.corpus import stopwords\n", 38 | "import time\n", 39 | "\n", 40 | "\n", 41 | "#定义加载数据的函数\n", 42 | "def load_data(file, K):\n", 43 | " '''\n", 44 | " INPUT:\n", 45 | " file - (str) 数据文件的路径\n", 46 | " K - (int) 设定的话题数\n", 47 | " \n", 48 | " OUTPUT:\n", 49 | " org_topics - (list) 原始话题标签列表\n", 50 | " text - (list) 文本列表\n", 51 | " words - (list) 单词列表\n", 52 | " alpha - (list) 话题概率分布,模型超参数\n", 53 | " beta - (list) 单词概率分布,模型超参数\n", 54 | " \n", 55 | " '''\n", 56 | " df = pd.read_csv(file) #读取文件\n", 57 | " org_topics = df['category'].unique().tolist() #保存文本原始的话题标签\n", 58 | " M = df.shape[0] #文本数\n", 59 | " alpha = np.zeros(K) #alpha是LDA模型的一个超参数,是对话题概率的预估计,这里取文本数据中各话题的比例作为alpha值,实际可以通过模型训练得到\n", 60 | " beta = np.zeros(1000) #beta是LDA模型的另一个超参数,是词汇表中单词的概率分布,这里取各单词在所有文本中的比例作为beta值,实际也可以通过模型训练得到\n", 61 | " #计算各话题的比例作为alpha值\n", 62 | " for k, topic in enumerate(org_topics):\n", 63 | " alpha[k] = df[df['category'] == topic].shape[0] / M\n", 64 | " df.drop('category', axis=1, inplace=True)\n", 65 | " n = df.shape[0] #n为文本数量\n", 66 | " text = []\n", 67 | " words = []\n", 68 | " for i in df['text'].values:\n", 69 | " t = i.translate(str.maketrans('', '', string.punctuation)) #去除文本中的标点符号\n", 70 | " t = [j for j in t.split() if j not in stopwords.words('english')] #去除文本中的停止词\n", 71 | " t = [j for j in t if len(j) > 3] #长度小于等于3的单词大多是无意义的,直接去除\n", 72 | " text.append(t) #将处理后的文本保存到文本列表中\n", 73 | " words.extend(set(t)) #将文本中所包含的单词保存到单词列表中\n", 74 | " words = list(set(words)) #去除单词列表中的重复单词\n", 75 | " words_cnt = np.zeros(len(words)) #用来保存单词的出现频次\n", 76 | " #循环计算words列表中各单词出现的词频\n", 77 | " for i in range(len(text)):\n", 78 | " t = text[i] #取出第i条文本\n", 79 | " for w in t:\n", 80 | " ind = words.index(w) #取出第i条文本中的第t个单词在单词列表中的索引\n", 81 | " words_cnt[ind] += 1 #对应位置的单词出现频次加一\n", 82 | " sort_inds = np.argsort(words_cnt)[::-1] #对单词出现频次降序排列后取出其索引值\n", 83 | " words = [words[ind] for ind in sort_inds[:1000]] #将出现频次前1000的单词保存到words列表\n", 84 | " #去除文本text中不在词汇表words中的单词\n", 85 | " for i in range(len(text)):\n", 86 | " t = []\n", 87 | " for w in text[i]:\n", 88 | " if w in words:\n", 89 | " ind = words.index(w)\n", 90 | " t.append(w)\n", 91 | " beta[ind] += 1 #统计各单词在文本中的出现频次\n", 92 | " text[i] = t\n", 93 | " beta /= np.sum(beta) #除以文本的总单词数得到各单词所占比例,作为beta值\n", 94 | " return org_topics, text, words, alpha, beta\n", 95 | "\n", 96 | "\n", 97 | "#定义潜在狄利克雷分配函数,采用收缩的吉布斯抽样算法估计模型的参数theta和phi\n", 98 | "def do_lda(text, words, alpha, beta, K, iters):\n", 99 | " '''\n", 100 | " INPUT:\n", 101 | " text - (list) 文本列表\n", 102 | " words - (list) 单词列表\n", 103 | " alpha - (list) 话题概率分布,模型超参数\n", 104 | " beta - (list) 单词概率分布,模型超参数\n", 105 | " K - (int) 设定的话题数\n", 106 | " iters - (int) 设定的迭代次数\n", 107 | " \n", 108 | " OUTPUT:\n", 109 | " theta - (array) 话题的条件概率分布p(zk|dj),这里写成p(zk|dj)是为了和PLSA模型那一章的符号统一一下,方便对照着看\n", 110 | " phi - (array) 单词的条件概率分布p(wi|zk)\n", 111 | " \n", 112 | " '''\n", 113 | " M = len(text) #文本数\n", 114 | " V = len(words) #单词数\n", 115 | " N_MK = np.zeros((M, K)) #文本-话题计数矩阵\n", 116 | " N_KV = np.zeros((K, V)) #话题-单词计数矩阵\n", 117 | " N_M = np.zeros(M) #文本计数向量\n", 118 | " N_K = np.zeros(K) #话题计数向量\n", 119 | " Z_MN = [] #用来保存每条文本的每个单词所在位置处抽样得到的话题\n", 120 | " #算法20.2的步骤(2),对每个文本的所有单词抽样产生话题,并进行计数\n", 121 | " for m in range(M):\n", 122 | " zm = []\n", 123 | " t = text[m]\n", 124 | " for n, w in enumerate(t):\n", 125 | " v = words.index(w)\n", 126 | " z = np.random.randint(K)\n", 127 | " zm.append(z)\n", 128 | " N_MK[m, z] += 1\n", 129 | " N_M[m] += 1\n", 130 | " N_KV[z, v] += 1\n", 131 | " N_K[z] += 1\n", 132 | " Z_MN.append(zm)\n", 133 | " #算法20.2的步骤(3),多次迭代进行吉布斯抽样\n", 134 | " for i in range(iters):\n", 135 | " print('{}/{}'.format(i+1, iters))\n", 136 | " for m in range(M):\n", 137 | " t = text[m]\n", 138 | " for n, w in enumerate(t):\n", 139 | " v = words.index(w)\n", 140 | " z = Z_MN[m][n]\n", 141 | " N_MK[m, z] -= 1\n", 142 | " N_M[m] -= 1\n", 143 | " N_KV[z][v] -= 1\n", 144 | " N_K[z] -= 1\n", 145 | " p = [] #用来保存对K个话题的条件分布p(zi|z_i,w,alpha,beta)的计算结果\n", 146 | " sums_k = 0 \n", 147 | " for k in range(K):\n", 148 | " p_zk = (N_KV[k][v] + beta[v]) * (N_MK[m][k] + alpha[k]) #话题zi=k的条件分布p(zi|z_i,w,alpha,beta)的分子部分\n", 149 | " sums_v = 0\n", 150 | " sums_k += N_MK[m][k] + alpha[k] #累计(nmk + alpha_k)在K个话题上的和\n", 151 | " for t in range(V):\n", 152 | " sums_v += N_KV[k][t] + beta[t] #累计(nkv + beta_v)在V个单词上的和\n", 153 | " p_zk /= sums_v\n", 154 | " p.append(p_zk)\n", 155 | " p = p / sums_k\n", 156 | " p = p / np.sum(p) #对条件分布p(zi|z_i,w,alpha,beta)进行归一化,保证概率的总和为1\n", 157 | " new_z = np.random.choice(a=K, p=p) #根据以上计算得到的概率进行抽样,得到新的话题\n", 158 | " Z_MN[m][n] = new_z #更新当前位置处的话题为上面抽样得到的新话题\n", 159 | " #更新计数\n", 160 | " N_MK[m, new_z] += 1\n", 161 | " N_M[m] += 1\n", 162 | " N_KV[new_z, v] += 1\n", 163 | " N_K[new_z] += 1\n", 164 | " #算法20.2的步骤(4),利用得到的样本计数,估计模型的参数theta和phi\n", 165 | " theta = np.zeros((M, K))\n", 166 | " phi = np.zeros((K, V))\n", 167 | " for m in range(M):\n", 168 | " sums_k = 0\n", 169 | " for k in range(K):\n", 170 | " theta[m, k] = N_MK[m][k] + alpha[k] #参数theta的分子部分\n", 171 | " sums_k += theta[m, k] #累计(nmk + alpha_k)在K个话题上的和,参数theta的分母部分\n", 172 | " theta[m] /= sums_k #计算参数theta\n", 173 | " for k in range(K):\n", 174 | " sums_v = 0\n", 175 | " for v in range(V):\n", 176 | " phi[k, v] = N_KV[k][v] + beta[v] #参数phi的分子部分\n", 177 | " sums_v += phi[k][v] #累计(nkv + beta_v)在V个单词上的和,参数phi的分母部分\n", 178 | " phi[k] /= sums_v #计算参数phi\n", 179 | " return theta, phi\n", 180 | "\n", 181 | "\n", 182 | "if __name__ == \"__main__\":\n", 183 | " K = 5 #设定话题数为5\n", 184 | " org_topics, text, words, alpha, beta = load_data('bbc_text.csv', K) #加载数据\n", 185 | " print('Original Topics:')\n", 186 | " print(org_topics) #打印原始的话题标签列表\n", 187 | " start = time.time() #保存开始时间\n", 188 | " iters = 10 #为了避免运行时间过长,这里只迭代10次,实际上10次是不够的,要迭代足够的次数保证吉布斯抽样进入燃烧期,这样得到的参数才能尽可能接近样本的实际概率分布\n", 189 | " theta, phi = do_lda(text, words, alpha, beta, K, iters) #LDA的吉布斯抽样\n", 190 | " #打印出每个话题zk条件下出现概率最大的前10个单词,即P(wi|zk)在话题zk中最大的10个值对应的单词,作为对话题zk的文本描述\n", 191 | " for k in range(K):\n", 192 | " sort_inds = np.argsort(phi[k])[::-1] #对话题zk条件下的P(wi|zk)的值进行降序排列后取出对应的索引值\n", 193 | " topic = [] #定义一个空列表用于保存话题zk概率最大的前10个单词\n", 194 | " for i in range(10):\n", 195 | " topic.append(words[sort_inds[i]]) \n", 196 | " topic = ' '.join(topic) #将10个单词以空格分隔,构成对话题zk的文本表述\n", 197 | " print('Topic {}: {}'.format(k+1, topic)) #打印话题zk\n", 198 | " end = time.time()\n", 199 | " print('Time:', end-start)" 200 | ] 201 | }, 202 | { 203 | "cell_type": "code", 204 | "execution_count": 38, 205 | "metadata": {}, 206 | "outputs": [ 207 | { 208 | "data": { 209 | "text/plain": [ 210 | "array([[0.04935605, 0.10338287, 0.06575088, 0.46867464, 0.31283557],\n", 211 | " [0.18828892, 0.33184591, 0.36042376, 0.00247833, 0.11696308],\n", 212 | " [0.64543178, 0.13184591, 0.06042376, 0.15962119, 0.00267737],\n", 213 | " ...,\n", 214 | " [0.41026611, 0.05564755, 0.31881135, 0.21280899, 0.002466 ],\n", 215 | " [0.34581233, 0.01506225, 0.12993015, 0.06198299, 0.44721227],\n", 216 | " [0.74515492, 0.00347293, 0.15499489, 0.09353762, 0.00283963]])" 217 | ] 218 | }, 219 | "execution_count": 38, 220 | "metadata": {}, 221 | "output_type": "execute_result" 222 | } 223 | ], 224 | "source": [ 225 | "theta" 226 | ] 227 | }, 228 | { 229 | "cell_type": "code", 230 | "execution_count": 39, 231 | "metadata": {}, 232 | "outputs": [ 233 | { 234 | "data": { 235 | "text/plain": [ 236 | "array([[1.99768905e-02, 1.06570534e-02, 5.88055310e-03, ...,\n", 237 | " 1.18881649e-03, 8.27178858e-09, 1.50724726e-03],\n", 238 | " [3.50778918e-02, 1.05511733e-02, 8.79264523e-03, ...,\n", 239 | " 3.00802648e-04, 9.01573088e-09, 9.01573088e-09],\n", 240 | " [3.44183618e-02, 7.06465729e-03, 1.14162405e-02, ...,\n", 241 | " 4.52128900e-04, 1.69555219e-04, 1.10105081e-08],\n", 242 | " [1.81454758e-02, 3.13112016e-03, 1.39941969e-02, ...,\n", 243 | " 1.18602254e-04, 1.99237185e-03, 9.24197417e-09],\n", 244 | " [4.45921371e-02, 1.96021164e-02, 8.00255656e-03, ...,\n", 245 | " 6.17454557e-09, 6.17454557e-09, 3.01086896e-04]])" 246 | ] 247 | }, 248 | "execution_count": 39, 249 | "metadata": {}, 250 | "output_type": "execute_result" 251 | } 252 | ], 253 | "source": [ 254 | "phi" 255 | ] 256 | }, 257 | { 258 | "cell_type": "code", 259 | "execution_count": null, 260 | "metadata": {}, 261 | "outputs": [], 262 | "source": [] 263 | } 264 | ], 265 | "metadata": { 266 | "kernelspec": { 267 | "display_name": "Python 3", 268 | "language": "python", 269 | "name": "python3" 270 | }, 271 | "language_info": { 272 | "codemirror_mode": { 273 | "name": "ipython", 274 | "version": 3 275 | }, 276 | "file_extension": ".py", 277 | "mimetype": "text/x-python", 278 | "name": "python", 279 | "nbconvert_exporter": "python", 280 | "pygments_lexer": "ipython3", 281 | "version": "3.7.3" 282 | } 283 | }, 284 | "nbformat": 4, 285 | "nbformat_minor": 2 286 | } 287 | -------------------------------------------------------------------------------- /LDA/LDA.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | #Author:Harold 3 | #Date:2021-1-27 4 | #Email:zenghr_zero@163.com 5 | 6 | ''' 7 | 数据集:bbc_text 8 | 数据集数量:2225 9 | ----------------------------- 10 | 运行结果: 11 | 话题数:5 12 | 原始话题:'tech', 'business', 'sport', 'entertainment', 'politics' 13 | 生成话题: 14 | 1:'said game england would time first back play last good' 15 | 2:'said year would economy growth also economic bank government could' 16 | 3:'said year games sales company also market last firm 2004' 17 | 4:'film said music best also people year show number digital' 18 | 5:'said would people government labour election party blair could also' 19 | 运行时长:7620.51s 20 | ''' 21 | 22 | import numpy as np 23 | import pandas as pd 24 | import string 25 | from nltk.corpus import stopwords 26 | import time 27 | 28 | 29 | #定义加载数据的函数 30 | def load_data(file, K): 31 | ''' 32 | INPUT: 33 | file - (str) 数据文件的路径 34 | K - (int) 设定的话题数 35 | 36 | OUTPUT: 37 | org_topics - (list) 原始话题标签列表 38 | text - (list) 文本列表 39 | words - (list) 单词列表 40 | alpha - (list) 话题概率分布,模型超参数 41 | beta - (list) 单词概率分布,模型超参数 42 | 43 | ''' 44 | df = pd.read_csv(file) #读取文件 45 | org_topics = df['category'].unique().tolist() #保存文本原始的话题标签 46 | M = df.shape[0] #文本数 47 | alpha = np.zeros(K) #alpha是LDA模型的一个超参数,是对话题概率的预估计,这里取文本数据中各话题的比例作为alpha值,实际可以通过模型训练得到 48 | beta = np.zeros(1000) #beta是LDA模型的另一个超参数,是词汇表中单词的概率分布,这里取各单词在所有文本中的比例作为beta值,实际也可以通过模型训练得到 49 | #计算各话题的比例作为alpha值 50 | for k, topic in enumerate(org_topics): 51 | alpha[k] = df[df['category'] == topic].shape[0] / M 52 | df.drop('category', axis=1, inplace=True) 53 | n = df.shape[0] #n为文本数量 54 | text = [] 55 | words = [] 56 | for i in df['text'].values: 57 | t = i.translate(str.maketrans('', '', string.punctuation)) #去除文本中的标点符号 58 | t = [j for j in t.split() if j not in stopwords.words('english')] #去除文本中的停止词 59 | t = [j for j in t if len(j) > 3] #长度小于等于3的单词大多是无意义的,直接去除 60 | text.append(t) #将处理后的文本保存到文本列表中 61 | words.extend(set(t)) #将文本中所包含的单词保存到单词列表中 62 | words = list(set(words)) #去除单词列表中的重复单词 63 | words_cnt = np.zeros(len(words)) #用来保存单词的出现频次 64 | #循环计算words列表中各单词出现的词频 65 | for i in range(len(text)): 66 | t = text[i] #取出第i条文本 67 | for w in t: 68 | ind = words.index(w) #取出第i条文本中的第t个单词在单词列表中的索引 69 | words_cnt[ind] += 1 #对应位置的单词出现频次加一 70 | sort_inds = np.argsort(words_cnt)[::-1] #对单词出现频次降序排列后取出其索引值 71 | words = [words[ind] for ind in sort_inds[:1000]] #将出现频次前1000的单词保存到words列表 72 | #去除文本text中不在词汇表words中的单词 73 | for i in range(len(text)): 74 | t = [] 75 | for w in text[i]: 76 | if w in words: 77 | ind = words.index(w) 78 | t.append(w) 79 | beta[ind] += 1 #统计各单词在文本中的出现频次 80 | text[i] = t 81 | beta /= np.sum(beta) #除以文本的总单词数得到各单词所占比例,作为beta值 82 | return org_topics, text, words, alpha, beta 83 | 84 | 85 | #定义潜在狄利克雷分配函数,采用收缩的吉布斯抽样算法估计模型的参数theta和phi 86 | def do_lda(text, words, alpha, beta, K, iters): 87 | ''' 88 | INPUT: 89 | text - (list) 文本列表 90 | words - (list) 单词列表 91 | alpha - (list) 话题概率分布,模型超参数 92 | beta - (list) 单词概率分布,模型超参数 93 | K - (int) 设定的话题数 94 | iters - (int) 设定的迭代次数 95 | 96 | OUTPUT: 97 | theta - (array) 话题的条件概率分布p(zk|dj),这里写成p(zk|dj)是为了和PLSA模型那一章的符号统一一下,方便对照着看 98 | phi - (array) 单词的条件概率分布p(wi|zk) 99 | 100 | ''' 101 | M = len(text) #文本数 102 | V = len(words) #单词数 103 | N_MK = np.zeros((M, K)) #文本-话题计数矩阵 104 | N_KV = np.zeros((K, V)) #话题-单词计数矩阵 105 | N_M = np.zeros(M) #文本计数向量 106 | N_K = np.zeros(K) #话题计数向量 107 | Z_MN = [] #用来保存每条文本的每个单词所在位置处抽样得到的话题 108 | #算法20.2的步骤(2),对每个文本的所有单词抽样产生话题,并进行计数 109 | for m in range(M): 110 | zm = [] 111 | t = text[m] 112 | for n, w in enumerate(t): 113 | v = words.index(w) 114 | z = np.random.randint(K) 115 | zm.append(z) 116 | N_MK[m, z] += 1 117 | N_M[m] += 1 118 | N_KV[z, v] += 1 119 | N_K[z] += 1 120 | Z_MN.append(zm) 121 | #算法20.2的步骤(3),多次迭代进行吉布斯抽样 122 | for i in range(iters): 123 | print('{}/{}'.format(i+1, iters)) 124 | for m in range(M): 125 | t = text[m] 126 | for n, w in enumerate(t): 127 | v = words.index(w) 128 | z = Z_MN[m][n] 129 | N_MK[m, z] -= 1 130 | N_M[m] -= 1 131 | N_KV[z][v] -= 1 132 | N_K[z] -= 1 133 | p = [] #用来保存对K个话题的条件分布p(zi|z_i,w,alpha,beta)的计算结果 134 | sums_k = 0 135 | for k in range(K): 136 | p_zk = (N_KV[k][v] + beta[v]) * (N_MK[m][k] + alpha[k]) #话题zi=k的条件分布p(zi|z_i,w,alpha,beta)的分子部分 137 | sums_v = 0 138 | sums_k += N_MK[m][k] + alpha[k] #累计(nmk + alpha_k)在K个话题上的和 139 | for t in range(V): 140 | sums_v += N_KV[k][t] + beta[t] #累计(nkv + beta_v)在V个单词上的和 141 | p_zk /= sums_v 142 | p.append(p_zk) 143 | p = p / sums_k 144 | p = p / np.sum(p) #对条件分布p(zi|z_i,w,alpha,beta)进行归一化,保证概率的总和为1 145 | new_z = np.random.choice(a=K, p=p) #根据以上计算得到的概率进行抽样,得到新的话题 146 | Z_MN[m][n] = new_z #更新当前位置处的话题为上面抽样得到的新话题 147 | #更新计数 148 | N_MK[m, new_z] += 1 149 | N_M[m] += 1 150 | N_KV[new_z, v] += 1 151 | N_K[new_z] += 1 152 | #算法20.2的步骤(4),利用得到的样本计数,估计模型的参数theta和phi 153 | theta = np.zeros((M, K)) 154 | phi = np.zeros((K, V)) 155 | for m in range(M): 156 | sums_k = 0 157 | for k in range(K): 158 | theta[m, k] = N_MK[m][k] + alpha[k] #参数theta的分子部分 159 | sums_k += theta[m, k] #累计(nmk + alpha_k)在K个话题上的和,参数theta的分母部分 160 | theta[m] /= sums_k #计算参数theta 161 | for k in range(K): 162 | sums_v = 0 163 | for v in range(V): 164 | phi[k, v] = N_KV[k][v] + beta[v] #参数phi的分子部分 165 | sums_v += phi[k][v] #累计(nkv + beta_v)在V个单词上的和,参数phi的分母部分 166 | phi[k] /= sums_v #计算参数phi 167 | return theta, phi 168 | 169 | 170 | if __name__ == "__main__": 171 | K = 5 #设定话题数为5 172 | org_topics, text, words, alpha, beta = load_data('bbc_text.csv', K) #加载数据 173 | print('Original Topics:') 174 | print(org_topics) #打印原始的话题标签列表 175 | start = time.time() #保存开始时间 176 | iters = 10 #为了避免运行时间过长,这里只迭代10次,实际上10次是不够的,要迭代足够的次数保证吉布斯抽样进入燃烧期,这样得到的参数才能尽可能接近样本的实际概率分布 177 | theta, phi = do_lda(text, words, alpha, beta, K, iters) #LDA的吉布斯抽样 178 | #打印出每个话题zk条件下出现概率最大的前10个单词,即P(wi|zk)在话题zk中最大的10个值对应的单词,作为对话题zk的文本描述 179 | for k in range(K): 180 | sort_inds = np.argsort(phi[k])[::-1] #对话题zk条件下的P(wi|zk)的值进行降序排列后取出对应的索引值 181 | topic = [] #定义一个空列表用于保存话题zk概率最大的前10个单词 182 | for i in range(10): 183 | topic.append(words[sort_inds[i]]) 184 | topic = ' '.join(topic) #将10个单词以空格分隔,构成对话题zk的文本表述 185 | print('Topic {}: {}'.format(k+1, topic)) #打印话题zk 186 | end = time.time() 187 | print('Time:', end-start) -------------------------------------------------------------------------------- /LSA/LSA.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 2, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "Original Topics:\n", 13 | "['tech', 'business', 'sport', 'entertainment', 'politics']\n" 14 | ] 15 | }, 16 | { 17 | "name": "stderr", 18 | "output_type": "stream", 19 | "text": [ 20 | "C:\\Users\\zengh\\Anaconda3\\lib\\site-packages\\ipykernel_launcher.py:59: ComplexWarning: Casting complex values to real discards the imaginary part\n" 21 | ] 22 | }, 23 | { 24 | "name": "stdout", 25 | "output_type": "stream", 26 | "text": [ 27 | "Generated Topics:\n", 28 | "Topic 1: said people would music blair government best year film howard\n", 29 | "Topic 2: said would labour party people last could years kilroysilk show\n", 30 | "Topic 3: music microsoft year best urban industry record software email think\n", 31 | "Topic 4: wales first games lord government play house public control prime\n", 32 | "Topic 5: said mobile england people phone dallaglio rugby blair election would\n", 33 | "Time: 212.96439409255981\n" 34 | ] 35 | } 36 | ], 37 | "source": [ 38 | "import numpy as np\n", 39 | "import pandas as pd\n", 40 | "import string\n", 41 | "from nltk.corpus import stopwords\n", 42 | "import time \n", 43 | "\n", 44 | "\n", 45 | "#定义加载数据的函数\n", 46 | "def load_data(file):\n", 47 | " '''\n", 48 | " INPUT:\n", 49 | " file - (str) 数据文件的路径\n", 50 | " \n", 51 | " OUTPUT:\n", 52 | " org_topics - (list) 原始话题标签列表\n", 53 | " text - (list) 文本列表\n", 54 | " words - (list) 单词列表\n", 55 | " \n", 56 | " '''\n", 57 | " df = pd.read_csv(file) #读取文件\n", 58 | " org_topics = df['category'].unique().tolist() #保存文本原始的话题标签\n", 59 | " df.drop('category', axis=1, inplace=True)\n", 60 | " n = df.shape[0] #n为文本数量\n", 61 | " text = []\n", 62 | " words = []\n", 63 | " for i in df['text'].values:\n", 64 | " t = i.translate(str.maketrans('', '', string.punctuation)) #去除文本中的标点符号\n", 65 | " t = [j for j in t.split() if j not in stopwords.words('english')] #去除文本中的停止词\n", 66 | " t = [j for j in t if len(j) > 3] #长度小于等于3的单词大多是无意义的,直接去除\n", 67 | " text.append(t) #将处理后的文本保存到文本列表中\n", 68 | " words.extend(set(t)) #将文本中所包含的单词保存到单词列表中\n", 69 | " words = list(set(words)) #去除单词列表中的重复单词\n", 70 | " return org_topics, text, words\n", 71 | "\n", 72 | "\n", 73 | "#定义构建单词-文本矩阵的函数,这里矩阵的每一项表示单词在文本中的出现频次,也可以用TF-IDF来表示\n", 74 | "def frequency_counter(text, words):\n", 75 | " '''\n", 76 | " INPUT:\n", 77 | " text - (list) 文本列表\n", 78 | " words - (list) 单词列表\n", 79 | " \n", 80 | " OUTPUT:\n", 81 | " X - (array) 单词-文本矩阵\n", 82 | " \n", 83 | " '''\n", 84 | " X = np.zeros((len(words), len(text))) #定义m*n的矩阵,其中m为单词列表中的单词个数,n为文本个数\n", 85 | " for i in range(len(text)):\n", 86 | " t = text[i] #读取文本列表中的第i条文本\n", 87 | " for w in t:\n", 88 | " ind = words.index(w) #取出第i条文本中的第t个单词在单词列表中的索引\n", 89 | " X[ind][i] += 1 #对应位置的单词出现频次加一\n", 90 | " return X\n", 91 | "\n", 92 | "\n", 93 | "#定义潜在语义分析函数\n", 94 | "def do_lsa(X, k, words):\n", 95 | " '''\n", 96 | " INPUT:\n", 97 | " X - (array) 单词-文本矩阵\n", 98 | " k - (int) 设定的话题数\n", 99 | " words - (list) 单词列表\n", 100 | " \n", 101 | " OUTPUT:\n", 102 | " topics - (list) 生成的话题列表\n", 103 | " \n", 104 | " '''\n", 105 | " w, v = np.linalg.eig(np.matmul(X.T, X)) #计算Sx的特征值和特征向量,其中Sx=X.T*X,Sx的特征值w即为X的奇异值分解的奇异值,v即为对应的奇异向量\n", 106 | " sort_inds = np.argsort(w)[::-1] #对特征值降序排列后取出对应的索引值\n", 107 | " w = np.sort(w)[::-1] #对特征值降序排列\n", 108 | " V_T = [] #用来保存矩阵V的转置\n", 109 | " for ind in sort_inds:\n", 110 | " V_T.append(v[ind]/np.linalg.norm(v[ind])) #将降序排列后各特征值对应的特征向量单位化后保存到V_T中\n", 111 | " V_T = np.array(V_T) #将V_T转换为数组,方便之后的操作\n", 112 | " Sigma = np.diag(np.sqrt(w)) #将特征值数组w转换为对角矩阵,即得到SVD分解中的Sigma\n", 113 | " U = np.zeros((len(words), k)) #用来保存SVD分解中的矩阵U\n", 114 | " for i in range(k):\n", 115 | " ui = np.matmul(X, V_T.T[:, i]) / Sigma[i][i] #计算矩阵U的第i个列向量\n", 116 | " U[:, i] = ui #保存到矩阵U中\n", 117 | " topics = [] #用来保存k个话题\n", 118 | " for i in range(k):\n", 119 | " inds = np.argsort(U[:, i])[::-1] #U的每个列向量表示一个话题向量,话题向量的长度为m,其中每个值占向量值之和的比重表示对应单词在当前话题中所占的比重,这里对第i个话题向量的值降序排列后取出对应的索引值\n", 120 | " topic = [] #用来保存第i个话题\n", 121 | " for j in range(10):\n", 122 | " topic.append(words[inds[j]]) #根据索引inds取出当前话题中比重最大的10个单词作为第i个话题\n", 123 | " topics.append(' '.join(topic)) #保存话题i\n", 124 | " return topics\n", 125 | "\n", 126 | "\n", 127 | "if __name__ == \"__main__\":\n", 128 | " org_topics, text, words = load_data('bbc_text.csv') #加载数据\n", 129 | " print('Original Topics:')\n", 130 | " print(org_topics) #打印原始的话题标签列表\n", 131 | " start = time.time() #保存开始时间\n", 132 | " X = frequency_counter(text, words) #构建单词-文本矩阵\n", 133 | " k = 5 #设定话题数为5\n", 134 | " topics = do_lsa(X, k, words) #进行潜在语义分析\n", 135 | " print('Generated Topics:')\n", 136 | " for i in range(k):\n", 137 | " print('Topic {}: {}'.format(i+1, topics[i])) #打印分析后得到的每个话题\n", 138 | " end = time.time() #保存结束时间\n", 139 | " print('Time:', end-start)" 140 | ] 141 | }, 142 | { 143 | "cell_type": "code", 144 | "execution_count": null, 145 | "metadata": {}, 146 | "outputs": [], 147 | "source": [] 148 | } 149 | ], 150 | "metadata": { 151 | "kernelspec": { 152 | "display_name": "Python 3", 153 | "language": "python", 154 | "name": "python3" 155 | }, 156 | "language_info": { 157 | "codemirror_mode": { 158 | "name": "ipython", 159 | "version": 3 160 | }, 161 | "file_extension": ".py", 162 | "mimetype": "text/x-python", 163 | "name": "python", 164 | "nbconvert_exporter": "python", 165 | "pygments_lexer": "ipython3", 166 | "version": "3.7.3" 167 | } 168 | }, 169 | "nbformat": 4, 170 | "nbformat_minor": 2 171 | } 172 | -------------------------------------------------------------------------------- /LSA/LSA.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | #Author:Harold 3 | #Date:2021-1-27 4 | #Email:zenghr_zero@163.com 5 | 6 | ''' 7 | 数据集:bbc_text 8 | 数据集数量:2225 9 | ----------------------------- 10 | 运行结果: 11 | 话题数:5 12 | 原始话题:'tech', 'business', 'sport', 'entertainment', 'politics' 13 | 生成话题: 14 | 1:'said people would music blair government best year film howard' 15 | 2:'said would labour party people last could years kilroysilk show' 16 | 3:'music microsoft year best urban industry record software email think' 17 | 4:'wales first games lord government play house public control prime' 18 | 5:'said mobile england people phone dallaglio rugby blair election would' 19 | 运行时长:212.96s 20 | ''' 21 | 22 | import numpy as np 23 | import pandas as pd 24 | import string 25 | from nltk.corpus import stopwords 26 | import time 27 | 28 | 29 | #定义加载数据的函数 30 | def load_data(file): 31 | ''' 32 | INPUT: 33 | file - (str) 数据文件的路径 34 | 35 | OUTPUT: 36 | org_topics - (list) 原始话题标签列表 37 | text - (list) 文本列表 38 | words - (list) 单词列表 39 | 40 | ''' 41 | df = pd.read_csv(file) #读取文件 42 | org_topics = df['category'].unique().tolist() #保存文本原始的话题标签 43 | df.drop('category', axis=1, inplace=True) 44 | n = df.shape[0] #n为文本数量 45 | text = [] 46 | words = [] 47 | for i in df['text'].values: 48 | t = i.translate(str.maketrans('', '', string.punctuation)) #去除文本中的标点符号 49 | t = [j for j in t.split() if j not in stopwords.words('english')] #去除文本中的停止词 50 | t = [j for j in t if len(j) > 3] #长度小于等于3的单词大多是无意义的,直接去除 51 | text.append(t) #将处理后的文本保存到文本列表中 52 | words.extend(set(t)) #将文本中所包含的单词保存到单词列表中 53 | words = list(set(words)) #去除单词列表中的重复单词 54 | return org_topics, text, words 55 | 56 | 57 | #定义构建单词-文本矩阵的函数,这里矩阵的每一项表示单词在文本中的出现频次,也可以用TF-IDF来表示 58 | def frequency_counter(text, words): 59 | ''' 60 | INPUT: 61 | text - (list) 文本列表 62 | words - (list) 单词列表 63 | 64 | OUTPUT: 65 | X - (array) 单词-文本矩阵 66 | 67 | ''' 68 | X = np.zeros((len(words), len(text))) #定义m*n的矩阵,其中m为单词列表中的单词个数,n为文本个数 69 | for i in range(len(text)): 70 | t = text[i] #读取文本列表中的第i条文本 71 | for w in t: 72 | ind = words.index(w) #取出第i条文本中的第t个单词在单词列表中的索引 73 | X[ind][i] += 1 #对应位置的单词出现频次加一 74 | return X 75 | 76 | 77 | #定义潜在语义分析函数 78 | def do_lsa(X, k, words): 79 | ''' 80 | INPUT: 81 | X - (array) 单词-文本矩阵 82 | k - (int) 设定的话题数 83 | words - (list) 单词列表 84 | 85 | OUTPUT: 86 | topics - (list) 生成的话题列表 87 | 88 | ''' 89 | w, v = np.linalg.eig(np.matmul(X.T, X)) #计算Sx的特征值和特征向量,其中Sx=X.T*X,Sx的特征值w即为X的奇异值分解的奇异值,v即为对应的奇异向量 90 | sort_inds = np.argsort(w)[::-1] #对特征值降序排列后取出对应的索引值 91 | w = np.sort(w)[::-1] #对特征值降序排列 92 | V_T = [] #用来保存矩阵V的转置 93 | for ind in sort_inds: 94 | V_T.append(v[ind]/np.linalg.norm(v[ind])) #将降序排列后各特征值对应的特征向量单位化后保存到V_T中 95 | V_T = np.array(V_T) #将V_T转换为数组,方便之后的操作 96 | Sigma = np.diag(np.sqrt(w)) #将特征值数组w转换为对角矩阵,即得到SVD分解中的Sigma 97 | U = np.zeros((len(words), k)) #用来保存SVD分解中的矩阵U 98 | for i in range(k): 99 | ui = np.matmul(X, V_T.T[:, i]) / Sigma[i][i] #计算矩阵U的第i个列向量 100 | U[:, i] = ui #保存到矩阵U中 101 | topics = [] #用来保存k个话题 102 | for i in range(k): 103 | inds = np.argsort(U[:, i])[::-1] #U的每个列向量表示一个话题向量,话题向量的长度为m,其中每个值占向量值之和的比重表示对应单词在当前话题中所占的比重,这里对第i个话题向量的值降序排列后取出对应的索引值 104 | topic = [] #用来保存第i个话题 105 | for j in range(10): 106 | topic.append(words[inds[j]]) #根据索引inds取出当前话题中比重最大的10个单词作为第i个话题 107 | topics.append(' '.join(topic)) #保存话题i 108 | return topics 109 | 110 | 111 | if __name__ == "__main__": 112 | org_topics, text, words = load_data('bbc_text.csv') #加载数据 113 | print('Original Topics:') 114 | print(org_topics) #打印原始的话题标签列表 115 | start = time.time() #保存开始时间 116 | X = frequency_counter(text, words) #构建单词-文本矩阵 117 | k = 5 #设定话题数为5 118 | topics = do_lsa(X, k, words) #进行潜在语义分析 119 | print('Generated Topics:') 120 | for i in range(k): 121 | print('Topic {}: {}'.format(i+1, topics[i])) #打印分析后得到的每个话题 122 | end = time.time() #保存结束时间 123 | print('Time:', end-start) -------------------------------------------------------------------------------- /Logistic_and_maximum_entropy_models/logisticRegression.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # Author:Dodo 3 | # Date:2018-11-27 4 | # Email:lvtengchao@pku.edu.cn 5 | # Blog:www.pkudodo.com 6 | 7 | ''' 8 | 数据集:Mnist 9 | 训练集数量:60000 10 | 测试集数量:10000 11 | ------------------------------ 12 | 运行结果: 13 | 正确率:98.91% 14 | 运行时长:59s 15 | ''' 16 | 17 | import time 18 | import numpy as np 19 | 20 | 21 | def loadData(fileName): 22 | ''' 23 | 加载Mnist数据集 24 | :param fileName:要加载的数据集路径 25 | :return: list形式的数据集及标记 26 | ''' 27 | # 存放数据及标记的list 28 | dataList = []; labelList = [] 29 | # 打开文件 30 | fr = open(fileName, 'r') 31 | # 将文件按行读取 32 | for line in fr.readlines(): 33 | # 对每一行数据按切割福','进行切割,返回字段列表 34 | curLine = line.strip().split(',') 35 | 36 | # Mnsit有0-9是个标记,由于是二分类任务,所以将标记0的作为1,其余为0 37 | # 验证过<5为1 >5为0时正确率在90%左右,猜测是因为数多了以后,可能不同数的特征较乱,不能有效地计算出一个合理的超平面 38 | # 查看了一下之前感知机的结果,以5为分界时正确率81,重新修改为0和其余数时正确率98.91% 39 | # 看来如果样本标签比较杂的话,对于是否能有效地划分超平面确实存在很大影响 40 | if int(curLine[0]) == 0: 41 | labelList.append(1) 42 | else: 43 | labelList.append(0) 44 | #存放标记 45 | #[int(num) for num in curLine[1:]] -> 遍历每一行中除了以第一哥元素(标记)外将所有元素转换成int类型 46 | #[int(num)/255 for num in curLine[1:]] -> 将所有数据除255归一化(非必须步骤,可以不归一化) 47 | dataList.append([int(num)/255 for num in curLine[1:]]) 48 | # dataList.append([int(num) for num in curLine[1:]]) 49 | 50 | #返回data和label 51 | return dataList, labelList 52 | 53 | def predict(w, x): 54 | ''' 55 | 预测标签 56 | :param w:训练过程中学到的w 57 | :param x: 要预测的样本 58 | :return: 预测结果 59 | ''' 60 | #dot为两个向量的点积操作,计算得到w * x 61 | wx = np.dot(w, x) 62 | #计算标签为1的概率 63 | #该公式参考“6.1.2 二项逻辑斯蒂回归模型”中的式6.5 64 | P1 = np.exp(wx) / (1 + np.exp(wx)) 65 | #如果为1的概率大于0.5,返回1 66 | if P1 >= 0.5: 67 | return 1 68 | #否则返回0 69 | return 0 70 | 71 | def logisticRegression(trainDataList, trainLabelList, iter = 200): 72 | ''' 73 | 逻辑斯蒂回归训练过程 74 | :param trainDataList:训练集 75 | :param trainLabelList: 标签集 76 | :param iter: 迭代次数 77 | :return: 习得的w 78 | ''' 79 | #按照书本“6.1.2 二项逻辑斯蒂回归模型”中式6.5的规则,将w与b合在一起, 80 | #此时x也需要添加一维,数值为1 81 | #循环遍历每一个样本,并在其最后添加一个1 82 | for i in range(len(trainDataList)): 83 | trainDataList[i].append(1) 84 | 85 | #将数据集由列表转换为数组形式,主要是后期涉及到向量的运算,统一转换成数组形式比较方便 86 | trainDataList = np.array(trainDataList) 87 | #初始化w,维数为样本x维数+1,+1的那一位是b,初始为0 88 | w = np.zeros(trainDataList.shape[1]) 89 | 90 | #设置步长 91 | h = 0.001 92 | 93 | #迭代iter次进行随机梯度下降 94 | for i in range(iter): 95 | #每次迭代冲遍历一次所有样本,进行随机梯度下降 96 | for j in range(trainDataList.shape[0]): 97 | #随机梯度上升部分 98 | #在“6.1.3 模型参数估计”一章中给出了似然函数,我们需要极大化似然函数 99 | #但是似然函数由于有求和项,并不能直接对w求导得出最优w,所以针对似然函数求和 100 | #部分中每一项进行单独地求导w,得到针对该样本的梯度,并进行梯度上升(因为是 101 | #要求似然函数的极大值,所以是梯度上升,如果是极小值就梯度下降。梯度上升是 102 | #加号,下降是减号) 103 | #求和式中每一项单独对w求导结果为:xi * yi - (exp(w * xi) * xi) / (1 + exp(w * xi)) 104 | #如果对于该求导式有疑问可查看我的博客 www.pkudodo.com 105 | 106 | #计算w * xi,因为后式中要计算两次该值,为了节约时间这里提前算出 107 | #其实也可直接算出exp(wx),为了读者能看得方便一点就这么写了,包括yi和xi都提前列出了 108 | wx = np.dot(w, trainDataList[j]) 109 | yi = trainLabelList[j] 110 | xi = trainDataList[j] 111 | #梯度上升 112 | w += h * (xi * yi - (np.exp(wx) * xi) / ( 1 + np.exp(wx))) 113 | 114 | #返回学到的w 115 | return w 116 | 117 | def model_test(testDataList, testLabelList, w): 118 | ''' 119 | 验证 120 | :param testDataList:测试集 121 | :param testLabelList: 测试集标签 122 | :param w: 训练过程中学到的w 123 | :return: 正确率 124 | ''' 125 | 126 | #与训练过程一致,先将所有的样本添加一维,值为1,理由请查看训练函数 127 | for i in range(len(testDataList)): 128 | testDataList[i].append(1) 129 | 130 | #错误值计数 131 | errorCnt = 0 132 | #对于测试集中每一个测试样本进行验证 133 | for i in range(len(testDataList)): 134 | #如果标记与预测不一致,错误值加1 135 | if testLabelList[i] != predict(w, testDataList[i]): 136 | errorCnt += 1 137 | #返回准确率 138 | return 1 - errorCnt / len(testDataList) 139 | 140 | 141 | 142 | if __name__ == '__main__': 143 | start = time.time() 144 | 145 | # 获取训练集及标签 146 | print('start read transSet') 147 | trainData, trainLabel = loadData('../Mnist/mnist_train.csv') 148 | 149 | # 获取测试集及标签 150 | print('start read testSet') 151 | testData, testLabel = loadData('../Mnist/mnist_test.csv') 152 | 153 | # 开始训练,学习w 154 | print('start to train') 155 | w = logisticRegression(trainData, trainLabel) 156 | 157 | #验证正确率 158 | print('start to test') 159 | accuracy = model_test(testData, testLabel, w) 160 | 161 | # 打印准确率 162 | print('the accuracy is:', accuracy) 163 | # 打印时间 164 | print('time span:', time.time() - start) 165 | 166 | -------------------------------------------------------------------------------- /Logistic_and_maximum_entropy_models/maxEntropy.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # Author:Dodo 3 | # Date:2018-11-30 4 | # Email:lvtengchao@pku.edu.cn 5 | # Blog:www.pkudodo.com 6 | 7 | ''' 8 | 数据集:Mnist 9 | 训练集数量:60000(实际使用:20000) 10 | 测试集数量:10000 11 | ------------------------------ 12 | 运行结果: 13 | 正确率:96.9% 14 | 运行时长:8.8h 15 | 16 | 备注:对于mnist而言,李航的统计学习方法中有一些关键细节没有阐述, 17 | 建议先阅读我的个人博客,其中有详细阐述。阅读结束后再看该程序。 18 | Blog:www.pkudodo.com 19 | ''' 20 | 21 | import time 22 | import numpy as np 23 | from collections import defaultdict 24 | 25 | def loadData(fileName): 26 | ''' 27 | 加载Mnist数据集 28 | :param fileName:要加载的数据集路径 29 | :return: list形式的数据集及标记 30 | ''' 31 | # 存放数据及标记的list 32 | dataList = []; labelList = [] 33 | # 打开文件 34 | fr = open(fileName, 'r') 35 | # 将文件按行读取 36 | for line in fr.readlines(): 37 | # 对每一行数据按切割福','进行切割,返回字段列表 38 | curLine = line.strip().split(',') 39 | #二分类,list中放置标签 40 | if int(curLine[0]) == 0: 41 | labelList.append(1) 42 | else: 43 | labelList.append(0) 44 | #二值化 45 | dataList.append([int(int(num) > 128) for num in curLine[1:]]) 46 | 47 | #返回data和label 48 | return dataList, labelList 49 | 50 | 51 | class maxEnt: 52 | ''' 53 | 最大熵类 54 | ''' 55 | def __init__(self, trainDataList, trainLabelList, testDataList, testLabelList): 56 | ''' 57 | 各参数初始化 58 | ''' 59 | self.trainDataList = trainDataList #训练数据集 60 | self.trainLabelList = trainLabelList #训练标签集 61 | self.testDataList = testDataList #测试数据集 62 | self.testLabelList = testLabelList #测试标签集 63 | self.featureNum = len(trainDataList[0]) #特征数量 64 | 65 | self.N = len(trainDataList) #总训练集长度 66 | self.n = 0 #训练集中(xi,y)对数量 67 | self.M = 10000 # 68 | self.fixy = self.calc_fixy() #所有(x, y)对出现的次数 69 | self.w = [0] * self.n #Pw(y|x)中的w 70 | self.xy2idDict, self.id2xyDict = self.createSearchDict() #(x, y)->id和id->(x, y)的搜索字典 71 | self.Ep_xy = self.calcEp_xy() #Ep_xy期望值 72 | 73 | def calcEpxy(self): 74 | ''' 75 | 计算特征函数f(x, y)关于模型P(Y|X)与经验分布P_(X, Y)的期望值(P后带下划线“_”表示P上方的横线 76 | 程序中部分下划线表示“|”,部分表示上方横线,请根据具体公式自行判断,) 77 | 即“6.2.2 最大熵模型的定义”中第二个期望(83页最上方的期望) 78 | :return: 79 | ''' 80 | #初始化期望存放列表,对于每一个xy对都有一个期望 81 | #这里的x是单个的特征,不是一个样本的全部特征。例如x={x1,x2,x3.....,xk},实际上是(x1,y),(x2,y),。。。 82 | #但是在存放过程中需要将不同特诊的分开存放,李航的书可能是为了公式的泛化性高一点,所以没有对这部分提及 83 | #具体可以看我的博客,里面有详细介绍 www.pkudodo.com 84 | Epxy = [0] * self.n 85 | #对于每一个样本进行遍历 86 | for i in range(self.N): 87 | #初始化公式中的P(y|x)列表 88 | Pwxy = [0] * 2 89 | #计算P(y = 0 } X) 90 | #注:程序中X表示是一个样本的全部特征,x表示单个特征,这里是全部特征的一个样本 91 | Pwxy[0] = self.calcPwy_x(self.trainDataList[i], 0) 92 | #计算P(y = 1 } X) 93 | Pwxy[1] = self.calcPwy_x(self.trainDataList[i], 1) 94 | 95 | for feature in range(self.featureNum): 96 | for y in range(2): 97 | if (self.trainDataList[i][feature], y) in self.fixy[feature]: 98 | id = self.xy2idDict[feature][(self.trainDataList[i][feature], y)] 99 | Epxy[id] += (1 / self.N) * Pwxy[y] 100 | return Epxy 101 | 102 | def calcEp_xy(self): 103 | ''' 104 | 计算特征函数f(x, y)关于经验分布P_(x, y)的期望值(下划线表示P上方的横线, 105 | 同理Ep_xy中的“_”也表示p上方的横线) 106 | 即“6.2.2 最大熵的定义”中第一个期望(82页最下方那个式子) 107 | :return: 计算得到的Ep_xy 108 | ''' 109 | #初始化Ep_xy列表,长度为n 110 | Ep_xy = [0] * self.n 111 | 112 | #遍历每一个特征 113 | for feature in range(self.featureNum): 114 | #遍历每个特征中的(x, y)对 115 | for (x, y) in self.fixy[feature]: 116 | #获得其id 117 | id = self.xy2idDict[feature][(x, y)] 118 | #将计算得到的Ep_xy写入对应的位置中 119 | #fixy中存放所有对在训练集中出现过的次数,处于训练集总长度N就是概率了 120 | Ep_xy[id] = self.fixy[feature][(x, y)] / self.N 121 | 122 | #返回期望 123 | return Ep_xy 124 | 125 | def createSearchDict(self): 126 | ''' 127 | 创建查询字典 128 | xy2idDict:通过(x,y)对找到其id,所有出现过的xy对都有一个id 129 | id2xyDict:通过id找到对应的(x,y)对 130 | ''' 131 | #设置xy搜多id字典 132 | #这里的x指的是单个的特征,而不是某个样本,因此将特征存入字典时也需要存入这是第几个特征 133 | #这一信息,这是为了后续的方便,否则会乱套。 134 | #比如说一个样本X = (0, 1, 1) label =(1) 135 | #生成的标签对有(0, 1), (1, 1), (1, 1),三个(x,y)对并不能判断属于哪个特征的,后续就没法往下写 136 | #不可能通过(1, 1)就能找到对应的id,因为对于(1, 1),字典中有多重映射 137 | #所以在生成字典的时总共生成了特征数个字典,例如在mnist中样本有784维特征,所以生成784个字典,属于 138 | #不同特征的xy存入不同特征内的字典中,使其不会混淆 139 | xy2idDict = [{} for i in range(self.featureNum)] 140 | #初始化id到xy对的字典。因为id与(x,y)的指向是唯一的,所以可以使用一个字典 141 | id2xyDict = {} 142 | 143 | #设置缩影,其实就是最后的id 144 | index = 0 145 | #对特征进行遍历 146 | for feature in range(self.featureNum): 147 | #对出现过的每一个(x, y)对进行遍历 148 | #fixy:内部存放特征数目个字典,对于遍历的每一个特征,单独读取对应字典内的(x, y)对 149 | for (x, y) in self.fixy[feature]: 150 | #将该(x, y)对存入字典中,要注意存入时通过[feature]指定了存入哪个特征内部的字典 151 | #同时将index作为该对的id号 152 | xy2idDict[feature][(x, y)] = index 153 | #同时在id->xy字典中写入id号,val为(x, y)对 154 | id2xyDict[index] = (x, y) 155 | #id加一 156 | index += 1 157 | 158 | #返回创建的两个字典 159 | return xy2idDict, id2xyDict 160 | 161 | 162 | def calc_fixy(self): 163 | ''' 164 | 计算(x, y)在训练集中出现过的次数 165 | :return: 166 | ''' 167 | #建立特征数目个字典,属于不同特征的(x, y)对存入不同的字典中,保证不被混淆 168 | fixyDict = [defaultdict(int) for i in range(self.featureNum)] 169 | #遍历训练集中所有样本 170 | for i in range(len(self.trainDataList)): 171 | #遍历样本中所有特征 172 | for j in range(self.featureNum): 173 | #将出现过的(x, y)对放入字典中并计数值加1 174 | fixyDict[j][(self.trainDataList[i][j], self.trainLabelList[i])] += 1 175 | #对整个大字典进行计数,判断去重后还有多少(x, y)对,写入n 176 | for i in fixyDict: 177 | self.n += len(i) 178 | #返回大字典 179 | return fixyDict 180 | 181 | 182 | def calcPwy_x(self, X, y): 183 | ''' 184 | 计算“6.23 最大熵模型的学习” 式6.22 185 | :param X: 要计算的样本X(一个包含全部特征的样本) 186 | :param y: 该样本的标签 187 | :return: 计算得到的Pw(Y|X) 188 | ''' 189 | #分子 190 | numerator = 0 191 | #分母 192 | Z = 0 193 | #对每个特征进行遍历 194 | for i in range(self.featureNum): 195 | #如果该(xi,y)对在训练集中出现过 196 | if (X[i], y) in self.xy2idDict[i]: 197 | #在xy->id字典中指定当前特征i,以及(x, y)对:(X[i], y),读取其id 198 | index = self.xy2idDict[i][(X[i], y)] 199 | #分子是wi和fi(x,y)的连乘再求和,最后指数 200 | #由于当(x, y)存在时fi(x,y)为1,因为xy对肯定存在,所以直接就是1 201 | #对于分子来说,就是n个wi累加,最后再指数就可以了 202 | #因为有n个w,所以通过id将w与xy绑定,前文的两个搜索字典中的id就是用在这里 203 | numerator += self.w[index] 204 | #同时计算其他一种标签y时候的分子,下面的z并不是全部的分母,再加上上式的分子以后 205 | #才是完整的分母,即z = z + numerator 206 | if (X[i], 1-y) in self.xy2idDict[i]: 207 | #原理与上式相同 208 | index = self.xy2idDict[i][(X[i], 1-y)] 209 | Z += self.w[index] 210 | #计算分子的指数 211 | numerator = np.exp(numerator) 212 | #计算分母的z 213 | Z = np.exp(Z) + numerator 214 | #返回Pw(y|x) 215 | return numerator / Z 216 | 217 | 218 | def maxEntropyTrain(self, iter = 500): 219 | #设置迭代次数寻找最优解 220 | for i in range(iter): 221 | #单次迭代起始时间点 222 | iterStart = time.time() 223 | 224 | #计算“6.2.3 最大熵模型的学习”中的第二个期望(83页最上方哪个) 225 | Epxy = self.calcEpxy() 226 | 227 | #使用的是IIS,所以设置sigma列表 228 | sigmaList = [0] * self.n 229 | #对于所有的n进行一次遍历 230 | for j in range(self.n): 231 | #依据“6.3.1 改进的迭代尺度法” 式6.34计算 232 | sigmaList[j] = (1 / self.M) * np.log(self.Ep_xy[j] / Epxy[j]) 233 | 234 | #按照算法6.1步骤二中的(b)更新w 235 | self.w = [self.w[i] + sigmaList[i] for i in range(self.n)] 236 | 237 | #单次迭代结束 238 | iterEnd = time.time() 239 | #打印运行时长信息 240 | print('iter:%d:%d, time:%d'%(i, iter, iterStart - iterEnd)) 241 | 242 | def predict(self, X): 243 | ''' 244 | 预测标签 245 | :param X:要预测的样本 246 | :return: 预测值 247 | ''' 248 | #因为y只有0和1,所有建立两个长度的概率列表 249 | result = [0] * 2 250 | #循环计算两个概率 251 | for i in range(2): 252 | #计算样本x的标签为i的概率 253 | result[i] = self.calcPwy_x(X, i) 254 | #返回标签 255 | #max(result):找到result中最大的那个概率值 256 | #result.index(max(result)):通过最大的那个概率值再找到其索引,索引是0就返回0,1就返回1 257 | return result.index(max(result)) 258 | 259 | def test(self): 260 | ''' 261 | 对测试集进行测试 262 | :return: 263 | ''' 264 | #错误值计数 265 | errorCnt = 0 266 | #对测试集中所有样本进行遍历 267 | for i in range(len(self.testDataList)): 268 | #预测该样本对应的标签 269 | result = self.predict(self.testDataList[i]) 270 | #如果错误,计数值加1 271 | if result != self.testLabelList[i]: errorCnt += 1 272 | #返回准确率 273 | return 1 - errorCnt / len(self.testDataList) 274 | 275 | if __name__ == '__main__': 276 | start = time.time() 277 | 278 | # 获取训练集及标签 279 | print('start read transSet') 280 | trainData, trainLabel = loadData('../Mnist/mnist_train.csv') 281 | 282 | # 获取测试集及标签 283 | print('start read testSet') 284 | testData, testLabel = loadData('../Mnist/mnist_test.csv') 285 | 286 | #初始化最大熵类 287 | maxEnt = maxEnt(trainData[:20000], trainLabel[:20000], testData, testLabel) 288 | 289 | #开始训练 290 | print('start to train') 291 | maxEnt.maxEntropyTrain() 292 | 293 | #开始测试 294 | print('start to test') 295 | accuracy = maxEnt.test() 296 | print('the accuracy is:', accuracy) 297 | 298 | # 打印时间 299 | print('time span:', time.time() - start) 300 | -------------------------------------------------------------------------------- /Mnist/mnist_test.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dod-o/Statistical-Learning-Method_Code/cd1d28337d223bc164e4949c167958634f409939/Mnist/mnist_test.rar -------------------------------------------------------------------------------- /Mnist/mnist_train.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dod-o/Statistical-Learning-Method_Code/cd1d28337d223bc164e4949c167958634f409939/Mnist/mnist_train.rar -------------------------------------------------------------------------------- /NaiveBayes/NaiveBayes.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | # Author:Dodo 3 | # Date:2018-11-17 4 | # Email:lvtengchao@pku.edu.cn 5 | 6 | ''' 7 | 数据集:Mnist 8 | 训练集数量:60000 9 | 测试集数量:10000 10 | ------------------------------ 11 | 运行结果: 12 | 正确率:84.3% 13 | 运行时长:103s 14 | ''' 15 | 16 | import numpy as np 17 | import time 18 | 19 | def loadData(fileName): 20 | ''' 21 | 加载文件 22 | :param fileName:要加载的文件路径 23 | :return: 数据集和标签集 24 | ''' 25 | #存放数据及标记 26 | dataArr = []; labelArr = [] 27 | #读取文件 28 | fr = open(fileName) 29 | #遍历文件中的每一行 30 | for line in fr.readlines(): 31 | #获取当前行,并按“,”切割成字段放入列表中 32 | #strip:去掉每行字符串首尾指定的字符(默认空格或换行符) 33 | #split:按照指定的字符将字符串切割成每个字段,返回列表形式 34 | curLine = line.strip().split(',') 35 | #将每行中除标记外的数据放入数据集中(curLine[0]为标记信息) 36 | #在放入的同时将原先字符串形式的数据转换为整型 37 | #此外将数据进行了二值化处理,大于128的转换成1,小于的转换成0,方便后续计算 38 | dataArr.append([int(int(num) > 128) for num in curLine[1:]]) 39 | #将标记信息放入标记集中 40 | #放入的同时将标记转换为整型 41 | labelArr.append(int(curLine[0])) 42 | #返回数据集和标记 43 | return dataArr, labelArr 44 | 45 | def NaiveBayes(Py, Px_y, x): 46 | ''' 47 | 通过朴素贝叶斯进行概率估计 48 | :param Py: 先验概率分布 49 | :param Px_y: 条件概率分布 50 | :param x: 要估计的样本x 51 | :return: 返回所有label的估计概率 52 | ''' 53 | #设置特征数目 54 | featrueNum = 784 55 | #设置类别数目 56 | classNum = 10 57 | #建立存放所有标记的估计概率数组 58 | P = [0] * classNum 59 | #对于每一个类别,单独估计其概率 60 | for i in range(classNum): 61 | #初始化sum为0,sum为求和项。 62 | #在训练过程中对概率进行了log处理,所以这里原先应当是连乘所有概率,最后比较哪个概率最大 63 | #但是当使用log处理时,连乘变成了累加,所以使用sum 64 | sum = 0 65 | #获取每一个条件概率值,进行累加 66 | for j in range(featrueNum): 67 | sum += Px_y[i][j][x[j]] 68 | #最后再和先验概率相加(也就是式4.7中的先验概率乘以后头那些东西,乘法因为log全变成了加法) 69 | P[i] = sum + Py[i] 70 | 71 | #max(P):找到概率最大值 72 | #P.index(max(P)):找到该概率最大值对应的所有(索引值和标签值相等) 73 | return P.index(max(P)) 74 | 75 | 76 | def model_test(Py, Px_y, testDataArr, testLabelArr): 77 | ''' 78 | 对测试集进行测试 79 | :param Py: 先验概率分布 80 | :param Px_y: 条件概率分布 81 | :param testDataArr: 测试集数据 82 | :param testLabelArr: 测试集标记 83 | :return: 准确率 84 | ''' 85 | #错误值计数 86 | errorCnt = 0 87 | #循环遍历测试集中的每一个样本 88 | for i in range(len(testDataArr)): 89 | #获取预测值 90 | presict = NaiveBayes(Py, Px_y, testDataArr[i]) 91 | #与答案进行比较 92 | if presict != testLabelArr[i]: 93 | #若错误 错误值计数加1 94 | errorCnt += 1 95 | #返回准确率 96 | return 1 - (errorCnt / len(testDataArr)) 97 | 98 | 99 | def getAllProbability(trainDataArr, trainLabelArr): 100 | ''' 101 | 通过训练集计算先验概率分布和条件概率分布 102 | :param trainDataArr: 训练数据集 103 | :param trainLabelArr: 训练标记集 104 | :return: 先验概率分布和条件概率分布 105 | ''' 106 | #设置样本特诊数目,数据集中手写图片为28*28,转换为向量是784维。 107 | # (我们的数据集已经从图像转换成784维的形式了,CSV格式内就是) 108 | featureNum = 784 109 | #设置类别数目,0-9共十个类别 110 | classNum = 10 111 | 112 | #初始化先验概率分布存放数组,后续计算得到的P(Y = 0)放在Py[0]中,以此类推 113 | #数据长度为10行1列 114 | Py = np.zeros((classNum, 1)) 115 | #对每个类别进行一次循环,分别计算它们的先验概率分布 116 | #计算公式为书中"4.2节 朴素贝叶斯法的参数估计 公式4.8" 117 | for i in range(classNum): 118 | #下方式子拆开分析 119 | #np.mat(trainLabelArr) == i:将标签转换为矩阵形式,里面的每一位与i比较,若相等,该位变为Ture,反之False 120 | #np.sum(np.mat(trainLabelArr) == i):计算上一步得到的矩阵中Ture的个数,进行求和(直观上就是找所有label中有多少个 121 | #为i的标记,求得4.8式P(Y = Ck)中的分子) 122 | #np.sum(np.mat(trainLabelArr) == i)) + 1:参考“4.2.3节 贝叶斯估计”,例如若数据集总不存在y=1的标记,也就是说 123 | #手写数据集中没有1这张图,那么如果不加1,由于没有y=1,所以分子就会变成0,那么在最后求后验概率时这一项就变成了0,再 124 | #和条件概率乘,结果同样为0,不允许存在这种情况,所以分子加1,分母加上K(K为标签可取的值数量,这里有10个数,取值为10) 125 | #参考公式4.11 126 | #(len(trainLabelArr) + 10):标签集的总长度+10. 127 | #((np.sum(np.mat(trainLabelArr) == i)) + 1) / (len(trainLabelArr) + 10):最后求得的先验概率 128 | Py[i] = ((np.sum(np.mat(trainLabelArr) == i)) + 1) / (len(trainLabelArr) + 10) 129 | #转换为log对数形式 130 | #log书中没有写到,但是实际中需要考虑到,原因是这样: 131 | #最后求后验概率估计的时候,形式是各项的相乘(“4.1 朴素贝叶斯法的学习” 式4.7),这里存在两个问题:1.某一项为0时,结果为0. 132 | #这个问题通过分子和分母加上一个相应的数可以排除,前面已经做好了处理。2.如果特诊特别多(例如在这里,需要连乘的项目有784个特征 133 | #加一个先验概率分布一共795项相乘,所有数都是0-1之间,结果一定是一个很小的接近0的数。)理论上可以通过结果的大小值判断, 但在 134 | #程序运行中很可能会向下溢出无法比较,因为值太小了。所以人为把值进行log处理。log在定义域内是一个递增函数,也就是说log(x)中, 135 | #x越大,log也就越大,单调性和原数据保持一致。所以加上log对结果没有影响。此外连乘项通过log以后,可以变成各项累加,简化了计算。 136 | #在似然函数中通常会使用log的方式进行处理(至于此书中为什么没涉及,我也不知道) 137 | Py = np.log(Py) 138 | 139 | #计算条件概率 Px_y=P(X=x|Y = y) 140 | #计算条件概率分成了两个步骤,下方第一个大for循环用于累加,参考书中“4.2.3 贝叶斯估计 式4.10”,下方第一个大for循环内部是 141 | #用于计算式4.10的分子,至于分子的+1以及分母的计算在下方第二个大For内 142 | #初始化为全0矩阵,用于存放所有情况下的条件概率 143 | Px_y = np.zeros((classNum, featureNum, 2)) 144 | #对标记集进行遍历 145 | for i in range(len(trainLabelArr)): 146 | #获取当前循环所使用的标记 147 | label = trainLabelArr[i] 148 | #获取当前要处理的样本 149 | x = trainDataArr[i] 150 | #对该样本的每一维特诊进行遍历 151 | for j in range(featureNum): 152 | #在矩阵中对应位置加1 153 | #这里还没有计算条件概率,先把所有数累加,全加完以后,在后续步骤中再求对应的条件概率 154 | Px_y[label][j][x[j]] += 1 155 | 156 | 157 | #第二个大for,计算式4.10的分母,以及分子和分母之间的除法 158 | #循环每一个标记(共10个) 159 | for label in range(classNum): 160 | #循环每一个标记对应的每一个特征 161 | for j in range(featureNum): 162 | #获取y=label,第j个特诊为0的个数 163 | Px_y0 = Px_y[label][j][0] 164 | #获取y=label,第j个特诊为1的个数 165 | Px_y1 = Px_y[label][j][1] 166 | #对式4.10的分子和分母进行相除,再除之前依据贝叶斯估计,分母需要加上2(为每个特征可取值个数) 167 | #分别计算对于y= label,x第j个特征为0和1的条件概率分布 168 | Px_y[label][j][0] = np.log((Px_y0 + 1) / (Px_y0 + Px_y1 + 2)) 169 | Px_y[label][j][1] = np.log((Px_y1 + 1) / (Px_y0 + Px_y1 + 2)) 170 | 171 | #返回先验概率分布和条件概率分布 172 | return Py, Px_y 173 | 174 | 175 | if __name__ == "__main__": 176 | start = time.time() 177 | # 获取训练集 178 | print('start read transSet') 179 | trainDataArr, trainLabelArr = loadData('../Mnist/mnist_train.csv') 180 | 181 | # 获取测试集 182 | print('start read testSet') 183 | testDataArr, testLabelArr = loadData('../Mnist/mnist_test.csv') 184 | 185 | #开始训练,学习先验概率分布和条件概率分布 186 | print('start to train') 187 | Py, Px_y = getAllProbability(trainDataArr, trainLabelArr) 188 | 189 | #使用习得的先验概率分布和条件概率分布对测试集进行测试 190 | print('start to test') 191 | accuracy = model_test(Py, Px_y, testDataArr, testLabelArr) 192 | 193 | #打印准确率 194 | print('the accuracy is:', accuracy) 195 | #打印时间 196 | print('time span:', time.time() -start) 197 | -------------------------------------------------------------------------------- /PCA/PCA.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "Time: 0.14439177513122559\n" 13 | ] 14 | } 15 | ], 16 | "source": [ 17 | "import numpy as np\n", 18 | "import pandas as pd\n", 19 | "import time \n", 20 | "\n", 21 | "\n", 22 | "#定义加载数据的函数\n", 23 | "def load_data(file):\n", 24 | " '''\n", 25 | " INPUT:\n", 26 | " file - (str) 数据文件的路径\n", 27 | " \n", 28 | " OUTPUT:\n", 29 | " df - (dataframe) 读取的数据表格\n", 30 | " X - (array) 特征数据数组\n", 31 | " \n", 32 | " '''\n", 33 | " df = pd.read_csv(file) #读取csv文件\n", 34 | " df.drop('Sports', axis=1, inplace=True) #去掉类别数据\n", 35 | " X = np.asarray(df.values).T #将数据转换成数组\n", 36 | " return df, X\n", 37 | "\n", 38 | "\n", 39 | "#定义规范化函数,对每一列特征进行规范化处理,使其成为期望为0方差为1的标准分布\n", 40 | "def Normalize(X):\n", 41 | " '''\n", 42 | " INPUT:\n", 43 | " X - (array) 特征数据数组\n", 44 | " \n", 45 | " OUTPUT:\n", 46 | " X - (array) 规范化处理后的特征数据数组\n", 47 | " \n", 48 | " '''\n", 49 | " m, n = X.shape\n", 50 | " for i in range(m):\n", 51 | " E_xi = np.mean(X[i]) #第i列特征的期望\n", 52 | " Var_xi = np.var(X[i], ddof=1) #第i列特征的方差\n", 53 | " for j in range(n):\n", 54 | " X[i][j] = (X[i][j] - E_xi) / np.sqrt(Var_xi) #对第i列特征的第j条数据进行规范化处理\n", 55 | " return X\n", 56 | "\n", 57 | "\n", 58 | "#定义奇异值分解函数,计算V矩阵和特征值\n", 59 | "def cal_V(X):\n", 60 | " '''\n", 61 | " INPUT:\n", 62 | " X - (array) 特征数据数组\n", 63 | " \n", 64 | " OUTPUT:\n", 65 | " eigvalues - (list) 特征值列表,其中特征值按从大到小排列\n", 66 | " V - (array) V矩阵\n", 67 | " \n", 68 | " '''\n", 69 | " newX = X.T / np.sqrt(X.shape[1]-1) #构造新矩阵X'\n", 70 | " Sx = np.matmul(newX.T, newX) #计算X的协方差矩阵Sx = X'.T * X'\n", 71 | " V_T = [] #用于保存V的转置\n", 72 | " w, v = np.linalg.eig(Sx) #计算Sx的特征值和对应的特征向量,即为X’的奇异值和奇异向量\n", 73 | " tmp = {} #定义一个字典用于保存特征值和特征向量,字典的键为特征值,值为对应的特征向量\n", 74 | " for i in range(len(w)):\n", 75 | " tmp[w[i]] = v[i]\n", 76 | " eigvalues = sorted(tmp, reverse=True) #将特征值逆序排列后保存到eigvalues列表中\n", 77 | " for i in eigvalues:\n", 78 | " d = 0\n", 79 | " for j in range(len(tmp[i])):\n", 80 | " d += tmp[i][j] ** 2\n", 81 | " V_T.append(tmp[i] / np.sqrt(d)) #计算特征值i的单位特征向量,即为V矩阵的列向量,将其保存到V_T中\n", 82 | " V = np.array(V_T).T #对V_T进行转置得到V矩阵\n", 83 | " return eigvalues, V\n", 84 | "\n", 85 | "\n", 86 | "#定义主成分分析函数\n", 87 | "def do_pca(X, k):\n", 88 | " '''\n", 89 | " INPUT:\n", 90 | " X - (array) 特征数据数组\n", 91 | " k - (int) 设定的主成分个数\n", 92 | " \n", 93 | " OUTPUT:\n", 94 | " fac_load - (array) 因子负荷量数组\n", 95 | " dimrates - (list) 可解释偏差列表\n", 96 | " Y - (array) 主成分矩阵\n", 97 | " \n", 98 | " '''\n", 99 | " eigvalues, V = cal_V(X) #计算特征值和V矩阵\n", 100 | " Vk = V[:, :k] #取V矩阵的前k列\n", 101 | " Y = np.matmul(Vk.T, X) #计算主成分矩阵,将m*n的样本矩阵X转换成k*n的样本主成分矩阵\n", 102 | " dimrates = [i / sum(eigvalues) for i in eigvalues[:k]] #计算可解释偏差,即前k个奇异值中每个奇异值占奇异值总和的比例,这个比例表示主成分i可解释原始数据中的可变性的比例\n", 103 | " fac_load = np.zeros((k, X.shape[0])) #用来保存主成分的因子负荷量\n", 104 | " for i in range(k): \n", 105 | " for j in range(X.shape[0]):\n", 106 | " fac_load[i][j] = np.sqrt(eigvalues[i]) * Vk[j][i] / np.sqrt(np.var(X[j])) #计算主成分i对应原始特征j的因子负荷量,保存到fac_load中\n", 107 | " return fac_load, dimrates, Y\n", 108 | "\n", 109 | "\n", 110 | "if __name__ == \"__main__\":\n", 111 | " df, X = load_data('cars.csv') #加载数据\n", 112 | " start = time.time() #保存开始时间\n", 113 | " X = Normalize(X) #对样本数据进行规范化处理\n", 114 | " k = 3 #设定主成分个数为3\n", 115 | " fac_load, dimrates, Y = do_pca(X, k) #进行主成分分析\n", 116 | " pca_result = pd.DataFrame(fac_load, index=['Dimension1', 'Dimension2', 'Dimension3'], columns=df.columns) #将结果保存为dataframe格式\n", 117 | " pca_result['Explained Variance'] = dimrates #将可解释偏差保存到pca_result的'Explained Variance'列\n", 118 | " end = time.time() #保存结束时间\n", 119 | " print('Time:', end-start)" 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": 2, 125 | "metadata": {}, 126 | "outputs": [ 127 | { 128 | "data": { 129 | "text/html": [ 130 | "
\n", 131 | "\n", 144 | "\n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | " \n", 167 | " \n", 168 | " \n", 169 | " \n", 170 | " \n", 171 | " \n", 172 | " \n", 173 | " \n", 174 | " \n", 175 | " \n", 176 | " \n", 177 | " \n", 178 | " \n", 179 | " \n", 180 | " \n", 181 | " \n", 182 | " \n", 183 | " \n", 184 | " \n", 185 | " \n", 186 | " \n", 187 | " \n", 188 | " \n", 189 | " \n", 190 | " \n", 191 | " \n", 192 | " \n", 193 | " \n", 194 | " \n", 195 | " \n", 196 | " \n", 197 | " \n", 198 | " \n", 199 | " \n", 200 | " \n", 201 | " \n", 202 | " \n", 203 | " \n", 204 | " \n", 205 | " \n", 206 | " \n", 207 | " \n", 208 | " \n", 209 | " \n", 210 | " \n", 211 | " \n", 212 | " \n", 213 | " \n", 214 | " \n", 215 | " \n", 216 | " \n", 217 | " \n", 218 | " \n", 219 | " \n", 220 | " \n", 221 | " \n", 222 | " \n", 223 | " \n", 224 | " \n", 225 | " \n", 226 | " \n", 227 | " \n", 228 | " \n", 229 | " \n", 230 | " \n", 231 | " \n", 232 | " \n", 233 | "
SUVWagonMinivanPickupAWDRWDRetailDealerEngineCylindersHorsepowerCityMPGHighwayMPGWeightWheelbaseLengthWidthExplained Variance
Dimension10.093402-1.203972-0.2384520.744765-0.8177940.628960-1.410562-0.913081-0.3540610.306548-0.718787-0.0120870.7761560.3065250.024460-0.1657100.0049590.435236
Dimension20.2188850.381160-0.8257740.2881590.3514360.299775-0.5313480.8514090.3885870.181236-0.198039-0.0064270.286177-0.519626-0.205063-0.214403-0.0094510.166736
Dimension3-0.0383480.014097-0.065819-1.162422-0.4582300.171052-0.3346200.0875110.181597-0.024812-0.0540030.0012390.060208-0.069595-0.023274-0.027595-0.0261040.103441
\n", 234 | "
" 235 | ], 236 | "text/plain": [ 237 | " SUV Wagon Minivan Pickup AWD RWD \\\n", 238 | "Dimension1 0.093402 -1.203972 -0.238452 0.744765 -0.817794 0.628960 \n", 239 | "Dimension2 0.218885 0.381160 -0.825774 0.288159 0.351436 0.299775 \n", 240 | "Dimension3 -0.038348 0.014097 -0.065819 -1.162422 -0.458230 0.171052 \n", 241 | "\n", 242 | " Retail Dealer Engine Cylinders Horsepower CityMPG \\\n", 243 | "Dimension1 -1.410562 -0.913081 -0.354061 0.306548 -0.718787 -0.012087 \n", 244 | "Dimension2 -0.531348 0.851409 0.388587 0.181236 -0.198039 -0.006427 \n", 245 | "Dimension3 -0.334620 0.087511 0.181597 -0.024812 -0.054003 0.001239 \n", 246 | "\n", 247 | " HighwayMPG Weight Wheelbase Length Width \\\n", 248 | "Dimension1 0.776156 0.306525 0.024460 -0.165710 0.004959 \n", 249 | "Dimension2 0.286177 -0.519626 -0.205063 -0.214403 -0.009451 \n", 250 | "Dimension3 0.060208 -0.069595 -0.023274 -0.027595 -0.026104 \n", 251 | "\n", 252 | " Explained Variance \n", 253 | "Dimension1 0.435236 \n", 254 | "Dimension2 0.166736 \n", 255 | "Dimension3 0.103441 " 256 | ] 257 | }, 258 | "execution_count": 2, 259 | "metadata": {}, 260 | "output_type": "execute_result" 261 | } 262 | ], 263 | "source": [ 264 | "pca_result" 265 | ] 266 | } 267 | ], 268 | "metadata": { 269 | "kernelspec": { 270 | "display_name": "Python 3", 271 | "language": "python", 272 | "name": "python3" 273 | }, 274 | "language_info": { 275 | "codemirror_mode": { 276 | "name": "ipython", 277 | "version": 3 278 | }, 279 | "file_extension": ".py", 280 | "mimetype": "text/x-python", 281 | "name": "python", 282 | "nbconvert_exporter": "python", 283 | "pygments_lexer": "ipython3", 284 | "version": "3.7.3" 285 | } 286 | }, 287 | "nbformat": 4, 288 | "nbformat_minor": 2 289 | } 290 | -------------------------------------------------------------------------------- /PCA/PCA.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | #Author:Harold 3 | #Date:2021-1-27 4 | #Email:zenghr_zero@163.com 5 | 6 | ''' 7 | 数据集:cars 8 | 数据集数量:387 9 | ----------------------------- 10 | 运行结果: 11 | 主成分个数:3 12 | 可解释偏差:0.71 13 | 运行时长:0.14s 14 | ''' 15 | 16 | import numpy as np 17 | import pandas as pd 18 | import time 19 | 20 | 21 | #定义加载数据的函数 22 | def load_data(file): 23 | ''' 24 | INPUT: 25 | file - (str) 数据文件的路径 26 | 27 | OUTPUT: 28 | df - (dataframe) 读取的数据表格 29 | X - (array) 特征数据数组 30 | 31 | ''' 32 | df = pd.read_csv(file) #读取csv文件 33 | df.drop('Sports', axis=1, inplace=True) #去掉类别数据 34 | X = np.asarray(df.values).T #将数据转换成数组 35 | return df, X 36 | 37 | 38 | #定义规范化函数,对每一列特征进行规范化处理,使其成为期望为0方差为1的标准分布 39 | def Normalize(X): 40 | ''' 41 | INPUT: 42 | X - (array) 特征数据数组 43 | 44 | OUTPUT: 45 | X - (array) 规范化处理后的特征数据数组 46 | 47 | ''' 48 | m, n = X.shape 49 | for i in range(m): 50 | E_xi = np.mean(X[i]) #第i列特征的期望 51 | Var_xi = np.var(X[i], ddof=1) #第i列特征的方差 52 | for j in range(n): 53 | X[i][j] = (X[i][j] - E_xi) / np.sqrt(Var_xi) #对第i列特征的第j条数据进行规范化处理 54 | return X 55 | 56 | 57 | #定义奇异值分解函数,计算V矩阵和特征值 58 | def cal_V(X): 59 | ''' 60 | INPUT: 61 | X - (array) 特征数据数组 62 | 63 | OUTPUT: 64 | eigvalues - (list) 特征值列表,其中特征值按从大到小排列 65 | V - (array) V矩阵 66 | 67 | ''' 68 | newX = X.T / np.sqrt(X.shape[1]-1) #构造新矩阵X' 69 | Sx = np.matmul(newX.T, newX) #计算X的协方差矩阵Sx = X'.T * X' 70 | V_T = [] #用于保存V的转置 71 | w, v = np.linalg.eig(Sx) #计算Sx的特征值和对应的特征向量,即为X’的奇异值和奇异向量 72 | tmp = {} #定义一个字典用于保存特征值和特征向量,字典的键为特征值,值为对应的特征向量 73 | for i in range(len(w)): 74 | tmp[w[i]] = v[i] 75 | eigvalues = sorted(tmp, reverse=True) #将特征值逆序排列后保存到eigvalues列表中 76 | for i in eigvalues: 77 | d = 0 78 | for j in range(len(tmp[i])): 79 | d += tmp[i][j] ** 2 80 | V_T.append(tmp[i] / np.sqrt(d)) #计算特征值i的单位特征向量,即为V矩阵的列向量,将其保存到V_T中 81 | V = np.array(V_T).T #对V_T进行转置得到V矩阵 82 | return eigvalues, V 83 | 84 | 85 | #定义主成分分析函数 86 | def do_pca(X, k): 87 | ''' 88 | INPUT: 89 | X - (array) 特征数据数组 90 | k - (int) 设定的主成分个数 91 | 92 | OUTPUT: 93 | fac_load - (array) 因子负荷量数组 94 | dimrates - (list) 可解释偏差列表 95 | Y - (array) 主成分矩阵 96 | 97 | ''' 98 | eigvalues, V = cal_V(X) #计算特征值和V矩阵 99 | Vk = V[:, :k] #取V矩阵的前k列 100 | Y = np.matmul(Vk.T, X) #计算主成分矩阵,将m*n的样本矩阵X转换成k*n的样本主成分矩阵 101 | dimrates = [i / sum(eigvalues) for i in eigvalues[:k]] #计算可解释偏差,即前k个奇异值中每个奇异值占奇异值总和的比例,这个比例表示主成分i可解释原始数据中的可变性的比例 102 | fac_load = np.zeros((k, X.shape[0])) #用来保存主成分的因子负荷量 103 | for i in range(k): 104 | for j in range(X.shape[0]): 105 | fac_load[i][j] = np.sqrt(eigvalues[i]) * Vk[j][i] / np.sqrt(np.var(X[j])) #计算主成分i对应原始特征j的因子负荷量,保存到fac_load中 106 | return fac_load, dimrates, Y 107 | 108 | 109 | if __name__ == "__main__": 110 | df, X = load_data('cars.csv') #加载数据 111 | start = time.time() #保存开始时间 112 | X = Normalize(X) #对样本数据进行规范化处理 113 | k = 3 #设定主成分个数为3 114 | fac_load, dimrates, Y = do_pca(X, k) #进行主成分分析 115 | pca_result = pd.DataFrame(fac_load, index=['Dimension1', 'Dimension2', 'Dimension3'], columns=df.columns) #将结果保存为dataframe格式 116 | pca_result['Explained Variance'] = dimrates #将可解释偏差保存到pca_result的'Explained Variance'列 117 | end = time.time() #保存结束时间 118 | print('Time:', end-start) -------------------------------------------------------------------------------- /PLSA/PLSA.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 55, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "Original Topics:\n", 13 | "['tech', 'business', 'sport', 'entertainment', 'politics']\n", 14 | "1/10\n", 15 | "2/10\n", 16 | "3/10\n", 17 | "4/10\n", 18 | "5/10\n", 19 | "6/10\n", 20 | "7/10\n", 21 | "8/10\n", 22 | "9/10\n", 23 | "10/10\n", 24 | "Topic 1: said year government people mobile last number growth phone market\n", 25 | "Topic 2: said people film could would also technology made make government\n", 26 | "Topic 3: said would could best music also world election labour people\n", 27 | "Topic 4: said first england also time game players wales would team\n", 28 | "Topic 5: said also would company year world sales firm market last\n", 29 | "Time: 531.1292963027954\n" 30 | ] 31 | } 32 | ], 33 | "source": [ 34 | "import numpy as np\n", 35 | "import pandas as pd\n", 36 | "import string\n", 37 | "from nltk.corpus import stopwords\n", 38 | "import time\n", 39 | "\n", 40 | "\n", 41 | "#定义加载数据的函数\n", 42 | "def load_data(file):\n", 43 | " '''\n", 44 | " INPUT:\n", 45 | " file - (str) 数据文件的路径\n", 46 | " \n", 47 | " OUTPUT:\n", 48 | " org_topics - (list) 原始话题标签列表\n", 49 | " text - (list) 文本列表\n", 50 | " words - (list) 单词列表\n", 51 | " \n", 52 | " '''\n", 53 | " df = pd.read_csv(file) #读取文件\n", 54 | " org_topics = df['category'].unique().tolist() #保存文本原始的话题标签\n", 55 | " df.drop('category', axis=1, inplace=True)\n", 56 | " n = df.shape[0] #n为文本数量\n", 57 | " text = []\n", 58 | " words = []\n", 59 | " for i in df['text'].values:\n", 60 | " t = i.translate(str.maketrans('', '', string.punctuation)) #去除文本中的标点符号\n", 61 | " t = [j for j in t.split() if j not in stopwords.words('english')] #去除文本中的停止词\n", 62 | " t = [j for j in t if len(j) > 3] #长度小于等于3的单词大多是无意义的,直接去除\n", 63 | " text.append(t) #将处理后的文本保存到文本列表中\n", 64 | " words.extend(set(t)) #将文本中所包含的单词保存到单词列表中\n", 65 | " words = list(set(words)) #去除单词列表中的重复单词\n", 66 | " return org_topics, text, words\n", 67 | "\n", 68 | "\n", 69 | "#定义构建单词-文本矩阵的函数,这里矩阵的每一项表示单词在文本中的出现频次,也可以用TF-IDF来表示\n", 70 | "def frequency_counter(text, words):\n", 71 | " '''\n", 72 | " INPUT:\n", 73 | " text - (list) 文本列表\n", 74 | " words - (list) 单词列表\n", 75 | " \n", 76 | " OUTPUT:\n", 77 | " words - (list) 出现频次为前1000的单词列表\n", 78 | " X - (array) 单词-文本矩阵\n", 79 | " \n", 80 | " '''\n", 81 | " words_cnt = np.zeros(len(words)) #用来保存单词的出现频次\n", 82 | " X = np.zeros((1000, len(text))) #定义m*n的矩阵,其中m为单词列表中的单词个数,为避免运行时间过长,这里只取了出现频次为前1000的单词,因此m为1000,n为文本个数\n", 83 | " #循环计算words列表中各单词出现的词频\n", 84 | " for i in range(len(text)):\n", 85 | " t = text[i] #取出第i条文本\n", 86 | " for w in t:\n", 87 | " ind = words.index(w) #取出第i条文本中的第t个单词在单词列表中的索引\n", 88 | " words_cnt[ind] += 1 #对应位置的单词出现频次加一\n", 89 | " sort_inds = np.argsort(words_cnt)[::-1] #对单词出现频次降序排列后取出其索引值\n", 90 | " words = [words[ind] for ind in sort_inds[:1000]] #将出现频次前1000的单词保存到words列表\n", 91 | " #构建单词-文本矩阵\n", 92 | " for i in range(len(text)):\n", 93 | " t = text[i] #取出第i条文本\n", 94 | " for w in t:\n", 95 | " if w in words: #如果文本t中的单词w在单词列表中,则将X矩阵中对应位置加一\n", 96 | " ind = words.index(w)\n", 97 | " X[ind, i] += 1\n", 98 | " return words, X\n", 99 | "\n", 100 | "\n", 101 | "#定义概率潜在语义分析函数,采用EM算法进行PLSA模型的参数估计\n", 102 | "def do_plsa(X, K, words, iters = 10):\n", 103 | " '''\n", 104 | " INPUT:\n", 105 | " X - (array) 单词-文本矩阵\n", 106 | " K - (int) 设定的话题数\n", 107 | " words - (list) 出现频次为前1000的单词列表\n", 108 | " iters - (int) 设定的迭代次数\n", 109 | " \n", 110 | " OUTPUT:\n", 111 | " P_wi_zk - (array) 话题zk条件下产生单词wi的概率数组\n", 112 | " P_zk_dj - (array) 文本dj条件下属于话题zk的概率数组\n", 113 | " \n", 114 | " '''\n", 115 | " M, N = X.shape #M为单词数,N为文本数\n", 116 | " #P_wi_zk表示P(wi|zk),是一个K*M的数组,其中每个值表示第k个话题zk条件下产生第i个单词wi的概率,这里将每个值随机初始化为0-1之间的浮点数\n", 117 | " P_wi_zk = np.random.rand(K, M)\n", 118 | " #对于每个话题zk,保证产生单词wi的概率的总和为1\n", 119 | " for k in range(K):\n", 120 | " P_wi_zk[k] /= np.sum(P_wi_zk[k])\n", 121 | " #P_zk_dj表示P(zk|dj),是一个N*K的数组,其中每个值表示第j个文本dj条件下产生第k个话题zk的概率,这里将每个值随机初始化为0-1之间的浮点数\n", 122 | " P_zk_dj = np.random.rand(N, K)\n", 123 | " #对于每个文本dj,属于话题zk的概率的总和为1\n", 124 | " for n in range(N):\n", 125 | " P_zk_dj[n] /= np.sum(P_zk_dj[n])\n", 126 | " #P_zk_wi_dj表示P(zk|wi,dj),是一个M*N*K的数组,其中每个值表示在单词-文本对(wi,dj)的条件下属于第k个话题zk的概率,这里设置初始值为0\n", 127 | " P_zk_wi_dj = np.zeros((M, N, K))\n", 128 | " #迭代执行E步和M步\n", 129 | " for i in range(iters):\n", 130 | " print('{}/{}'.format(i+1, iters)) \n", 131 | " #执行E步\n", 132 | " for m in range(M):\n", 133 | " for n in range(N):\n", 134 | " sums = 0\n", 135 | " for k in range(K):\n", 136 | " P_zk_wi_dj[m, n, k] = P_wi_zk[k, m] * P_zk_dj[n, k] #计算P(zk|wi,dj)的分子部分,即P(wi|zk)*P(zk|dj)\n", 137 | " sums += P_zk_wi_dj[m, n, k] #计算P(zk|wi,dj)的分母部分,即P(wi|zk)*P(zk|dj)在K个话题上的总和\n", 138 | " P_zk_wi_dj[m, n, :] = P_zk_wi_dj[m, n, :] / sums #得到单词-文本对(wi,dj)条件下的P(zk|wi,dj)\n", 139 | " #执行M步,计算P(wi|zk)\n", 140 | " for k in range(K):\n", 141 | " s1 = 0\n", 142 | " for m in range(M):\n", 143 | " P_wi_zk[k, m] = 0\n", 144 | " for n in range(N):\n", 145 | " P_wi_zk[k, m] += X[m, n] * P_zk_wi_dj[m, n, k] #计算P(wi|zk)的分子部分,即n(wi,dj)*P(zk|wi,dj)在N个文本上的总和,其中n(wi,dj)为单词-文本矩阵X在文本对(wi,dj)处的频次\n", 146 | " s1 += P_wi_zk[k, m] #计算P(wi|zk)的分母部分,即n(wi,dj)*P(zk|wi,dj)在N个文本和M个单词上的总和\n", 147 | " P_wi_zk[k, :] = P_wi_zk[k, :] / s1 #得到话题zk条件下的P(wi|zk)\n", 148 | " #执行M步,计算P(zk|dj)\n", 149 | " for n in range(N):\n", 150 | " for k in range(K):\n", 151 | " P_zk_dj[n, k] = 0\n", 152 | " for m in range(M):\n", 153 | " P_zk_dj[n, k] += X[m, n] * P_zk_wi_dj[m, n, k] #同理计算P(zk|dj)的分子部分,即n(wi,dj)*P(zk|wi,dj)在N个文本上的总和\n", 154 | " P_zk_dj[n, k] = P_zk_dj[n, k] / np.sum(X[:, n]) #得到文本dj条件下的P(zk|dj),其中n(dj)为文本dj中的单词个数,由于我们只取了出现频次前1000的单词,所以这里n(dj)计算的是文本dj中在单词列表中的单词数\n", 155 | " return P_wi_zk, P_zk_dj\n", 156 | "\n", 157 | "\n", 158 | "if __name__ == \"__main__\":\n", 159 | " org_topics, text, words = load_data('bbc_text.csv') #加载数据\n", 160 | " print('Original Topics:')\n", 161 | " print(org_topics) #打印原始的话题标签列表\n", 162 | " start = time.time() #保存开始时间\n", 163 | " words, X = frequency_counter(text, words) #取频次前1000的单词重新构建单词列表,并构建单词-文本矩阵\n", 164 | " K = 5 #设定话题数为5\n", 165 | " P_wi_zk, P_zk_dj = do_plsa(X, K, words, iters = 10) #采用EM算法对PLSA模型进行参数估计\n", 166 | " #打印出每个话题zk条件下出现概率最大的前10个单词,即P(wi|zk)在话题zk中最大的10个值对应的单词,作为对话题zk的文本描述\n", 167 | " for k in range(K):\n", 168 | " sort_inds = np.argsort(P_wi_zk[k])[::-1] #对话题zk条件下的P(wi|zk)的值进行降序排列后取出对应的索引值\n", 169 | " topic = [] #定义一个空列表用于保存话题zk概率最大的前10个单词\n", 170 | " for i in range(10):\n", 171 | " topic.append(words[sort_inds[i]]) \n", 172 | " topic = ' '.join(topic) #将10个单词以空格分隔,构成对话题zk的文本表述\n", 173 | " print('Topic {}: {}'.format(k+1, topic)) #打印话题zk\n", 174 | " end = time.time()\n", 175 | " print('Time:', end-start)" 176 | ] 177 | }, 178 | { 179 | "cell_type": "code", 180 | "execution_count": null, 181 | "metadata": {}, 182 | "outputs": [], 183 | "source": [] 184 | } 185 | ], 186 | "metadata": { 187 | "kernelspec": { 188 | "display_name": "Python 3", 189 | "language": "python", 190 | "name": "python3" 191 | }, 192 | "language_info": { 193 | "codemirror_mode": { 194 | "name": "ipython", 195 | "version": 3 196 | }, 197 | "file_extension": ".py", 198 | "mimetype": "text/x-python", 199 | "name": "python", 200 | "nbconvert_exporter": "python", 201 | "pygments_lexer": "ipython3", 202 | "version": "3.7.3" 203 | } 204 | }, 205 | "nbformat": 4, 206 | "nbformat_minor": 2 207 | } 208 | -------------------------------------------------------------------------------- /PLSA/PLSA.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | #Author:Harold 3 | #Date:2021-1-27 4 | #Email:zenghr_zero@163.com 5 | 6 | ''' 7 | 数据集:bbc_text 8 | 数据集数量:2225 9 | ----------------------------- 10 | 运行结果: 11 | 话题数:5 12 | 原始话题:'tech', 'business', 'sport', 'entertainment', 'politics' 13 | 生成话题: 14 | 1:'said year government people mobile last number growth phone market' 15 | 2:'said people film could would also technology made make government' 16 | 3:'said would could best music also world election labour people' 17 | 4:'said first england also time game players wales would team' 18 | 5:'said also would company year world sales firm market last' 19 | 运行时长:531.13s 20 | ''' 21 | 22 | import numpy as np 23 | import pandas as pd 24 | import string 25 | from nltk.corpus import stopwords 26 | import time 27 | 28 | 29 | #定义加载数据的函数 30 | def load_data(file): 31 | ''' 32 | INPUT: 33 | file - (str) 数据文件的路径 34 | 35 | OUTPUT: 36 | org_topics - (list) 原始话题标签列表 37 | text - (list) 文本列表 38 | words - (list) 单词列表 39 | 40 | ''' 41 | df = pd.read_csv(file) #读取文件 42 | org_topics = df['category'].unique().tolist() #保存文本原始的话题标签 43 | df.drop('category', axis=1, inplace=True) 44 | n = df.shape[0] #n为文本数量 45 | text = [] 46 | words = [] 47 | for i in df['text'].values: 48 | t = i.translate(str.maketrans('', '', string.punctuation)) #去除文本中的标点符号 49 | t = [j for j in t.split() if j not in stopwords.words('english')] #去除文本中的停止词 50 | t = [j for j in t if len(j) > 3] #长度小于等于3的单词大多是无意义的,直接去除 51 | text.append(t) #将处理后的文本保存到文本列表中 52 | words.extend(set(t)) #将文本中所包含的单词保存到单词列表中 53 | words = list(set(words)) #去除单词列表中的重复单词 54 | return org_topics, text, words 55 | 56 | 57 | #定义构建单词-文本矩阵的函数,这里矩阵的每一项表示单词在文本中的出现频次,也可以用TF-IDF来表示 58 | def frequency_counter(text, words): 59 | ''' 60 | INPUT: 61 | text - (list) 文本列表 62 | words - (list) 单词列表 63 | 64 | OUTPUT: 65 | words - (list) 出现频次为前1000的单词列表 66 | X - (array) 单词-文本矩阵 67 | 68 | ''' 69 | words_cnt = np.zeros(len(words)) #用来保存单词的出现频次 70 | X = np.zeros((1000, len(text))) #定义m*n的矩阵,其中m为单词列表中的单词个数,为避免运行时间过长,这里只取了出现频次为前1000的单词,因此m为1000,n为文本个数 71 | #循环计算words列表中各单词出现的词频 72 | for i in range(len(text)): 73 | t = text[i] #取出第i条文本 74 | for w in t: 75 | ind = words.index(w) #取出第i条文本中的第t个单词在单词列表中的索引 76 | words_cnt[ind] += 1 #对应位置的单词出现频次加一 77 | sort_inds = np.argsort(words_cnt)[::-1] #对单词出现频次降序排列后取出其索引值 78 | words = [words[ind] for ind in sort_inds[:1000]] #将出现频次前1000的单词保存到words列表 79 | #构建单词-文本矩阵 80 | for i in range(len(text)): 81 | t = text[i] #取出第i条文本 82 | for w in t: 83 | if w in words: #如果文本t中的单词w在单词列表中,则将X矩阵中对应位置加一 84 | ind = words.index(w) 85 | X[ind, i] += 1 86 | return words, X 87 | 88 | 89 | #定义概率潜在语义分析函数,采用EM算法进行PLSA模型的参数估计 90 | def do_plsa(X, K, words, iters = 10): 91 | ''' 92 | INPUT: 93 | X - (array) 单词-文本矩阵 94 | K - (int) 设定的话题数 95 | words - (list) 出现频次为前1000的单词列表 96 | iters - (int) 设定的迭代次数 97 | 98 | OUTPUT: 99 | P_wi_zk - (array) 话题zk条件下产生单词wi的概率数组 100 | P_zk_dj - (array) 文本dj条件下属于话题zk的概率数组 101 | 102 | ''' 103 | M, N = X.shape #M为单词数,N为文本数 104 | #P_wi_zk表示P(wi|zk),是一个K*M的数组,其中每个值表示第k个话题zk条件下产生第i个单词wi的概率,这里将每个值随机初始化为0-1之间的浮点数 105 | P_wi_zk = np.random.rand(K, M) 106 | #对于每个话题zk,保证产生单词wi的概率的总和为1 107 | for k in range(K): 108 | P_wi_zk[k] /= np.sum(P_wi_zk[k]) 109 | #P_zk_dj表示P(zk|dj),是一个N*K的数组,其中每个值表示第j个文本dj条件下产生第k个话题zk的概率,这里将每个值随机初始化为0-1之间的浮点数 110 | P_zk_dj = np.random.rand(N, K) 111 | #对于每个文本dj,属于话题zk的概率的总和为1 112 | for n in range(N): 113 | P_zk_dj[n] /= np.sum(P_zk_dj[n]) 114 | #P_zk_wi_dj表示P(zk|wi,dj),是一个M*N*K的数组,其中每个值表示在单词-文本对(wi,dj)的条件下属于第k个话题zk的概率,这里设置初始值为0 115 | P_zk_wi_dj = np.zeros((M, N, K)) 116 | #迭代执行E步和M步 117 | for i in range(iters): 118 | print('{}/{}'.format(i+1, iters)) 119 | #执行E步 120 | for m in range(M): 121 | for n in range(N): 122 | sums = 0 123 | for k in range(K): 124 | P_zk_wi_dj[m, n, k] = P_wi_zk[k, m] * P_zk_dj[n, k] #计算P(zk|wi,dj)的分子部分,即P(wi|zk)*P(zk|dj) 125 | sums += P_zk_wi_dj[m, n, k] #计算P(zk|wi,dj)的分母部分,即P(wi|zk)*P(zk|dj)在K个话题上的总和 126 | P_zk_wi_dj[m, n, :] = P_zk_wi_dj[m, n, :] / sums #得到单词-文本对(wi,dj)条件下的P(zk|wi,dj) 127 | #执行M步,计算P(wi|zk) 128 | for k in range(K): 129 | s1 = 0 130 | for m in range(M): 131 | P_wi_zk[k, m] = 0 132 | for n in range(N): 133 | P_wi_zk[k, m] += X[m, n] * P_zk_wi_dj[m, n, k] #计算P(wi|zk)的分子部分,即n(wi,dj)*P(zk|wi,dj)在N个文本上的总和,其中n(wi,dj)为单词-文本矩阵X在文本对(wi,dj)处的频次 134 | s1 += P_wi_zk[k, m] #计算P(wi|zk)的分母部分,即n(wi,dj)*P(zk|wi,dj)在N个文本和M个单词上的总和 135 | P_wi_zk[k, :] = P_wi_zk[k, :] / s1 #得到话题zk条件下的P(wi|zk) 136 | #执行M步,计算P(zk|dj) 137 | for n in range(N): 138 | for k in range(K): 139 | P_zk_dj[n, k] = 0 140 | for m in range(M): 141 | P_zk_dj[n, k] += X[m, n] * P_zk_wi_dj[m, n, k] #同理计算P(zk|dj)的分子部分,即n(wi,dj)*P(zk|wi,dj)在N个文本上的总和 142 | P_zk_dj[n, k] = P_zk_dj[n, k] / np.sum(X[:, n]) #得到文本dj条件下的P(zk|dj),其中n(dj)为文本dj中的单词个数,由于我们只取了出现频次前1000的单词,所以这里n(dj)计算的是文本dj中在单词列表中的单词数 143 | return P_wi_zk, P_zk_dj 144 | 145 | 146 | if __name__ == "__main__": 147 | org_topics, text, words = load_data('bbc_text.csv') #加载数据 148 | print('Original Topics:') 149 | print(org_topics) #打印原始的话题标签列表 150 | start = time.time() #保存开始时间 151 | words, X = frequency_counter(text, words) #取频次前1000的单词重新构建单词列表,并构建单词-文本矩阵 152 | K = 5 #设定话题数为5 153 | P_wi_zk, P_zk_dj = do_plsa(X, K, words, iters = 10) #采用EM算法对PLSA模型进行参数估计 154 | #打印出每个话题zk条件下出现概率最大的前10个单词,即P(wi|zk)在话题zk中最大的10个值对应的单词,作为对话题zk的文本描述 155 | for k in range(K): 156 | sort_inds = np.argsort(P_wi_zk[k])[::-1] #对话题zk条件下的P(wi|zk)的值进行降序排列后取出对应的索引值 157 | topic = [] #定义一个空列表用于保存话题zk概率最大的前10个单词 158 | for i in range(10): 159 | topic.append(words[sort_inds[i]]) 160 | topic = ' '.join(topic) #将10个单词以空格分隔,构成对话题zk的文本表述 161 | print('Topic {}: {}'.format(k+1, topic)) #打印话题zk 162 | end = time.time() 163 | print('Time:', end-start) -------------------------------------------------------------------------------- /Page_Rank/Page_Rank.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "### PageRank算法\n", 8 | "以下图所示的有向图为例,计算每个结点的PR:\n", 9 | "" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 26, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "import numpy as np\n", 19 | "\n", 20 | "\n", 21 | "n = 7 #有向图中一共有7个节点\n", 22 | "d = 0.85 #阻尼因子根据经验值确定,这里我们随意给一个值\n", 23 | "M = np.array([[0, 1/4, 1/3, 0, 0, 1/2, 0],\n", 24 | " [1/4, 0, 0, 1/5, 0, 0, 0],\n", 25 | " [0, 1/4, 0, 1/5, 1/4, 0, 0],\n", 26 | " [0, 0, 1/3, 0, 1/4, 0, 0],\n", 27 | " [1/4, 0, 0, 1/5, 0, 0, 0],\n", 28 | " [1/4, 1/4, 0, 1/5, 1/4, 0, 0],\n", 29 | " [1/4, 1/4, 1/3, 1/5, 1/4, 1/2, 0]]) #根据有向图中各节点的连接情况写出转移矩阵\n", 30 | "R0 = np.full((7, 1), 1/7) #设置初始向量R0,R0是一个7*1的列向量,因为有7个节点,我们把R0的每一个值都设为1/7\n", 31 | "eps = 0.000001 #设置计算精度" 32 | ] 33 | }, 34 | { 35 | "cell_type": "markdown", 36 | "metadata": {}, 37 | "source": [ 38 | "### 1. PageRank的迭代算法" 39 | ] 40 | }, 41 | { 42 | "cell_type": "code", 43 | "execution_count": 27, 44 | "metadata": {}, 45 | "outputs": [], 46 | "source": [ 47 | "t = 0 #用来累计迭代次数\n", 48 | "R = R0 #对R向量进行初始化\n", 49 | "judge = False #用来判断是否继续迭代\n", 50 | "while not judge:\n", 51 | " next_R = d * np.matmul(M, R) + (1 - d) / n * np.ones((7, 1)) #计算新的R向量\n", 52 | " diff = np.linalg.norm(R - next_R) #计算新的R向量与之前的R向量之间的距离,这里采用的是欧氏距离\n", 53 | " if diff < eps: #若两向量之间的距离足够小\n", 54 | " judge = True #则停止迭代\n", 55 | " R = next_R #更新R向量\n", 56 | " t += 1 #迭代次数加一\n", 57 | "R = R / np.sum(R) #对R向量进行规范化,保证其总和为1,表示各节点的概率分布" 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": 28, 63 | "metadata": {}, 64 | "outputs": [ 65 | { 66 | "name": "stdout", 67 | "output_type": "stream", 68 | "text": [ 69 | "迭代次数: 24\n", 70 | "PageRank: \n", 71 | " [[0.17030305]\n", 72 | " [0.10568394]\n", 73 | " [0.11441021]\n", 74 | " [0.10629792]\n", 75 | " [0.10568394]\n", 76 | " [0.15059975]\n", 77 | " [0.24702119]]\n" 78 | ] 79 | } 80 | ], 81 | "source": [ 82 | "print('迭代次数:', t)\n", 83 | "print('PageRank: \\n', R)" 84 | ] 85 | }, 86 | { 87 | "cell_type": "markdown", 88 | "metadata": {}, 89 | "source": [ 90 | "### 1. PageRank的幂法" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": 29, 96 | "metadata": {}, 97 | "outputs": [], 98 | "source": [ 99 | "t = 0 #用来累计迭代次数\n", 100 | "x = R0 #对x向量进行初始化\n", 101 | "judge = False #用来判断是否继续迭代\n", 102 | "A = d * M + (1 - d) / n * np.eye(n) #计算A矩阵,其中np.eye(n)用来创建n阶单位阵E\n", 103 | "while not judge:\n", 104 | " next_y = np.matmul(A, x) #计算新的y向量\n", 105 | " next_x = next_y / np.linalg.norm(next_y) #对新的y向量规范化得到新的x向量\n", 106 | " diff = np.linalg.norm(x - next_x) #计算新的x向量与之前的x向量之间的距离,这里采用的是欧氏距离\n", 107 | " if diff < eps: #若两向量之间的距离足够小\n", 108 | " judge = True #则停止迭代\n", 109 | " R = x #得到R向量\n", 110 | " x = next_x #更新x向量\n", 111 | " t += 1 #迭代次数加一\n", 112 | "R = R / np.sum(R) #对R向量进行规范化,保证其总和为1,表示各节点的概率分布" 113 | ] 114 | }, 115 | { 116 | "cell_type": "code", 117 | "execution_count": 30, 118 | "metadata": {}, 119 | "outputs": [ 120 | { 121 | "name": "stdout", 122 | "output_type": "stream", 123 | "text": [ 124 | "迭代次数: 25\n", 125 | "PageRank: \n", 126 | " [[0.18860772]\n", 127 | " [0.09038084]\n", 128 | " [0.0875305 ]\n", 129 | " [0.07523049]\n", 130 | " [0.09038084]\n", 131 | " [0.15604764]\n", 132 | " [0.31182196]]\n" 133 | ] 134 | } 135 | ], 136 | "source": [ 137 | "print('迭代次数:', t)\n", 138 | "print('PageRank: \\n', R)" 139 | ] 140 | }, 141 | { 142 | "cell_type": "code", 143 | "execution_count": null, 144 | "metadata": {}, 145 | "outputs": [], 146 | "source": [] 147 | } 148 | ], 149 | "metadata": { 150 | "kernelspec": { 151 | "display_name": "Python 3", 152 | "language": "python", 153 | "name": "python3" 154 | }, 155 | "language_info": { 156 | "codemirror_mode": { 157 | "name": "ipython", 158 | "version": 3 159 | }, 160 | "file_extension": ".py", 161 | "mimetype": "text/x-python", 162 | "name": "python", 163 | "nbconvert_exporter": "python", 164 | "pygments_lexer": "ipython3", 165 | "version": "3.7.3" 166 | } 167 | }, 168 | "nbformat": 4, 169 | "nbformat_minor": 2 170 | } 171 | -------------------------------------------------------------------------------- /Page_Rank/Page_Rank.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | #Author:Harold 3 | #Date:2021-1-27 4 | #Email:zenghr_zero@163.com 5 | 6 | ''' 7 | 有向图:directed_graph.png 8 | 结点数量:7 9 | ----------------------------- 10 | 运行结果: 11 | 迭代算法: 12 | 迭代次数:24 13 | PageRank: [[0.17030305] 14 | [0.10568394] 15 | [0.11441021] 16 | [0.10629792] 17 | [0.10568394] 18 | [0.15059975] 19 | [0.24702119]] 20 | 运行时长:0.0010s 21 | 幂法: 22 | 迭代次数:25 23 | PageRank: [[0.18860772] 24 | [0.09038084] 25 | [0.0875305 ] 26 | [0.07523049] 27 | [0.09038084] 28 | [0.15604764] 29 | [0.31182196]] 30 | 运行时长:0.0020s 31 | ''' 32 | 33 | import numpy as np 34 | import time 35 | 36 | 37 | #PageRank的迭代算法 38 | def iter_method(n, d, M, R0, eps): 39 | t = 0 #用来累计迭代次数 40 | R = R0 #对R向量进行初始化 41 | judge = False #用来判断是否继续迭代 42 | while not judge: 43 | next_R = d * np.matmul(M, R) + (1 - d) / n * np.ones((7, 1)) #计算新的R向量 44 | diff = np.linalg.norm(R - next_R) #计算新的R向量与之前的R向量之间的距离,这里采用的是欧氏距离 45 | if diff < eps: #若两向量之间的距离足够小 46 | judge = True #则停止迭代 47 | R = next_R #更新R向量 48 | t += 1 #迭代次数加一 49 | R = R / np.sum(R) #对R向量进行规范化,保证其总和为1,表示各节点的概率分布 50 | return t, R 51 | 52 | 53 | def power_method(n, d, M, R0, eps): 54 | t = 0 #用来累计迭代次数 55 | x = R0 #对x向量进行初始化 56 | judge = False #用来判断是否继续迭代 57 | A = d * M + (1 - d) / n * np.eye(n) #计算A矩阵,其中np.eye(n)用来创建n阶单位阵E 58 | while not judge: 59 | next_y = np.matmul(A, x) #计算新的y向量 60 | next_x = next_y / np.linalg.norm(next_y) #对新的y向量规范化得到新的x向量 61 | diff = np.linalg.norm(x - next_x) #计算新的x向量与之前的x向量之间的距离,这里采用的是欧氏距离 62 | if diff < eps: #若两向量之间的距离足够小 63 | judge = True #则停止迭代 64 | R = x #得到R向量 65 | x = next_x #更新x向量 66 | t += 1 #迭代次数加一 67 | R = R / np.sum(R) #对R向量进行规范化,保证其总和为1,表示各节点的概率分布 68 | return t, R 69 | 70 | 71 | if __name__ == "__main__": 72 | n = 7 #有向图中一共有7个节点 73 | d = 0.85 #阻尼因子根据经验值确定,这里我们随意给一个值 74 | M = np.array([[0, 1/4, 1/3, 0, 0, 1/2, 0], 75 | [1/4, 0, 0, 1/5, 0, 0, 0], 76 | [0, 1/4, 0, 1/5, 1/4, 0, 0], 77 | [0, 0, 1/3, 0, 1/4, 0, 0], 78 | [1/4, 0, 0, 1/5, 0, 0, 0], 79 | [1/4, 1/4, 0, 1/5, 1/4, 0, 0], 80 | [1/4, 1/4, 1/3, 1/5, 1/4, 1/2, 0]]) #根据有向图中各节点的连接情况写出转移矩阵 81 | R0 = np.full((7, 1), 1/7) #设置初始向量R0,R0是一个7*1的列向量,因为有7个节点,我们把R0的每一个值都设为1/7 82 | eps = 0.000001 #设置计算精度 83 | 84 | start = time.time() #保存开始时间 85 | t, R = iter_method(n, d, M, R0, eps) 86 | end = time.time() #保存结束时间 87 | print('-------PageRank的迭代算法-------') 88 | print('迭代次数:', t) 89 | print('PageRank: \n', R) 90 | print('Time:', end-start) 91 | 92 | start = time.time() #保存开始时间 93 | t, R = power_method(n, d, M, R0, eps) 94 | end = time.time() #保存结束时间 95 | print('-------PageRank的幂法-------') 96 | print('迭代次数:', t) 97 | print('PageRank: \n', R) 98 | print('Time:', end-start) -------------------------------------------------------------------------------- /Page_Rank/directed_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dod-o/Statistical-Learning-Method_Code/cd1d28337d223bc164e4949c167958634f409939/Page_Rank/directed_graph.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 【广告】每日Arxiv(中文版) 2 | 每日Arxiv(中文版)立志paper**汉化**,目前翻译目前涵盖**标题**和**摘要**,AI学科近期支持论文**全文汉化** 3 | 4 | 一天阅读百篇paper不是梦! 5 | 6 | 链接: [学术巷子(xueshuxiangzi.com)](https://www.xueshuxiangzi.com/) 7 | 8 | 9 | 前言 10 | ==== 11 | 12 | 力求每行代码都有注释,重要部分注明公式来源。具体会追求下方这样的代码,学习者可以照着公式看程序,让代码有据可查。 13 | 14 | ![image](https://github.com/Dod-o/Statistical-Learning-Method_Code/blob/master/CodePic.png) 15 | 16 | 17 | 如果时间充沛的话,可能会试着给每一章写一篇博客。先放个博客链接吧:[传送门](http://www.pkudodo.com/)。 18 | 19 | ##### 注:其中Mnist数据集已转换为csv格式,由于体积为107M超过限制,改为压缩包形式。下载后务必先将Mnist文件内压缩包直接解压。 20 | 21 | ### 【Updates】 22 | **书籍出版**:目前已与**人民邮电出版社**签订合同,未来将结合该repo整理出版机器学习实践相关书籍。同时会在book分支中对代码进行重构,欢迎在issue中提建议!同时issue中现有的问题也会考虑进去。(Feb 12 2022) 23 | 24 | **线下培训**:女朋友计划近期开办**ML/MLP/CV线下培训班**,地点**北上广深杭**,目标各方向**快速入门**,正在筹备。这里帮她打个广告,可以添加微信15324951814(备注线下培训)。本人也会被拉过去义务评估课程质量。。。(Feb 12 2022) 25 | 26 | **无监督部分更新**:部分**无监督**算法已更新!!! 该部分由[Harold-Ran](https://github.com/Harold-Ran)提供,在此感谢! 有其他算法补充的同学也欢迎添加我微信并pr!(Jan 27 2021) 27 | 28 | 实现 29 | ====== 30 | 31 | ## 监督部分 32 | 33 | ### 第二章 感知机: 34 | 博客:[统计学习方法|感知机原理剖析及实现](https://github.com/Dod-o/Statistical-Learning-Method_Code/blob/master/blogs/%E6%84%9F%E7%9F%A5%E6%9C%BA%E5%8E%9F%E7%90%86%E5%89%96%E6%9E%90%E5%8F%8A%E5%AE%9E%E7%8E%B0.pdf) 35 | 实现:[perceptron/perceptron_dichotomy.py](https://github.com/Dod-o/Statistical-Learning-Method_Code/blob/master/perceptron/perceptron_dichotomy.py) 36 | 37 | ### 第三章 K近邻: 38 | 博客:[统计学习方法|K近邻原理剖析及实现](https://github.com/Dod-o/Statistical-Learning-Method_Code/blob/master/blogs/K%E8%BF%91%E9%82%BB%E5%8E%9F%E7%90%86%E5%89%96%E6%9E%90%E5%8F%8A%E5%AE%9E%E7%8E%B0.pdf) 39 | 实现:[KNN/KNN.py](https://github.com/Dod-o/Statistical-Learning-Method_Code/blob/master/KNN/KNN.py) 40 | 41 | ### 第四章 朴素贝叶斯: 42 | 博客:[统计学习方法|朴素贝叶斯原理剖析及实现](https://github.com/Dod-o/Statistical-Learning-Method_Code/blob/master/blogs/%E6%9C%B4%E7%B4%A0%E8%B4%9D%E5%8F%B6%E6%96%AF%E5%8E%9F%E7%90%86%E5%89%96%E6%9E%90%E5%8F%8A%E5%AE%9E%E7%8E%B0.pdf) 43 | 实现:[NaiveBayes/NaiveBayes.py](https://github.com/Dod-o/Statistical-Learning-Method_Code/blob/master/NaiveBayes/NaiveBayes.py) 44 | 45 | ### 第五章 决策树: 46 | 博客:[统计学习方法|决策树原理剖析及实现](https://github.com/Dod-o/Statistical-Learning-Method_Code/blob/master/blogs/%E5%86%B3%E7%AD%96%E6%A0%91%E5%8E%9F%E7%90%86%E5%89%96%E6%9E%90%E5%8F%8A%E5%AE%9E%E7%8E%B0.pdf) 47 | 实现:[DecisionTree/DecisionTree.py](https://github.com/Dod-o/Statistical-Learning-Method_Code/blob/master/DecisionTree/DecisionTree.py) 48 | 49 | ### 第六章 逻辑斯蒂回归与最大熵模型: 50 | 博客:逻辑斯蒂回归:[统计学习方法|逻辑斯蒂原理剖析及实现](https://github.com/Dod-o/Statistical-Learning-Method_Code/blob/master/blogs/%E9%80%BB%E8%BE%91%E6%96%AF%E8%92%82%E5%8E%9F%E7%90%86%E5%89%96%E6%9E%90%E5%8F%8A%E5%AE%9E%E7%8E%B0.pdf) 51 | 博客:最大熵:[统计学习方法|最大熵原理剖析及实现](https://github.com/Dod-o/Statistical-Learning-Method_Code/blob/master/blogs/%E6%9C%80%E5%A4%A7%E7%86%B5%E5%8E%9F%E7%90%86%E5%89%96%E6%9E%90%E5%8F%8A%E5%AE%9E%E7%8E%B0.pdf) 52 | 53 | 实现:逻辑斯蒂回归:[Logistic_and_maximum_entropy_models/logisticRegression.py](https://github.com/Dod-o/Statistical-Learning-Method_Code/blob/master/Logistic_and_maximum_entropy_models/logisticRegression.py) 54 | 实现:最大熵:[Logistic_and_maximum_entropy_models/maxEntropy.py](https://github.com/Dod-o/Statistical-Learning-Method_Code/blob/master/Logistic_and_maximum_entropy_models/maxEntropy.py) 55 | 56 | ### 第七章 支持向量机: 57 | 博客:[统计学习方法|支持向量机(SVM)原理剖析及实现](https://github.com/Dod-o/Statistical-Learning-Method_Code/blob/master/blogs/%E6%94%AF%E6%8C%81%E5%90%91%E9%87%8F%E6%9C%BA(SVM)%E5%8E%9F%E7%90%86%E5%89%96%E6%9E%90%E5%8F%8A%E5%AE%9E%E7%8E%B0.pdf) 58 | 实现:[SVM/SVM.py](https://github.com/Dod-o/Statistical-Learning-Method_Code/blob/master/SVM/SVM.py) 59 | 60 | ### 第八章 提升方法: 61 | 实现:[AdaBoost/AdaBoost.py](https://github.com/Dod-o/Statistical-Learning-Method_Code/blob/master/AdaBoost/AdaBoost.py) 62 | 63 | ### 第九章 EM算法及其推广: 64 | 实现:[EM/EM.py](https://github.com/Dod-o/Statistical-Learning-Method_Code/blob/master/EM/EM.py) 65 | 66 | ### 第十章 隐马尔可夫模型: 67 | 实现:[HMM/HMM.py](https://github.com/Dod-o/Statistical-Learning-Method_Code/blob/master/HMM/HMM.py) 68 | 69 | ## 无监督部分 70 | 71 | ### 第十四章 聚类方法 72 | 实现:[K-means_Clustering.py](https://github.com/Dod-o/Statistical-Learning-Method_Code/blob/master/Clustering/K-means_Clustering/K-means_Clustering.py) 73 | 74 | 实现:[Hierachical_Clustering.py](https://github.com/Dod-o/Statistical-Learning-Method_Code/blob/master/Clustering/Hierachical_Clustering/Hierachical_Clustering.py) 75 | 76 | ### 第十六章 主成分分析 77 | 实现:[PCA.py](https://github.com/Dod-o/Statistical-Learning-Method_Code/blob/master/PCA/PCA.py) 78 | 79 | ### 第十七章 潜在语意分析 80 | 实现:[LSA.py](https://github.com/Dod-o/Statistical-Learning-Method_Code/blob/master/LSA/LSA.py) 81 | 82 | ### 第十八章 概率潜在语意分析 83 | 实现:[PLSA.py](https://github.com/Dod-o/Statistical-Learning-Method_Code/blob/master/PLSA/PLSA.py) 84 | 85 | ### 第二十章 潜在狄利克雷分配 86 | 实现:[LDA.py](https://github.com/Dod-o/Statistical-Learning-Method_Code/blob/master/LDA/LDA.py) 87 | 88 | ### 第二十一章 PageRank算法 89 | 实现:[Page_Rank.py](https://github.com/Dod-o/Statistical-Learning-Method_Code/blob/master/Page_Rank/Page_Rank.py) 90 | 91 | 92 | ## 许可 / License 93 | 本项目内容许可遵循[Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](https://creativecommons.org/licenses/by-nc-sa/4.0/)。 94 | 95 | The content of this project itself is licensed under the [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](https://creativecommons.org/licenses/by-nc-sa/4.0/) 96 | 97 | 联系 98 | ====== 99 | 欢迎pr,有疑问也可通过issue、微信或邮件联系。 100 | 此外如果有需要**MSRA**实习内推的同学,欢迎骚扰。 101 | **Wechat:** lvtengchao(备注“blog-学校/单位-姓名”) 102 | **Email:** lvtengchao@pku.edu.cn 103 | 104 | 105 | 项目历史 106 | ====== 107 | 108 | 109 | 110 | Star History Chart 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /SVM/SVM.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | #Author:Dodo 3 | #Date:2018-12-03 4 | #Email:lvtengchao@pku.edu.cn 5 | #Blog:www.pkudodo.com 6 | ''' 7 | 数据集:Mnist 8 | 训练集数量:60000(实际使用:1000) 9 | 测试集数量:10000(实际使用:100) 10 | ------------------------------ 11 | 运行结果: 12 | 正确率:99% 13 | 运行时长:50s 14 | ''' 15 | 16 | import time 17 | import numpy as np 18 | import math 19 | import random 20 | 21 | def loadData(fileName): 22 | ''' 23 | 加载文件 24 | :param fileName:要加载的文件路径 25 | :return: 数据集和标签集 26 | ''' 27 | #存放数据及标记 28 | dataArr = []; labelArr = [] 29 | #读取文件 30 | fr = open(fileName) 31 | #遍历文件中的每一行 32 | for line in fr.readlines(): 33 | #获取当前行,并按“,”切割成字段放入列表中 34 | #strip:去掉每行字符串首尾指定的字符(默认空格或换行符) 35 | #split:按照指定的字符将字符串切割成每个字段,返回列表形式 36 | curLine = line.strip().split(',') 37 | #将每行中除标记外的数据放入数据集中(curLine[0]为标记信息) 38 | #在放入的同时将原先字符串形式的数据转换为0-1的浮点型 39 | dataArr.append([int(num) / 255 for num in curLine[1:]]) 40 | #将标记信息放入标记集中 41 | #放入的同时将标记转换为整型 42 | #数字0标记为1 其余标记为-1 43 | if int(curLine[0]) == 0: 44 | labelArr.append(1) 45 | else: 46 | labelArr.append(-1) 47 | #返回数据集和标记 48 | return dataArr, labelArr 49 | 50 | class SVM: 51 | ''' 52 | SVM类 53 | ''' 54 | def __init__(self, trainDataList, trainLabelList, sigma = 10, C = 200, toler = 0.001): 55 | ''' 56 | SVM相关参数初始化 57 | :param trainDataList:训练数据集 58 | :param trainLabelList: 训练测试集 59 | :param sigma: 高斯核中分母的σ 60 | :param C:软间隔中的惩罚参数 61 | :param toler:松弛变量 62 | 注: 63 | 关于这些参数的初始值:参数的初始值大部分没有强要求,请参照书中给的参考,例如C是调和间隔与误分类点的系数, 64 | 在选值时通过经验法依据结果来动态调整。(本程序中的初始值参考于《机器学习实战》中SVM章节,因为书中也 65 | 使用了该数据集,只不过抽取了很少的数据测试。参数在一定程度上有参考性。) 66 | 如果使用的是其他数据集且结果不太好,强烈建议重新通读所有参数所在的公式进行修改。例如在核函数中σ的值 67 | 高度依赖样本特征值范围,特征值范围较大时若不相应增大σ会导致所有计算得到的核函数均为0 68 | ''' 69 | self.trainDataMat = np.mat(trainDataList) #训练数据集 70 | self.trainLabelMat = np.mat(trainLabelList).T #训练标签集,为了方便后续运算提前做了转置,变为列向量 71 | 72 | self.m, self.n = np.shape(self.trainDataMat) #m:训练集数量 n:样本特征数目 73 | self.sigma = sigma #高斯核分母中的σ 74 | self.C = C #惩罚参数 75 | self.toler = toler #松弛变量 76 | 77 | self.k = self.calcKernel() #核函数(初始化时提前计算) 78 | self.b = 0 #SVM中的偏置b 79 | self.alpha = [0] * self.trainDataMat.shape[0] # α 长度为训练集数目 80 | self.E = [0 * self.trainLabelMat[i, 0] for i in range(self.trainLabelMat.shape[0])] #SMO运算过程中的Ei 81 | self.supportVecIndex = [] 82 | 83 | 84 | def calcKernel(self): 85 | ''' 86 | 计算核函数 87 | 使用的是高斯核 详见“7.3.3 常用核函数” 式7.90 88 | :return: 高斯核矩阵 89 | ''' 90 | #初始化高斯核结果矩阵 大小 = 训练集长度m * 训练集长度m 91 | #k[i][j] = Xi * Xj 92 | k = [[0 for i in range(self.m)] for j in range(self.m)] 93 | 94 | #大循环遍历Xi,Xi为式7.90中的x 95 | for i in range(self.m): 96 | #每100个打印一次 97 | #不能每次都打印,会极大拖慢程序运行速度 98 | #因为print是比较慢的 99 | if i % 100 == 0: 100 | print('construct the kernel:', i, self.m) 101 | #得到式7.90中的X 102 | X = self.trainDataMat[i, :] 103 | #小循环遍历Xj,Xj为式7.90中的Z 104 | # 由于 Xi * Xj 等于 Xj * Xi,一次计算得到的结果可以 105 | # 同时放在k[i][j]和k[j][i]中,这样一个矩阵只需要计算一半即可 106 | #所以小循环直接从i开始 107 | for j in range(i, self.m): 108 | #获得Z 109 | Z = self.trainDataMat[j, :] 110 | #先计算||X - Z||^2 111 | result = (X - Z) * (X - Z).T 112 | #分子除以分母后去指数,得到的即为高斯核结果 113 | result = np.exp(-1 * result / (2 * self.sigma**2)) 114 | #将Xi*Xj的结果存放入k[i][j]和k[j][i]中 115 | k[i][j] = result 116 | k[j][i] = result 117 | #返回高斯核矩阵 118 | return k 119 | 120 | def isSatisfyKKT(self, i): 121 | ''' 122 | 查看第i个α是否满足KKT条件 123 | :param i:α的下标 124 | :return: 125 | True:满足 126 | False:不满足 127 | ''' 128 | gxi =self.calc_gxi(i) 129 | yi = self.trainLabelMat[i] 130 | 131 | #判断依据参照“7.4.2 变量的选择方法”中“1.第1个变量的选择” 132 | #式7.111到7.113 133 | #-------------------- 134 | #依据7.111 135 | if (math.fabs(self.alpha[i]) < self.toler) and (yi * gxi >= 1): 136 | return True 137 | #依据7.113 138 | elif (math.fabs(self.alpha[i] - self.C) < self.toler) and (yi * gxi <= 1): 139 | return True 140 | #依据7.112 141 | elif (self.alpha[i] > -self.toler) and (self.alpha[i] < (self.C + self.toler)) \ 142 | and (math.fabs(yi * gxi - 1) < self.toler): 143 | return True 144 | 145 | return False 146 | 147 | def calc_gxi(self, i): 148 | ''' 149 | 计算g(xi) 150 | 依据“7.101 两个变量二次规划的求解方法”式7.104 151 | :param i:x的下标 152 | :return: g(xi)的值 153 | ''' 154 | #初始化g(xi) 155 | gxi = 0 156 | #因为g(xi)是一个求和式+b的形式,普通做法应该是直接求出求和式中的每一项再相加即可 157 | #但是读者应该有发现,在“7.2.3 支持向量”开头第一句话有说到“对应于α>0的样本点 158 | #(xi, yi)的实例xi称为支持向量”。也就是说只有支持向量的α是大于0的,在求和式内的 159 | #对应的αi*yi*K(xi, xj)不为0,非支持向量的αi*yi*K(xi, xj)必为0,也就不需要参与 160 | #到计算中。也就是说,在g(xi)内部求和式的运算中,只需要计算α>0的部分,其余部分可 161 | #忽略。因为支持向量的数量是比较少的,这样可以再很大程度上节约时间 162 | #从另一角度看,抛掉支持向量的概念,如果α为0,αi*yi*K(xi, xj)本身也必为0,从数学 163 | #角度上将也可以扔掉不算 164 | #index获得非零α的下标,并做成列表形式方便后续遍历 165 | index = [i for i, alpha in enumerate(self.alpha) if alpha != 0] 166 | #遍历每一个非零α,i为非零α的下标 167 | for j in index: 168 | #计算g(xi) 169 | gxi += self.alpha[j] * self.trainLabelMat[j] * self.k[j][i] 170 | #求和结束后再单独加上偏置b 171 | gxi += self.b 172 | 173 | #返回 174 | return gxi 175 | 176 | def calcEi(self, i): 177 | ''' 178 | 计算Ei 179 | 根据“7.4.1 两个变量二次规划的求解方法”式7.105 180 | :param i: E的下标 181 | :return: 182 | ''' 183 | #计算g(xi) 184 | gxi = self.calc_gxi(i) 185 | #Ei = g(xi) - yi,直接将结果作为Ei返回 186 | return gxi - self.trainLabelMat[i] 187 | 188 | def getAlphaJ(self, E1, i): 189 | ''' 190 | SMO中选择第二个变量 191 | :param E1: 第一个变量的E1 192 | :param i: 第一个变量α的下标 193 | :return: E2,α2的下标 194 | ''' 195 | #初始化E2 196 | E2 = 0 197 | #初始化|E1-E2|为-1 198 | maxE1_E2 = -1 199 | #初始化第二个变量的下标 200 | maxIndex = -1 201 | 202 | #这一步是一个优化性的算法 203 | #实际上书上算法中初始时每一个Ei应当都为-yi(因为g(xi)由于初始α为0,必然为0) 204 | #然后每次按照书中第二步去计算不同的E2来使得|E1-E2|最大,但是时间耗费太长了 205 | #作者最初是全部按照书中缩写,但是本函数在需要3秒左右,所以进行了一些优化措施 206 | #-------------------------------------------------- 207 | #在Ei的初始化中,由于所有α为0,所以一开始是设置Ei初始值为-yi。这里修改为与α 208 | #一致,初始状态所有Ei为0,在运行过程中再逐步更新 209 | #因此在挑选第二个变量时,只考虑更新过Ei的变量,但是存在问题 210 | #1.当程序刚开始运行时,所有Ei都是0,那挑谁呢? 211 | # 当程序检测到并没有Ei为非0时,将会使用随机函数随机挑选一个 212 | #2.怎么保证能和书中的方法保持一样的有效性呢? 213 | # 在挑选第一个变量时是有一个大循环的,它能保证遍历到每一个xi,并更新xi的值, 214 | #在程序运行后期后其实绝大部分Ei都已经更新完毕了。下方优化算法只不过是在程序运行 215 | #的前半程进行了时间的加速,在程序后期其实与未优化的情况无异 216 | #------------------------------------------------------ 217 | 218 | #获得Ei非0的对应索引组成的列表,列表内容为非0Ei的下标i 219 | nozeroE = [i for i, Ei in enumerate(self.E) if Ei != 0] 220 | #对每个非零Ei的下标i进行遍历 221 | for j in nozeroE: 222 | #计算E2 223 | E2_tmp = self.calcEi(j) 224 | #如果|E1-E2|大于目前最大值 225 | if math.fabs(E1 - E2_tmp) > maxE1_E2: 226 | #更新最大值 227 | maxE1_E2 = math.fabs(E1 - E2_tmp) 228 | #更新最大值E2 229 | E2 = E2_tmp 230 | #更新最大值E2的索引j 231 | maxIndex = j 232 | #如果列表中没有非0元素了(对应程序最开始运行时的情况) 233 | if maxIndex == -1: 234 | maxIndex = i 235 | while maxIndex == i: 236 | #获得随机数,如果随机数与第一个变量的下标i一致则重新随机 237 | maxIndex = int(random.uniform(0, self.m)) 238 | #获得E2 239 | E2 = self.calcEi(maxIndex) 240 | 241 | #返回第二个变量的E2值以及其索引 242 | return E2, maxIndex 243 | 244 | def train(self, iter = 100): 245 | #iterStep:迭代次数,超过设置次数还未收敛则强制停止 246 | #parameterChanged:单次迭代中有参数改变则增加1 247 | iterStep = 0; parameterChanged = 1 248 | 249 | #如果没有达到限制的迭代次数以及上次迭代中有参数改变则继续迭代 250 | #parameterChanged==0时表示上次迭代没有参数改变,如果遍历了一遍都没有参数改变,说明 251 | #达到了收敛状态,可以停止了 252 | while (iterStep < iter) and (parameterChanged > 0): 253 | #打印当前迭代轮数 254 | print('iter:%d:%d'%( iterStep, iter)) 255 | #迭代步数加1 256 | iterStep += 1 257 | #新的一轮将参数改变标志位重新置0 258 | parameterChanged = 0 259 | 260 | #大循环遍历所有样本,用于找SMO中第一个变量 261 | for i in range(self.m): 262 | #查看第一个遍历是否满足KKT条件,如果不满足则作为SMO中第一个变量从而进行优化 263 | if self.isSatisfyKKT(i) == False: 264 | #如果下标为i的α不满足KKT条件,则进行优化 265 | 266 | #第一个变量α的下标i已经确定,接下来按照“7.4.2 变量的选择方法”第二步 267 | #选择变量2。由于变量2的选择中涉及到|E1 - E2|,因此先计算E1 268 | E1 = self.calcEi(i) 269 | 270 | #选择第2个变量 271 | E2, j = self.getAlphaJ(E1, i) 272 | 273 | #参考“7.4.1两个变量二次规划的求解方法” P126 下半部分 274 | #获得两个变量的标签 275 | y1 = self.trainLabelMat[i] 276 | y2 = self.trainLabelMat[j] 277 | #复制α值作为old值 278 | alphaOld_1 = self.alpha[i] 279 | alphaOld_2 = self.alpha[j] 280 | #依据标签是否一致来生成不同的L和H 281 | if y1 != y2: 282 | L = max(0, alphaOld_2 - alphaOld_1) 283 | H = min(self.C, self.C + alphaOld_2 - alphaOld_1) 284 | else: 285 | L = max(0, alphaOld_2 + alphaOld_1 - self.C) 286 | H = min(self.C, alphaOld_2 + alphaOld_1) 287 | #如果两者相等,说明该变量无法再优化,直接跳到下一次循环 288 | if L == H: continue 289 | 290 | #计算α的新值 291 | #依据“7.4.1两个变量二次规划的求解方法”式7.106更新α2值 292 | #先获得几个k值,用来计算事7.106中的分母η 293 | k11 = self.k[i][i] 294 | k22 = self.k[j][j] 295 | k21 = self.k[j][i] 296 | k12 = self.k[i][j] 297 | #依据式7.106更新α2,该α2还未经剪切 298 | alphaNew_2 = alphaOld_2 + y2 * (E1 - E2) / (k11 + k22 - 2 * k12) 299 | #剪切α2 300 | if alphaNew_2 < L: alphaNew_2 = L 301 | elif alphaNew_2 > H: alphaNew_2 = H 302 | #更新α1,依据式7.109 303 | alphaNew_1 = alphaOld_1 + y1 * y2 * (alphaOld_2 - alphaNew_2) 304 | 305 | #依据“7.4.2 变量的选择方法”第三步式7.115和7.116计算b1和b2 306 | b1New = -1 * E1 - y1 * k11 * (alphaNew_1 - alphaOld_1) \ 307 | - y2 * k21 * (alphaNew_2 - alphaOld_2) + self.b 308 | b2New = -1 * E2 - y1 * k12 * (alphaNew_1 - alphaOld_1) \ 309 | - y2 * k22 * (alphaNew_2 - alphaOld_2) + self.b 310 | 311 | #依据α1和α2的值范围确定新b 312 | if (alphaNew_1 > 0) and (alphaNew_1 < self.C): 313 | bNew = b1New 314 | elif (alphaNew_2 > 0) and (alphaNew_2 < self.C): 315 | bNew = b2New 316 | else: 317 | bNew = (b1New + b2New) / 2 318 | 319 | #将更新后的各类值写入,进行更新 320 | self.alpha[i] = alphaNew_1 321 | self.alpha[j] = alphaNew_2 322 | self.b = bNew 323 | 324 | self.E[i] = self.calcEi(i) 325 | self.E[j] = self.calcEi(j) 326 | 327 | #如果α2的改变量过于小,就认为该参数未改变,不增加parameterChanged值 328 | #反之则自增1 329 | if math.fabs(alphaNew_2 - alphaOld_2) >= 0.00001: 330 | parameterChanged += 1 331 | 332 | #打印迭代轮数,i值,该迭代轮数修改α数目 333 | print("iter: %d i:%d, pairs changed %d" % (iterStep, i, parameterChanged)) 334 | 335 | #全部计算结束后,重新遍历一遍α,查找里面的支持向量 336 | for i in range(self.m): 337 | #如果α>0,说明是支持向量 338 | if self.alpha[i] > 0: 339 | #将支持向量的索引保存起来 340 | self.supportVecIndex.append(i) 341 | 342 | def calcSinglKernel(self, x1, x2): 343 | ''' 344 | 单独计算核函数 345 | :param x1:向量1 346 | :param x2: 向量2 347 | :return: 核函数结果 348 | ''' 349 | #按照“7.3.3 常用核函数”式7.90计算高斯核 350 | result = (x1 - x2) * (x1 - x2).T 351 | result = np.exp(-1 * result / (2 * self.sigma ** 2)) 352 | #返回结果 353 | return np.exp(result) 354 | 355 | 356 | def predict(self, x): 357 | ''' 358 | 对样本的标签进行预测 359 | 公式依据“7.3.4 非线性支持向量分类机”中的式7.94 360 | :param x: 要预测的样本x 361 | :return: 预测结果 362 | ''' 363 | 364 | result = 0 365 | for i in self.supportVecIndex: 366 | #遍历所有支持向量,计算求和式 367 | #如果是非支持向量,求和子式必为0,没有必须进行计算 368 | #这也是为什么在SVM最后只有支持向量起作用 369 | #------------------ 370 | #先单独将核函数计算出来 371 | tmp = self.calcSinglKernel(self.trainDataMat[i, :], np.mat(x)) 372 | #对每一项子式进行求和,最终计算得到求和项的值 373 | result += self.alpha[i] * self.trainLabelMat[i] * tmp 374 | #求和项计算结束后加上偏置b 375 | result += self.b 376 | #使用sign函数返回预测结果 377 | return np.sign(result) 378 | 379 | 380 | 381 | def test(self, testDataList, testLabelList): 382 | ''' 383 | 测试 384 | :param testDataList:测试数据集 385 | :param testLabelList: 测试标签集 386 | :return: 正确率 387 | ''' 388 | #错误计数值 389 | errorCnt = 0 390 | #遍历测试集所有样本 391 | for i in range(len(testDataList)): 392 | #打印目前进度 393 | print('test:%d:%d'%(i, len(testDataList))) 394 | #获取预测结果 395 | result = self.predict(testDataList[i]) 396 | #如果预测与标签不一致,错误计数值加一 397 | if result != testLabelList[i]: 398 | errorCnt += 1 399 | #返回正确率 400 | return 1 - errorCnt / len(testDataList) 401 | 402 | 403 | 404 | 405 | if __name__ == '__main__': 406 | start = time.time() 407 | 408 | # 获取训练集及标签 409 | print('start read transSet') 410 | trainDataList, trainLabelList = loadData('../Mnist/mnist_train.csv') 411 | 412 | # 获取测试集及标签 413 | print('start read testSet') 414 | testDataList, testLabelList = loadData('../Mnist/mnist_test.csv') 415 | 416 | #初始化SVM类 417 | print('start init SVM') 418 | svm = SVM(trainDataList[:1000], trainLabelList[:1000], 10, 200, 0.001) 419 | 420 | # 开始训练 421 | print('start to train') 422 | svm.train() 423 | 424 | # 开始测试 425 | print('start to test') 426 | accuracy = svm.test(testDataList[:100], testLabelList[:100]) 427 | print('the accuracy is:%d'%(accuracy * 100), '%') 428 | 429 | # 打印时间 430 | print('time span:', time.time() - start) -------------------------------------------------------------------------------- /blogs/How Does Batch Normalizetion Help Optimization.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dod-o/Statistical-Learning-Method_Code/cd1d28337d223bc164e4949c167958634f409939/blogs/How Does Batch Normalizetion Help Optimization.pdf -------------------------------------------------------------------------------- /blogs/K近邻原理剖析及实现.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dod-o/Statistical-Learning-Method_Code/cd1d28337d223bc164e4949c167958634f409939/blogs/K近邻原理剖析及实现.pdf -------------------------------------------------------------------------------- /blogs/决策树原理剖析及实现.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dod-o/Statistical-Learning-Method_Code/cd1d28337d223bc164e4949c167958634f409939/blogs/决策树原理剖析及实现.pdf -------------------------------------------------------------------------------- /blogs/对话系统中的DST.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dod-o/Statistical-Learning-Method_Code/cd1d28337d223bc164e4949c167958634f409939/blogs/对话系统中的DST.pdf -------------------------------------------------------------------------------- /blogs/微软、头条、滴滴、爱奇艺NLP面试感想.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dod-o/Statistical-Learning-Method_Code/cd1d28337d223bc164e4949c167958634f409939/blogs/微软、头条、滴滴、爱奇艺NLP面试感想.pdf -------------------------------------------------------------------------------- /blogs/感知机原理剖析及实现.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dod-o/Statistical-Learning-Method_Code/cd1d28337d223bc164e4949c167958634f409939/blogs/感知机原理剖析及实现.pdf -------------------------------------------------------------------------------- /blogs/支持向量机(SVM)原理剖析及实现.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dod-o/Statistical-Learning-Method_Code/cd1d28337d223bc164e4949c167958634f409939/blogs/支持向量机(SVM)原理剖析及实现.pdf -------------------------------------------------------------------------------- /blogs/最大熵原理剖析及实现.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dod-o/Statistical-Learning-Method_Code/cd1d28337d223bc164e4949c167958634f409939/blogs/最大熵原理剖析及实现.pdf -------------------------------------------------------------------------------- /blogs/朴素贝叶斯原理剖析及实现.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dod-o/Statistical-Learning-Method_Code/cd1d28337d223bc164e4949c167958634f409939/blogs/朴素贝叶斯原理剖析及实现.pdf -------------------------------------------------------------------------------- /blogs/机器学习和NLP入门规划.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dod-o/Statistical-Learning-Method_Code/cd1d28337d223bc164e4949c167958634f409939/blogs/机器学习和NLP入门规划.pdf -------------------------------------------------------------------------------- /blogs/机器学习面试之最大熵模型.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dod-o/Statistical-Learning-Method_Code/cd1d28337d223bc164e4949c167958634f409939/blogs/机器学习面试之最大熵模型.pdf -------------------------------------------------------------------------------- /blogs/记忆网络之Memory Networks.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dod-o/Statistical-Learning-Method_Code/cd1d28337d223bc164e4949c167958634f409939/blogs/记忆网络之Memory Networks.pdf -------------------------------------------------------------------------------- /blogs/逻辑斯蒂原理剖析及实现.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dod-o/Statistical-Learning-Method_Code/cd1d28337d223bc164e4949c167958634f409939/blogs/逻辑斯蒂原理剖析及实现.pdf -------------------------------------------------------------------------------- /perceptron/perceptron_dichotomy.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | #Author:Dodo 3 | #Date:2018-11-15 4 | #Email:lvtengchao@pku.edu.cn 5 | 6 | ''' 7 | 数据集:Mnist 8 | 训练集数量:60000 9 | 测试集数量:10000 10 | ------------------------------ 11 | 运行结果: 12 | 正确率:81.72%(二分类) 13 | 运行时长:78.6s 14 | ''' 15 | 16 | import numpy as np 17 | import time 18 | 19 | def loadData(fileName): 20 | ''' 21 | 加载Mnist数据集 22 | :param fileName:要加载的数据集路径 23 | :return: list形式的数据集及标记 24 | ''' 25 | print('start to read data') 26 | # 存放数据及标记的list 27 | dataArr = []; labelArr = [] 28 | # 打开文件 29 | fr = open(fileName, 'r') 30 | # 将文件按行读取 31 | for line in fr.readlines(): 32 | # 对每一行数据按切割福','进行切割,返回字段列表 33 | curLine = line.strip().split(',') 34 | 35 | # Mnsit有0-9是个标记,由于是二分类任务,所以将>=5的作为1,<5为-1 36 | if int(curLine[0]) >= 5: 37 | labelArr.append(1) 38 | else: 39 | labelArr.append(-1) 40 | #存放标记 41 | #[int(num) for num in curLine[1:]] -> 遍历每一行中除了以第一哥元素(标记)外将所有元素转换成int类型 42 | #[int(num)/255 for num in curLine[1:]] -> 将所有数据除255归一化(非必须步骤,可以不归一化) 43 | dataArr.append([int(num)/255 for num in curLine[1:]]) 44 | 45 | #返回data和label 46 | return dataArr, labelArr 47 | 48 | def perceptron(dataArr, labelArr, iter=50): 49 | ''' 50 | 感知器训练过程 51 | :param dataArr:训练集的数据 (list) 52 | :param labelArr: 训练集的标签(list) 53 | :param iter: 迭代次数,默认50 54 | :return: 训练好的w和b 55 | ''' 56 | print('start to trans') 57 | #将数据转换成矩阵形式(在机器学习中因为通常都是向量的运算,转换称矩阵形式方便运算) 58 | #转换后的数据中每一个样本的向量都是横向的 59 | dataMat = np.mat(dataArr) 60 | #将标签转换成矩阵,之后转置(.T为转置)。 61 | #转置是因为在运算中需要单独取label中的某一个元素,如果是1xN的矩阵的话,无法用label[i]的方式读取 62 | #对于只有1xN的label可以不转换成矩阵,直接label[i]即可,这里转换是为了格式上的统一 63 | labelMat = np.mat(labelArr).T 64 | #获取数据矩阵的大小,为m*n 65 | m, n = np.shape(dataMat) 66 | #创建初始权重w,初始值全为0。 67 | #np.shape(dataMat)的返回值为m,n -> np.shape(dataMat)[1])的值即为n,与 68 | #样本长度保持一致 69 | w = np.zeros((1, np.shape(dataMat)[1])) 70 | #初始化偏置b为0 71 | b = 0 72 | #初始化步长,也就是梯度下降过程中的n,控制梯度下降速率 73 | h = 0.0001 74 | 75 | #进行iter次迭代计算 76 | for k in range(iter): 77 | #对于每一个样本进行梯度下降 78 | #李航书中在2.3.1开头部分使用的梯度下降,是全部样本都算一遍以后,统一 79 | #进行一次梯度下降 80 | #在2.3.1的后半部分可以看到(例如公式2.6 2.7),求和符号没有了,此时用 81 | #的是随机梯度下降,即计算一个样本就针对该样本进行一次梯度下降。 82 | #两者的差异各有千秋,但较为常用的是随机梯度下降。 83 | for i in range(m): 84 | #获取当前样本的向量 85 | xi = dataMat[i] 86 | #获取当前样本所对应的标签 87 | yi = labelMat[i] 88 | #判断是否是误分类样本 89 | #误分类样本特诊为: -yi(w*xi+b)>=0,详细可参考书中2.2.2小节 90 | #在书的公式中写的是>0,实际上如果=0,说明改点在超平面上,也是不正确的 91 | if -1 * yi * (w * xi.T + b) >= 0: 92 | #对于误分类样本,进行梯度下降,更新w和b 93 | w = w + h * yi * xi 94 | b = b + h * yi 95 | #打印训练进度 96 | print('Round %d:%d training' % (k, iter)) 97 | 98 | #返回训练完的w、b 99 | return w, b 100 | 101 | 102 | def model_test(dataArr, labelArr, w, b): 103 | ''' 104 | 测试准确率 105 | :param dataArr:测试集 106 | :param labelArr: 测试集标签 107 | :param w: 训练获得的权重w 108 | :param b: 训练获得的偏置b 109 | :return: 正确率 110 | ''' 111 | print('start to test') 112 | #将数据集转换为矩阵形式方便运算 113 | dataMat = np.mat(dataArr) 114 | #将label转换为矩阵并转置,详细信息参考上文perceptron中 115 | #对于这部分的解说 116 | labelMat = np.mat(labelArr).T 117 | 118 | #获取测试数据集矩阵的大小 119 | m, n = np.shape(dataMat) 120 | #错误样本数计数 121 | errorCnt = 0 122 | #遍历所有测试样本 123 | for i in range(m): 124 | #获得单个样本向量 125 | xi = dataMat[i] 126 | #获得该样本标记 127 | yi = labelMat[i] 128 | #获得运算结果 129 | result = -1 * yi * (w * xi.T + b) 130 | #如果-yi(w*xi+b)>=0,说明该样本被误分类,错误样本数加一 131 | if result >= 0: errorCnt += 1 132 | #正确率 = 1 - (样本分类错误数 / 样本总数) 133 | accruRate = 1 - (errorCnt / m) 134 | #返回正确率 135 | return accruRate 136 | 137 | if __name__ == '__main__': 138 | #获取当前时间 139 | #在文末同样获取当前时间,两时间差即为程序运行时间 140 | start = time.time() 141 | 142 | #获取训练集及标签 143 | trainData, trainLabel = loadData('../Mnist/mnist_train.csv') 144 | #获取测试集及标签 145 | testData, testLabel = loadData('../Mnist/mnist_test.csv') 146 | 147 | #训练获得权重 148 | w, b = perceptron(trainData, trainLabel, iter = 30) 149 | #进行测试,获得正确率 150 | accruRate = model_test(testData, testLabel, w, b) 151 | 152 | #获取当前时间,作为结束时间 153 | end = time.time() 154 | #显示正确率 155 | print('accuracy rate is:', accruRate) 156 | #显示用时时长 157 | print('time span:', end - start) 158 | 159 | -------------------------------------------------------------------------------- /transMnist/Mnist/t10k-images.idx3-ubyte: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dod-o/Statistical-Learning-Method_Code/cd1d28337d223bc164e4949c167958634f409939/transMnist/Mnist/t10k-images.idx3-ubyte -------------------------------------------------------------------------------- /transMnist/Mnist/t10k-labels.idx1-ubyte: -------------------------------------------------------------------------------- 1 | '                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             -------------------------------------------------------------------------------- /transMnist/Mnist/train-images.idx3-ubyte: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dod-o/Statistical-Learning-Method_Code/cd1d28337d223bc164e4949c167958634f409939/transMnist/Mnist/train-images.idx3-ubyte -------------------------------------------------------------------------------- /transMnist/Mnist/train-labels.idx1-ubyte: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dod-o/Statistical-Learning-Method_Code/cd1d28337d223bc164e4949c167958634f409939/transMnist/Mnist/train-labels.idx1-ubyte -------------------------------------------------------------------------------- /transMnist/transMnist.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | ''' 3 | Mnsit原始数据集为字符格式,将数据集转换为cvs格式, 4 | 后续代码都会在cvs文件的基础上进行编写,这样大家看代码也能清楚很多 5 | 代码由以下网址提供,表示感谢。 6 | https://pjreddie.com/projects/mnist-in-csv/ 7 | 8 | 该py文件属于一个补充,不使用也不影响后续算法的实践。 9 | 转换后的CVS文件在Mnist文件夹中 10 | ''' 11 | def convert(imgf, labelf, outf, n): 12 | f = open(imgf, "rb") 13 | o = open(outf, "w") 14 | l = open(labelf, "rb") 15 | 16 | f.read(16) 17 | l.read(8) 18 | images = [] 19 | 20 | for i in range(n): 21 | image = [ord(l.read(1))] 22 | for j in range(28*28): 23 | image.append(ord(f.read(1))) 24 | images.append(image) 25 | 26 | for image in images: 27 | o.write(",".join(str(pix) for pix in image)+"\n") 28 | f.close() 29 | o.close() 30 | l.close() 31 | 32 | if __name__ == '__main__': 33 | convert(".\Mnist\\t10k-images.idx3-ubyte", ".\Mnist\\t10k-labels.idx1-ubyte", 34 | ".\Mnist\\mnist_test.csv", 10000) 35 | convert(".\Mnist\\train-images.idx3-ubyte", ".\Mnist\\train-labels.idx1-ubyte", 36 | ".\Mnist\mnist_train.csv", 60000) --------------------------------------------------------------------------------