一,数据预处理
(1)将所有空值填充成0。
(2)由于倒数第一列和第二列的值相较于其他数值过大,所有除去他们自身的平均数使其拉回到相同的scale。
(3)将第2列到第58列作为feature_x,将第59列作为label_y。
将前3500作为训练集x_train(3500×57)、y_train(3500×1);后500作为验证集x_val(500×57)、y_val(500×1)。
(4)代码
df = pd.read_csv('spam_train.csv')
df = df.fillna(0)#将空值填充成0
array = np.array(df)
x = array[:,1:-1]
x[:,-1] /= np.mean(x[:,-1])
x[:,-2] /= np.mean(x[:,-2])
y = array[:,-1]
x_train,x_val = x[0:3500,:],x[3500:4000,:]
y_train,y_train = y[0:3500,:],y[3500:4000,:]
二,模型建立
2.1 线性回归
根据课上所讲,先对数据进行线性回归:
Z
n
=
∑
i
=
1
57
w
i
x
i
n
+
b
Z^n=\sum\limits_{i=1}^{57}w_ix_i^n+b
Zn=i=1∑57wixin+b
Z = weight.dot(x_train[i,:])+bias
weight为更新之后 w i w_i wi的权重
2.2 sigmoid 函数压缩回归值
将回归结果压缩到sigmod函数中,得到概率值
σ
(
z
)
=
1
1
+
e
−
z
\sigma(z)=\frac{1}{1+e^{-z}}
σ(z)=1+e−z1
如果
σ
(
z
)
≥
0.5
\sigma(z)\geq0.5
σ(z)≥0.5则输入类别1。
simga = 1/1+np.exp(-Z)
2.3 误差反向传播
接着就到重头戏了。众所周知,不管线性回归还是Logistic回归,其关键和核心就在于通过误差的反向传播来更新参数,进而使模型不断优化。因此,损失函数的确定及对各参数的求导就成了重中之重。在分类问题中,模型一般针对各类别输出一个概率分布,因此常用交叉熵作为损失函数。交叉熵可用于衡量两个概率分布之间的相似、统一程度,两个概率分布越相似、越统一,则交叉熵越小;反之,两概率分布之间差异越大、越混乱,则交叉熵越大。
2.3.1 损失函数(Loss Function)
对于一般的k分类问题都有P为label,是一个概率分布,常用one_hot编码。例如针对3分类问题而言,若样本属于第一类,则P为(1,0,0),若属于第二类,则P为(0,1,0),若属于第三类,则为(0,0,1)。即所属的类概率值为1,其他类概率值为0。Q为模型得出的概率分布,可以是(0.1,0.8,0.1)等。
L
o
s
s
n
=
−
∑
1
k
P
n
l
n
Q
n
Loss^n=-\sum\limits_1^kP^nlnQ^n
Lossn=−1∑kPnlnQn
这次作业是一个二分类问题不需要,可以直接求出另一个概率值为1-p,并不需要单独计算。
所以Loss Function为:
L
o
s
s
n
=
−
[
z
^
n
l
n
σ
n
+
(
1
−
z
^
n
)
l
n
(
1
−
σ
n
)
]
Loss^n = -[\hat{z}^nln\sigma^n+(1-\hat{z}^n)ln(1-\sigma^n)]
Lossn=−[z^nlnσn+(1−z^n)ln(1−σn)]
这样通过简单的求偏导运算可以得出(具体步骤老师上课有讲),可以计算出
∂
L
o
s
s
n
∂
w
i
=
−
∑
i
n
(
z
^
n
−
σ
n
)
x
^
i
n
\frac{\partial{Loss^n}}{\partial{w_i}}=-\sum_i^n(\hat{z}^n-\sigma^n)\hat{x}_i^n
∂wi∂Lossn=−∑in(z^n−σn)x^in
同理
∂
L
o
s
s
n
∂
b
=
−
∑
i
n
(
z
^
n
−
σ
n
)
\frac{\partial{Loss^n}}{\partial{b}}=-\sum_i^n(\hat{z}^n-\sigma^n)
∂b∂Lossn=−∑in(z^n−σn)
2.3.2 使用Gradient desent的方法更新参数
其中
b
i
a
s
=
b
i
a
s
−
η
bias = bias - \eta
bias=bias−η
for j in range(num):
y_pre = weights.dot(x_train[j,:])+bias
sig = 1/(1+np.exp(-y_pre))
b_g += (-1) * (y_train[j] - sig)
for k in range(dim):
w_g[k] += (-1) * (y_train[j] - sig) * x_train[j, k] + 2 * reg_rate * weights[k]#正则化
b_g /= num
w_g /= num
2.3.3 参数更新
求出梯度后,再拿原参数减去梯度与学习率的乘积,即可实现参数的更新。
#adagrad
bg2_sum += b_g**2
wg2_sum += w_g**2
#更新b和w_i
bias -= learn_rate / bg2_sum**0.5*b_g
weights -= learn_rate / wg2_sum**0.5*w_g
2.4 参数验证
通过验证集观测对训练集训练出来的参数效果:
def validate(x_val,y_val,weights,bias):
num = 500
acc = 0
result = np.zeros(num)
for j in range(num):
y_pre = weights.dot(x_val[j,:])+bias
sig = 1 / (1 + np.exp(-y_pre))
if sig >= 0.5:
result[j] = 1
else:
result[j] = 0
if result[j] == y_val[j]:
acc += 1.0
return acc/num
三、代码展示
import pandas as pd
import numpy as np
def train(x_train,y_train,epoch):
num = x_train.shape[0]
dim = x_train.shape[1]
bias = 0 # 偏置值初始化
weights = np.ones(dim) # 权重初始化
learn_rate = 1 # 初始学习率
reg_rate = 0.001 # 正则项系数
bg2_sum = 0
wg2_sum = np.zeros(dim)
for i in range(epoch):
b_g = 0 #b的partial值
w_g = np.zeros(dim) #w_i的partial值
for j in range(num):
y_pre = weights.dot(x_train[j,:])+bias
sig = 1/(1+np.exp(-y_pre))
b_g += (-1) * (y_train[j] - sig)
for k in range(dim):
w_g[k] += (-1) * (y_train[j] - sig) * x_train[j, k] + 2 * reg_rate * weights[k]#正则化
b_g /= num
w_g /= num
#adagrad
bg2_sum += b_g**2
wg2_sum += w_g**2
#更新b和w_i
bias -= learn_rate / bg2_sum**0.5*b_g
weights -= learn_rate / wg2_sum**0.5*w_g
# 每训练100轮,输出一次在训练集上的正确率
# 在计算loss时,由于涉及到log()运算,因此可能出现无穷大,计算并打印出来的loss为nan
# 有兴趣的同学可以把下面涉及到loss运算的注释去掉,观察一波打印出的loss
if i % 3 == 0:
# loss = 0
acc = 0
result = np.zeros(num)
for j in range(num):
y_pre = weights.dot(x_train[j, :]) + bias
sig = 1 / (1 + np.exp(-y_pre))
if sig >= 0.5:
result[j] = 1
else:
result[j] = 0
if result[j] == y_train[j]:
acc += 1.0
# loss += (-1) * (y_train[j] * np.log(sig) + (1 - y_train[j]) * np.log(1 - sig))
# print('after {} epochs, the loss on train data is:'.format(i), loss / num)
print('经过 {} 轮次的Grdient desent, 模型在训练集上的准确率为:'.format(i), acc / num)
return weights, bias
def validate(x_val,y_val,weights,bias):
num = 500
acc = 0
result = np.zeros(num)
for j in range(num):
y_pre = weights.dot(x_val[j,:])+bias
sig = 1 / (1 + np.exp(-y_pre))
if sig >= 0.5:
result[j] = 1
else:
result[j] = 0
if result[j] == y_val[j]:
acc += 1.0
return acc/num
def main():
df = pd.read_csv('spam_train.csv')
df = df.fillna(0)#将空值填充成0
array = np.array(df)
x = array[:,1:-1]
x[:,-1] /= np.mean(x[:,-1])
x[:,-2] /= np.mean(x[:,-2])
y = array[:,-1]
x_train,x_val = x[0:3500,:],x[3500:4000,:]
y_train,y_val = y[0:3500],y[3500:4000]
epoch = 30
w,b = train(x_train,y_train,epoch)
acc = validate(x_val,y_val,w,b)
print('最终在验证集的准确性为:',acc)
if __name__ == '__main__':
main()