系列回顾:前六篇(第11-16篇)把概率与信息论打透了——随机变量、贝叶斯定理、MLE、交叉熵、KL散度。我们现在知道了训练的目标:最小化损失函数(NLL/交叉熵)。这一篇回答下一个问题:怎么最小化? 梯度下降是深度学习优化的核心算法,从它的几何直觉讲起,把全量梯度下降、学习率的选择、鞍点与局部最优,以及梯度下降在深度网络上的实际表现,全部讲透。
训练一个神经网络,数学上就是求解一个优化问题:
$$\hat{\theta} = \arg\min_\theta L(\theta)$$
其中 $L(\theta)$ 是损失函数(比如交叉熵),$\theta$ 是所有参数(DeepSeek V3 有 6710 亿个)。
这个问题有几个特点让它很难直接求解:
第一,参数空间极高维。 6710 亿维的空间,不可能枚举所有点,也不可能画图直觉感受。
第二,没有解析解。 神经网络的非线性使得令 $\nabla_\theta L = 0$ 求解析最优解几乎不可能。
第三,损失函数极其复杂。 不是简单的凸函数,有无数局部极值、鞍点、平坦区域。
梯度下降是在这些约束下最实用的迭代优化方法。它不保证找到全局最优,但在实践中效果极好——几乎所有现代大模型都是用梯度下降的某个变体训练的。
第4篇我们讲过导数:$f'(x)$ 表示函数在 $x$ 点的变化率——往 $x$ 增大方向走一小步 $\Delta x$,函数值变化约 $f'(x) \cdot \Delta x$。
要让 $f(x)$ 减小,应该往 $-f'(x)$ 的方向走。
对多变量函数 $L(\theta_1, \theta_2, \ldots, \theta_n)$,梯度(Gradient) 是所有偏导数组成的向量:
$$\nabla_\theta L = \left(\frac{\partial L}{\partial \theta_1},\ \frac{\partial L}{\partial \theta_2},\ \ldots,\ \frac{\partial L}{\partial \theta_n}\right)^T$$
梯度的方向:函数值增加最快的方向。
梯度的模长:该方向上函数值的增加速率。
负梯度的方向:函数值减小最快的方向——这就是梯度下降的核心。
想象损失函数 $L(\theta)$ 是一片山地,参数 $\theta$ 是你在山地上的位置。
梯度下降就是:每次沿着最陡下坡方向走一小步。
目标是走到山谷(极小值点),在那里梯度为零(平坦),无论往哪个方向走都是上坡。
沿单位方向向量 $\vec{v}$($\|\vec{v}\|=1$)的方向导数:
$$\nabla_{\vec{v}} L = \nabla_\theta L \cdot \vec{v} = \|\nabla_\theta L\| \cos\alpha$$
其中 $\alpha$ 是 $\vec{v}$ 与梯度方向的夹角。
这严格证明了:负梯度方向是函数值下降最快的方向。 梯度下降不是经验法则,是数学上可以证明的最优局部下降方向。
从某个初始参数 $\theta^{(0)}$ 出发,重复以下更新:
$$\theta^{(t+1)} = \theta^{(t)} - \alpha \nabla_\theta L(\theta^{(t)})$$
这就是完整的梯度下降算法,简单得出奇,却是深度学习优化的基础。
设损失函数 $L(\theta) = \theta^2$(二次函数,唯一最小值在 $\theta^* = 0$)。
梯度:$\frac{dL}{d\theta} = 2\theta$
从 $\theta^{(0)} = 5$ 开始,学习率 $\alpha = 0.1$:
$$\theta^{(1)} = 5 - 0.1 \times 2 \times 5 = 5 - 1 = 4$$ $$\theta^{(2)} = 4 - 0.1 \times 2 \times 4 = 4 - 0.8 = 3.2$$ $$\theta^{(3)} = 3.2 - 0.1 \times 2 \times 3.2 = 3.2 - 0.64 = 2.56$$
每步乘以因子 $(1 - 2\alpha) = 0.8$,指数级收敛:
$$\theta^{(t)} = (1 - 2\alpha)^t \cdot \theta^{(0)} = 0.8^t \times 5$$
当 $t = 50$:$\theta^{(50)} = 0.8^{50} \times 5 \approx 0.000072$,已经非常接近 0。
设 $L(\theta_1, \theta_2) = \theta_1^2 + 2\theta_2^2$,最小值在 $(0, 0)$。
梯度:$\nabla L = (2\theta_1,\ 4\theta_2)^T$
从 $(\theta_1, \theta_2) = (3, 2)$ 出发,$\alpha = 0.1$:
第一步:
$$\theta_1^{(1)} = 3 - 0.1 \times 2 \times 3 = 3 - 0.6 = 2.4$$ $$\theta_2^{(1)} = 2 - 0.1 \times 4 \times 2 = 2 - 0.8 = 1.2$$
第二步:
$$\theta_1^{(2)} = 2.4 - 0.1 \times 2 \times 2.4 = 2.4 - 0.48 = 1.92$$ $$\theta_2^{(2)} = 1.2 - 0.1 \times 4 \times 1.2 = 1.2 - 0.48 = 0.72$$
注意:$\theta_2$ 方向梯度是 $\theta_1$ 方向的两倍,$\theta_2$ 下降更快,但也更容易振荡(如果学习率太大)。
这个不对称性(不同参数的梯度量级差异大)是后来发展自适应优化器(Adagrad、Adam)的核心动机。
学习率 $\alpha$ 是梯度下降中最重要的超参数,选不好会直接导致训练失败。
每步走得太短,收敛极慢。
对 $L(\theta) = \theta^2$,$\alpha = 0.001$:
$$\theta^{(t)} = (1 - 0.002)^t \times \theta^{(0)} = 0.998^t \times 5$$
需要 $t = \frac{\log(0.001/5)}{\log(0.998)} \approx 4350$ 步才能从 5 降到 0.001。
若 $\alpha = 0.1$ 只需约 80 步。学习率差 100 倍,收敛步数差 50 倍。
步子太大,可能跳过最小值点,来回振荡甚至发散。
对 $L(\theta) = \theta^2$,更新因子 $(1-2\alpha)$:
稳定收敛条件(对 $L(\theta)=\theta^2$):$\alpha < \frac{1}{2}$
一般地,对光滑函数,稳定收敛要求 $\alpha < \frac{2}{L_{smooth}}$,其中 $L_{smooth}$ 是损失函数的 Lipschitz 常数(梯度变化的上界)。
损失函数在不同方向上的"曲率"(二阶导数/Hessian 矩阵的特征值)不同,适合不同方向的学习率也不同:
这是条件数(Condition Number) 问题:如果 Hessian 矩阵最大特征值和最小特征值之比(条件数)很大,朴素梯度下降在两个方向上会有非常不同的收敛速度,造成"之字形"(zigzag)振荡。
这正是动量(Momentum)和自适应学习率(Adam)出现的根本动机——第19篇会详细讲。
DeepSeek V3 的学习率调度:
为什么需要预热?
训练初期,参数随机初始化,离最优值很远,损失函数在很多方向上变化剧烈(梯度噪声大)。此时用大学习率直接冲,容易导致参数剧烈震荡,破坏预训练(继续训练场景)或导致 loss spike(损失突然飙升)。
预热阶段用小学习率先"稳住",让优化器的动量($m_t$)和方差估计($v_t$)积累到合理值,再放大步长加速训练。
对凸函数(损失曲面无局部极小值,只有全局最小值),梯度下降有理论保证:
固定学习率下的收敛率:设 $L$ 是 $\beta$-光滑(Lipschitz 梯度)的凸函数,使用 $\alpha = \frac{1}{\beta}$:
$$L(\theta^{(t)}) - L(\theta^*) \leq \frac{\|\theta^{(0)} - \theta^*\|^2}{2\alpha t} = O\left(\frac{1}{t}\right)$$
每多走 $t$ 步,误差减小到原来的 $\frac{1}{t}$。
强凸函数的指数收敛:如果 $L$ 还是 $\mu$-强凸的(类似 $\theta^2$),收敛更快:
$$L(\theta^{(t)}) - L(\theta^*) \leq \left(1 - \frac{\mu}{\beta}\right)^t \cdot [L(\theta^{(0)}) - L(\theta^*)]$$
这是指数收敛(线性收敛率),类似我们在 $L(\theta)=\theta^2$ 例子里看到的。
神经网络的损失函数是非凸的:有无数个局部极小值、鞍点、平坦区域。
理论保证:对非凸函数,梯度下降只能保证收敛到临界点(梯度为零的点),不保证是全局最小值。
$$\min_{0 \leq t \leq T} \|\nabla_\theta L(\theta^{(t)})\|^2 \leq \frac{2[L(\theta^{(0)}) - L^*]}{\alpha T} = O\left(\frac{1}{T}\right)$$
经过 $T$ 步后,至少有一步的梯度范数很小(接近临界点)。
这是深度学习的一个"奇迹":尽管理论上没有找到全局最优的保证,实践中梯度下降通常找到很好的解。原因有几个:
原因一:过参数化下局部极小值都差不多好
DeepSeek V3 有 6710 亿参数,训练数据约 14.8 万亿 token,参数远多于约束。在这种过参数化情形下,损失曲面的大多数局部极小值都有接近相同的损失值——几乎所有"谷底"都同样深。
原因二:鞍点不是大问题
高维空间里的鞍点,在几乎所有方向上都有负曲率(下降方向),梯度下降可以逃离。真正的局部极小值(所有方向都是上坡)在高维下极其罕见。
原因三:随机性帮助逃脱
SGD(随机梯度下降)引入的噪声可以帮助逃离浅的局部极小值(第18篇详细讲)。
原因四:损失曲面比想象的好
理论研究表明,在合理的假设下,随机初始化后梯度下降以高概率找到全局最优(或接近最优)的临界点。
每步使用全部训练数据计算精确梯度:
$$\nabla_\theta L = \frac{1}{n} \sum_{i=1}^n \nabla_\theta \ell(\theta; x^{(i)}, y^{(i)})$$
优点:梯度精确,方向稳定,理论收敛保证强。
缺点:计算代价极大。DeepSeek V3 的训练集有 14.8 万亿 token,每步都遍历一遍,不可能。
每步只用一个随机样本:
$$\nabla_\theta L \approx \nabla_\theta \ell(\theta; x^{(i)}, y^{(i)})$$
优点:计算快,噪声有助于逃离局部极值。
缺点:梯度估计方差大,更新方向噪声大,不稳定。
每步使用一小批(batch)样本:
$$\nabla_\theta L \approx \frac{1}{B} \sum_{i \in \mathcal{B}} \nabla_\theta \ell(\theta; x^{(i)}, y^{(i)})$$
$B$ 是 batch size(常见值:32、256、4096……)。
优点:方差比 SGD 小($\text{Var} \propto \frac{1}{B}$),比 Batch GD 快,GPU 并行效率高。
DeepSeek V3 的 batch size:全局 batch size 约 4608 序列(约 18.9M token/步),通过梯度累积实现(每累积 $k$ 个小 batch 才做一次参数更新)。
第18篇会详细讲 SGD 和 mini-batch 的数学。
神经网络的损失曲面(Loss Landscape)有几种典型地形:
极小值(Local Minimum):所有方向都是上坡,Hessian 正定。在高维空间中,真正的局部极小值(所有特征值都正)极为罕见,但浅的"宽谷"很常见。
鞍点(Saddle Point):有些方向上坡,有些方向下坡,Hessian 有正有负特征值。高维空间里鞍点数量远多于极小值。梯度下降会暂时停在这里,但 SGD 的随机噪声可以帮助逃离。
平坦区域(Plateau):梯度几乎为零的大片区域,训练进展极慢。
陡峭方向(Sharp Directions):某些方向曲率极大,容易导致梯度爆炸或振荡。
神经网络优化中有一个重要现象:收敛到宽谷(flat minima)的解往往比收敛到尖谷(sharp minima)的解泛化性更好。
直觉:宽谷意味着参数在一个大范围内损失都很低——训练数据和测试数据的损失值差距小。尖谷意味着参数稍微偏移就损失大幅增加——对训练数据过度拟合,测试集性能差。
这解释了为什么: - SGD 的随机噪声往往让模型收敛到更宽的谷(泛化更好),而 Batch GD 容易陷入尖谷 - 大 batch size 训练的模型泛化往往不如小 batch size——大 batch 的梯度噪声小,更容易收敛到尖谷 - 权重衰减(L2 正则化)偏好小参数,而小参数通常对应更宽的谷
从不同的初始化出发,梯度下降可能收敛到不同的极小值。
初始化的重要性:
DeepSeek V3 的某些专家参数从 DeepSeek V2 初始化,正是利用了这个原理——不从随机噪声起步,而是从已经学到大量知识的参数起步。
反向传播时,梯度通过链式法则从输出层传到输入层。在 $L$ 层的深度网络中,梯度大致是各层局部梯度的乘积:
$$\frac{\partial L}{\partial \theta_1} = \frac{\partial L}{\partial h_L} \cdot \frac{\partial h_L}{\partial h_{L-1}} \cdots \frac{\partial h_2}{\partial h_1} \cdot \frac{\partial h_1}{\partial \theta_1}$$
若每层的局部梯度(Jacobian)的奇异值大约为 $\lambda$:
DeepSeek V3 有 61 层,这个问题非常现实。
应对梯度爆炸的标准方法:如果梯度的全局范数超过阈值 $C$,等比例缩小:
$$g \leftarrow g \cdot \min\left(1,\ \frac{C}{\|g\|}\right)$$
其中 $\|g\| = \sqrt{\sum_i g_i^2}$ 是全局梯度范数。
关键是裁剪全局范数而不是逐参数裁剪——这样保持了梯度的方向,只改变了步长,不会扭曲更新方向。
DeepSeek V3 的设置:梯度裁剪阈值 $C = 1.0$。
残差连接(Residual Connection):
$$h_{l+1} = h_l + F(h_l; \theta_l)$$
梯度可以通过"高速公路"直接从输出层流到输入层,不被层间 Jacobian 连乘稀释:
$$\frac{\partial L}{\partial h_l} = \frac{\partial L}{\partial h_{l+1}} \cdot \left(1 + \frac{\partial F}{\partial h_l}\right)$$
即使 $\frac{\partial F}{\partial h_l} \approx 0$(某层退化),至少有 1 保持梯度流动。DeepSeek V3 的每个 Transformer 层都有残差连接。
归一化层(LayerNorm / RMSNorm):控制激活值的方差,防止梯度在层间被放大或缩小太多。DeepSeek V3 每个子层前都有 RMSNorm。
适当的激活函数:ReLU(局部线性,梯度要么是 1 要么是 0)比 Sigmoid(梯度在饱和区趋近 0)更不容易梯度消失。DeepSeek V3 用 SiLU(Swish),类 ReLU 的平滑版本。
import numpy as np import torch import torch.nn as nn np.random.seed(42) torch.manual_seed(42) # ===== 1. 梯度下降的基本实现 ===== print("===== 梯度下降:L(θ) = θ² =====\n") def gradient_descent_1d(init, lr, n_steps): theta = init history = [theta] for _ in range(n_steps): grad = 2 * theta # dL/dθ = 2θ theta = theta - lr * grad history.append(theta) return np.array(history) print(f"{'学习率':>8} | {'初始θ':>6} | {'10步后':>10} | {'50步后':>10} | {'100步后':>10} | 状态") print("-" * 70) for lr in [0.001, 0.1, 0.4, 0.5, 0.9, 1.1]: h = gradient_descent_1d(5.0, lr, 100) status = "发散" if abs(h[-1]) > 1e6 else ("振荡" if h[-1] * h[-2] < 0 else "收敛") print(f"{lr:>8.3f} | {h[0]:>6.2f} | {h[10]:>10.4f} | {h[50]:>10.4f} | {h[100]:>10.6f} | {status}") print(f"\n理论稳定条件:α < 1/L_smooth = 1/2 = 0.5(对 L(θ)=θ²,L_smooth=2)") # ===== 2. 二维梯度下降:路径可视化 ===== print("\n===== 二维梯度下降:L(θ₁,θ₂) = θ₁² + 5θ₂² =====\n") def gradient_2d(theta): return np.array([2 * theta[0], 10 * theta[1]]) # 两个方向曲率不同 def gd_2d(init, lr, n_steps): theta = np.array(init, dtype=float) path = [theta.copy()] for _ in range(n_steps): grad = gradient_2d(theta) theta -= lr * grad path.append(theta.copy()) return np.array(path) init = [3.0, 2.0] print(f"初始点: {init}") print(f"最优解: [0.0, 0.0]\n") print(f"{'步数':>5} | {'θ₁':>8} | {'θ₂':>8} | {'L(θ)':>10} | {'‖∇L‖':>10}") print("-" * 50) path = gd_2d(init, lr=0.09, n_steps=50) for t in [0, 5, 10, 20, 50]: th = path[t] loss = th[0]**2 + 5*th[1]**2 grad_norm = np.linalg.norm(gradient_2d(th)) print(f"{t:>5} | {th[0]:>8.4f} | {th[1]:>8.4f} | {loss:>10.6f} | {grad_norm:>10.6f}") print(f"\n注意:θ₂方向(曲率5×大)收敛更快但也更容易振荡") # ===== 3. 学习率调度:Warmup + 余弦退火 ===== print("\n===== 学习率调度(DeepSeek V3 风格)=====\n") def lr_schedule(step, warmup_steps, total_steps, lr_max, lr_min): if step < warmup_steps: return lr_max * step / warmup_steps # 线性预热 progress = (step - warmup_steps) / (total_steps - warmup_steps) return lr_min + 0.5 * (lr_max - lr_min) * (1 + np.cos(np.pi * progress)) # 余弦退火 warmup = 2000 total = 100000 lr_max = 4.2e-4 lr_min = 4.2e-5 print(f"配置:预热={warmup}步,总步数={total},峰值LR={lr_max:.1e},最小LR={lr_min:.1e}") print(f"\n{'步数':>8} | {'学习率':>12} | {'阶段'}") print("-" * 35) for step in [0, 500, 1000, 2000, 5000, 10000, 50000, 90000, 100000]: lr = lr_schedule(step, warmup, total, lr_max, lr_min) phase = "预热" if step <= warmup else "余弦退火" print(f"{step:>8,} | {lr:>12.2e} | {phase}") # ===== 4. 梯度裁剪 ===== print("\n===== 梯度裁剪(Gradient Clipping)=====\n") def clip_grad_norm(gradients, max_norm): """全局梯度范数裁剪""" total_norm = np.sqrt(sum(np.sum(g**2) for g in gradients)) clip_coef = min(1.0, max_norm / (total_norm + 1e-6)) clipped = [g * clip_coef for g in gradients] return clipped, total_norm, clip_coef # 模拟正常和爆炸梯度 np.random.seed(7) grads_normal = [np.random.randn(100) * 0.1 for _ in range(5)] # 正常梯度 grads_exploded = [np.random.randn(100) * 10.0 for _ in range(5)] # 爆炸梯度 max_norm = 1.0 for name, grads in [("正常梯度", grads_normal), ("爆炸梯度", grads_exploded)]: clipped, total_norm, coef = clip_grad_norm(grads, max_norm) clipped_norm = np.sqrt(sum(np.sum(g**2) for g in clipped)) print(f"{name}:") print(f" 裁剪前范数: {total_norm:.4f}") print(f" 缩放系数: {coef:.4f}") print(f" 裁剪后范数: {clipped_norm:.4f} (应 ≤ {max_norm})") print(f" 方向保持: {'是(系数相同)' if coef < 1.0 else '是(无需裁剪)'}\n") # ===== 5. 梯度消失演示:深度网络的反向传播 ===== print("===== 梯度消失与残差连接 =====\n") def sigmoid(x): return 1 / (1 + np.exp(-x)) def sigmoid_grad(x): return sigmoid(x) * (1 - sigmoid(x)) def relu_grad(x): return (x > 0).astype(float) n_layers = 20 # 模拟不同架构下梯度从输出层传到第1层的衰减 x = np.random.randn(100) # 模拟激活值 # Sigmoid 网络(梯度消失经典案例) grad_sigmoid = 1.0 for l in range(n_layers): local_grad = np.mean(sigmoid_grad(x)) # 约0.25在中间,更小在饱和区 grad_sigmoid *= local_grad # ReLU 网络(部分神经元梯度为1) grad_relu = 1.0 for l in range(n_layers): local_grad = np.mean(relu_grad(x)) # 约0.5的神经元激活 grad_relu *= local_grad # 残差网络(每层有+1的跳跃连接) grad_resnet = 1.0 for l in range(n_layers): local_grad = np.mean(relu_grad(x)) grad_resnet *= (1.0 + local_grad) # 残差:梯度乘以(1 + local_grad) print(f"网络深度: {n_layers}层") print(f"\n{'架构':15s} | {'到达第1层的梯度':>18} | 状态") print("-" * 45) print(f"{'Sigmoid网络':15s} | {grad_sigmoid:>18.2e} | 严重消失!") print(f"{'ReLU网络':15s} | {grad_relu:>18.4f} | 部分消失") print(f"{'残差网络(ResNet)':15s} | {grad_resnet:>18.2f} | 正常流动 ✓") print(f"\n残差连接让梯度路径绕过层间乘法,避免连乘导致的指数衰减") # ===== 6. 梯度下降求解逻辑回归(完整训练循环)===== print("\n===== 完整训练循环:逻辑回归 =====\n") # 生成二分类数据 np.random.seed(42) n = 200 X = np.vstack([np.random.randn(n//2, 2) + [2, 2], np.random.randn(n//2, 2) + [-2, -2]]) y = np.array([1]*(n//2) + [0]*(n//2), dtype=float) # 添加偏置项 X_b = np.hstack([X, np.ones((n, 1))]) theta = np.zeros(3) def sigmoid_np(z): return 1 / (1 + np.exp(-np.clip(z, -500, 500))) def compute_loss_and_grad(theta, X, y): p = sigmoid_np(X @ theta) loss = -np.mean(y * np.log(p + 1e-10) + (1-y) * np.log(1 - p + 1e-10)) grad = X.T @ (p - y) / len(y) # ∂NLL/∂z_k = p̂ - y return loss, grad lr = 0.5 print(f"{'步数':>6} | {'损失':>10} | {'准确率':>8} | {'‖梯度‖':>10}") print("-" * 42) for step in range(201): loss, grad = compute_loss_and_grad(theta, X_b, y) if step % 40 == 0: pred = (sigmoid_np(X_b @ theta) >= 0.5).astype(float) acc = (pred == y).mean() gn = np.linalg.norm(grad) print(f"{step:>6} | {loss:>10.6f} | {acc:>8.2%} | {gn:>10.6f}") theta -= lr * grad print(f"\n最终参数: θ = {theta.round(3)}") print(f"梯度下降从 acc=50% 收敛到接近完美分类,共200步")
这篇文章从梯度的几何直觉出发,把梯度下降的数学原理、学习率的选择逻辑、收敛性分析、以及深层网络的梯度问题,系统讲透了。
第一,梯度 $\nabla_\theta L$ 是损失函数在当前参数处增加最快的方向,负梯度方向是下降最快的方向(用方向导数严格证明)。梯度下降沿负梯度方向更新:$\theta \leftarrow \theta - \alpha \nabla_\theta L$。
第二,学习率 $\alpha$ 是最关键的超参数:太小收敛极慢,太大振荡或发散。稳定条件是 $\alpha < \frac{2}{L_{smooth}}$,实践中用预热(Warmup)+ 余弦退火调度——DeepSeek V3 用 2000 步预热 + 余弦衰减,峰值 $4.2 \times 10^{-4}$。
第三,批量 GD(全数据)精确但不实用;纯 SGD(单样本)快但噪声大;Mini-batch 是实践中的折中,DeepSeek V3 全局 batch size 约 18.9M token/步。
第四,非凸损失函数下梯度下降只能保证收敛到临界点,不保证全局最优。但过参数化使得大多数局部极小值同等优良,SGD 的噪声帮助逃离鞍点,宽谷比尖谷泛化更好(这解释了为什么小 batch 训练泛化性更好)。
第五,深层网络的梯度消失/爆炸来自链式法则的连乘。应对方案:梯度裁剪(阈值 $C=1.0$)防止爆炸;残差连接让梯度绕过层间乘法防止消失;RMSNorm 控制激活值方差;SiLU 激活避免饱和区梯度为零。
下一篇预告:
第18篇,我们讲 随机梯度下降(SGD)与 Mini-batch:为什么不用全量数据?
梯度下降的理论版本需要全量数据,但实践中用 mini-batch 随机近似。这不只是"计算上省事"——SGD 引入的噪声有深刻的正则化效果,帮助模型泛化,帮助逃离鞍点。第18篇会推导 SGD 梯度的统计性质(无偏性、方差、信噪比),并分析 batch size 对收敛速度和泛化性的影响。
梯度下降是一种哲学:不要试图一步找到最优解,只需每步都让情况略微变好。从局部信息(梯度)出发,一小步一小步走,最终能到达很好的地方。这个思想朴素到近乎平凡,却足以训练出理解语言、推理数学、生成代码的智能系统。
还没有评论,来抢沙发吧!
博客管理员
40 篇文章
还没有评论,来抢沙发吧!