0


动手学深度学习(预备知识)

1.安装

下面这个网址有具体安装步骤

【PyTorch深度学习快速入门教程(绝对通俗易懂!)【小土堆】】https://www.bilibili.com/video/BV1hE411t7RN?vd_source=1b9a0d7cc95ffe0bada9111b709b98a4

https://zh-v2.d2l.ai/chapter_installation/index.html

笔者用的是conda环境的pytorch,大家也记得装一下,讲解的代码都是基于pytorch的。

如果想要其他替换的代码,可以自主在上面链接书中找一下。

如果安装不懂的后台踢就行

2.预备知识

2.1数据操作

2.11.Tensor基础

(1)Tensor定义

中文名字叫做张量(可以视为多维数组)

基于 标量 向量 矩阵 来看张量,上述三个分别为零维,一维,二维张量

(2)GPU和CPU
  • CPU:CPU更适合处理复杂逻辑和少量线程的计算任务。它拥有少量的核心,但每个核心都能处理复杂的任务和逻辑。在处理逻辑复杂、条件分支多的任务上,CPU具有优势。然而,在处理深度学习中大量并行计算任务时,CPU的效率相对较低。由于CPU并非专为大规模并行计算设计,其计算速度在处理这类任务时可能无法满足需求。
  • GPU:GPU则专为并行计算而设计,拥有大量的小核心,这些小核心适合并行执行相似的任务。在深度学习中,GPU针对大量并行处理的任务(如矩阵乘法、卷积等)表现出色,可以大大缩短计算时间。GPU的并行计算能力在处理深度学习中的计算密集型任务时,通常远超CPU,能够提供更高的计算效率。
(3)Tensor数据类型

分为GPU和CPU两种变体

在PyTorch中,量化是一种将浮点计算转换为定点计算的技术,旨在减少模型的大小、加速推理过程,同时尽量保持模型的精度

以下是一些基本操作

(4)Tensor的创建

共享内存....

import torch
​
# 创建 形状为 2x3x4 的张量, 默认用 0 填充
t = torch.Tensor(2, 3, 4)
​
print(type(t))              # <class 'torch.Tensor'>
print(t.type())             # torch.FloatTensor
print(t.dtype)              # torch.float32
print(t.size())             # torch.Size([2, 3, 4])
print(t.shape)              # torch.Size([2, 3, 4])
print(t)
"""
tensor([[[0., 0., 0., 0.],
         [0., 0., 0., 0.],
         [0., 0., 0., 0.]],
​
        [[0., 0., 0., 0.],
         [0., 0., 0., 0.],
         [0., 0., 0., 0.]]])
"""
​
# 使用预先存在的数据 (Python序列 或 numpy.ndarray) 创建张量
t = torch.Tensor([[1, 2, 3], [4, 5, 6]])
print(t.dtype)              # torch.float32
print(t)
"""
tensor([[1., 2., 3.],
        [4., 5., 6.]])
"""
(5)数学运算
import torch
​
t = torch.zeros((2, 3))         # size 可以以 序列的形式传入, 也可以以 *size 的形式传入
print(t, t.dtype)
"""
tensor([[0., 0., 0.],
        [0., 0., 0.]]) torch.float32
"""
​
t0 = torch.tensor([[1, 2, 3], [4, 5, 6]], dtype=torch.int8)
t = torch.ones_like(t0)         # size 和 dtype 都保持与 t0 一致
print(t)
"""
tensor([[1, 1, 1],
        [1, 1, 1]], dtype=torch.int8) torch.int8
"""
​
t = torch.full((3, 5), 100)
print(t)
"""
tensor([[100, 100, 100, 100, 100],
        [100, 100, 100, 100, 100],
        [100, 100, 100, 100, 100]])
"""
​
t = torch.eye(3, 5)
print(t)
"""
tensor([[1., 0., 0., 0., 0.],
        [0., 1., 0., 0., 0.],
        [0., 0., 1., 0., 0.]])
"""
​
t = torch.arange(0, 10, 1)
print(t)
"""
tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
"""
​
t = torch.linspace(0, 10, 5)
print(t)
"""
tensor([ 0.0000,  2.5000,  5.0000,  7.5000, 10.0000])
"""
​
t = torch.rand((1, 5))
print(t)
"""
tensor([[0.7706, 0.1781, 0.2407, 0.4579, 0.0864]])
"""
​
t = torch.randint(0, 9, (3, 5))
print(t)
"""
tensor([[3, 4, 8, 2, 7],
        [5, 8, 7, 0, 7],
        [0, 0, 8, 1, 8]])
"""
​
t = torch.randn((3, 6))
print(t)
"""
tensor([[ 0.9932, -1.1636, -0.3698, -0.6131,  0.0571,  0.6054],
        [-0.5878, -0.1389, -1.6374, -0.2527,  0.3637, -0.3284],
        [-0.9119,  0.3085,  0.8913,  0.9905,  0.6498, -0.7845]])
"""
​
(6)索引和切片

可以参考csdn中tensor基础讲解,在此就不罗嗦了

(7)连接cat
同上
(8)拆分chunk
import torch
​
t = torch.tensor([[1, 2, 3, 4], [5, 6, 7, 8]])
print(t.shape)          # torch.Size([2, 5])
print(t)
"""
tensor([[1, 2, 3, 4],
        [5, 6, 7, 8]])
"""
​
for x in torch.chunk(t, chunks=2, dim=0):
    print(x)
"""
tensor([[1, 2, 3, 4]])
tensor([[5, 6, 7, 8]])
"""
​
for x in torch.chunk(t, chunks=2, dim=1):
    print(x)
"""
tensor([[1, 2],
        [5, 6]])
tensor([[3, 4],
        [7, 8]])
"""

这里需要了解dim=0和等于1的区别

一个是 外维 一个是内维

dim范围是-2到1(在这里的范围)

dim范围和输入的维度有关

2.12 常用的操作入门

import torch
x=torch.arange(12)
print([x])
print(x.shape)

a=x.reshape(3,-1)
print(a)

y=x.reshape(3,4)
print(y)
print(y.shape)
print(y.numel())

print(torch.zeros(3,4))
print(torch.ones(3,4))
print(torch.randn(2,3,4))#随机梯度
print(torch.rand(9))#随机0,1

arange(12)表示0到11顺序的矩阵

print([x])是在print(x)整体的输出在加[]而不是仅仅在矩阵外面加一层括号

x.shape表示这个矩阵的大小,比如x就是一维矩阵,而y就是3*4的矩阵

x.reshape(3,4)表示把一维矩阵转换为二维矩阵,好用多用,但是记得两个矩阵的元素数量需要相同不然就报错了

x.reshape(3,-1)等同于x.reshape(3,4)也等同于x.reshape(-1,4),因为上面我们以及定义了x的元素个数总共有12个,所以给定矩阵的行数或者列数的一个,系统自动就转换了

后面的东西前面都见过了就不赘述了

2.13运算符

import torch
# x=torch.tensor([1,2,3,4])
# y=torch.tensor([5,6,7,8])
# print(x+y)
# print(x/y)
# print(x**y)
# print(x.exp())#指数
# print(torch.exp(y))

a=torch.arange(12,dtype=torch.float64).reshape(3,4)
b=torch.tensor([[4,3,2,1],[8,7,6,5],[4,3,2,1]])
print(a)
print(b)

print(a<b)
print(a==b)
# print(torch.cat((a,b),dim=0))#dim范围在-2到1
# print(torch.cat((a,b),dim=1))
# print(torch.cat((a,b),dim=-1))
# print(torch.cat((a,b),dim=-2))
print(a.sum())

这个运算结果就不展示了,大家自己动手探索一下

特别是cat的几种连接方式

2.14广播机制

广播机制就是两个形状不同的矩阵,通过适当复制元素来扩展一个或两个数组,以便在转换之后,两个张量具有相同的形状

看上面图片,我用的是第一个矩阵加第二个矩阵哈,所以你一一对应,第一行就是0+0,0+1,0+2

第二行1+0,1+1,1+2以此类推

2.15索引和切片

import torch
a=torch.arange(12,dtype=torch.float64).reshape(3,4)
# print(a)
# print(a[-1])
# print(a[1:3])
# a[0,1]=9
# print(a)

a[0:1,:1]=12
print(a)
a[0:1,:]=12
print(a)
a[0:1,-1:]=12
print(a)

注释部分的输出也在下面这个图片里

a[-1]就是最后一行

a[1:3]就是第二行和最后一行,记得是从1开始,但不包含3,如果把3改为2,那么就只输出第一行了,还有记得是从0开始计数

a[0,1]j就是把对应的位置数值进行修改

这里留一个问题,

a[0:1,-1:]=12
print(a)这行代码其实正常运行与下面图片输出不符,大家可以把a重置自己运行一遍

2.16节省内存

import torch
a=torch.arange(12,dtype=torch.float64).reshape(3,4)
b=torch.tensor([[4,3,2,1],[8,7,6,5],[4,3,2,1]])
print(id(a))
c=torch.zeros_like(a)
print(id(c))
c[:]=a+b  #c=a+b是不行的
print(id(c))
# a+=b
# print(id(a))
# a+=b
# print(id(a))
# a+=b
# print(id(a))
# a+=b
# print(id(a))
# a+=b
# print(id(a))
# for i in range(100):  #重复了100次依旧没有改变
#     a += b
#     print(id(a))

运行一些操作可能会导致为新结果分配内存。 例如,如果我们用

Y = X + Y

,我们将取消引用

Y

指向的张量,而是指向新分配的内存处的张量

我们可以使用切片表示法将操作的结果分配给先前分配的数组,例如

Y[:] = <expression>

。 为了说明这一点,我们首先创建一个新的矩阵

Z

,其形状与另一个

Y

相同, 使用

zeros_like

来分配一个全0的块。

如果在后续计算中没有重复使用

X

, 我们也可以使用

X[:] = X + Y

X += Y

来减少操作的内存开销。

2.17张量标量转换

张量转标量用.item()

标量转张量用.tensor()【比如说一个numpy数组c,可以

d=torch.tensor(c)来转为张量

2.2数据预处理

数据预处理一般用pandas库

pandas库啊,你用用就知道咋样了

一般啊,你先用os库创建个文件夹,存放人工数据集

在一顿操作进行命名,然后求值的时候你可能会遇到求平均值.mean()好用多用

最后处理缺失值(NaN)

摘抄于书中便于大家理解(主要我有点瞌睡了,不好意思):通过位置索引

iloc

,我们将

data

分成

inputs

outputs

, 其中前者为

data

的前两列,而后者为

data

的最后一列。 对于

inputs

中缺少的数值,我们用同一列的均值替换“NaN”项。

pandas库真是好库

  • Series:一维数组,可以存储任何数据类型(整数、字符串、浮点数、Python 对象等),每个元素都有一个标签(索引)。
  • DataFrame:二维的、表格型的数据结构,可以看作是由多个 Series 组成的字典(共享同一个索引)

这是他的核心结构,第一次见他还是爬虫处理数据时,好久了啊,旧码重写,就像旧事重提。

2.3线性代数

明天更新啊哈哈

———————————————————————————————————————————

早上好朋友们 今天是7.10号,初中朋友要同学聚会,可惜这次去不了了...

2.31标量

标量由只有一个元素的张量表示

x = torch.tensor(3.0)
y = torch.tensor(2.0)

2.32向量

向量可以被视为标量值组成的列表。 这些标量值被称为向量的元素(element)或分量(component)

x = torch.arange(4)

2.33张量

基于 标量 向量 矩阵 来看张量,上述三个分别为零维,一维,二维张量

引入矩阵的转置概念:假设有一个34的矩阵,转置之后就成了43的矩阵

这个就是转置的样式,假设B=A转置,那么A【ij】=B【ji】

两个矩阵的按元素乘法称为Hadamard积(Hadamard product)

假设A和B都是34的有序矩阵,那么AB的每一个结果就是A【ij】*B【ij】

tips:将张量乘以或加上一个标量不会改变张量的形状,其中张量的每个元素都将与标量相加或相乘

2.34降维

我们先理解axis

对矩阵内的元素我们常常使用求和操作,正常的.sum()会将矩阵内所有的元素进行求和

假设我们有一个形状为

(m, n, p)

的三维张量,其

axis

的范围可以解释如下:

  • axis = 0:沿着第一个维度(大小为m的维度)进行操作。
  • axis = 1:沿着第二个维度(大小为n的维度)进行操作。
  • axis = 2:沿着第三个维度(大小为p的维度)进行操作。
  • axis = -1:与axis = 2相同,也是沿着第三个维度(大小为p的维度)进行操作。
  • axis = -2:沿着第二个维度(大小为n的维度)进行操作,与axis = 1相同

然后我们以实际例子来验证

由结果不难发现,axis=0时把(2,3,4)中的2压缩了,生成了一个(3,4)的矩阵

大家可以试试更多维度的张量,axis范围处于最大维度的正负数之间

2.35点积

这个就是x,y向量点积的表示方法

点积的结果:相同位置的按元素乘积的和

torch.dot(x,y)为代码形式

2.36矩阵向量积

学会了点积就会向量积了

a的行向量中每一个元素与相对应的b的元素相乘再加和得到向量积输出

比如说14=00+11+22+33

2.37矩阵相乘

分析一下42=00+13+26+39也就是a矩阵的第一行元素与b向量的第一列元素分别相乘再求和

也就是进行第一个矩阵行数m*第二个矩阵列数n次向量积运算

2.38范数

矩阵范数分为四类:列范数,行范数,l2范数和F范数

1..列范数,就是每一列绝对值求和中的最大值

比如说这个矩阵求每一列绝对值求和为【6,14,4】

那么列范数就是14

2.行范数,就是每一行绝对值求和中的最大值

依旧按上面矩阵为例每一行绝对值求和为【8,3,13】

  1. 2范数

矩阵a转置a的最大特征值下的平方根

先算a转置*a

入e-a=0转置a中入为最大特征值

求这个入是线代中求特征值的

4.F范数就是各个元素平方和再开根

2.4微积分

2.41导数和微分

这里提一个东西

如果出现:OMP: Error #15: Initializing libiomp5md.dll, but found libiomp5md.dll already initialized.的报错

import os
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"#在程序中发现了多个 OpenMP 运行时库的初始化。这可能会导致性能下降或产生错误结果,因为多个运行时库之间的交互可能会导致混乱。
#上述代码可以避免报错

以f(x)=3x*2-4x为例,内容都体现在代码注释里了

import torch
import numpy as np
from matplotlib_inline import backend_inline
from matplotlib import pyplot as plt
from d2l import torch as d2l

def f(x):
    return 3 * x ** 2 - 4 * x
def numerical_lim(f, x, h):
    return (f(x + h) - f(x)) / h

h = 0.1
for i in range(5):
    print(f'h={h:.5f}, numerical limit={numerical_lim(f, 1, h):.5f}')
    h *= 0.1#使h接近于0
def use_svg_display():  #@save
    """使用svg格式在Jupyter中显示绘图"""
    backend_inline.set_matplotlib_formats('svg')
def set_figsize(figsize=(3.5, 2.5)):
    """设置matplotlib的图表大小"""
    use_svg_display()
    d2l.plt.rcParams['figure.figsize'] = figsize

def set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend):
    """设置matplotlib的轴

        设置matplotlib Axes对象的属性。

        参数:
        axes (matplotlib.axes.Axes): 需要设置的Axes对象。
        xlabel (str): X轴的标签。
        ylabel (str): Y轴的标签。
        xlim (tuple): X轴的范围,格式为(最小值, 最大值)。
        ylim (tuple): Y轴的范围,格式为(最小值, 最大值)。
        xscale (str): X轴的比例尺,如'linear', 'log'等。线性比例尺和对数比例尺
        yscale (str): Y轴的比例尺,如'linear', 'log'等。
        legend (list of tuples, optional): 图例项,格式为[(label1, line1), (label2, line2), ...]
                                          其中label是字符串,line是Axes上的线对象。
        """
    axes.set_xlabel(xlabel)
    axes.set_ylabel(ylabel)
    axes.set_xscale(xscale)
    axes.set_yscale(yscale)
    axes.set_xlim(xlim)
    axes.set_ylim(ylim)
    if legend:
        axes.legend(legend)
    axes.grid()#添加网格线
#@save
def plot(X, Y=None, xlabel=None, ylabel=None, legend=None, xlim=None,
         ylim=None, xscale='linear', yscale='linear',#linear指的是线性刻度

         fmts=('-', 'm--', 'g-.', 'r:'), figsize=(3.5, 2.5), axes=None):
    # ax.plot(x, y1, fmts[0], label='Data 1')  # 使用'-'线型
    # ax.plot(x, y2, fmts[1], label='Data 2')  # 使用'm--'线型,m代表品红色
    # ax.plot(x, y3, fmts[2], label='Data 3')  # 使用'g-.'线型,g代表绿色
    # ax.plot(x, y4, fmts[3], label='Data 4')  # 使用'r:'线型,r代表红色
    """绘制数据点"""
    if legend is None:
        legend = []

    set_figsize(figsize)
    axes = axes if axes else d2l.plt.gca()#获取当前坐标轴

    # 如果X有一个轴,输出True
    def has_one_axis(X):
        return (hasattr(X, "ndim") and X.ndim == 1 or isinstance(X, list)
                and not hasattr(X[0], "__len__"))
    '''
    hasattr(X, "ndim") and X.ndim == 1:
这部分首先检查X是否具有ndim属性。在NumPy数组中,ndim属性表示数组的维度数。
如果X具有ndim属性,并且X.ndim == 1,即X是一个一维数组,那么这部分条件为真。

isinstance(X, list) and not hasattr(X[0], "__len__"):

这部分首先检查X是否是一个列表(list)。
如果X是列表,接下来检查列表中的第一个元素(X[0])是否没有__len__方法。在Python中,许多容器类型(如列表、元组、字符串等)都有__len__方法,用于返回容器中元素的数量。如果一个对象没有__len__方法,那么它通常不是容器类型,也就是说,它不是另一个列表、元组、字符串等。
如果X是列表,并且其第一个元素不是容器类型(即没有__len__方法),那么这部分条件为真。'''

    if has_one_axis(X):
        X = [X]
    if Y is None:
        X, Y = [[]] * len(X), X
        '''
        X, Y = [[]] * len(X), X:这行代码做了两件事。
    首先,它创建了一个与X长度相同的列表列表(二维列表),其中每个内部列表都是空的([[]] * len(X))。
    然后,它将原始的X赋值给Y。'''
    elif has_one_axis(Y):
        Y = [Y]
    if len(X) != len(Y):
        X = X * len(Y)
    axes.cla()#清除坐标轴内容
    for x, y, fmt in zip(X, Y, fmts):
        if len(x):
            axes.plot(x, y, fmt)
        else:
            axes.plot(y, fmt)
    set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
x = np.arange(0, 3, 0.1)
plot(x, [f(x), 2 * x - 3], 'x', 'f(x)', legend=['f(x)', 'Tangent line (x=1)'])

plt.show()

2.42偏导数和梯度

一、定义

  1. 偏导数(Partial Derivative): - 偏导数用于多变量函数,它描述了函数在一个特定变量方向上的变化率,而其他变量保持不变。- 例如,对于函数f(x,y),其关于x的偏导数表示为∂x∂f​,关于y的偏导数表示为∂y∂f​。
  2. 梯度(Gradient): - 梯度是一个矢量,它包含了一个多变量函数所有偏导数的信息。- 对于函数f(x,y),其梯度是一个二维向量,表示为∇f=(∂x∂f​,∂y∂f​)。对于更高维的函数f(x1​,x2​,…,xn​),其梯度则是一个n维向量。 ---

二、关系

  1. 梯度是偏导数的向量形式: - 梯度将函数在各个方向上的偏导数组合成一个向量,这个向量在几何上表示了函数在该点处变化最快的方向。- 因此,可以说梯度是偏导数的集合,以向量的形式呈现。
  2. 梯度方向与变化率: - 梯度的方向是函数在该点处变化最快的方向(即最陡峭的方向),而梯度的模(长度)则表示了这种变化率的大小。- 换句话说,梯度不仅告诉我们函数在哪个方向上变化最快,还告诉我们这种变化的速度有多快

可以参考哔哩哔哩中的梯度动画进行理解

2.43自动微分

import torch

x = torch.arange(4.0)
x
x.requires_grad_(True)  # 等价于x=torch.arange(4.0,requires_grad=True)
x.grad  # 默认值是None

x.requires_grad_是在设置随后的操作之后能够计算x的梯度

x.grad是存储x的梯度

y = 2 * torch.dot(x, x)
y

然后

x

是一个长度为4的向量,计算

x

x

的点积,得到了我们赋值给

y

的标量输出。 接下来,通过调用反向传播函数来自动计算

y

关于

x

每个分量的梯度,并打印这些梯度。

y.backward()
x.grad

tips:在PyTorch中,每次调用

.backward()

后,PyTorch会保留计算的梯度,但如果你再次调用

.backward()

而不先清零梯度(使用

optimizer.zero_grad()

x.grad.zero_()

),PyTorch会累加梯度而不是覆盖它们。然而,这通常不会导致

x.grad

None

,除非在调用

.backward()

之前或之后显式地将

x.grad

设置为

None

另外x得为向量,标量可不行

2.44分离计算

z与y,x有关,y与x有关

z与两个变量有关,想求x的偏导,就得先用一个u存放y的数值视作常数。

x.grad.zero_()
y = x * x
u = y.detach()
z = u * x

z.sum().backward()
x.grad == u

.detach()就是创建一个与y一样的张量给u

计算图(Computational Graph)是一个用于表示和存储所有操作(如加法、乘法、激活函数等)及其依赖关系的结构。当你对张量(Tensor)进行操作时,PyTorch会自动构建一个计算图来跟踪这些操作。这个计算图对于自动微分(Automatic Differentiation)至关重要,因为它允许PyTorch在需要时反向遍历图来计算梯度。

就是啥吧,你z只想影响x,不想影响y与x的梯度,这个时候就需要u暂时顶替一下

x成绩不好,z是老师要找家长,x怕父母知道了影响家庭和睦,所以找来u来充当父母去跟老师谈话。

2.5概率

求我现在是否晚饭的概率

先掷一个骰子吧

fair_probs = torch.ones([6]) / 6
multinomial.Multinomial(1, fair_probs).sample()

.sample()是把样本都取出来

6个样本只有一个是1其他都是0

multinomial.Multinomial(10, fair_probs).sample()

这个的话就是掷10次骰子看每个反向的次数

如果是概率的话就除以掷的次数

2.51概率论知识

1.任意概率不为0

2.总概率为1

3.联合概率,也就是a和b同时发生

4.贝叶斯定理

想象你是一位侦探,正在调查一起案件。你有两个嫌疑人,我们称他们为A和B。在没有进一步证据的情况下,你根据初步调查认为A和B的作案嫌疑是相等的,即各占50%的嫌疑。然而,随着调查的深入,你发现了一些新的线索,这些线索可能与案件有关,也可能与嫌疑人有关。

贝叶斯定理的比喻

  1. 先验概率:在没有新线索之前,你认为A和B的作案嫌疑各为50%。这里的50%就是先验概率,它是你根据已知信息(即初步调查)对事件发生的概率所做的估计。
  2. 新线索(证据):调查过程中,你发现了一个重要的指纹线索,经过比对发现该指纹与嫌疑人A的指纹相匹配。这个指纹线索就是新的证据,它会影响你对A和B作案嫌疑的判断。
  3. 似然率(调整因子):指纹线索与A相匹配这一事实,增加了A作案的可能性,同时降低了B作案的可能性。这个增加或减少的比例就是似然率或调整因子。在这个比喻中,似然率可能非常高,因为指纹是高度个人化的证据。
  4. 后验概率:结合先验概率和新线索的似然率,你可以计算出在发现指纹线索后A和B的作案嫌疑。这个新的嫌疑比例就是后验概率。由于指纹线索与A高度匹配,因此A的后验概率会远高于50%,而B的后验概率则会相应降低。

具体计算

虽然在这个比喻中我们没有进行具体的数值计算,但贝叶斯定理的公式可以帮助我们进行这样的计算。如果设A作案为事件A,指纹线索为事件B,则贝叶斯定理可以表示为:

P(A|B) = P(B|A) * P(A) / P(B)

其中:

  • P(A|B) 是后验概率,即在发现指纹线索后A作案的概率。
  • P(B|A) 是似然率,即如果A作案,则发现指纹线索的概率。
  • P(A) 是先验概率,即在没有新线索之前A作案的概率。
  • P(B) 是指纹线索出现的总概率,它可以通过全概率公式计算得到。

期望和方差呢,都蛮不错的


本文转载自: https://blog.csdn.net/Q268191051011/article/details/140302737
版权归原作者 斩棘生. 所有, 如有侵权,请联系我们删除。

“动手学深度学习(预备知识)”的评论:

还没有评论