逻辑回归
逻辑回归是分类最简单的算法。
题目是有一个学校招生,考2门专业课。给出了100个样本,每一个样本包含2门课的成绩以及是否被录取。也就是LogiReg_data.csv数据。原始是txt格式,为了方便,人为的加上了表头。
1 import numpy as np 2 import pandas as pd 3 import matplotlib.pyplot as plt 4 # 导入相关库,并重命名 5 pdData = pd.read_csv("LogiReg_data.csv") 6 #打开文件 7 positive = pdData[pdData['Admitted'] == 1] #将'Admitted'一列为1的行(样本)提取出来,放入positive 8 negative = pdData[pdData['Admitted'] == 0] #将'Admitted'一列为0的行(样本)提取出来,放入negative 9 fig, ax = plt.subplots(figsize=(10,5)) 10 #subplots固定用法 11 #subplots是matplotlib中的画子图像的函数, 12 #原型: 13 #plt.subplots( 14 # nrows=1, 15 # ncols=1, 16 # sharex=False, 17 # sharey=False, 18 # squeeze=True, 19 # subplot_kw=None, 20 # gridspec_kw=None, 21 # **fig_kw, 22 #) 23 #nrows,ncols控制几个子图,例如:nrows=1,ncols=2就是2个子图,左一个右一个。sharex、sharey控制 24 #是否共享x、y轴。返回值是两个,一个是fig,图像,一个是axes。figsize是图片大小。 25 ax.scatter(positive['Exam 1'], positive['Exam 2'], s=30, c='b', marker='o', label='Admitted') 26 ax.scatter(negative['Exam 1'], negative['Exam 2'], s=30, c='r', marker='x', label='Not Admitted') 27 #scatter可以用来画散点图,里边参数positive['Exam 1'], positive['Exam 2']分别是x轴和y轴是什么。“c”是可选颜色 28 #“marker”是可选点的形状,具体可以百度。s是size,为点的大小,label是图中'o'的点是'Admitted','x'是'Not Admitted' 29 ax.legend() 30 #ax.legend控制图例的参数,例如大小、位置、标题等等。https://blog.csdn.net/qq_35240640/article/details/89478439?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~first_rank_v2~rank_v25-2-89478439.nonecase 31 ax.set_xlabel('Exam 1 Score') 32 ax.set_ylabel('Exam 2 Score') 33 #set_ylabel是x轴和y轴的名称叫什么。用plt.xlabel和plt.ylabel也可以。 34 35 def sigmoid(z): 36 return 1 / (1 + np.exp(-z)) 37 #sigmoid 函数,用来映射成【0,1】的一个概率值 38 39 nums = np.arange(-10, 10, step=1) #np.arange构造一个【-10,10】的一个ndarray,步长为1.(10,-9,-8,-7…………,7,8,9,10) 40 fig, ax = plt.subplots(figsize=(12,4)) 41 ax.plot(nums, sigmoid(nums), 'r') #ax.plot画出sigmoid函数。其中sigmoid(0)=0.5 42 print(type(nums)) #发现nums就是一个ndarray。 43 44 def model(X, theta): 45 return sigmoid(np.dot(X, theta.T)) #np.dot 计算点积,需要注意的是,点积计算时,要求第一个参数的列数要和第二个参数的行数一样才可以。最后的返回值也是一个矩阵,列数为1 46 47 pdData.insert(0, 'Ones', 1,allow_duplicates=True) #在第一列数据前增加新的一列,变为0、1、2、3列 48 orig_data = pdData.iloc[:,:].values #将dataframe转成numpy.array。 49 cols = orig_data.shape[1] #计算出二位数组有几列,也可以写成cols=len(orig_data[0]) 50 X = orig_data[:,0:cols-1] #将前3列0、1、2列赋值给x 51 y = orig_data[:,cols-1:cols] #将最后一列赋值给y 52 theta = np.zeros([1, 3]) #创建一个theta [[0. 0. 0.]] 这里写np.zeros((1,3))也可以 53 54 55 56 def cost(X, y, theta): #定义损失函数,损失函数可以评判估计值和实际值的误差 57 left = np.multiply(-y, np.log(model(X, theta))) #np.multiply是参数相乘、乘法。np.log计算数学的log,model返回值是计算sigmoid,结果(0,1) 58 right = np.multiply(1 - y, np.log(1 - model(X, theta))) 59 return np.sum(left - right) / (len(X)) #np.sum求和,计算总的损失,再除以样本数量,就是平均损失。 60 61 cost(X, y, theta) 62 63 def gradient(X, y, theta): #用来计算梯度下降方向的,这里需要先对损失函数求导,目的是计算出损失最小的位置,参考数学公式。 64 grad = np.zeros(theta.shape) #grad是一个二维数组,[[0. 0. 0.]] 65 error = (model(X, theta)- y).ravel() #把error转成一维数组,是为后边的和X第j列相乘做准备 66 for j in range(len(theta.ravel())): #theta本身是二维数组,但是通过theta.ravel() 将theta转成一维数组, len(theta.ravel()) 计算数组长度,在这里实际长度是3。 67 term = np.multiply(error, X[:,j]) #注意X是一个二维数组,所以采用X[:,j]选取所有行的第j列。 68 grad[0, j] = np.sum(term) / len(X) #grad是一个二维数组,[[0. 0. 0.]],theta有三个参数,所以要计算三个梯度的方向 69 return grad #grad是一个二维数组,[[0. 0. 0.]] 70 71 STOP_ITER = 0 72 STOP_COST = 1 73 STOP_GRAD = 2 74 #设定三种不同的停止策略 75 def stopCriterion(type, value, threshold): 76 if type == STOP_ITER: return value > threshold #设定迭代次数 77 elif type == STOP_COST: return abs(value[-1]-value[-2]) < threshold #给损失函数cost设定阈值 78 elif type == STOP_GRAD: return np.linalg.norm(value) < threshold #给梯度设定阈值 注意三种情况返回的都是bool值 79 80 import numpy.random 81 #洗牌,无法保证数据是具有随机性,通过np.random.shuffle函数打乱数据顺序,使数据具有随机性 82 def shuffleData(data): #函数的功能就是将X和y数据重新排列组合 83 np.random.shuffle(data) 84 cols = data.shape[1] #计算出二位数组有几列,也可以写成cols=len(orig_data[0]) 85 X = data[:, 0:cols-1] #重新选择X,但是和之前一样,还是第0、1、2列 86 y = data[:, cols-1:] #重新选择y,但是和之前一样,还是第3列 87 return X, y 88 89 90 import time 91 92 93 def descent(data, theta, batchSize, stopType, thresh, alpha): 94 # 梯度下降求解 95 96 init_time = time.time() # 显示计算时间 97 i = 0 # 迭代次数 迭代次数越多,损失函数越趋于0,看图像就可以 98 k = 0 # batch 选取的样本,当k=1时,一个样本计算一次,计算速度快,但不一定收敛,这是随机梯度下降; 99 # 但是慢当k=10时,10个样本计算一次,比较实用,这是小批量梯度下降;当k=样本数量时,计算所有样本,速度慢,但是容易找到最优解,这是批量梯度下降。 100 X, y = shuffleData(data) # 调用shuffleData函数,打乱数据 101 grad = np.zeros(theta.shape) # 初始化grad,grad是梯度 102 costs = [cost(X, y, theta)] # 初始化损失值,这里打算用appand,所以新生成的costs是二维数组。 103 # 以上是将变量初始化。 104 while True: # 死循环,碰到break停止 105 grad = gradient(X[k:k + batchSize], y[k:k + batchSize], 106 theta) # X和y是二维数组X[k:k+batchSize],意思是第k行到第k+batchSize行,y同理 107 k += batchSize # 取batch数量个数据 108 if k >= n: # n会在后边赋值,这个n代表如果计算的样本数量超出了原来的样本数量,就重新洗牌,重新计算参数 109 k = 0 110 X, y = shuffleData(data) # 重新洗牌 111 theta = theta - alpha * grad # 参数更新,可以参考数学部分 112 costs.append(cost(X, y, theta)) # 计算新的损失 113 i += 1 114 115 if stopType == STOP_ITER: 116 value = i 117 elif stopType == STOP_COST: 118 value = costs 119 elif stopType == STOP_GRAD: 120 value = grad 121 if stopCriterion(stopType, value, thresh): break 122 123 return theta, i - 1, costs, grad, time.time() - init_time 124 125 def runExpe(data, theta, batchSize, stopType, thresh, alpha): 126 #import pdb; pdb.set_trace(); 127 theta, iter, costs, grad, dur = descent(data, theta, batchSize, stopType, thresh, alpha) 128 #以下都是为图的标题做准备的 129 name = "Original" if (data[:,1]>2).sum() > 1 else "Scaled" 130 name += " data - learning rate: {} - ".format(alpha) 131 if batchSize==n: strDescType = "Gradient" 132 elif batchSize==1: strDescType = "Stochastic" 133 else: strDescType = "Mini-batch ({})".format(batchSize) 134 name += strDescType + " descent - Stop: " 135 if stopType == STOP_ITER: strStop = "{} iterations".format(thresh) 136 elif stopType == STOP_COST: strStop = "costs change < {}".format(thresh) 137 else: strStop = "gradient norm < {}".format(thresh) 138 name += strStop 139 print ("***{}\nTheta: {} - Iter: {} - Last cost: {:03.2f} - Duration: {:03.2f}s".format( 140 name, theta, iter, costs[-1], dur)) 141 #以上都是为图的标题做准备的 142 fig, ax = plt.subplots(figsize=(12,4)) 143 ax.plot(np.arange(len(costs)), costs, 'r') 144 ax.set_xlabel('Iterations') 145 ax.set_ylabel('Cost') 146 ax.set_title(name.upper() + ' - Error vs. Iteration') 147 plt.show() 148 return theta 149 n=100 150 runExpe(orig_data, theta, n, STOP_ITER, thresh=5000, alpha=0.000001) #1.1次 151 #一次选取整个样本,采用控制迭代次数的方式,5000次,Last cost: 0.63,time=1.69s 152 runExpe(orig_data, theta, n, STOP_ITER, thresh=50000, alpha=0.000001) #1.2次 153 #一次选取整个样本,采用控制迭代次数的方式,5000次,Last cost: 0.63,time=17.30s 154 runExpe(orig_data, theta, n, STOP_ITER, thresh=5000, alpha=0.001) #1.3次 155 #一次选取整个样本,采用控制迭代次数的方式,5000次,Last cost: 0.61,time=1.69s 156 #1.2和1.1次相比,迭代次数*10,时间也大约*10,但是损失值并没有降低,所以这种情况下增加迭代次数并没有用。 157 #1.1和1.3相比,迭代次数不变,学习率降低,但是cost并没有降低很多,一般的cost取0.001比较合适 158 runExpe(orig_data, theta, n, STOP_COST, thresh=0.000001, alpha=0.001) #2次 159 #一次选取整个样本,采用控制cost方式,Last cost: 0.38,time: 38.37s,需要迭代11000次左右 160 #通过增大或者减小cost的值很容易发现,cost越小,迭代次数越多,消耗时间越多;cost增大则相反。 161 runExpe(orig_data, theta, 100, STOP_GRAD, thresh=0.02, alpha=0.001) # 3.1次 162 #一次选取整个样本,采用控制grad梯度下降速度方式,Last cost: 0.31,time: 78.83s,需要迭代220000次左右 163 runExpe(orig_data, theta, 100, STOP_GRAD, thresh=0.05, alpha=0.001) # 3.2次 164 #一次选取整个样本,采用控制grad梯度下降速度方式,Last cost: 0.49,time: 14.96s,需要迭代40000次左右 165 #3.2和3.1对比,增大grad,发现迭代次数降低,cost升高。实际上grad为0时为最理想值,但是耗时非常大。 166 runExpe(orig_data, theta, 1, STOP_ITER, thresh=5000, alpha=0.001) #4次 167 #一次选取1个样本,采用控制迭代次数方式,图像不稳定,说明1个样本下模型不收敛,也就是结果不趋于0 168 #即使增加迭代次数也没用,减小学习率alpha也没用,图像始终不稳定。 169 runExpe(orig_data, theta, 16, STOP_ITER, thresh=15000, alpha=0.001) #5次 170 #一次选取16个样本,采用控制迭代次数方式,图像仍然不稳定,所以在控制迭代次数方式这里,问题出在了数据 171 #需要对数据进行标准化 172 173 #以下是数据进行标准化 174 from sklearn import preprocessing as pp 175 scaled_data = orig_data.copy() 176 scaled_data[:, 1:3] = pp.scale(orig_data[:, 1:3]) 177 #以上是数据进行标准化 178 runExpe(scaled_data, theta, n, STOP_ITER, thresh=5000, alpha=0.001) #6次 179 #一次选取整个样本,采用控制迭代次数的方式,5000次,Last cost: 0.38,time:1.92s 180 #6次和1.1次相比,是数据标准化之后的结果,发现时间上没有太大改善(因为迭代次数没有变),但是cost下降了很多 181 #所以数据预处理很重要,要先进行标准化,去量纲,变为纯数 182 runExpe(scaled_data, theta, n, STOP_GRAD, thresh=0.02, alpha=0.001) #7次 183 #一次选取整个样本,采用控制grad梯度下降速度方式,迭代次数不到60000次,Last cost: 0.22 - time: 22.24s 184 #7次和3.1相比,是数据标准化之后的结果,迭代次数减小,时间减小,cost降到了0.22 185 runExpe(scaled_data, theta, 1, STOP_GRAD, thresh=0.02, alpha=0.001) #8次 186 #一次选取1个样本,采用控制grad梯度下降速度方式,迭代次数15000次左右,cost: 0.28,time:1.95s 187 #8次和7次相比,样本数为1,整体迭代次数缩减了很多,时间缩减了很多 188 189 theta = runExpe(scaled_data, theta, 1, STOP_GRAD, thresh=0.002/5, alpha=0.001) #9次 190 #一次选取1个样本,采用控制grad梯度下降速度方式,70000多次,cost降到了0.22 - time: 8.78s 191 runExpe(scaled_data, theta, 16, STOP_GRAD, thresh=0.002*2, alpha=0.001) #10次 192 #一次选取16个样本,采用控制grad梯度下降速度方式,6000多次,cost降到了0.21 - time: 1.19s 193 #需要注意的是10次和9次比,虽然看起来只是变了计算一次的样本数量,但是10的theta初试值并不是0, 194 #而是用的9次的计算结果,也就是说初始值是经过训练之后的值,所以迭代次数大幅下降。 195 196 def predict(X, theta): #函数功能是用原始数据验证模型的准确性 197 #return [1 if x >= 0.5 else 0 for x in model(X, theta)] #这是原博主写的,但是不太好理解,但是习惯矩阵运算很重要 198 a = [] 199 for i in model(X, theta): 200 if i>=0.5: 201 a.append(1) 202 else: 203 a.append(0) 204 return a #函数的返回值是一个list,是每一个样本是否被录取的结果[1, 0, 1, 1, 0,…………, 1, 1, 1, 0] 205 206 scaled_X = scaled_data[:, :3] #选取前三列数据 207 y = scaled_data[:, 3] #选取最后一列数据 208 predictions = predict(scaled_X, theta) #predictions是预测值 209 correct = [1 if ((a == 1 and b == 1) or (a == 0 and b == 0)) else 0 for (a, b) in zip(predictions, y)] 210 #correct是一个list,如果真实值和预测值相等,该位置写1,否则写0.这里也可以用for循环实现。 211 #zip函数是将(predictions, y)组合成一个可迭代对象,不然for循环无法实现。 212 accuracy = (sum(map(int, correct)) % len(correct)) #map是将correct转成int型list(猜的),可以不用map,分开计算也可以 213 print ('accuracy = {0}%'.format(accuracy)) #计算概率,需要注意的是,这里是整数除以整数。format很简单,百度就可以
数学公式:
以上是根据唐宇迪视频,以及某博客https://www.cnblogs.com/douzujun/p/8370993.html所写
代码是基于python 3.7,没有任何问题。