1. K-Means 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 import mathimport randomX = [ [1 , 2 ], [1 , 3 ], [2 , 2 ], [8 , 8 ], [9 , 8 ], [8 , 9 ] ] def kmeans (X, k, max_iters=100 ): centers = random.sample(X,k) for _ in range (max_iters): clusters = [ [] for _ in range (k) ] for x in X: distances = [] for c in centers: distance = 0 for i in range (len (x)): distance += (x[i] - c[i])**2 distance = math.sqrt(distance) distances.append(distance) label = distances.index(min (distances)) clusters[label].append(x) new_centers = [] for cluster in clusters: if len (cluster) == 0 : new_centers.append(random.choice(X)) continue center = [] for i in range (len (X[0 ])): total = 0 for x in cluster: total += x[i] mean = total / len (cluster) center.append(mean) new_centers.append(center) if new_centers == centers: break centers = new_centers return centers, clusters centers, clusters = kmeans(X, k=2 ) print ("Centers:" , centers)print ("Clusters:" , clusters)
1. 简单介绍一下 K-Means 算法?
K-Means 是一种无监督学习算法,主要用于聚类,不需要标签。它会预先指定 K 个簇,首先随机选择 K 个中心点,然后计算每个样本到各个中心点的距离,将样本分配给最近的中心点,再重新计算每个簇的均值作为新的中心点,不断重复这个过程,直到中心点不再发生变化或者达到最大迭代次数。
本质上就是让相似的数据聚在一起,不相似的数据分开。通过不断更新聚类中心,最小化簇内平方误差(SSE),最终实现数据的自动聚类。
2. K-Means 为什么要计算平均值?
K-Means 的损失函数是簇内平方误差 SSE ,表示所有样本点到各自所属簇中心的距离平方和。而使用均值作为聚类中心,可以使簇内所有样本到中心点的平方距离之和(也就是SSE)尽可能最小,所以每次更新中心点时都要计算平均值。
3. K-Means 有什么优点和缺点?
优点:
算法简单,容易理解和实现。计算效率比较高。
聚类结果比较容易解释。
缺点:
需要提前指定 K 值。
对初始中心点比较敏感。
对异常值比较敏感。
不适合形状复杂、密度差异较大的数据集。
6. K-Means 的收敛条件是什么?
通常有三种情况:第一,聚类中心不再发生变化;第二,聚类中心的变化小于设定的误差阈值;第三,达到最大迭代次数。满足其中一个条件就可以停止迭代。
7. K-Means 对数据有什么要求?需要注意什么?
K-Means 要求输入的是数值型数据 ,并且需要提前指定 K 值。另外,由于它基于距离计算,所以不同特征之间的量纲可能会影响聚类结果,通常需要先进行标准化处理,例如 StandardScaler。
2. 梯度下降 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 X = [1 , 2 , 3 , 4 , 5 ] Y = [2 , 4 , 6 , 8 , 10 ] w = 0 b = 0 epochs = 1000 lr = 0.01 n = len (X) for _ in range (epochs): dw = 0 db = 0 for i in range (n): y_pred = w * X[i] + b error = y_pred - Y[i] dw += 2 *(error)*X[i] db += 2 *(error) w = w - lr * dw / n b = b - lr * db / n print ("w:" , w)print ("b:" , b)def gradient (lr = 0.01 ,epochs = 1000 ): x = 10 for _ in range (epochs): grad = 2 * x + 4 x = x - lr * grad y = x ** 2 + 4 * x + 4 return x, y x,y = gradient() print ('x:{}, y:{}' .format (x, y))def gradient_loss (lr=0.01 ,epochs = 100 ): x = 1 for _ in range (epochs): error = x ** 2 + 4 * x + 4 loss = error ** 2 grad = 2 * error * (2 *x + 4 ) x = x - lr*grad if abs (x**2 + 4 * x + 4 ) < 1e-6 : break print ("迭代后的近似值:" , x) gradient_loss()
最后一个例子里的方程有重根。对 g(x)² 做梯度下降时,越靠近根梯度越小,因此少量迭代只能得到近似值。
损失函数:我们希望预测值尽可能接近真实值 ,所以一般会使用均方误差 MSE 。求导是要对着这个损失函数去求导的。
1. 什么是梯度下降?
梯度下降是一种优化算法,主要用于寻找函数的最小值。它通过计算函数的梯度,并沿着梯度反方向不断更新参数,使函数值逐渐减小,最终达到收敛。
这里的函数其实就是损失函数。
3. 为什么要沿着梯度反方向更新?
因为梯度代表函数值上升最快的方向 ,而我们希望函数值不断减小,所以需要沿着梯度的反方向更新参数。
4. 学习率是什么?过大或过小有什么影响?
学习率决定每次参数更新的步长。学习率过大,可能导致错过最优点,甚至无法收敛;学习率过小,则会导致收敛速度非常慢。
5. 如果让你用梯度下降求一个方程的解,应该怎么做?
首先将方程左边的表达式记为 g(x),然后构造损失函数 L(x) = g(x)²。接着对损失函数求导,使用梯度下降不断更新 x,当损失足够小时,就认为找到了方程的解。
6. 梯度下降有哪些停止条件?
常见的停止条件有三种:
损失函数的变化小于设定阈值。
梯度接近于 0。
达到最大迭代次数。
7. 梯度下降有什么缺点?
学习率不好选择。
可能陷入局部最优。
可能出现梯度消失或梯度爆炸。
对复杂函数,收敛速度可能比较慢。
3. MHA 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 import mathimport torchfrom torch import nnclass MHA (nn.Module): def __init__ (self, d_model, n_head ): super ().__init__() if d_model % n_head != 0 : raise ValueError('d_model 必须能被 n_head 整除' ) self .d_model = d_model self .n_head = n_head self .dk = d_model // n_head self .wq = nn.Linear(d_model, d_model) self .wk = nn.Linear(d_model, d_model) self .wv = nn.Linear(d_model, d_model) self .wo = nn.Linear(d_model, d_model) def forward (self, x ): B,L,D = x.shape q = self .wq(x) k = self .wk(x) v = self .wv(x) q = q.reshape(B,L,self .n_head,self .dk).transpose(1 ,2 ) k = k.reshape(B,L,self .n_head,self .dk).transpose(1 ,2 ) v = v.reshape(B,L,self .n_head,self .dk).transpose(1 ,2 ) attn = q @ k.transpose(-1 ,-2 ) / math.sqrt(self .dk) score = torch.softmax(attn,dim=-1 ) out = score @ v out = out.transpose(1 ,2 ).reshape(B,L,D) return self .wo(out) class SelfAttention (nn.Module): def __init__ (self, d_model ): super ().__init__() self .d_model = d_model self .dk = d_model self .wq = nn.Linear(d_model, d_model) self .wk = nn.Linear(d_model, d_model) self .wv = nn.Linear(d_model, d_model) self .wo = nn.Linear(d_model, d_model) def forward (self,x ): B,L,D = x.shape q = self .wq(x) k = self .wk(x) v = self .wv(x) attn = q @ k.transpose(-1 ,-2 ) / math.sqrt(self .dk) score = torch.softmax(attn,dim=-1 ) out = score @ v return self .wo(out)
1. 为什么要拆分多头?
让模型能够从不同表示子空间、不同角度学习注意力关系,token之间的关联 。
2. 什么是 Attention?
Self-Attention(自注意力机制)是指序列中的每个 Token 都可以与其他 Token 计算注意力分数,根据注意力权重对其他 Token 的信息进行加权求和,从而得到当前token融合了上下文信息的表示。
3. Q、K、V 分别代表什么?
Q(Query):查询,表示当前 Token 想获取什么信息。
K(Key):键,表示当前 Token 可以提供什么信息。
V(Value):值,表示当前 Token 实际提供的内容。
简单理解:Q 和 K 计算相关性,V 提供实际信息。
4. 为什么要除以 √dₖ?
因为 Q 和 K 的维度较大时,点积结果可能比较大,导致 Softmax 进入梯度很小的区域 ,影响模型训练。除以 √dₖ 可以稳定数值。
4. softmax 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import mathx = [2 , 3 , 5 ] def softmax (x ): max_x = max (x) exp_x = [] for i in x: exp_x.append(math.exp(i-max_x)) total_x = sum (exp_x) results = [] for i in exp_x: results.append(i/total_x) return results print (softmax(x))
Q1:为什么 Softmax 要减去最大值?
防止输入数值过大时,指数运算发生溢出,提高计算的稳定性。
Q2:Softmax 有什么特点?
输出值都在 0 到 1 之间。
所有输出值之和为 1。
输入值越大,对应的概率通常越大。
常用于多分类任务的输出层。
5. RMSnorm 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import mathdef rms_norm (x, gamma, eps=1e-6 ): square_sum = 0 for i in x: square_sum += i ** 2 rms = math.sqrt( square_sum / len (x) + eps ) x_norm = [] for i in x: x_norm.append(i / rms * gamma) return x_norm
核心思想:计算均方根,然后用它对输入进行归一化。LayerNorm 是先减均值,再除以标准差;RMSNorm 不减均值,直接除以均方根。
Q1:为什么大模型使用 RMSNorm?
RMSNorm 省去了均值和方差的计算,计算过程更简单,计算开销更小,同时在很多大模型中能够取得很好的效果。
Q2:RMSNorm 中为什么要加 epsilon?
防止均方根为 0,避免除零错误,提高数值稳定性 。
Q3:RMSNorm 中的 gamma 是什么?
Gamma 是可学习的缩放参数,用于调整归一化之后的特征。
6. AUC 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def auc (y_true, y_score ): pos = [] neg = [] for i in range (len (y_true)): if y_true[i] == 1 : pos.append(y_score[i]) else : neg.append(y_score[i]) count = 0 for p in pos: for n in neg: if p>n: count += 1 elif p == n: count += 0.5 if not pos or not neg: raise ValueError('AUC 需要同时包含正样本和负样本' ) return count / (len (pos) * len (neg))
Q1:AUC 是什么?
衡量二分类模型性能的指标。随机选择一个正样本和一个负样本,模型给正样本的预测分数高于负样本的概率。AUC 越大,说明模型区分正负样本的能力越强。
Q2:AUC 的取值范围是多少?
通常在 0 到 1 之间,0.5 表示随机排序水平,1 表示完美区分。
Q3:AUC 和 Accuracy 有什么区别?
Accuracy 衡量的是模型预测正确的比例,需要指定分类阈值;AUC 衡量模型对正负样本的整体排序能力 ,不依赖某一个固定阈值。
7. Dropout 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import randomdef dropout (x, p=0.5 , training=True ): if not training: return x result = [] for i in x: if random.random() < p: result.append(0 ) else : result.append(i / (1 -p)) return result
Dropout 是一种正则化方法,主要用于防止神经网络过拟合。它的做法很简单:训练过程中,随机将一部分神经元的输出置为 0,让模型不要过度依赖某几个神经元。
Q1:Dropout 有什么作用?
Dropout 通过随机丢弃部分神经元,减少神经元之间的过度依赖,从而缓解过拟合,提高模型的泛化能力。
Q2:Dropout 在训练和推理阶段有什么区别?
训练阶段随机丢弃神经元;推理阶段不再丢弃神经元,直接使用完整网络。
Q3:Dropout 为什么要缩放?保留的元素还要除以 1-p?
因为我们随机丢弃了一部分神经元,为了保持训练时输出的期望值与推理时一致,需要对保留的元素进行缩放。
8. Adam 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import mathdef adam (lr=0.01 , epochs=1000 ): x = 5.0 m = 0 v = 0 beta1 = 0.9 beta2 = 0.99 eps = 1e-8 for t in range (1 ,epochs+1 ): grad = 2 * x m = beta1 * m + (1 -beta1) * grad v = beta2 * v + (1 -beta2) * grad ** 2 m_hat = m / (1 -beta1**t) v_hat = v / (1 -beta2**t) x = x - lr / (math.sqrt(v_hat)+eps) * m_hat if abs (grad) < 1e-6 : break return x ** 2
例如训练一个分类模型:
输入数据,模型得到预测结果。
使用 Cross Entropy 计算 Loss。
通过反向传播用损失函数计算所有参数的梯度。
Adam 根据梯度计算一阶矩和二阶矩。
更新模型参数。
不断重复,降低 Loss。
Q1:Adam 是什么?
Adam 是一种可以自适应调整学习率的优化算法,全称 Adaptive Moment Estimation。它结合了 Momentum(动量)和 RMSProp 的思想,通过记录梯度的一阶矩和二阶矩,通过偏差修正动态调整 参数的学习率。
Adam 和梯度下降本质上都是优化算法,都是用来优化某个目标函数(通常是损失函数)的。它们的区别主要在于:参数更新的方式不同。
一阶矩:梯度的指数移动平均,类似动量。
二阶矩:梯度平方的指数移动平均,代表梯度的大小。用于调整更新步长。
Q2:Adam 的一阶矩和二阶矩分别是什么?
一阶矩是梯度 的指数移动平均 ,记录梯度方向 ;二阶矩是梯度平方 的指数移动平均 ,记录梯度大小。
Q3:为什么 Adam 需要偏差修正?
因为一阶矩和二阶矩初始值都是 0,训练初期会存在偏差,因此需要进行修正,使估计更加准确。
Q4:Adam 和 SGD 有什么区别?
SGD 通常使用统一的学习率更新参数,而 Adam 会根据每个参数的梯度历史,自适应调整更新步长。
Q5:Adam 一定比 SGD 好吗?
不一定。Adam 通常收敛较快,适合深度学习训练;SGD 在某些任务中可能具有更好的泛化表现。