线性分类--DiagnosisRegression

题目

逻辑回归模型实现对急性炎症的诊断数据进行逻辑回归预测。
参考资料:【ML】一文详尽系列之逻辑回归

代码

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
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
150
151
152
153
154
155
156
157
import numpy as np
import matplotlib.pyplot as plt

def load_data(data_file_path): #文件导入
feats_list = []
with open(data_file_path) as f: #为什么这么写,老师上课有说。
for line in f: #按行读取数据
line = line.rstrip() #默认删除空格
feats_list.append(line.split("\t")) #按‘\t’切割
return feats_list

def convert_data_value(feats_list): #进行数据替换
replace_value = {'yes':1.,'no':0.} #这个不用解释吧= =反正就是先设一个字典,方便之后将yes转为1,no转为0,为什么是1,0,等sigmoid函数在说
for index, feats in enumerate(feats_list):
temp = feats[0].split(",") #将36,7分为36与7,让在下面那行进行计算为36.7
feats_list[index][0] = float(temp[0]) + float(temp[1])/10.0
for n in range(1,len(feats)): #示例当中只有一个循环是因为需要替换的在数组第一位,而这个是
feats_list[index][n] = replace_value[feats[n]]
'''
假设有以下数据:
35,5 no yes no no no no no
35,9 no no yes yes yes yes no
35,9 no yes no no no no no

第一个循环后:
35,5 no yes no no no no no
第二个循环:
35,5
no
。。。

这里两个循环,第一个循环是将数据按行读取,如上面所示,在以此为基础上,在进行下一个循环,第二个循环从行中获取相应的数据(35,5、yes、no。。)以便下面的替换
老师所给的代码之所以只有一个循环,因为它只需要替换第一个数据,也就是M啥啥的,它也是以0为标志。

for index, feats in enumerate(feats_list)这个for循环这么解释:
按照上面的数据,当第一个循环后取相应的行时,index为行的下标,以35,5这一行为例就是0,而feat的数值为35,5 no yes no no no no no:
其主要原因是enumerate(),具体可以百度它。
第二个循环同理

'''
return feats_list

def sigmoid(value): #sigmoid函数,即:激活函数。这个是线性分类的关键
return 1./(1+np.exp(-value)) #sigmoid函数的函数分布为(0,1),具体看百度,特别适合用于二分类
#因为其分布偏向0和1,所以把yes和no弄为0和1,如果把yes转为0,no转为1其实也没啥问题,就是有点反常理。
#https://baike.baidu.com/item/Sigmoid%E5%87%BD%E6%95%B0/7981407?fr=aladdin

def get_cost(Y, gt_Y): # 交叉熵损失函数。https://blog.csdn.net/red_stone1/article/details/80735068?tdsourcetag=s_pctim_aiomsg
num_example = Y.shape[0] #计算传入的数据个数
cost1 = (-gt_Y).T * np.log(Y)
cost2 = (1-gt_Y).T * np.log(1-Y)
cost = cost1 - cost2
return np.squeeze(np.asarray(cost))/num_example

def sigmoid_logistic_regression(arr_X, arr_Y, iter_num=500, alpha=0.005, log_iter=20):
#线性分类主体,传入的数据分别为(训练数据的判断依据,训练数据的真实值,训练次数,学习率,几次打印一次)
bias = np.ones((arr_X.shape[0], 1), dtype=float) #设置一个bias,全为1
new_arr_x = np.append(arr_X, bias, axis=1) #将bias加在训练数据的判断依据后面,并传给新变量
'''
(训练数据的判断依据) (bias)
35.5 0 1 0 0 0 1
35.9 0 0 1 1 1 1
35.9 0 1 0 0 0 1
'''
mat_X = np.mat(new_arr_x) #转为矩阵
mat_Y = np.mat(arr_Y).T #转为矩阵,并转置
mat_W = np.mat(np.zeros((mat_X.shape[1], 1))) #先设置一个全为1的W矩阵(之后通过反向传播会对它进行修正

cost_rec = [] #定义损失函数数组

for n in range(iter_num): #训练的循环
h = sigmoid(mat_X * mat_W) #激活函数放在乘积之后
cost = get_cost(h, mat_Y) #通过损失函数获得损失值
if(n%log_iter == 0): #每log_iter打印一次
print("iter num {:d}: cost={:f}".format(n, float(cost))) #打印,格式化输出
gradient = (mat_X.T * (h-mat_Y)) / mat_X.shape[0] #求梯度,具体为损失函数的求导
mat_W -= alpha*gradient #通过梯度与学习率对W权重进行更新

cost_rec.append(cost) #将损失值放进数组

return mat_W, cost_rec

def plot_cost(cost_rec): #通过matplotlib进行画图
arr_cost = np.asarray(cost_rec) #以损失函数的数据为纵坐标
iter_num = np.arange(len(cost_rec))#以损失函数的顺序为横坐标

plt.plot(iter_num, arr_cost, 'r', label='Cost') #打印,标题设为Cost
plt.legend() #显示图例信息
plt.show() #显示图

def predict(val_X, W): #预测函数,在主函数当中传入预测的判断数据以及权重
bias = np.ones((val_X.shape[0], 1), dtype=float) #设置bias(全为1)
new_val_X = np.append(val_X, bias, axis=1) #将bias补在判断数据之后,作为预测的传入数据
y = np.squeeze(np.asarray(np.rint(sigmoid(new_val_X*W)))) #预测过程
'''
预测过程的具体解析(从内到外)为:
new_val_X*W将w权重以传入数据相乘,得到相应的值
通过sigmoid对上述数据进行分类,使其偏向0或1。
利用np.rint进行四舍五入的取整(取为0或者1)
np.asarray将输入转成 ndarray数组
np.squeeze删除上述数组当中维度值为1的值
'''
print(y)
return y

if __name__ == "__main__":
#在main函数这边先解释一下线性分类的这一过程:
# 导入数据→==→数据清洗→==→数据分类(分为训练数据与测试数据)→==→线性回归→==→激活函数→==→损失函数→==→反向传播
# 其中与老师所给的样例不同的是:老师没给激活函数。
feats_list = load_data("diagnosis.data") #导入数据
feats_list = convert_data_value(feats_list) #数据清洗

feats_list = np.asarray(feats_list,dtype=np.float) #转化为矩阵,之后以矩阵计算
np.random.shuffle(feats_list) # 矩阵按行随机排列,体现随机性,减少偶然性,适合训练更有说服力

trainval_radio = 0.8 #按8:2的比例设置训练样本和测试样本
train_num = int(trainval_radio * feats_list.shape[0]) #计算前80%的行数,其中int主要是因为行数没有小数= =(照理说这应该是固定的数)
#这里就体现了上面随机的作用了= =
train_data = feats_list[:train_num] #取前80%的数据作为测试样本
val_data = feats_list[train_num:] #取后20%的数据作为测试样本


##############################################
train_X = train_data[:, :-2] ##训练
train_Y = train_data[:, -2] ##集
'''
有这么一个数据:35.5 0 1 0 0 0 0 0
其中有以下解释:
'35.5' 病人体温
'0' 是否恶心
'1' 腰椎疼痛
'0' 是否需要持续排尿
'0' 排尿疼痛
'0' 尿道灼伤、瘙痒、尿道出口肿胀

'1' 决定性:膀胱发炎
'0' 决定性:肾盂源性肾炎 ===这玩意为什么卵用

前面六个为主要的判断依据,故在train_x里面取这六个值,其中[:, :-2]意思为从头到倒数第二个(不取到)
膀胱炎为判断结果,所以在train_Y取这个值为真实结果集,其中[:, -2]的意思是取倒数第二个的值
'''
val_X = val_data[:, :-2] ##测试
val_Y = val_data[:, -2] ##集
###############################################
W, cost_rec = sigmoid_logistic_regression(train_X, train_Y, iter_num=50000 , log_iter=5000)
#以上为线性分类函数,该代码的最最最最重要的部分,传入的数分别为(训练集中的判断数据,训练集中的真实数据,训练次数,多少次数打印一次)
#返回的W为训练调整之后的权重(六个数据相应的权重),cost_rec为每次的损失函数的值的集合
print("Get final weights: {}".format(W)) #打印最后的权重,format为python的格式化输出(java也有)
plot_cost(cost_rec) #安装损失函数的值进行画图

y = predict(val_X , W) #预测函数,传入测试集的判断依据 与 训练得到的权重

error = np.sum(np.absolute(y-val_Y)) #计算预测的误差的绝对值之和,作为此模型的正确率依据
print("Number of mismatch result in validation data: {:d}".format(int(error))) #打印

test_value = np.asarray([[37.2, 0, 0, 1, 1, 0]]) #单个预测,并不知道正确的答案
print("The ground truth 1(Yes)'s result is {:f}".format(float(predict(test_value, W)))) #打印预测值

损失变化函数的曲线图:

学习率为0.0005,损失函数为均方差损失函数
学习率为0.0005,损失函数为均方差损失函数

结语

1、从数学的角度谈谈你对梯度的理解?

在微积分里面,对多元函数的参数求∂偏导数,把求得的各个参数的偏导数以向量的形式写出来,就是梯度。比如函数f(x,y), 分别对x,y求偏导数,求得的梯度向量就是(∂f/∂x, ∂f/∂y)T,简称grad f(x,y)或者▽f(x,y)。对于在点(x0,y0)的具体梯度向量就是(∂f/∂x0, ∂f/∂y0)T.或者▽f(x0,y0),如果是3个参数的向量梯度,就是(∂f/∂x, ∂f/∂y,∂f/∂z)T,以此类推。
那么这个梯度向量求出来有什么意义呢?他的意义从几何意义上讲,就是函数变化增加最快的地方。具体来说,对于函数f(x,y),在点(x0,y0),沿着梯度向量的方向就是(∂f/∂x0, ∂f/∂y0)T的方向是f(x,y)增加最快的地方。或者说,沿着梯度向量的方向,更加容易找到函数的最大值。反过来说,沿着梯度向量相反的方向,也就是 -(∂f/∂x0, ∂f/∂y0)T的方向,梯度减少最快,也就是更加容易找到函数的最小值。
具体看这:https://www.cnblogs.com/pinard/p/5970503.html

2、实验中遇到的问题:

为什么在求梯度的时候不加上sigmoid函数的导数?

0%