用线性单分逻辑回归解决二分类问题
这里拿医院的数据做一个简单的线性分类任务,任务特征是病人的年龄和肿瘤大小,任务目标是病人的肿瘤是良性的还是恶性的。
1、生成样本集
def generate(sample_size, mean, cov, diff, regression):
num_classes = 2 # len(diff)
samples_per_class = int(sample_size / 2)
X0 = np.random.multivariate_normal(mean, cov, samples_per_class)
Y0 = np.zeros(samples_per_class)
for ci, d in enumerate(diff):
# ci=0 d=3
X1 = np.random.multivariate_normal(mean + d, cov, samples_per_class)
Y1 = (ci + 1) * np.ones(samples_per_class)
X0 = np.concatenate((X0, X1))
Y0 = np.concatenate((Y0, Y1))
if regression == False: # one-hot 0 into the vector "1 0
class_ind = [Y == class_number for class_number in range(num_classes)]
Y = np.asarray(np.hstack(class_ind), dtype=np.float32)
X, Y = shuffle(X0, Y0)
return X, Y
#定义种子值,这样可以保证每次生成的随机值都一样
np.random.seed(10)
#生成类的个数
num_classes = 2
mean = np.random.randn(num_classes)
cov = np.eye(num_classes)
X, Y = generate(1000, mean, cov, [3.0],True)
colors = ['r' if l == 0 else 'b' for l in Y[:]]
plt.scatter(X[:,0], X[:,1], c=colors)
plt.xlabel("Scaled age (in yrs)")
plt.ylabel("Tumor size (in cm)")
plt.show()
函数
numpy.random.randn()
返回一个或一组样本,具有标准正态分布,传入的参数是一个元组,代表产生的array的维度。传入的最后一个参数regression=True表明使用非one-hot编码标签。
传入的参数[3.0]表示两类数据的x和y差距3.0。
以上产生样本集的函数返回的y是0或1两类
补充知识:
numpy.random.randint(low,high=None,size=None,dtype='l')
返回随机整数,范围区间为[low,high),size为数组维度大小matplotlib.pyplot.scatter(x,y,s=20,c='b')
各个参数的作用
参数 | 作用 |
---|---|
x , y | 输入数据 |
c | 色彩或颜色序列,可以是一个RGB或RGBA二维行数组,比如:['r','b'] |
2、构建网络结构
lab_dim = 1
input_dim = 2
#定义输入、输出两个占位符
input_features = tf.placeholder(tf.float32,[None,input_dim])
input_lables = tf.placeholder(tf.float32,[None,lab_dim])
#定义学习参数
W = tf.Variable(tf.random_normal([input_dim,lab_dim]),name="weight")
b = tf.Variable(tf.zeros([lab_dim]), name="bias")
output = tf.nn.sigmoid(tf.matmul(input_features,W) + b)
cross_entropy = -(input_lables * tf.log(output) + (1 - input_lables) * tf.log(1 - output))
ser = tf.square(input_lables- output)
loss = tf.reduce_mean(cross_entropy)
err = tf.reduce_mean(ser)
optimizer = tf.train.AdamOptimizer(0.04) #尽量用这个--收敛快,会动态调节梯度
train = optimizer.minimize(loss) # let the optimizer train
补充知识:
- 损失函数一般有两种,一是均值平方差(MSE),二是交叉熵(cross entropy)
MSE的公式为:
cross entropy一般用于分类问题,表达的意思是样本属于某一类的概率,公式为:
这里用于计算的a也是经过分布统一化处理的(或者是经过Sigmoid函数激活的结果),取值范围在0~1之间。
在tensorflow中常见的交叉熵函数有:Sgimoid交叉熵,softmax交叉熵,Sparse交叉熵,加权Sgimoid交叉熵
MSE的预测值和真实值要控制在同样的数据分布内,假设预测值经过Sigmoid激活函数得到取值范围时候01之间,那么真实值也要归一化成01之间。
在tensorflow中没有单独的MSE函数,可以自己组合:
MSE=tf.reduce_mean(tf.square(logits-outputs))
`tf.nn.sigmoid_cross_entropy_with_logits(logits,targets,name=None)`
`tf.nn.softmax_cross_entropy_with_logits(logits,targets,name=None)`
`tf.nn.sparse_cross_entropy_with_logits(logits,targets,name=None)`
`tf.nn.weighted_cross_entropy_with_logits(logits,targets,name=None)`
以上函数中的targets是未经softmax后的,对于已经softmax转换过的scaled,在计算loss时就不能使用在Tensorflow中定义的
softmax_cross_entropy_with_logits
函数,必须要自己定义,比如:
loss = tf.reduce_mean(-tf.reduce_sum(labels*tf.log(logits_scaled),1))
损失函数的选取取决于输入标签数据的类型:如果输入是实数、无界的值,多使用MSE;如果输入标签是位矢量(分类标志),使用cross entropy比较合适
3、设置参数进行训练
# 训练
maxEpochs = 50
minibatchSize = 25
# 启动session
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
#向模型输送数据
for epoch in range(maxEpochs):
sumerr = 0
for i in range(np.int32(len(Y)/minibatchSize)):
x1 = X[i*minibatchSize:(i+1)*minibatchSize,:]
y1 = np.reshape(Y[i*minibatchSize:(i+1)*minibatchSize] , [-1,1])
tf.reshape(y1, [-1, 1])
_, lossval, outputval, errval = sess.run([train, loss, output, err],
feed_dict={input_features: x1, input_lables: y1})
sumerr = sumerr + errval
print("Epoch:", '%04d' % (epoch + 1), "cost=", "{:.9f}".format(lossval), "err=", sumerr / minibatchSize)
补充知识点:
reshape()
函数接受-1时,该行(列)数可以为任意值。[-1,1]代表行数随意,列数变成1。
4、数据可视化
# 启动session
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
#向模型输送数据
for epoch in range(maxEpochs):
sumerr = 0
for i in range(np.int32(len(Y)/minibatchSize)):
x1 = X[i*minibatchSize:(i+1)*minibatchSize,:]
y1 = np.reshape(Y[i*minibatchSize:(i+1)*minibatchSize] , [-1,1])
tf.reshape(y1, [-1, 1])
_, lossval, outputval, errval = sess.run([train, loss, output, err],
feed_dict={input_features: x1, input_lables: y1})
sumerr = sumerr + errval
print("Epoch:", '%04d' % (epoch + 1), "cost=", "{:.9f}".format(lossval), "err=", sumerr / minibatchSize)
x = np.linspace(-1,8,200)
# x1w1+x2*w2+b=0
# x2=-x1* w1/w2-b/w2
y=-x*(sess.run(W)[0]/sess.run(W)[1])-sess.run(b)/sess.run(W)[1]
plt.plot(x,y,label="fitted line")
plt.show()
模型生成的z用公式可以表示成z=x1w1+x2w2+b,如果将x1和x2映射到直角坐标系中的x和y坐标,那么z就可以被分为小于0和大于0两部分。当z=0时,就代表直线本身。
用线性逻辑回归处理多分类问题
这次再在刚刚的二分类基础上再增加一类,变成三类,可以使用多条直线将数据分成多类。
1、生成样本集
def onehot(y,start,end):
ohe = OneHotEncoder()
a = np.linspace(start,end-1,end-start)
b =np.reshape(a,[-1,1]).astype(np.int32)
ohe.fit(b)
c=ohe.transform(y).toarray()
return c
# 模拟数据点
def generate(sample_size, num_classes, diff, regression=False):
np.random.seed(10)
mean = np.random.randn(2)
cov = np.eye(2)
# len(diff)
samples_per_class = int(sample_size / num_classes)
X0 = np.random.multivariate_normal(mean, cov, samples_per_class)
Y0 = np.zeros(samples_per_class)
for ci, d in enumerate(diff):
X1 = np.random.multivariate_normal(mean + d, cov, samples_per_class)
Y1 = (ci + 1) * np.ones(samples_per_class)
X0 = np.concatenate((X0, X1))
Y0 = np.concatenate((Y0, Y1))
# print(X0, Y0)
if regression == False: # one-hot 0 into the vector "1 0
Y0 = np.reshape(Y0, [-1, 1])
# print(Y0.astype(np.int32))
Y0 = onehot(Y0.astype(np.int32), 0, num_classes)
# print(Y0)
X, Y = shuffle(X0, Y0)
# print(X, Y)
return X, Y
np.random.seed(10)
input_dim = 2 #输入数据特征的维度
num_classes = 3 #分类的类别数
X, Y = generate(2000,num_classes, [[3.0],[3.0,0]],False)
print(X)
print(Y)
aa = [np.argmax(l) for l in Y]
colors = ['r' if l == 0 else 'b' if l == 1 else 'y' for l in aa[:]]
plt.scatter(X[:, 0], X[:, 1], c=colors)
plt.xlabel("Scaled age (in yrs)")
plt.ylabel("Tumor size (in cm)")
plt.show()
生成的X,Y的数据样本如下内容:
X
[[ 1.83322304 -0.21208312]
[ 0.76327133 1.72447501]
[ 1.36199426 -1.06355419]
...
[ 2.21595411 -0.67154356]
[ 6.68974365 4.75529356]
[ 5.10517359 -0.50115783]]
Y
[[1. 0. 0.]
[1. 0. 0.]
[1. 0. 0.]
...
[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]]
2、构建网络结构
常用的激活函数比如sigmoid,relu,tanh输出值只有两种,面对以上多分类问题,就需要使用softmax算法。该算法的主要应用就是多分类,而且是互斥的,即只能属于某一类。(对于不是互斥的分类问题,一般使用多个二分类来组成)
lab_dim = num_classes
# tf Graph Input
input_features = tf.placeholder(tf.float32, [None, input_dim])
input_lables = tf.placeholder(tf.float32, [None, lab_dim])
# Set model weights
W = tf.Variable(tf.random_normal([input_dim,lab_dim]), name="weight")
b = tf.Variable(tf.zeros([lab_dim]), name="bias")
output = tf.matmul(input_features, W) + b
z= tf.nn.softmax(output)
a1 = tf.argmax(z,axis=1)
b1 = tf.argmax(input_lables,axis=1)
err = tf.count_nonzero(a1-b1) #两个数组相减,不为0的就是错误个数
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=input_lables,logits=output)
loss = tf.reduce_mean(cross_entropy) #对交叉熵取均值很有必要
optimizer = tf.train.AdamOptimizer(0.04) #尽量用这个--收敛快,会动态调节梯度
train = optimizer.minimize(loss) # let the optimizer train
补充知识:
在实际使用softmax算法的过程中,分类标签都是one_hot编码。
参数axis=1,表示沿着每一行或者列标签执行响应的方法;axis=0,表示沿着每一列或者行标签执行响应的方法。
函数
tf.nn.softmax_cross_entropy_with_logits()
只代表了所以还需要对交叉熵再次求均值。
3、设置参数进行训练
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
for epoch in range(maxEpochs):
sumerr = 0
for i in range(np.int32(len(Y) / minibatchSize)):
x1 = X[i * minibatchSize:(i + 1) * minibatchSize, :]
y1 = Y[i * minibatchSize:(i + 1) * minibatchSize, :]
_, lossval, outputval, errval = sess.run([train, loss, output, err],
feed_dict={input_features: x1, input_lables: y1})
sumerr = sumerr + (errval / minibatchSize)
print("Epoch:", '%04d' % (epoch + 1), "cost=", "{:.9f}".format(lossval), "err=", sumerr / minibatchSize)
4、数据可视化
train_X, train_Y = generate(200, num_classes, [[3.0], [3.0, 0]], False)
aa = [np.argmax(l) for l in train_Y]
colors = ['r' if l == 0 else 'b' if l == 1 else 'y' for l in aa[:]]
plt.scatter(train_X[:, 0], train_X[:, 1], c=colors)
x = np.linspace(-1, 8, 200)
y = -x * (sess.run(W)[0][0] / sess.run(W)[1][0]) - sess.run(b)[0] / sess.run(W)[1][0]
plt.plot(x, y, label='first line', lw=3)
y = -x * (sess.run(W)[0][1] / sess.run(W)[1][1]) - sess.run(b)[1] / sess.run(W)[1][1]
plt.plot(x, y, label='second line', lw=2)
y = -x * (sess.run(W)[0][2] / sess.run(W)[1][2]) - sess.run(b)[2] / sess.run(W)[1][2]
plt.plot(x, y, label='third line', lw=1)
plt.legend()
plt.show()
print(sess.run(W), sess.run(b))
train_X和train_Y分别是取的200个测试点。
-
根据softmax算法的定义,输出结果z应该是一组数组,某个类别的值(概率)越大,那么此样本属于某一类。
因为输出端是三个值,所以相当于是3条直线。根据直线公式:
根据直线公式:
得出:
也可以画出更直观的图示:
nb_of_xs = 200
xs1 = np.linspace(-1, 8, num=nb_of_xs)
xs2 = np.linspace(-1, 8, num=nb_of_xs)
xx, yy = np.meshgrid(xs1, xs2) # create the grid
# Initialize and fill the classification plane
classification_plane = np.zeros((nb_of_xs, nb_of_xs))
for i in range(nb_of_xs):
for j in range(nb_of_xs):
# classification_plane[i,j] = nn_predict(xx[i,j], yy[i,j])
classification_plane[i, j] = sess.run(a1, feed_dict={input_features: [[xx[i, j], yy[i, j]]]})
# Create a color map to show the classification colors of each grid point
cmap = ListedColormap([
colorConverter.to_rgba('r', alpha=0.30),
colorConverter.to_rgba('b', alpha=0.30),
colorConverter.to_rgba('y', alpha=0.30)])
# Plot the classification plane with decision boundary and input samples
plt.contourf(xx, yy, classification_plane, cmap=cmap)
plt.show()
- 函数
plt.contourf()
是绘制等高线的函数。contour和contourf都是画三维等高线图的,不同点在于contour() 是绘制轮廓线,contourf()会填充轮廓。除非另有说明,否则两个版本的函数是相同的。
coutourf([X, Y,] Z,[levels], **kwargs)
例如:
# 计算x,y坐标对应的高度值
def f(x, y):
return (1-x/2+x**3+y**5) * np.exp(-x**2-y**2)
# 生成x,y的数据
n = 256
x = np.linspace(-3, 3, n)
y = np.linspace(-3, 3, n)
# 把x,y数据生成mesh网格状的数据,因为等高线的显示是在网格的基础上添加上高度值
X, Y = np.meshgrid(x, y)
# 填充等高线
plt.contourf(X, Y, f(X, Y))
# 显示图表
plt.show()
使用隐藏层解决非线性问题
对于线性不可分的数据样本,可以使用多层神经网络来解决,也就是在输入层和输出层中间多加一些神经元,每一层可以加多个,也可以加多层。
import tensorflow as tf
import numpy as np
learning_rate = 1e-4
n_input = 2 #两个数
n_label = 1 #代表最终结果
n_hidden = 2 #隐藏层里有两个节点
x = tf.placeholder(tf.float32,[None,n_input])
y = tf.placeholder(tf.float32,[None,n_label])
weights = {
'h1':tf.Variable(tf.truncated_normal([n_input,n_hidden],stddev=0.1)),
'h2':tf.Variable(tf.truncated_normal([n_hidden,n_label],stddev=0.1))
}
biases = {
'h1':tf.Variable(tf.zeros([n_hidden])),
'h2':tf.Variable(tf.zeros([n_label]))
}
# 定义网络模型,第一层用relu激活函数,第二层用tanh函数
layer_1 = tf.nn.relu(tf.add(tf.matmul(x, weights['h1']), biases['h1']))
# y_pred = tf.nn.tanh(tf.add(tf.matmul(weights['h2'],layer_1),biases['h2']))
y_pred = tf.nn.tanh(tf.add(tf.matmul(layer_1, weights['h2']),biases['h2']))
loss = tf.reduce_mean((y_pred - y) ** 2) # MSE损失函数
optimizer = tf.train.AdamOptimizer(learning_rate)
trian_step = optimizer.minimize(loss)
#构建模拟数据,数据为异或关系
X=[[0,0],[0,1],[1,0],[1,1]]
Y=[[0],[1],[1],[0]]
X = np.array(X).astype('float32')
Y = np.array(Y).astype('int16')
#加载session
sess = tf.InteractiveSession()
sess.run(tf.global_variables_initializer())
#训练
for i in range(10000):
sess.run(trian_step,feed_dict={x:X,y:Y})
print(sess.run(y_pred,feed_dict={x:X}))
print(sess.run(layer_1,feed_dict={x:X}))
y_pred = tf.nn.tanh(tf.add(tf.matmul(layer_1, weights['h2']),biases['h2']))
这句话不能像注释里写的那样,矩阵乘法不满足交换律。tf.InteractiveSession()
和tf.Session()
的区别在于,前者可以在构建session后再定义操作,后者必须在会话构建之前定义好全部的操作(operation)。
欠拟合和过拟合
在模型训练过程中会出现欠拟合和过拟合的问题,欠拟合的原因并不是模型不行,而是我们的学习方法无法更精准地学习到适合的模型参数。模型越薄弱,对训练的要求就越高,但是可以采用增加节点或者增加隐藏层的方式,让模型具有更高的拟合性,从而降低模型的训练难度。过拟合的表现在模型在训练集上的表现非常好,loss很小;但是在测试集上的表现却非常差。
避免过拟合的方法很多:常用的有early stopping、数据集扩增、正则化、dropout
early stopping:在发生过拟合之前提前结束,缺点是这个点不好把握
data augmentation:让模型接受更多的样本数据,在实际使用中对于未来事件的预测显得鞭长莫及。
regulatization:引入范数的概念,增强模型的泛化能力,包括L1和L2(weight dacay)
dropout:每次训练时舍去一些节点来增强泛化能力。
1、正则化
本质就是加入噪声,在计算loss时,在损失后面再加上意向,这样预测结果与标签间的误差就会受到干扰,导致学习参数W和b无法按照目标方向来调整,从而实现模型与训练数据无法完全拟合的效果,从而防止过拟合。
这个添加的干扰项必须具有如下特性:
如果出现欠拟合,希望这个干扰项对模型误差的影响越小越好,以便让模型快速拟合实际。
如果是过拟合,希望它对模型的影响越大越好。
这里有两个范数L1和L2:
- L1 : 所有学习参数w的绝对值的和 L1范数是指向量中各个元素绝对值之和
tf.reduce_sum(tf.abs(w))
- L2:所有学习参数w的平方和的平方根 L2范数是指向量各元素的平方和然后求平方根
tf.nn.l2_loss(t,name=None)
2、dropout----训练过程中,将部分神经单元暂时丢弃
拿上面的异或数据做举例,dropout方法就是在刚刚的layer_1层后面再添加一个dropout层。
# 定义网络模型,第一层用relu激活函数,第二层用tanh函数
layer_1 = tf.nn.relu(tf.add(tf.matmul(x, weights['h1']), biases['h1']))
keep_prob = tf.placeholder("float")
layer_1_drop = tf.nn.dropout(layer_1,keep_prob)
layer_2 = tf.add(tf.matmul(layer_1_drop,weights['h2']),biases['h2'])
y_pred = tf.nn.relu(tf.add(tf.matmul(layer_1, weights['h2']),biases['h2']))
实际训练时,将keep_prob设置成0.6,意味着每次训练将仅允许0.6的节点参与学习运算。由于学习速度这样就变慢了,可以将learning_rate调大,加快训练速度。注意:在测试时,需要将keep_prob设置为1。
全连接神经网络是一个通用的拟合数据的框架,只要有足够多的神经元,及时只有一层hidden layer,利用常见的Sigmoid,relu等激活函数,就可以无限逼近任何连续函数。在实际使用中,如果想利用浅层神经网络拟合复杂非线性函数,就需要你靠增加的神经元个数来实现,神经元过多会造成参数过多,从而增加网络的学习难度,并影响网络的泛化能力。因此,在实际构建网络结构时,一般倾向于使用更深的模型,开减少所需要的神经元数量。