10-714:hw0

不过,我们要说的是,在Python版本中,应该大量使用numpy中的线性代数函数调用:尝试使用显式循环通常会使代码慢得多。

10-714课程的所有代码开发都可以在Google Colab环境中完成。但是,与其在colab笔记本内部广泛使用实际的代码块不同,你将大部分开发的代码编写为.py文件,这些文件将被自动下载到您的Google Drive中,你将主要使用笔记本运行shell脚本来测试和提交代码到自动评分系统(或者选择性地用于在开发过程中测试代码片段,但这不是必需的)。这是一种非常标准的Colab笔记本使用方法(通常人们更多地将其用作交互式编码环境,直接在笔记本中使用代码单元格)。然而,我们之所以按照这种方式使用它们的理由实际上很简单:除了是一个很好的基于云的笔记本环境之外,Colab还提供了非常好的访问“标准”基于云的GPU系统的方式,可以快速启动这些系统,从而使你能够开发一些后期(基于CUDA)的代码,而无需获得物理GPU的访问权限或自行设置CUDA库。话虽如此,你可以选择在任何您喜欢的环境中开发和提交代码,我们只是无法保证支持Colab以外的任何环境。

要开始,请通过从“文件”菜单中选择“在Drive中保存副本”来复制此笔记本文件,然后运行下面的代码块。这将将您的Google Drive文件夹加载到Colab

Q1

Q2

import gzip
import numpy as np
import struct

def parse_mnist(image_filename, label_filename):
    with gzip.open(image_filename, 'rb') as image_file, gzip.open(label_filename, 'rb') as label_file:
        # Read magic numbers from image and label files
        image_magic = struct.unpack('>I', image_file.read(4))[0]
        label_magic = struct.unpack('>I', label_file.read(4))[0]

        # Verify magic numbers
        if image_magic != 2051 or label_magic != 2049:
            raise ValueError("Invalid MNIST file format")

        # Read number of images and labels
        num_images = struct.unpack('>I', image_file.read(4))[0]
        num_labels = struct.unpack('>I', label_file.read(4))[0]

        # Verify that the number of images and labels match
        if num_images != num_labels:
            raise ValueError("Number of images and labels do not match")

        # Read image dimensions
        num_rows = struct.unpack('>I', image_file.read(4))[0]
        num_cols = struct.unpack('>I', image_file.read(4))[0]
        input_dim = num_rows * num_cols

        # Read images and labels into numpy arrays
        X = np.frombuffer(image_file.read(), dtype=np.uint8).reshape(num_images, input_dim)
        y = np.frombuffer(label_file.read(), dtype=np.uint8)

        # Normalize X to have values between 0.0 and 1.0
        X = X.astype(np.float32) / 255.0

        return X, y
  • 数据集

在MNIST数据集中,训练集由两个文件组成:一个是包含标签的文件(train-labels-idx1-ubyte),另一个是包含图像数据的文件(train-images-idx3-ubyte)。

训练集标签文件(train-labels-idx1-ubyte)的格式描述如下:

  • 偏移量0-3:32位整数,魔术数字(Magic Number),其值为0x00000801(2049),用于标识文件类型(高位优先)。

  • 偏移量4-7:32位整数,60000,表示训练集中样本的数量。

  • 偏移量8及之后:每个字节表示一个样本的标签,取值范围为0到9。

训练集图像文件(train-images-idx3-ubyte)的格式描述如下:

  • 偏移量0-3:32位整数,魔术数字(Magic Number),其值为0x00000803(2051),用于标识文件类型。

  • 偏移量4-7:32位整数,60000,表示训练集中图像的数量。

  • 偏移量8-11:32位整数,28,表示图像的行数。

  • 偏移量12-15:32位整数,28,表示图像的列数。

  • 偏移量16及之后:每个字节表示一个像素值,取值范围为0到255。像素值以逐行的方式组织,每行的像素从左到右排列。

需要注意的是,图像的像素值为0到255,其中0表示背景(白色),255表示前景(黑色)

(**读取像素点的时候 ,是uint8 **)

  • '>I'是一个格式化字符串,用于指定数据的解析格式。在这个上下文中,'>I'表示按照大端字节序(Big Endian)解析数据,并将其转换为无符号整数(unsigned integer)。

    • >表示使用大端字节序,即高位字节在前、低位字节在后的方式表示数据。

    • I表示将数据解析为一个无符号整数(32位)。

    通过使用struct.unpack('>I', data)struct模块能够按照指定的格式解析二进制数据,并将其转换为对应的Python对象。在这种情况下,它将读取的4个字节数据解析为一个大端字节序的无符号整数。

  • image_file.read()读取操作会从当前文件指针位置开始读取指定数量的字节,并且在读取后会更新文件指针的位置。每次调用read()方法会将文件指针向后移动读取的字节数。

  • 对于图像数据,使用np.frombuffer(image_file.read(), dtype=np.uint8)将读取的文件内容转换为NumPy数组。frombuffer函数将字节流转换为数组,dtype=np.uint8指定了数组元素的数据类型为无符号8位整数,即像素值的范围为0-255。然后,使用.reshape(num_images, input_dim)将一维的数组变为二维数组

Q3

In this code, Z represents the logit predictions for each class, and y represents the true labels. Here's a breakdown of the steps:

  1. Calculate the exponential of Z using np.exp(Z).

  2. Compute the sum of the exponential values along the axis 1 (which represents the classes) using np.sum(exp_Z, axis=1, keepdims=True).

  3. Divide each element of exp_Z by the sum to get the softmax probabilities for each example.

  4. Select the probabilities for the correct class using advanced indexing probs[np.arange(batch_size), y], where np.arange(batch_size) generates an array [0, 1, ..., batch_size-1] to select the correct elements for each example.

  5. Compute the negative logarithm of the correct probabilities using -np.log(correct_probs).

  6. Finally, calculate the average loss over the batch by taking the mean of the log probabilities using np.mean(log_probs).

Q4

我好蠢啊,我才明白矩阵运算过来

ablaθlce(Xθ,y)=XT(ZIy)abla_{\theta} l_{ce}(X\theta,y)=X^T(Z-I_y)

这样算出来的梯度是该批次的梯度和,所以需要除以BB。所以矩阵运算其实就是做并行计算

跑一个epoch其实就是把数据集随机化以后,跑完全部的数据集

Q5

Q6

Z=normalize(Xθ)Z=normalize(X\theta)

X=B乘n,θ\theta= n乘k,Z=B乘k

ablaθlce(Xθ,y)=1BXT(ZIy)abla_{\theta} l_{ce}(X\theta,y)=\frac{1}{B} \bold{{X^T(Z-I_y)}}

每个batch

θ=θlrθlce(Xθ,y)\theta=\theta-lr \cdot \nabla_{\theta} l_{ce}(X\theta,y)

参数

why

softmax函数的目标是将一个k维的向量映射到另一个k维的向量,其值在0和1之间且总和为1,可以被解释为概率。softmax函数定义如下:

softmax(xi)=exiexjsoftmax(x_i) = \frac{e^{x_i}}{\sum e^{x_j}}

其中,i和j都在1到k的范围内。

但是,直接这样计算可能会有数值稳定性问题。当x的某个值非常大的时候,exi{e^{x_i}}可能会超出浮点数的表示范围,导致数值溢出。

为了解决这个问题,一种常见的技巧是在计算softmax的时候首先减去x中的最大值。即:

softmax(xi)=eximax(x)exjmax(x)softmax(x_i) = \frac{e^{x_i-max(x)}}{\sum e^{x_j-max(x)}}

可以证明这样做并不改变softmax的结果,但是可以防止因为x的值过大而导致的数值溢出。