神经网络.

题目:

使用逻辑回归,识别手写数字 

import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
import scipy.optimize as opt # 最小化
from scipy.io import loadmat #用于加载MATLAB格式的数据
from sklearn.metrics import classification_report

处理训练集

用于从给定的路径加载数据

def load_data(path, transpose=True):
    data = loadmat(path)
    X = data['X']
    y = data['y']
    y = y.reshape(y.shape[0])
    print(type(X))
    if transpose:
        X = np.array([im.reshape((20,20)).T.reshape(400) for im in X])
    return X, y

这段代码定义了一个名为 load_data 的函数,它用于从给定的路径加载数据

  1. 函数参数:

    • path: 数据文件的路径
    • transpose: 一个布尔值,默认为 True。表示是否需要对加载的图像数据进行转置
  2. 功能:

    1. 使用 loadmat 函数(这个函数来自 scipy.io 模块,用于加载MATLAB格式的数据)从给定的路径加载数据。
    2. 从加载的数据中提取键为 'X' 'y' 的值,分别赋给 X 和 y。这意味着数据文件中应该包含这两个键,分别代表图像数据标签
    3. 将 y 数据的形状从二维或更高维度更改为一维。y = y.reshape(y.shape[0])

在 load_data 函数中,y = y.reshape(y.shape[0]) 这行代码的作用就是将 y 重新塑形,使其成为一个一维数组,其中 y.shape[0] 表示 y 的第一个维度的大小,即样本数量。这样做通常是为了确保 y 的形状与 X 的行数(样本数量)相匹配,并且便于后续处理。

               4.  打印 X 的数据类型,这可能是为了调试或确认数据的加载方式print(type(X))

如果 X 中的图像已经是 400x1 的形状,那么转置操作可能会导致错误或不必要的操作。确保你的数据格式与这段代码的预期输入相匹配。

              5.  如果 transpose 参数为 True,则对 X 中的每个图像进行转置操作。具体来说,它将每个图像从 20x20 的形状重新塑造为 400x1 的形状。这里使用了列表推导式来完成这个操作。

 X = np.array([im.reshape((20,20)).T.reshape(400) for im in X]) 

  • im 是对列表(或其他可迭代对象)X 中的每一个元素的临时引用
  • 遍历 X 中的每一个元素(即每一个图像 im),并对每个图像执行一系列的操作:
  1. im.reshape((20,20)): 将当前图像 im 重新塑形为一个 20x20 的二维数组。这假设原始图像可以被完整地重塑为 20x20 的形状,即它的总像素数(原始高度乘以原始宽度)等于 400。

  2. .T: 对 20x20 的图像执行转置操作,将其变为一个 20x20 的转置矩阵。在图像处理的上下文中,这通常意味着将图像的行和列交换

  3. .reshape(400): 将转置后的 20x20 图像再次重塑为一个一维数组,形状为 (400,)

最后,这个列表推导式生成了一个新的列表,其中包含了所有经过上述处理步骤的图像数据。然后,np.array() 函数将这个列表转换为一个 NumPy 数组,赋值给 X

raw_x, raw_y = load_data('ex3data1.mat')

raw_x.shape, raw_y.shape

# ((5000, 400), (5000,))
# raw_x是一个(包含5000个样本,每个样本有四百个特征值)的矩阵
#raw_y是一个包含5000个样本的标签向量

1.  load_data函数被调用,并传入一个字符串参数'ex3data1.mat',这个函数的作用是从MATLAB格式的文件'ex3data1.mat'中加载数据,并返回两个变量raw_x和raw_y。

2. 是在询问raw_x和raw_y这两个数组的维度。raw_x.shape会返回raw_x数组的形状(即维度),而raw_y.shape会返回raw_y数组的形状。

  • 需要知道raw_x有多少行和多少列,以便正确地将其划分为训练集和测试集,或者为了调整模型输入而重新塑形它。
  • 同样,了解raw_y的形状可以帮助你确定是否有必要进行任何标签编码或转换。

创建图像 

def plot_an_image(image):
    fig, ax = plt.subplots(figsize=(1, 1))
    ax.matshow(image.reshape((20,20)), cmap=matplotlib.cm.binary)
    plt.xticks(np.array([]))
    plt.yticks(np.array([]))
    plt.show()

这里使用了matplotlib库来创建图像,并使用了numpy库来处理数组

  1. def plot_an_image(image):

    • 这是一个函数定义,函数名为plot_an_image,它接受一个参数image
  2. fig, ax = plt.subplots(figsize=(1, 1))

    • 使用matplotlib.pyplotsubplots函数来创建一个新的图形窗口和一个坐标轴。figsize=(1, 1)指定了图形窗口的大小为1x1单位。
  3. ax.matshow(image.reshape((20,20)), cmap=matplotlib.cm.binary)

    • 使用ax(坐标轴对象)的matshow方法来显示一个矩阵。
    • image.reshape((20,20))将输入的image数组重新塑形为一个20x20的矩阵。这里假设image的总元素数(即原始形状的长x宽)为400,这样它才能被重新塑形为20x20。
    • cmap=matplotlib.cm.binary指定了颜色映射为二进制,这意味着矩阵中的值将被映射为黑色和白色,其中0通常表示黑色,1表示白色。
  4. plt.xticks(np.array([])) 和 plt.yticks(np.array([]))

    • 这两行代码移除了x轴和y轴上的刻度标签,使得图像更加简洁

这里调用了上面创建图像的函数 

随机选取raw_x中的一个样本,显示其图像,并打印出对应的标签。👇

pick_one = np.random.randint(0, 5000)
plot_an_image(raw_x[pick_one, :])
print('this should be {}'.format(raw_y[pick_one]))

比如得到:

     这段代码首先使用numpyrandom.randint函数从0到4999(包含0但不包含5000)之间随机选择一个整数,并将其赋值给pick_one

     接着,它调用之前定义的plot_an_image函数来绘制并显示raw_x中对应pick_one索引的样本图像。这里假设raw_x是一个二维数组,其中每一行代表一个样本,每一列代表一个特征。因此,raw_x[pick_one, :]将选取raw_x中第pick_one行的所有列,即选取一个样本。

      最后,它打印出raw_y中对应pick_one索引的标签。这里假设raw_y是一个一维数组,其中每个元素代表对应样本的标签或分类。print('this should be {}'.format(raw_y[pick_one]))将输出类似"this should be 7"这样的字符串,其中7是raw_y中第pick_one个元素的值。

综上所述,这段代码的目的是随机选取raw_x中的一个样本,显示其图像,并打印出对应的标签。

def sigmoid(z):
    return 1 / (1 + np.exp(-z))



准备数据

X = np.insert(raw_x, 0, np.ones(raw_x.shape[0]), axis=1)
X.shape

👆(5000, 401)

     最终,X将是一个新的数组,它与raw_x有相同的行数,但列数多了一列。这新增的一列是全1的,并且位于所有原有列的最前面。

     这种操作在机器学习中有时是很有用的,特别是当你需要给每个样本添加一个偏置项时。通过将偏置项设置为1,并将其作为一个单独的特征列添加到数据集中,你可以轻松地在模型中包含一个偏置项。

 多元分类

多元分类,也称为多类分类,是机器学习中的一个重要任务,其目标是将实例(或样本)划分到多个类别中的某一个。每个类别都对应一个不同的标签,且这些标签是互斥的,即一个实例只能属于一个类别。

将类别标签转换为模型可以使用的格式的方法

# 初始化一个空列表y,用于存储后面生成的子列表
y = []


# 遍历`raw_y`中的每个元素`i`
for k in range(1, 11):
# 如果`i`等于`k`,则在当前子列表中对应位置添加1;否则添加0。  
    y.append([1 if i==k else 0 for i in raw_y])


y = np.array([y[-1]] + y[:-1])


y.shape  # (10, 5000)
y
# 最终,`y`是一个one-hot编码形式的数组,其中每一行对应一个类别标签,每一列对应一个样本。
array([[1, 1, 1, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       ...,
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 0, 0, 0],
       [0, 0, 0, ..., 1, 1, 1]])

       这段代码的目的是将原始的标签数组 raw_y 转换为一个 one-hot 编码的数组 y。在机器学习中,one-hot 编码是一种常见的将类别标签转换为模型可以使用的格式的方法。对于具有多个类别的分类问题,one-hot 编码将每个类别标签转换为一个二进制向量,其中向量的长度等于类别的数量,且只有一个位置是 1(表示该样本属于该类别),其余位置都是 0。

        在这个特定的例子中,假设 raw_y 是一个包含整数标签的数组,这些整数表示不同的类别(从 1 到 10)。代码的目的是创建一个新的数组 y,其中每一行对应一个类别,每一列对应 raw_y 中的一个样本。如果 raw_y 中的某个样本属于第 k 个类别,则 y 中对应第 k 行的那一列将是 1,其余位置都是 0。

       通过这样做,代码将原始的整数标签转换为了一个二进制的、更易于模型处理的格式。例如,如果 raw_y 中的某个样本的标签是 3,那么在 y 中对应的那一列将是 [0, 0, 0, 1, 0, 0, 0, 0, 0, 0]

      最终,y 的形状是 (10, 5000),其中 10 是类别的数量,5000 是样本的数量。这种格式对于许多机器学习算法来说是非常有用的,特别是那些期望输入是 one-hot 编码的算法。

代价函数

def cost(theta, X, y):
    first = y * np.log(sigmoid(X @ theta.T))
    second = (1 - y) * np.log(1 - sigmoid(X @ theta.T))
    return -np.mean(first + second)


def regularized_cost(theta, X, y, l):
    reg = l / (2 * len(X)) * (theta[1:] ** 2).sum()
    return cost(theta, X, y) + reg

梯度下降

def gradient(theta, X, y, l):
    error = sigmoid(X@theta.T) - y
    grad = X.T @ error / len(X)
    reg = theta * l / len(X)
    reg[0] = 0
    return grad + reg


def logistic_regression(X, y, l=1):
    theta = np.zeros(X.shape[1])
    res = opt.minimize(fun = regularized_cost, x0=theta, args=(X, y, l), method='TNC', jac=gradient, options={'disp': True})
    return res.x

预测分析

def predict(theta, X):
    prob = sigmoid(X @ theta)
    return [1 if i >= 0.5 else 0 for i in prob]

训练1维

计算  模型预测准确性的百分比

theta_0 = logistic_regression(X, y[0])
theta_0.shape
# 
# y_pred中的每一列代表模型对相应样本的预测结果
y_pred = predict(theta_0, X)
# 误差
# y[0]取出了y的第一行,即第一个类别
print('Accuracy = {}'.format(np.mean(y[0] == y_pred)))


计算模型预测的第一个类别(通常是正类或者我们关心的类别)的准确性
表示模型预测准确性的百分比
# Accuracy = 0.9974
  1. y[0] == y_pred: 这个操作是逐元素地比较y[0]y_pred中的元素是否相等。如果y[0]中的某个位置是1,并且y_pred中对应位置也是1,那么比较的结果就是True,否则是False。这将返回一个布尔数组,其形状与y_pred相同。

  2. np.mean(y[0] == y_pred): 这个操作计算了布尔数组中True元素的比例,即计算了预测正确的样本所占的比例,从而得到了模型的准确性。

  3. 'Accurary = {}'.format(...): 这是一个字符串格式化操作,它将前面计算得到的准确性值插入到字符串中,从而得到一个包含准确性值的字符串。

训练k维

theta_k = np.array([logistic_regression(X, y[k]) for k in range(10)])
theta_k.shape

# (10, 401)

X:(5000, 401)

y:(10, 5000)

theta_k:(10,401)

# prob_matrix 是一个变量,用于存储计算出的概率矩阵
prob_matrix = sigmoid(X @ theta_k.T)

np.set_printoptions(suppress=True)
prob_matrix
array([[0.99577396, 0.        , 0.00053364, ..., 0.00006469, 0.00003904,
        0.00171709],
       [0.99834687, 0.0000001 , 0.00005602, ..., 0.00009679, 0.0000029 ,
        0.00008478],
       [0.99140084, 0.        , 0.00056919, ..., 0.00000654, 0.02653221,
        0.0019758 ],
       ...,
       [0.00000068, 0.04139908, 0.00320838, ..., 0.00012723, 0.00297294,
        0.70789152],
       [0.00001843, 0.00000013, 0.00000009, ..., 0.00164835, 0.06816915,
        0.8609958 ],
       [0.02881654, 0.        , 0.00012967, ..., 0.36605901, 0.00497141,
        0.14807259]])
  1. np.set_printoptions(suppress=True): 这行代码设置了 NumPy 的打印选项,其中 suppress=True 表示在打印数组时,不会显示小的浮点数(接近于零的值)。这可以使输出的概率矩阵更加简洁,避免显示大量的接近于零的值。

y_pred = np.argmax(prob_matrix, axis=1) # 返回每行最大的列索引
y_pred = np.array([10 if i == 0 else i for i in y_pred])
y_pred


# array([10, 10, 10, ...,  9,  9,  7], dtype=int64)
  • 为什么这个不加1?(y_pred = np.argmax(prob_matrix, axis=1))
  • 如果您的类别标签是从 0 开始的,但您不希望有任何样本被分类为第 0 个类别,那么您需要进行替换操作,将索引 0 替换为一个特殊的值(如 10)。

确保 prob_matrix 的列与您的类别标签正确对应,这样 np.argmax 才能返回正确的索引。

print(classification_report(raw_y, y_pred))

y_pred 是用于存储模型预测结果的数组。这个数组是通过找到 prob_matrix 中每行最大值的索引来得到的,这些索引代表了模型对每个样本预测的类别标签。 

              precision    recall  f1-score   support

           1       0.95      0.99      0.97       500
           2       0.95      0.92      0.93       500
           3       0.95      0.91      0.93       500
           4       0.95      0.95      0.95       500
           5       0.92      0.92      0.92       500
           6       0.97      0.98      0.97       500
           7       0.95      0.95      0.95       500
           8       0.93      0.92      0.92       500
           9       0.92      0.92      0.92       500
          10       0.97      0.99      0.98       500

    accuracy                           0.94      5000
   macro avg       0.94      0.94      0.94      5000
weighted avg       0.94      0.94      0.94      5000


神经网络——前向传播

已经给出训练得到的theta1,theta2,通过前向传播计算得到预测结果

def load_weight(path):
    data = loadmat(path)
    return data['Theta1'], data['Theta2']
theta1, theta2 = load_weight('ex3weights.mat')
theta1.shape, theta2.shape

#((25, 401), (10, 26))

X, y = load_data('ex3data1.mat', transpose=False)
X = np.insert(X, 0, np.ones(X.shape[0]), axis=1)
X.shape, y.shape

#((5000, 401), (5000,))

 

输入层

a1 = X

z2 = a1 @ theta1.T
z2 = np.insert(z2, 0, np.ones(z2.shape[0]), axis=1)
z2.shape
# (5000, 26)

第二层

a2 = sigmoid(z2)
a2.shape
# (5000, 26)

z3 = a2 @ theta2.T
z3.shape
# (5000, 10)

输出层

a3 = sigmoid(z3)
a3.shape
# (5000, 10)
a3

array([[0.00013825, 0.0020554 , 0.00304012, ..., 0.00049102, 0.00774326,
        0.99622946],
       [0.00058776, 0.00285027, 0.00414688, ..., 0.00292311, 0.00235617,
        0.99619667],
       [0.00010868, 0.0038266 , 0.03058551, ..., 0.07514539, 0.0065704 ,
        0.93586278],
       ...,
       [0.06278247, 0.00450406, 0.03545109, ..., 0.0026367 , 0.68944816,
        0.00002744],
       [0.00101909, 0.00073436, 0.00037856, ..., 0.01456166, 0.97598976,
        0.00023337],
       [0.00005908, 0.00054172, 0.0000259 , ..., 0.00700508, 0.73281465,
        0.09166961]])
y_pred = np.argmax(a3, axis=1)+1
y_pred

# array([10, 10, 10, ...,  9,  9,  9], dtype=int64)

       在给出的代码片段 y_pred = np.argmax(a3, axis=1) + 1 中,y_pred 是一个数组,它存储了模型对每个样本的预测类别标签。这里的 a3 很可能是一个经过某些变换(例如,通过 softmax 函数)后的概率矩阵,其中每一行代表一个样本在不同类别上的概率分布。

   np.argmax(a3, axis=1) 的作用是沿着每一行(即每个样本的概率分布)找到最大值的索引。这个索引代表了模型对该样本预测最有可能的类别。由于 np.argmax 返回的索引是从 0 开始的,如果 a3 中的列对应于从 1 开始的类别标签(例如,第一列对应类别 1,第二列对应类别 2,等等),那么需要加 1 来将索引转换为正确的类别标签

简而言之,y_pred = np.argmax(a3, axis=1) + 1 的意思是:

  1. 对于 a3 中的每一行(每个样本的概率分布),找到最大概率对应的列的索引。
  2. 将这个索引加 1,以得到正确的类别标签。

      这样,y_pred 数组中的每个元素都是模型预测的样本所属类别的标签,且这些标签是从 1 开始的(而不是从 0 开始)。这在处理多类分类问题时很常见,尤其是当类别标签从 1 开始编号时。

print(classification_report(y, y_pred))
              precision    recall  f1-score   support

           1       0.97      0.98      0.97       500
           2       0.98      0.97      0.97       500
           3       0.98      0.96      0.97       500
           4       0.97      0.97      0.97       500
           5       0.98      0.98      0.98       500
           6       0.97      0.99      0.98       500
           7       0.98      0.97      0.97       500
           8       0.98      0.98      0.98       500
           9       0.97      0.96      0.96       500
          10       0.98      0.99      0.99       500

    accuracy                           0.98      5000
   macro avg       0.98      0.98      0.98      5000
weighted avg       0.98      0.98      0.98      5000

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值