实现多元逻辑回归

实现多元逻辑回归,第1张

鸢尾花数据集中一共有150个样本,分为3类,每个样本中有四个属性。

三种鸢尾花类别,每种类别有50个样本。每个样本中包括四种鸢尾花的属性特征和鸢尾花的品种。这四种属性特征分别为花萼的长度和宽度以及花瓣的长度和宽度。

标签就是类别。

下面是三种属性两两组合之后的可视化结果。

可以看到蓝色的点是山鸢尾,红色的点是变色鸢尾,绿色的点是维吉尼亚鸢尾。可以看到,蓝色的点和其他两种颜色的点差距比较大,选择任何两种属性的组合都能够很好的将它们区分开。

现在,我们就选取山鸢尾(蓝色的点)和变色鸢尾(红色的点)这两种类型的样本,使用花萼长度和花萼宽度这两种属性通过逻辑回归实现对它们的分类。

1、使用训练集来训练模型

(此处的代码是下面的代码的一部分,不再重复写了,只选择与下面不同的来写,建议从第二部分开始看。)

1.1、输出训练集在训练模型上的损失率和准确率

输出准确率和结果,这是运行结果:

i: 0, Train Loss: 0.994269, Accuracy: 0.230769
i: 30, Train Loss: 0.481892, Accuracy: 0.961538
i: 60, Train Loss: 0.319128, Accuracy: 0.987179
i: 90, Train Loss: 0.246626, Accuracy: 0.987179
i: 120, Train Loss: 0.204982, Accuracy: 1.000000

可以看到一开始的准确率只有23%,随着迭代次数的增加,准确率不断提高,最后达到了百分之百。同时损失也在不断地下降。

1.2、绘制损失和准确率的变化曲线
plt.figure(figsize=(5, 3))
plt.plot(cross_train, color="blue", label="Loss")
plt.plot(acc_train, color="red", label="Acc")
plt.legend()
plt.title("损失和准确率变化曲线", fontsize=22)


损失一直单调递减,所以损失变化曲线很光滑,而准确率上升到一定的数值之后,有时候会停留在某个数值一段时间,因此会呈现出台阶上升的态势。

1.3、绘制出线性分类器的决策边界
plt.figure()
plt.scatter(x_train[:, 0], x_train[:, 1], c=y_train, cmap=cm_pt)
x_ = [-1.5, 1.5]
y_ = -(W[1]*x_+W[0])/W[2]
plt.plot(x_, y_, color="g")
plt.title("决策边界", fontsize=22)


这条直线的表达式如下:
w1x1 + w2x2 + w0 =0
其中的 wn 就是我们训练得到的模型参数。
可以把它变换成下面这种形式:

(表达式中的 w1 就是程序中的 x_ ,而 w2 就是程序中的 y_)

我们也可以把这段代码添加到模型训练的过程中,从而在迭代的过程中分类边界的变化情况。

可以看到,

# 第四步:设置模型参数初始值
np.random.seed(612)
# 这里的W是一个列向量
W = tf.Variable(np.random.randn(3, 1), dtype=tf.float32)
x_ = [-1.5, 1.5]
y_ = -(W[1]*x_+W[0])/W[2]

plt.figure()
plt.scatter(x_train[:, 0], x_train[:, 1], c=y_train, cmap=cm_pt)
plt.plot(x_, y_, color="red", linewidth=3)
plt.xlim([-1.5, 1.5])
plt.ylim([-1.5, 1.5])
plt.title("决策边界变化图", fontsize=22)

# 第五步:训练模型
......
......
for i in range(0, itar+1):
	......
	......
		......
		......
	    if i % display_step == 0:
        	print("i: %i, Train Loss: %f, Accuracy: %f" % (i, Loss_train, Accuarcy_train))
        	y_ = -(W[1] * x_ + W[0]) / W[2]
        	plt.plot(x_, y_)
plt.show()


在上面的例子中,只使用了训练集数据,没有使用测试集。下面我们在训练模型的同时,使用测试集来评价模型的性能。

2、使用测试集来评价模型的性能 第一步:加载训练数据集和测试数据集
import pandas as pd
import numpy as np
import tensorflow as tf
import matplotlib as mpl
import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif'] = "SimHei"
plt.rcParams['axes.unicode_minus'] = False

# 第一步:加载数据集
TRAIN_URL = "http://download.tensorflow.org/data/iris_training.csv"
train_path = tf.keras.utils.get_file(TRAIN_URL.split('/')[-1], TRAIN_URL)
df_iris_train = pd.read_csv(train_path, header=0)  # 表示第一行数据作为列标题

TEST_URL = "http://download.tensorflow.org/data/iris_test.csv"
test_path = tf.keras.utils.get_file(TEST_URL.split('/')[-1], TEST_URL)
df_iris_test = pd.read_csv(test_path, header=0)  
第二步:数据处理 2.1 转化为NumPy数组
iris_train = np.array(df_iris_train)  # 将二维数据表转换为 Numpy 数组, (120, 5), iris的训练集中有120条样本,
iris_test = np.array(df_iris_test)  # 将二维数据表转换为 Numpy 数组, (30, 5), iris的测试集中有30条样本,
2.2 提取属性和标签
train_x = iris_train[:, 0:2]  # 取出鸢尾花训练数据集的前两列属性值
train_y = iris_train[:, 4]  # 取出最后一列作为标签值, (120,)

test_x = iris_test[:, 0:2]  # 取出鸢尾花训练数据集的前两列属性值
test_y = iris_test[:, 4]  # 取出最后一列作为标签值, (30, )
2.3 提取指定标签的样本
# 2.3 提取山鸢尾和变色鸢尾 (标签值——品种, 0 —— 山鸢尾、1 —— 变色鸢尾、2 —— 维吉尼亚鸢尾)
x_train = train_x[train_y < 2]  # 提取出标签值为 01 的样本
y_train = train_y[train_y < 2]
print(x_train.shape, y_train.shape)  # (78, 2) (78,)

x_test = test_x[test_y < 2]  # 提取出标签值为 01 的样本
y_test = test_y[test_y < 2]
print(x_test.shape, y_test.shape)  # (22, 2) (22,)
2.4 记录训练集和测试集中的样本数
num_train = len(x_train)  # 78
num_test = len(x_test)  # 22
2.5 数据可视化
# 2.5 可视化样本
plt.figure(figsize=(12, 5))
cm_pt = mpl.colors.ListedColormap(["blue", "red"])

plt.subplot(121)
# 取出花萼长度和花萼宽度作为样本点的横坐标和纵坐标, 根据样本点的标签值确定样本的颜色, 设置色彩方案为cm_pt
plt.scatter(x_train[:, 0], x_train[:, 1], c=y_train, cmap=cm_pt)
# 之前的例程中, 我们使用的都是 matplotlib 中预设的色彩方案,这里我们使用自己定义的色彩方案,
# 在散点图中, 蓝色的是山鸢尾, 红色的是变色鸢尾.
# 设置标题
plt.title("山鸢尾和变色鸢尾的训练集样本", fontsize=22)
plt.xlabel('花萼长度', color='r', fontsize=16)
plt.ylabel('花萼宽度', color='r', fontsize=16)

plt.subplot(122)
# 取出花萼长度和花萼宽度作为样本点的横坐标和纵坐标, 根据样本点的标签值确定样本的颜色, 设置色彩方案为cm_pt
plt.scatter(x_test[:, 0], x_test[:, 1], c=y_test, cmap=cm_pt)
# 设置标题
plt.title("山鸢尾和变色鸢尾的测试集样本", fontsize=22)
plt.xlabel('花萼长度', color='r', fontsize=16)
plt.ylabel('花萼宽度', color='r', fontsize=16)

运行代码如下:

2.6 数据归一化

需要注意:在机器学习中,要求训练集和测试集是独立同分布的,也就是说,他们具有相同的方差和均值,在样本数量有限的情况下,可能无法做到完全相等,只需要尽量接近就可以了。

# 2.6 数据归一化
print(np.mean(x_train, axis=0))  # axis=0, 计算数组中每一列的均值
# [5.42692308 3.1025641 ]
print(np.mean(x_test, axis=0))
# [5.62727273 3.06363636]

# 可以看出这两个属性的尺寸相同,因此不需要进行归一化,可以直接对其进行中心化处理
# 对每个属性进行中心化, 也就是按列中心化, 所以使用下面这种方式
x_train = x_train-np.mean(x_train, axis=0)
x_test = x_test-np.mean(x_test, axis=0)
# 此时样本点的横坐标和纵坐标的均值都是0

从输出结果可以看出,这两个数据集的均值虽然不同,但是已经非常接近了,所以该测试集可以用于测试。

2.7 绘制中心化之后的散点图
plt.figure(figsize=(12, 5))
plt.subplot(121)
plt.scatter(x_train[:, 0], x_train[:, 1], c=y_train, cmap=cm_pt)
plt.title("归一化后的山鸢尾和变色鸢尾的训练集样本", fontsize=22)
plt.xlabel('花萼长度', color='r', fontsize=16)
plt.ylabel('花萼宽度', color='r', fontsize=16)

plt.subplot(122)
plt.scatter(x_test[:, 0], x_test[:, 1], c=y_test, cmap=cm_pt)
plt.title("归一化后的山鸢尾和变色鸢尾的测试集样本", fontsize=22)
plt.xlabel('花萼长度', color='r', fontsize=16)
plt.ylabel('花萼宽度', color='r', fontsize=16)

运行代码如下:

2.8 生成多元模型的属性矩阵和标签列向量
# 2.8 生成多元模型的属性矩阵和标签列向量
x0_train = np.ones(num_train).reshape(-1, 1)  # (78, 1)
# 改变张量中元素的数据类型函数 tf.cast()
# 拼接就是将多个张量在某个维度上合并,在TensorFlow中使
# 用tf.concat()函数来拼接张量, 拼接并不会产生新的维度。
X_train = tf.cast(tf.concat((x0_train, x_train), axis=1), tf.float32)
Y_train = tf.cast(y_train.reshape(-1, 1), tf.float32)
print(X_train.shape)  # (78, 3)
print(Y_train.shape)  # (78, 1)

x0_test = np.ones(num_test).reshape(-1, 1)  # (22, 1)
# 改变张量中元素的数据类型函数 tf.cast()
# 拼接就是将多个张量在某个维度上合并,在TensorFlow中使
# 用tf.concat()函数来拼接张量, 拼接并不会产生新的维度。
X_test = tf.cast(tf.concat((x0_test, x_test), axis=1), tf.float32)
Y_test = tf.cast(y_test.reshape(-1, 1), tf.float32)
print(X_test.shape)  # (22, 3)
print(Y_test.shape)  # (22, 1)
第三步:设置超参数和显示间隔
# 第三步:设置超参数和显示间隔
learn_rate = 0.2
itar = 120

display_step = 30
第四步:设置模型参数初始值
# 第四步:设置模型参数初始值
np.random.seed(612)
# 这里的W是一个列向量
W = tf.Variable(np.random.randn(3, 1), dtype=tf.float32)
第五步:训练模型
# 第五步:训练模型
cross_train = []  # 列表cross_train用来保存每一次迭代的交叉熵损失
acc_train = []  # 用来存放训练集的分类准确率

cross_test = []  # 列表cross_test用来保存每一次迭代的交叉熵损失
acc_test = []  # 用来存放测试集的分类准确率

for i in range(0, itar + 1):

    with tf.GradientTape() as tape:

        # Sigmoid 函数
        # 属性矩阵X和参数向量W相乘的结果是一个列向量
        # X - (78, 3), W - (3, 1) , 所以 Pred_train - (78, 1), 是每个样本的预测概率
        Pred_train = 1 / (1 + tf.exp(-tf.matmul(X_train, W)))
        # 计算平均交叉熵损失函数
        Loss_train = -tf.reduce_mean(Y_train * tf.math.log(Pred_train) + (1 - Y_train) * tf.math.log(1 - Pred_train))

        Pred_test = 1 / (1 + tf.exp(-tf.matmul(X_test, W)))
        # 计算平均交叉熵损失函数
        Loss_test = -tf.reduce_mean(Y_test * tf.math.log(Pred_test) + (1 - Y_test) * tf.math.log(1 - Pred_test))

    # 计算准确率函数 -- 因为不需要对其进行求导运算, 因此也可以把这条语句写在 with 语句的外面
    Accuarcy_train = tf.reduce_mean(tf.cast(tf.equal(tf.where(Pred_train.numpy() < 0.5, 0., 1.), Y_train), tf.float32))
    Accuarcy_test = tf.reduce_mean(tf.cast(tf.equal(tf.where(Pred_test.numpy() < 0.5, 0., 1.), Y_test), tf.float32))

    # 记录每一次迭代的交叉熵损失和准确率
    cross_train.append(Loss_train)
    cross_test.append(Loss_test)
    acc_train.append(Accuarcy_train)
    acc_test.append(Accuarcy_test)

    # 对交叉熵损失函数W求偏导
    dL_dW = tape.gradient(Loss_train, W)
    # 更新模型参数
    W.assign_sub(learn_rate * dL_dW)

    if i % display_step == 0:
        print("i: %i, TrainLoss: %f, TrainAccuracy: %f, TestLoss: %f, TestAccuracy: %f"
              % (i, Loss_train, Accuarcy_train, Loss_test, Accuarcy_test))

运行结果如下:

从上面结果可以看出,虽然训练集的准确率达到了百分之百,但是测试集的准确率只有86%,训练集和测试集的损失仍然在持续下降。可以尝试继续训练这个模型,看下测试集的准确率能否达到百分之百。

这里将迭代次数提升到510次,然后运行代码。

可以看出在迭代330次的时候,达到了100%

第六步:分别绘制训练集和测试集的损失曲线
plt.figure(figsize=(10, 4))

plt.subplot(121)
plt.plot(cross_train, color="blue", label="TrainLoss")
plt.plot(cross_test, color="red", label="TestLoss")
plt.ylabel("Loss")
plt.title("训练集和测试集的损失率变化曲线", fontsize=20)
plt.legend()

plt.subplot(122)
plt.plot(acc_train, color="blue", label="TrainAccuracy")
plt.plot(acc_test, color="red", label="TestAccuracy")
plt.ylabel("Accuracy")
plt.title("训练集和测试集的准确率变化曲线", fontsize=20)
plt.legend()

总结

前面一开始提到通过三种属性两两组合之后的可视化结果得到下图。
第一行是 花萼长度、和花萼长度、 花萼宽度、花瓣长度和花瓣宽度的组合
第二行是 花萼宽度、和花萼长度、 花萼宽度、花瓣长度和花瓣宽度的组合
第三行是 花瓣长度、和花萼长度、 花萼宽度、花瓣长度和花瓣宽度的组合
第四行是 花瓣宽度、和花萼长度、 花萼宽度、花瓣长度和花瓣宽度的组合

通过这个图可以看出来,山鸢尾是比较容易和其他两种鸢尾花区分开的(蓝色的点是山鸢尾,红色的点是变色鸢尾,绿色的点是维吉尼亚鸢尾。),我们其实选择的是一种难度最大的组合(花萼长度和花萼宽度,图中为第一行第二个图),选择其他几种属性的组合,看起来更容易把山鸢尾区分开来。另外,观察第三行第一张图(花萼长度和花瓣长度),可以发现山鸢尾的花瓣长度和其他两种鸢尾花不在一个区间,也就是说其实只需要花瓣长度这一个属性就能够把山鸢尾区分开来了,这采用一元逻辑回归就可以完成。

如果我们要把变色鸢尾和维吉尼亚鸢尾区分开来,应该选择哪两个属性呢?
也就是说要分开这些红色的点和绿色的点,显然混在一起特别多的不能选,比如可以选择四行三列那个图(花瓣宽度和花瓣长度属性的组合),也可以找到一个准确率比较高的线性分类器。

那么是否可以找到一个更好的分类方法呢?

鸢尾花数据集中有四个属性,这个图中只给出了两种属性组合的情况,如果采用三种或者四种属性去训练模型应该可以把变色鸢尾和维吉尼亚鸢尾完全区分开来。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/893713.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-14
下一篇 2022-05-14

发表评论

登录后才能评论

评论列表(0条)

保存