本文主要是介绍AutoCV第九课:ML基础,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
- ML基础
- 注意事项
- 一、2023/6/28更新
- 前言
- 1. 矩阵乘法和求导
- 2. 多元线性回归
- 3. 多模型线性回归
- 4. 多分类逻辑回归
- 总结
ML基础
注意事项
一、2023/6/28更新
新增多元和多模型线性回归的矩阵表达,即第 2、3 小节内容
新增逻辑回归多分类 mnist 模型,即第 4 小节内容
前言
手写AI推出的全新保姆级从零手写自动驾驶CV课程,链接。记录下个人学习笔记,仅供自己参考。
本次课程主要学习矩阵运算的基础,考虑使用矩阵来表达多个线性回归模型。
课程大纲可看下面的思维导图。
1. 矩阵乘法和求导
先回忆下矩阵相关知识
定义矩阵乘法:
{ a b d e } × { 1 3 2 4 } = { a 1 + b 2 a 3 + b 4 d 1 + e 2 d 3 + e 4 } \left\{\begin{array}{cc}a&b\\ d&e\end{array}\right\}\times\left\{\begin{array}{cc}1&3\\ 2&4\end{array}\right\}=\left\{\begin{array}{cc}a1+b2&a3+b4\\ d1+e2&d3+e4\end{array}\right\} {adbe}×{1234}={a1+b2d1+e2a3+b4d3+e4}
记法:C[r][c] = 乘加(A中取 r 行,B中取 c 列)
参考:https://www.cnblogs.com/ljy-endl/p/11411665.html
矩阵求导:
对于 A ⋅ B = C A\cdot B = C A⋅B=C 定义 L L L 是关于 C C C 的损失函数
设 G = ∂ L ∂ C G = \dfrac{\partial L}{\partial C} G=∂C∂L 若直接 C C C 对 A A A 求导,则 G G G 定义为 C C C 大小的全 1 矩阵,则有:
∂ L ∂ A = G ⋅ B T ∂ L ∂ B = A T ⋅ G \dfrac{\partial L}{\partial A}=G\cdot B^T \ \ \ \ \ \dfrac{\partial L}{\partial B}=A^T \cdot G ∂A∂L=G⋅BT ∂B∂L=AT⋅G
矩阵求导推导:
-
考虑矩阵乘法 A ⋅ B = C A \cdot B = C A⋅B=C
-
考虑 Loss 函数 L = ∑ i m ∑ j n ( C i j − p ) 2 L = \sum^m_{i}\sum^n_{j}{(C_{ij} - p)^2} L=i∑mj∑n(Cij−p)2
-
考虑 C C C 的每一项导数 ▽ C i j = ∂ L ∂ C i j \triangledown C_{ij} = \frac{\partial L}{\partial C_{ij}} ▽Cij=∂Cij∂L
-
考虑 A B C ABC ABC 都为 2x2 矩阵时,定义 G G G 为 L L L 对 C C C 的导数
A = [ a b c d ] B = [ e f g h ] C = [ i j k l ] G = ∂ L ∂ C = [ ∂ L ∂ i ∂ L ∂ j ∂ L ∂ k ∂ L ∂ l ] = [ w x y z ] A = \begin{bmatrix} a & b\\ c & d \end{bmatrix} \quad B = \begin{bmatrix} e & f \\ g & h \end{bmatrix} \quad C = \begin{bmatrix} i & j \\ k & l \end{bmatrix} \quad G = \frac{\partial L}{\partial C} = \begin{bmatrix} \frac{\partial L}{\partial i} & \frac{\partial L}{\partial j} \\ \frac{\partial L}{\partial k} & \frac{\partial L}{\partial l} \end{bmatrix} = \begin{bmatrix} w & x \\ y & z \end{bmatrix} A=[acbd]B=[egfh]C=[ikjl]G=∂C∂L=[∂i∂L∂k∂L∂j∂L∂l∂L]=[wyxz] -
展开左边 A ⋅ B A \cdot B A⋅B
C = [ i = a e + b g j = a f + b h k = c e + d g l = c f + d h ] C = \begin{bmatrix} i = ae + bg & j = af + bh\\ k = ce + dg & l = cf + dh \end{bmatrix} C=[i=ae+bgk=ce+dgj=af+bhl=cf+dh]
-
L L L 对于每一个 A A A 的导数
▽ A i j = ∂ L ∂ A i j \triangledown A_{ij} = \frac{\partial L}{\partial A_{ij}} ▽Aij=∂Aij∂L∂ L ∂ a = ∂ L ∂ i ∗ ∂ i ∂ a + ∂ L ∂ j ∗ ∂ j ∂ a ∂ L ∂ b = ∂ L ∂ i ∗ ∂ i ∂ b + ∂ L ∂ j ∗ ∂ j ∂ b ∂ L ∂ c = ∂ L ∂ k ∗ ∂ k ∂ c + ∂ L ∂ l ∗ ∂ l ∂ c ∂ L ∂ d = ∂ L ∂ k ∗ ∂ k ∂ d + ∂ L ∂ l ∗ ∂ l ∂ d \begin{aligned} \frac{\partial L}{\partial a} &= \frac{\partial L}{\partial i} * \frac{\partial i}{\partial a} + \frac{\partial L}{\partial j} * \frac{\partial j}{\partial a} \\ \frac{\partial L}{\partial b} &= \frac{\partial L}{\partial i} * \frac{\partial i}{\partial b} + \frac{\partial L}{\partial j} * \frac{\partial j}{\partial b} \\ \frac{\partial L}{\partial c} &= \frac{\partial L}{\partial k} * \frac{\partial k}{\partial c} + \frac{\partial L}{\partial l} * \frac{\partial l}{\partial c} \\ \frac{\partial L}{\partial d} &= \frac{\partial L}{\partial k} * \frac{\partial k}{\partial d} + \frac{\partial L}{\partial l} * \frac{\partial l}{\partial d} \end{aligned} ∂a∂L∂b∂L∂c∂L∂d∂L=∂i∂L∗∂a∂i+∂j∂L∗∂a∂j=∂i∂L∗∂b∂i+∂j∂L∗∂b∂j=∂k∂L∗∂c∂k+∂l∂L∗∂c∂l=∂k∂L∗∂d∂k+∂l∂L∗∂d∂l
∂ L ∂ a = w e + x f ∂ L ∂ b = w g + x h ∂ L ∂ c = y e + z f ∂ L ∂ d = y g + z h \begin{aligned} \frac{\partial L}{\partial a} &= we + xf \\ \frac{\partial L}{\partial b} &= wg + xh \\ \frac{\partial L}{\partial c} &= ye + zf \\ \frac{\partial L}{\partial d} &= yg + zh \end{aligned} ∂a∂L∂b∂L∂c∂L∂d∂L=we+xf=wg+xh=ye+zf=yg+zh
-
因此 A A A 的导数为
∂ L ∂ A = [ w e + x f w g + x h y e + z f y g + z h ] ∂ L ∂ A = [ w x y z ] [ e g f h ] \frac{\partial L}{\partial A} = \begin{bmatrix} we + xf & wg + xh\\ ye + zf & yg + zh \end{bmatrix} \quad \frac{\partial L}{\partial A} = \begin{bmatrix} w & x\\ y & z \end{bmatrix} \begin{bmatrix} e & g\\ f & h \end{bmatrix} ∂A∂L=[we+xfye+zfwg+xhyg+zh]∂A∂L=[wyxz][efgh]
∂ L ∂ A = G ⋅ B T \frac{\partial L}{\partial A} = G \cdot B^T ∂A∂L=G⋅BT
-
同理 B B B 的导数为
∂ L ∂ e = w a + y c ∂ L ∂ f = x a + z c ∂ L ∂ g = w b + y d ∂ L ∂ h = x b + z d \begin{aligned} \frac{\partial L}{\partial e} &= wa + yc \\ \frac{\partial L}{\partial f} &= xa + zc \\ \frac{\partial L}{\partial g} &= wb + yd \\ \frac{\partial L}{\partial h} &= xb + zd \end{aligned} ∂e∂L∂f∂L∂g∂L∂h∂L=wa+yc=xa+zc=wb+yd=xb+zd∂ L ∂ B = [ w a + y c x a + z c w b + y d x b + z d ] ∂ L ∂ B = [ a c b d ] [ w x y z ] \frac{\partial L}{\partial B} = \begin{bmatrix} wa + yc & xa + zc\\ wb + yd & xb + zd \end{bmatrix} \quad \frac{\partial L}{\partial B} = \begin{bmatrix} a & c\\ b & d \end{bmatrix} \begin{bmatrix} w & x\\ y & z \end{bmatrix} ∂B∂L=[wa+ycwb+ydxa+zcxb+zd]∂B∂L=[abcd][wyxz]
∂ L ∂ B = A T ⋅ G \frac{\partial L}{\partial B} = A^T \cdot G ∂B∂L=AT⋅G
2. 多元线性回归
之前我们是通过房价预测的案例来讲解线性回归的,在前面的分析中我们假设房价只与房屋面积一个自变量相关,因此可以将其视为一元线性回归模型处理,其对应的房价计算公式如下:
房价 = 房屋面积 * 系数 + 偏置
而实际情况下,房价应该与多个自变量相关,比如房屋的面积(m2)、距离地铁的远近(km)、装修的程度(0-1)等等,因此需要将其视为多元线性回归模型处理,其对应的房价计算公式如下:
房价 = 房屋面积 * 系数1 + 距离 * 系数2 + 装修 * 系数3 + 偏置
我们先初步写个 demo 确保各部分的维度是正确的,示例代码如下:
import numpy as np# 定义变量
# 面积,距离,装修
x = np.array([[80, 3, 1],[100, 5, 0.3],[130, 2, 0.1],[300, 20, 1],[60, 1, 0.8]
])# 定义输出
# 房价
y = np.array([50000,60000,50000,30000,70000,
]).reshape(-1, 1)k = np.random.randn(3).reshape(1, 3)
b = 0# x(5x3), k(1x3), b(1,)
predict = (x * k + b).sum(axis=1, keepdims=True)# predict(5x1) y(5x1)
loss = 0.5 * ((predict - y) ** 2).sum()
print(loss)
在上面的示例代码中,我们使用 numpy 来进行数值计算和对应的矩阵操作。首先定义了一个自变量 x(5,3),表示 5 个样本的特征值,每行包含房屋面积、距离和装修情况三个特征,并定义了因变量房价 y(5,1);接下来初始化权重参数 k(1,3) 和偏置 b(1,);然后利用矩阵运算的方式得出了每个样本的房价预测结果;最后根据预测结果和实际房价 y 之间的差异来计算 loss,使用的是均方差损失函数,最终得出的 loss 是一个标量,符合我们的预期。
我们确保了最终输出 loss 的 shape 是正确的,接下来我们来对其进行优化,包括对数据进行正则化、循环迭代更新参数以及验证
优化后的示例代码如下:
import numpy as np# 定义变量
# 面积,距离,装修
x = np.array([[80, 3, 1],[100, 5, 0.3],[130, 2, 0.1],[300, 20, 1],[60, 1, 0.8]
])# 定义输出
# 房价
y = np.array([50000,60000,50000,30000,70000,
]).reshape(-1, 1)k = np.random.randn(3).reshape(1, 3)
b = 0# 数据正则化
def normalize(x):x_std = x.std(axis=0, keepdims=True)x_mean = x.mean(axis=0, keepdims=True)norm_x = (x - x_mean) / x_stdreturn norm_x, x_std, x_meannorm_x, x_std, x_mean = normalize(x)
norm_y, y_std, y_mean = normalize(y)
lr = 1e-2for iter in range(1000):# x(5x3), k(1x3), b(1,)predict = (norm_x * k + b).sum(axis=1, keepdims=True)# predict(5x1) y(5x1)loss = 0.5 * ((predict - norm_y) ** 2).sum()if iter % 100 == 0:print(f"Iter {iter}, Loss: {loss:.3f}")delta_k = ((predict - norm_y) * norm_x).sum(axis=0, keepdims=True)delta_b = (predict - norm_y).sum()# 参数更新k = k - lr * delta_kb = b - lr * delta_b# 验证
while True:text = input("请输入你想预测的房子的相关信息[面积,距离,装修]:")feature = list(map(float, text.strip().split(" ")))if len(feature) != 3:print(f"输入有误,请重新输入")continuefeature = np.array([feature])norm_feature = (feature - x_mean) / x_stdpredict = (norm_feature * k + b).sum()real_predict = predict * y_std + y_meanprint(real_predict)
上述示例代码是完整的多元线性回归的房价预测案例。通过给定的房屋特征(面积、距离、装修),使用梯度下降法不断调整权重系数和偏置,以最小化预测值与实际房价之间的损失。通过数据正则化和参数更新,预测新房屋的房价。值得一提的是,模型最终的效果似乎并没有我们想象的那么好😂,这并不重要,我们主要通过这个案例来学习多元线性回归模型。
运行效果如下:
3. 多模型线性回归
在上面的分析中,我们考虑的是多变量单输出的情况,也就是最终我们只需要预测房价,如果是多变量多输出的情况又该如何考虑呢?🤔
假设目前我们根据面积、距离和装修程度三个特征不仅要预测房价还需要预测租金,也就是有两个输出,其实对应的就是两个模型,此时我们可以使用上节课学习到的矩阵知识来处理多个模型的问题。
具体的实现代码如下,与上述单输出的情况并没有太大的差异,只是需要从矩阵的角度来考虑这个问题
import numpy as np# 定义变量
# 面积,距离,装修
x = np.array([[80, 3, 1],[100, 5, 0.3],[130, 2, 0.1],[300, 20, 1],[60, 1, 0.8]
])# 定义输出
# 房价,租金
y = np.array([50000, 3000,50000, 6000,60000, 8000,30000, 9000,70000, 3000
]).reshape(-1, 2)# 权重系数和偏置
# 看作两个模型,每个模型包含3个权重和一个偏置
k = np.random.randn(3, 2)
b = np.zeros((1, 2))# 数据正则化
def normalize(x):x_std = x.std(axis=0, keepdims=True)x_mean = x.mean(axis=0, keepdims=True)norm_x = (x - x_mean) / x_stdreturn norm_x, x_std, x_meannorm_x, x_std, x_mean = normalize(x)
norm_y, y_std, y_mean = normalize(y)
lr = 1e-3for iter in range(1000):# norm_x(5x3), k(3x2), b(1x2)# predict(5x2)predict = norm_x @ k + b# predict(5x2) y(5x2)loss = 0.5 * ((predict - norm_y) ** 2).sum()if iter % 100 == 0:print(f"Iter {iter}, Loss: {loss:.3f}")# predict(5x2), norm_y(5x2)# C = AB# dA = G @ B.T# dB = A.T @ GG = predict - norm_ydelta_k = norm_x.T @ Gdelta_b = G.sum(axis=0, keepdims=True)# 参数更新k = k - lr * delta_kb = b - lr * delta_b# 验证
while True:text = input("请输入你想预测的房子的相关信息[面积,距离,装修]:")feature = list(map(float, text.strip().split(" ")))if len(feature) != 3:print(f"输入有误,请重新输入")continuefeature = np.array([feature])norm_feature = (feature - x_mean) / x_std# 1x2predict = norm_feature @ k + breal_predict = predict * y_std + y_meanprice1, price2 = real_predict[0]print(f"对于房价的预测为:{price1:.3f},对于租金预测为:{price2:.3f}")
上述示例代码是一个多输出多元线性回归的示例,旨在通过给定的房屋特征(面积、距离、装修)同时预测房价和租金。这种多输出的线性回归模型适用于需要同时预测多个相关输出的场景。
运行效果如下:
我们再来回顾下其中使用到的矩阵求导的相关知识
对于 A ⋅ B = C A \cdot B = C A⋅B=C 定义 L L L 是关于 C C C 的损失函数
设 G = ∂ L ∂ C G = \dfrac{\partial L}{\partial C} G=∂C∂L,则有
∂ L ∂ A = G ⋅ B T ∂ L ∂ B = A T ⋅ G \dfrac{\partial L}{\partial A}=G\cdot B^T \ \ \ \ \dfrac{\partial L}{\partial B}=A^T \cdot G ∂A∂L=G⋅BT ∂B∂L=AT⋅G
回到代码中,我们需要求 ∂ L ∂ k \dfrac{\partial L}{\partial k} ∂k∂L 即 delta_k
,其中 G = ∂ L ∂ C = p r e d i c t − n o r m _ y G = \dfrac{\partial L}{\partial C} = predict-norm\_y G=∂C∂L=predict−norm_y, C = A ⋅ B = n o r m _ x ⋅ k C = A \cdot B = norm\_x \cdot k C=A⋅B=norm_x⋅k,因此最终的 ∂ L ∂ k = n o r m _ x . T ⋅ G \dfrac{\partial L}{\partial k} = norm\_x.T \cdot G ∂k∂L=norm_x.T⋅G
我们也可以从 shape 维度来验证下,norm_x(5x3) G(5x2) => norm_x.T(3x5) => delta_k(3x2)
4. 多分类逻辑回归
之前我们是通过苹果分类案例来讲解逻辑回归问题的,可以将其视为一个二分类问题,为此我们引入了 sigmoid 函数并定义了逻辑回归模型的损失函数即二元交叉熵损失函数。
这节课我们来学习多分类逻辑回归,对 mnist 手写识别数据集中的 10 个类别进行分类。
在正式开始之前,我们需要考虑如下问题:
-
由于是 10 个数字的分类任务,所以可以将其视为 10 个逻辑回归模型
-
关于正则化的考虑
- 我们是否应该参考线性回归的正则化,对每个 pixel 都统计其 mean 和 std 呢?🤔
- 如果每个数据都是独立分布,则需要独立统计 mean 和 std,而对于 2d 图像,每个 pixel 并不是独立分布的,它们是存在空间相关性的,因此需要统一考虑 mean 和 std。对于训练集,需要统计 mean 和 std,对于测试集,使用训练集统计的 mean 和 std 即可
- 我们之前使用 sigmoid 处理二分类问题,即 sigmoid(predict)->logits(0-1),在这里是多分类问题,我们考虑使用 softmax(predict)->logits(0-1)
我们先把前面写过的 mnist 的 dataset 和 dataloader 的示例代码 copy 过来,如下所示:
import numpy as np
import matplotlib.pyplot as pltclass MNISTDataset:def __init__(self, images_path, labels_path):self.images_path = images_pathself.labels_path = labels_pathself.images, self.labels = self.load_mnist_data()def __len__(self):return len(self.images)def __getitem__(self, index):image, label = self.images[index], self.labels[index]return image, labeldef load_mnist_data(self):# load labelswith open(self.labels_path, "rb") as f:magic_number, num_of_items = np.frombuffer(f.read(8), dtype=">i", count=2, offset=0)labels = np.frombuffer(f.read(), dtype=np.uint8, count=-1, offset=0)# load imageswith open(self.images_path, "rb") as f:magic_number, num_of_images, rows, cols = np.frombuffer(f.read(16), dtype=">i", count=4, offset=0)pixels = np.frombuffer(f.read(), dtype=np.uint8, count=-1, offset=0)images_matrix = pixels.reshape(num_of_images, rows, cols)return images_matrix, labelsclass MNISTDataLoader:def __init__(self, dataset, batch_size, shuffle=True):self.dataset = datasetself.batch_size = batch_sizeself.shuffle = shuffledef __iter__(self):self.indexs = np.arange(len(self.dataset))if self.shuffle:np.random.shuffle(self.indexs)self.cursor = 0return selfdef __next__(self):begin = self.cursorend = self.cursor + self.batch_sizeif end > len(self.dataset):raise StopIteration()self.cursor = endbatched_data = []for index in self.indexs[begin:end]:item = self.dataset[index]batched_data.append(item)return batched_datadef show_mnist_image(images, labels, n=1):for i in range(n):image = images[i]label = labels[i]plt.title(f"label is {label}")plt.imshow(image)plt.show()# example usage
dataset = MNISTDataset("mnist/t10k-images-idx3-ubyte", "mnist/t10k-labels-idx1-ubyte")
loader = MNISTDataLoader(dataset, batch_size=32, shuffle=True)
for i, batched_data in enumerate(loader):if i == 0:images, labels = list(zip(*batched_data))print(len(batched_data))show_mnist_image(images, labels)
首先是正则化的考虑,计算 mean 和 std
class MNISTDataset:def __init__(self, images_path, labels_path, train, mean=None, std=None):self.images_path = images_pathself.labels_path = labels_pathself.images, self.labels = self.load_mnist_data()# flatten Nx28x28 -> Nx784self.images = self.images.reshape(len(self.images), -1)if train:self.images, self.mean, self.std = self.normalize(self.images)else:self.images, self.mean, self.std = self.normalize(self.images, mean, std)@staticmethoddef normalize(x, mean=None, std=None):if mean is None:mean = x.mean()if std is None:std = x.std()x = (x - mean) / stdreturn x, mean, std...
然后是考虑 softmax 来处理多分类问题,其计算公式如下:
σ ( z ) j = e z j ∑ k = 1 K e z k f o r j = 1 , . . . , K . \sigma(\mathbf{z})_{j}={\frac{e^{z_{j}}}{\sum_{k=1}^{K}e^{z_{k}}}}\quad{\mathrm{for}}\ \ j=1,...,K. σ(z)j=∑k=1Kezkezjfor j=1,...,K.
假设当前输入 x = [x0, x1, x2],经过 softmax 后的输出是怎样的呢?
我们定义 esum = exp(x0) + exp(x1) + exp(x2)
故最终的输出为 output = [exp(x0)/esum,exp(x1)/esum,exp(x2)/esum]
我们还需要考虑一个溢出问题, 假设当前的输入 x = [100, 500, 1000],exp(1000)会导致溢出,而我们常见的解决手段是将输入中的每个值减去其中的最大值即 x-x.max()
对应的 softmax crossentropy loss 的计算如下:
l o s s = − [ y ∗ ln ( p ) ] loss = -[y * \ln(p)] loss=−[y∗ln(p)]
关于 loss 的导数推导具体可参考 一文详解Softmax函数
还有一点值得注意的是对于 softmax 而言,labels 需要转换为 onehot 编码的格式
通过上述分析,循环更新迭代的示例代码如下所示:
# 定义k和b
num_classes = 10
k = np.random.randn(784, num_classes)
b = np.zeros((1, num_classes))def softmax(x, dim):x = np.exp(x - x.max())return x / x.sum(axis=dim, keepdims=True)def crossentropy_loss(logits, onehot_labels):batch = logits.shape[0]return -(onehot_labels * np.log(logits)).sum() / batchlr = 1e-2
niter = 0for epoch in range(10):for images, labels in train_loader:niter += 1# 32x784 @ 784x10predict = images @ k + b# predict -> logitslogits = softmax(predict, dim=1)# softmax crossentropy loss# loss = -(y * ln(p))batch = logits.shape[0]onehot_labels = np.zeros_like(logits)# labels(32,) -> onehot(32,10)onehot_labels[np.arange(batch), labels] = 1loss = crossentropy_loss(logits, onehot_labels)if niter % 100 == 0:print(f"Epoch: {epoch}, Iter: {niter}, Loss: {loss:.3f}")G = (logits - onehot_labels) / batch# C = AB# dA = G @ B.T# dB = A.T @ Gdelta_k = images.T @ Gdelta_b = G.sum(axis=0, keepdims=True)k = k - lr * delta_kb = b - lr * delta_b# evaluateall_predict = []for images, labels in test_loader:predict = images @ k + blogits = softmax(predict, dim=1)predict_labels = logits.argmax(axis=1)all_predict.extend(predict_labels == labels)accuracy = np.sum(all_predict) / len(all_predict) * 100print(f"Epoch: {epoch}, Evaluate Test Set, Accuracy: {accuracy:.3f} %")
上述示例代码展示了 MNIST 数据集上使用逻辑回归进行多分类的示例。它使用 softmax 函数将模型输出转换为概率分布,并使用交叉熵损失函数衡量模型输出与真实标签之间的差异。通过梯度下降法更新权重和偏置,以最小化损失函数。我们还使用测试集计算模型的准确率,并通过迭代训练和评估,可以逐步优化模型,并得到在 MNIST 数据集上分类准确的结果。
运行效果如下:
为了更好的查看结果,我们可以对其进行可视化操作,具体实现代码如下:
for images, labels in test_loader:predict = images @ k + blogits = softmax(predict, dim=1)predict_labels = logits.argmax(axis=1)pixels = (images * train_dataset.std + train_dataset.mean).astype(np.uint8).reshape(-1, 28, 28)for image, predict, gt in zip(pixels, predict_labels, labels):plt.imshow(image)plt.title(f"Predict: {predict}, GT: {gt}")plt.show()
需要注意的是,在可视化时需要将对应图片的像素值通过 mean 和 std 恢复到原始范围,并将其 reshape 为 28x28 的大小,方便可视化。
模型预测的部分结果如下所示:
可以看到模型预测的部分结果还是有误的,这也很正常,毕竟分类识别准确率在 86.6% 左右。
完整的示例代码如下所示:
import numpy as np
import matplotlib.pyplot as pltclass MNISTDataset:def __init__(self, images_path, labels_path, train, mean=None, std=None):self.images_path = images_pathself.labels_path = labels_pathself.images, self.labels = self.load_mnist_data()# flatten Nx28x28 -> Nx784self.images = self.images.reshape(len(self.images), -1)if train:self.images, self.mean, self.std = self.normalize(self.images)else:self.images, self.mean, self.std = self.normalize(self.images, mean, std)@staticmethoddef normalize(x, mean=None, std=None):if mean is None:mean = x.mean()if std is None:std = x.std()x = (x - mean) / stdreturn x, mean, stddef __len__(self):return len(self.images)def __getitem__(self, index):image, label = self.images[index], self.labels[index]return image, labeldef load_mnist_data(self):# load labelswith open(self.labels_path, "rb") as f:magic_number, num_of_items = np.frombuffer(f.read(8), dtype=">i", count=2, offset=0)labels = np.frombuffer(f.read(), dtype=np.uint8, count=-1, offset=0)# load imageswith open(self.images_path, "rb") as f:magic_number, num_of_images, rows, cols = np.frombuffer(f.read(16), dtype=">i", count=4, offset=0)pixels = np.frombuffer(f.read(), dtype=np.uint8, count=-1, offset=0)images_matrix = pixels.reshape(num_of_images, rows, cols)return images_matrix, labelsclass MNISTDataLoader:def __init__(self, dataset, batch_size, shuffle=True):self.dataset = datasetself.batch_size = batch_sizeself.shuffle = shuffledef __iter__(self):self.indexs = np.arange(len(self.dataset))if self.shuffle:np.random.shuffle(self.indexs)self.cursor = 0return selfdef __next__(self):begin = self.cursorend = self.cursor + self.batch_sizeif end > len(self.dataset):raise StopIteration()self.cursor = endbatched_data = []for index in self.indexs[begin:end]:item = self.dataset[index]batched_data.append(item)# return batched_datareturn [np.stack(item, axis=0) for item in list(zip(*batched_data))] # 训练集
train_dataset = MNISTDataset("mnist/train-images-idx3-ubyte", "mnist/train-labels-idx1-ubyte", train=True)
train_loader = MNISTDataLoader(train_dataset, batch_size=32, shuffle=True)# 测试集
test_dataset = MNISTDataset("mnist/t10k-images-idx3-ubyte", "mnist/t10k-labels-idx1-ubyte",train=False,mean=train_dataset.mean,std=train_dataset.std
)
test_loader = MNISTDataLoader(test_dataset, 10)# 定义k和b
num_classes = 10
k = np.random.randn(784, num_classes)
b = np.zeros((1, num_classes))def softmax(x, dim):x = np.exp(x - x.max())return x / x.sum(axis=dim, keepdims=True)def crossentropy_loss(logits, onehot_labels):batch = logits.shape[0]return -(onehot_labels * np.log(logits)).sum() / batchlr = 1e-2
niter = 0for epoch in range(10):for images, labels in train_loader:niter += 1# 32x784 @ 784x10predict = images @ k + b# predict -> logitslogits = softmax(predict, dim=1)# softmax crossentropy loss# loss = -(y * ln(p))batch = logits.shape[0]onehot_labels = np.zeros_like(logits)# labels(32,) -> onehot(32,10)onehot_labels[np.arange(batch), labels] = 1loss = crossentropy_loss(logits, onehot_labels)if niter % 100 == 0:print(f"Epoch: {epoch}, Iter: {niter}, Loss: {loss:.3f}")G = (logits - onehot_labels) / batch# C = AB# dA = G @ B.T# dB = A.T @ Gdelta_k = images.T @ Gdelta_b = G.sum(axis=0, keepdims=True)k = k - lr * delta_kb = b - lr * delta_b# evaluateall_predict = []for images, labels in test_loader:predict = images @ k + blogits = softmax(predict, dim=1)predict_labels = logits.argmax(axis=1)all_predict.extend(predict_labels == labels)accuracy = np.sum(all_predict) / len(all_predict) * 100print(f"Epoch: {epoch}, Evaluate Test Set, Accuracy: {accuracy:.3f} %")for images, labels in test_loader:predict = images @ k + blogits = softmax(predict, dim=1)predict_labels = logits.argmax(axis=1)pixels = (images * train_dataset.std + train_dataset.mean).astype(np.uint8).reshape(-1, 28, 28)for image, predict, gt in zip(pixels, predict_labels, labels):plt.imshow(image)plt.title(f"Predict: {predict}, GT: {gt}")plt.show()
总结
本次课程学习了矩阵求导相关知识,后续实现多个线性回归或者逻辑逻辑回归模型可以考虑使用矩阵方式来表达
本次课程主要学习了多元和多模型线性回归,还是从房价预测的案例出发,考虑多个变量影响房价,从而引出多元线性回归模型;考虑多个输出即多个模型,不仅预测房价还预测租金,引出多模型线性回归,从矩阵角度对其进行分析,重新回顾并学习了矩阵求导的相关知识。
本次课程主要学习了多分类逻辑回归模型,对手写识别数据集进行分类。我们对于正则化的考虑是对训练集只使用一个 mean 和 std,因为我们认为每个 pixel 之间并不是相互独立的,而需要将其视为一个整体,而对于测试集的 mean 和 std 我们直接使用训练集的即可。此外,与二分类不同的是我们使用 softmax 函数将模型的输出转换为概率分布,并使用 softmax crossentropy loss 作为该多分类逻辑回归模型的损失函数。
这篇关于AutoCV第九课:ML基础的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!