【记录贴 | 持续更新】搜广推/aigc 面试题记录

目录

nlp/搜广推

transformer结构?

在这里插入图片描述

encoder-decoder模型。编码组件由6个encoder组成,解码组件由6个decoder组成。

每个encoder结构相同,但不共享参数。每个encoder可以分为两个子层:自注意力层和前馈神经网络。每个decoder可以分为三层:自注意力层、编码-解码注意力层、前馈神经网络。

注意力层包括:多头注意力机制、ResNet和LayerNorm。前馈神经网络层包括:前馈神经网络、ResNet和LayerNorm。

位置编码的作用?

位置编码:解决时序问题。“我爱你”和“你爱我”不同。

基于正余弦:
在这里插入图片描述

解释 self-attention?

self-attention宏观上做了这件事:要更好的理解句中某个特定单词的含义,你要把它放到整个语境之中去理解,比如通过对上下文的把握。所以self-attention计算其他词语对判断特定词语词义的重要程度,把该词编码为该词在内的所有词的加权和。

self-attention怎么计算?由编码器的输入向量乘不同的权重生成三个向量:查询向量Q、键向量K、值向量V(在论文中的维度是64,multihead attention设置8个头,每个头的维度是512/8=64)
在这里插入图片描述
QKV的作用是什么?

Q:当前单词的表示形式,用于对于其他单词评分
K:序列中所有单词的标签,我们找相关单词时的对照物
V:单词的实际表示,一旦我们队每个单词的相关度打分之后,我们就要读value进行相加表示当前正在处理的单词的value

每个计算步骤的作用?

Q K T QK^T QKT:计算两个单词的相似度
/ √ ( d k ) /√(d_k) /√(dk):随着 d k d_k dk增大, Q K T QK^T QKT结果也随之增大,这样会将softmax函数推向非常小的取余,使收敛困难,可能会出现梯度消失的情况。(假设Q和K是具有均值为0方差为1的独立随机变量,那它们的点积均值为0方差为 d k d_k dk
s o f t m a x ( ) softmax() softmax():使所有单词的分数归一化,得到分数是正值且和为1
⋅ V ·V V:留下我们想要关注的词的value,把其他不相关的词丢掉

attention中 /√(d_k) 的作用是什么?

随着 d k d_k dk增大, Q K T QK^T QKT结果也随之增大,这样会将softmax函数推向非常小的取余,使收敛困难,可能会出现梯度消失的情况。(假设Q和K是具有均值为0方差为1的独立随机变量,那它们的点积均值为0方差为 d k d_k dk
s o f t m a x ( ) softmax() softmax():使所有单词的分数归一化,得到分数是正值且和为1

解释Multi-Head Attention?

multi-head attention的作用?

① 扩展了模型专注于不同位置的能力(不会把过多注意力放在自己身上)
② 得出了注意力层的多个表示子空间,每个注意力透支关注输出序列的一个子空间,相互独立。

multi-head attention怎么做?

① 输入向量乘 W Q W_Q WQ W K W_K WK W V W_V WV得到Q、K、V
② 经过linear层得到8个不同的Q、K、V
③ 计算八次self-attention
④ 得到八个结果,将它们concat起来
⑤ 乘一个附加权重 W O W_O WO

在这里插入图片描述

FeedForward的作用?

全连接的前馈神经网络,它对输入的特征进行非线性变换,以增加模型的表达能力和复杂度

在这里插入图片描述

ResNet 和 LayerNorm的作用?

避免梯度消失和梯度爆炸。

为什么用layernorm不用batchnorm?

① 在 NLP 中,序列的长度可以变化,而 Batch Normalization 对每个批次的序列长度有一定的要求

Batch Normalization 在 NLP中对每个批次的序列长度要求是序列长度一致,即在每个批次中的所有序列的长度必须相同。这是因为 Batch Normalization 中对每个feature 的均值和方差的计算是基于每个 feature 在一个 batch 中的样本计算的,而如果序列的长度不一致,则无法直接应用Batch Normalization。因此,在使用 Batch Normalization 进行神经网络训练时,需要保证每个 batch中的序列长度相同。

② Layer Normalization 是在每个样本的特征维度上进行归一化,因此能够处理长度不同的序列。

[扩展]batchnorm/layernorm/instancenorm/groupnorm
nlp: [b, seq_len, d_model]
bn: [b, seq_len, 1]
ln: [1, seq_len, d_model]
in: [1, seq_len, 1]
gn: [1, seq_len, group]
cv: [b, h, w, d_model]
bn: [b, h, w, 1]
ln: [1, h, w, d_model]
in: [1, h, w, 1]
gn: [1, h, w, group]

decoder中的注意力层和encoder有什么不同?

  1. 带masked的Multi-Head Attention,本质是Self-Attention,该自注意力层只允许关注已输出位置的信息
  2. 不带masked的Multi-Head Attention,本质是Cross-Attention

【补充】最后的linear层和softmax

linear:将解码器生成的向量映射到logits向量中,logits向量维数是词表大小,每一维对应一个单词的分数
softmax:将这些分数转化为概率,选择其中概率最大的位置的词汇作为当前时间步的输出

[来自Transformer通俗笔记:从Word2Vec、Seq2Seq逐步理解到GPT、BERT]

手撕multi-head attention?

import math
import torch
import collections
import numpy as np
import torch.nn as nn


class MultiHeadAttention(nn.Module):
    def __init__(self, heads, d_model, dropout=0.1):
        super().__init__()
        #输入的特征维度
        self.d_model=d_model
        #每个头的特征维度
        self.d_k=d_model//heads
        #头数
        self.h=heads

        # K Q V三个矩阵,分别是输入通过三个矩阵投影得到的
        self.q_linear=nn.Linear(d_model,d_model)
        self.k_linear=nn.Linear(d_model,d_model)
        self.v_linear=nn.Linear(d_model,d_model)

        self.dropout=nn.Dropout(dropout)

        #输出线性层
        self.out=nn.Linear(d_model,d_model)


    def attention(self,q,k,v,mask=None):
        scores=torch.matmul(q,k.transpose(-2,-1))/math.sqrt(self.d_k)


        if mask is not None:
            scores=scores.masked_fill(mask==0,float('-inf'))
        #对score进行softmax
        score=torch.nn.functional.softmax(scores,dim=-1)
        score=self.dropout(score)


        output=torch.matmul(score,v)
        return output


    def forward(self,q,k,v,mask=None):
        batch_size=q.shape[0]

        #转换成(batch个 head个 序列长度 特征维度)的张量
        q=self.q_linear(q).view(batch_size,-1,self.h,self.d_k).transpose(1,2)
        k=self.k_linear(k).view(batch_size,-1,self.h,self.d_k).transpose(1,2)
        v=self.v_linear(v).view(batch_size,-1,self.h,self.d_k).transpose(1,2)

        score=self.attention(q,k,v,mask)
        concat=score.transpose(1,2).contiguous().view(batch_size,-1,self.h*self.d_k)

        output=self.out(concat)

        return output

if __name__ == '__main__':

    heads=4
    d_model=128
    dropout=0.1

    model=MultiHeadAttention(heads,d_model,dropout)

    batch_size=2
    seq_len=5
    q=torch.rand(batch_size,seq_len,d_model)
    k=torch.rand(batch_size,seq_len,d_model)
    v=torch.rand(batch_size,seq_len,d_model)


    output=model.forward(q,k,v)

    loss=output.mean()
    loss.backward()

在这里插入图片描述

__init__需要初始化:

输入维度、多头注意力维度、头数
输入linear层,输出linear层
self-attention中的dropout层

self-attention中:

q/k/v的形状都是[b,h,seq_len,dk]

q k T qk^T qkT计算两个维度的内积,维度是[b, h, seq_len, seq_len],转置用transpose,内积用torch.matmul,根号用math.sqrt

如果需要maskmasked_fill是 PyTorch 中的一个函数,用于将 mask == 0 为 True 的位置的 scores 值替换为 float('-inf'),即负无穷大。为什么使用负无穷大?因为负无穷大会导致对应位置的 softmax 输出接近于零。

softmaxtorch.nn.functional.softmaxdim=-1 q k T / d k qk^T/\sqrt{d_k} qkT/dk 的维度是[b,h,seq_len,seq_len],dim=-1表示对[seq_len,seq_len]中的每行做softmax

dropout层将score中随机一些值设为0

与v相乘的内积依旧是torch.matmul

forward中:

input linear:qkv做linear

分成多头:view+transpose。为什么不直接view?因为 view 操作需要确保在内存中的数据布局是连续的,并且按照你指定的形状重新解释数据。然而,如果你直接尝试将 [batch_size, seq_len, d_model] 的张量 view 成 [batch_size, num_heads, seq_len, d_k],内存中的数据排列可能不会按照预期的顺序进行。

attention:得到的tensor的形状是[b,h,seq_len,dk]

concat:转换成[b,seq_len,h,dk]contiguous()保证内存连续,view转换回[b,seq_len,h*dk]

output linear:output linear

transfomer相比于cnn/rnn的优势是什么?

cnn:cnn提取的是局部特征,但是对于文本数据,忽略了长距离的依赖。卷积网络捕捉长依赖的能力非常弱,主要依靠深度来捕捉长距离依赖。
rnn:rnn随着距离的增加,信息衰减得越多。rnn只能用到前面的词,而transfomer可以用到前后的词。rnn是一个顺序的结构,无法同时并行计算,导致RNN的计算效率不高。

介绍一下bert?

  1. BERT基于Transformer的编码器(Encoder)架构,利用了多层的Transformer编码器来处理输入文本。是一种双向模型。

  2. BERT采用了“预训练+微调”的方法。它首先在大规模的文本语料库上进行无监督的预训练,然后通过监督学习在特定任务上进行微调。

预训练任务:

MLM:随机遮盖输入文本中的一些单词,并要求模型预测这些被遮盖的单词。

数据量:BERT会随机选择15%的单词,并用一个特殊的[MASK]标记替换其中的80%,用其他随机单词替换10%,保留原始单词的10%。

NSP:BERT在预训练阶段还会进行下一句预测任务。模型会输入成对的句子,并尝试判断第二个句子是否是第一个句子的实际后续句子。

数据量:50%的情况下,第二个句子确实是第一个句子的后续句子,另外50%的情况下则是随机抽取的一个无关句子。通过这个任务,BERT学习了句子间的关系和连贯性。

bert的输入有几个embedding?

每个单词有三个embedding:token embedding + 句子embedding + 位置信息embedding

bert对输入数据加入了哪些特殊标记?作用是什么?

两个句子之间通过分隔符「SEP」分割,最前面是起始标识「CLS」

[CLS] 标记: 用于表示整个输入序列的全局信息。在许多下游任务(如文本分类、情感分析、句子对分类等)中,最终的分类结果是基于 [CLS] 标记对应的隐藏状态向量进行的。

[SEP] 标记: 用于分隔两个句子或文本片段,在双句子任务中起到分隔符句子结束标记的作用。

roberta和bert的区别?

和项目强相关,几乎每个面试官都问

  1. 增加了训练数据量和预训练时间,增加batchsize,调整学习率,增加输入序列长度。
  2. 删除NSP预训练任务:
  3. 动态masking:对于每一个输入样本序列,都会复制10条,然后复制的每一个都会重新进行mask,即拥有不同的masked tokens。确保了模型在训练过程中可以看到更多样化的掩码模式,从而使模型能够更好地学习到词汇在不同上下文中的语义关系。

为什么nsp不好?

  1. 引入噪声,学到与实际任务无关的特征

在实际场景中,不连续的句子可能仍然在语义上存在一定的关联,但 NSP 中的随机负样本通常与前一句没有任何语义联系,这使得任务本身变得相对容易。模型可能会过度依赖表面的统计特征(例如句子长度、词汇重复率等)来区分正负样本,而不是真正理解句子之间的复杂关系。

  1. 与语言建模任务的冲突,减少训练有效性

NSP 关注句子关系:NSP 任务要求模型判断句子之间的连续性,这引导模型关注的是句子级别的关系。

MLM 关注词汇和上下文:MLM 任务要求模型通过掩盖的单词推测其含义,模型需要更深入地理解单词在句子中的作用及其上下文关系。

NSP 任务所占用的训练时间和计算资源,可能本可以更好地用于更具挑战性的 MLM 任务。如果没有 NSP 任务,所有的训练资源都可以集中在 MLM 上,从而提高模型的整体训练效率。

为什么分类模型bert的最后一层不做/√(dk)?

在BERT中的最后一层中不进行向量除以根号下dk(dk为向量维度的平方根)操作的原因是为了保持更多的信息。在BERT模型中,每个位置的输入是在经过多层Transformer编码器的处理之后得到的表示,这些表示包含了丰富的语义信息。进行向量除以根号下dk的操作会降低向量的范数,可能导致一些信息的损失。

在BERT模型中,最后一层的输出经过softmax操作,并作为分类器的输入,从而实现文本分类等任务。在这个过程中,不进行向量除以根号下dk的操作可以更好地保留输入表示中的信息,有助于提高模型的性能和泛化能力。

auc是什么?数据分布会影响auc吗?

混淆矩阵:

               Actual
               1   0
Predicted   1  TP  FP
            0  FN  TN

TPR(True Positive Rate)/真阳性率/灵敏度/召回率: TPR = TP TP + FN \text{TPR} = \frac{\text{TP}}{\text{TP} + \text{FN}} TPR=TP+FNTP
FPR(False Positive Rate)/假阳性率: FPR = FP FP + TN \text{FPR} = \frac{\text{FP}}{\text{FP} + \text{TN}} FPR=FP+TNFP

roc:receiver operating characteristic,接收者操纵特征曲线。纵轴是TPR,横轴是FPR.

auc:area under the curve,用于衡量分类模型性能的指标。特别适用于二分类任务。roc曲线下面的面积,取值范围[0,1],0.5表示模型预测效果等同于随机猜测,1表示完美分类器。auc接近于1,表示模型有更好的性能。

auc不受分类阈值的影响,可以综合考虑不同阈值下的性能表现。auc对样本不平衡的情况也比较鲁棒。

auc有几种计算方式?

来自https://blog.csdn.net/u012762410/article/details/127744611

  1. ROC曲线面积
  2. 正样本预测值pos_score大于负样本预测值neg_score的概率

选取所有正样本与负样本的两两组合
计算正样本预测值pos_score大于负样本预测值neg_score的概率:
如果pos_score>neg_score,概率为1
如果pos_score==neg_score,概率为0.5
如果pos_score<neg_score,概率为0
在这里插入图片描述

import numpy as np

y_true =  [0,   0,   1,   1,   0,   1,   0,   1,   1,   1]
y_score = [0.1, 0.4, 0.6, 0.6, 0.7, 0.7, 0.8, 0.8, 0.9, 0.9]

def get_roc_auc(y_true, y_score):
    gt_pred = list(zip(y_true, y_score))
    probs = []
    pos_samples = [x for x in gt_pred if x[0]==1]
    neg_samples = [x for x in gt_pred if x[0]==0]
    
    # 计算正样本大于负样本的概率
    for pos in pos_samples:
        for neg in neg_samples:
            if pos[1]>neg[1]:
                probs.append(1)
            elif pos[1]==neg[1]:
                probs.append(0.5)
            else:
                probs.append(0)
    return np.mean(probs)
print(get_roc_auc(y_true, y_score))

在这里插入图片描述

分子部分:我们统计的是正样本得分大于负样本得分的次数,可以看作是模型的分类准确度或精确度。这个次数表示了模型正确分类的情况。
分母部分:正样本数M乘以负样本数N代表着所有可能的正负样本组合数,即总共的样本对数量。这个值用于对计数结果进行归一化处理,避免受到样本数量的影响。
在这里插入图片描述
推导公式之后再补

def get_roc_auc(y_true, y_score):
    ranks = enumerate(sorted(zip(y_true, y_score), key=lambda x:x[-1]), start=1)
    pos_ranks = [x[0] for x in ranks if x[1][0]==1]
    M = sum(y_true)
    N = len(y_true)-M
    auc = (sum(pos_ranks)-M*(M+1)/2)/(M*N)
    return auc

分类模型的评估标准是什么?

分类模型的评估标准主要包括以下几个方面:

  1. 准确率(Accuracy):分类正确的样本数占总样本数的比例。
    Accuracy = (TP + TN) / (TP + TN + FP + FN)

  2. 精确率(Precision):预测为正类别且真实为正类别的样本占所有预测为正类别的样本的比例。
    Precision = TP / (TP + FP)

  3. 召回率(Recall):真实为正类别且被预测为正类别的样本占真实为正类别的样本的比例。
    Recall = TP / (TP + FN)

  4. F1分数(F1 Score):综合考虑精确率和召回率,是精确率和召回率的调和平均数。
    F 1 S c o r e = 2 ∗ ( P r e c i s i o n ∗ R e c a l l ) / ( P r e c i s i o n + R e c a l l ) F1Score = 2 * (Precision * Recall) / (Precision + Recall) F1Score=2(PrecisionRecall)/(Precision+Recall)

  5. ROC曲线与AUC值(Receiver Operating Characteristic curve and Area Under the Curve):用于评估二分类模型的整体性能,AUC表示ROC曲线下面积,可以衡量模型预测正例的能力。

  6. 混淆矩阵(Confusion Matrix):展示了模型在不同类别上的预测情况(真正例、假正例、真负例、假负例),可帮助更好地理解模型性能。

attention 和 multi-head attention 的时间复杂度和空间复杂度分别是什么?为什么?

attention:
时间复杂度: O ( n 2 ∗ d ) O(n^2*d) O(n2d)
空间复杂度: O ( n ∗ d ) O(n*d) O(nd)
n序列长度,d向量维度
时间复杂度分析:
Q K T QK^T QKT是两个矩阵的乘法 [ n , d ] × [ d , n ] = [ n , n ] [n,d] \times [d,n]=[n,n] [n,d]×[d,n]=[n,n],计算复杂度 n 2 ⋅ d n^{2} \cdot d n2d
其结果再乘 V V V,即 [ n , n ] × [ n , d ] = [ n , d ] [n,n] \times [n,d]=[n,d] [n,n]×[n,d]=[n,d],计算复杂度也为 n 2 ⋅ d n^{2} \cdot d n2d
空间复杂度分析:
在自注意力机制中,整个序列的表示通过对应的查询、键、值三种向量来表示,因此需要在空间中存储这三种向量,占用的空间 O ( 3 ∗ n ∗ d ) = O ( n ∗ d ) O(3 * n * d) = O(n * d) O(3nd)=O(nd)

MHA:
时间复杂度: O ( n ∗ d ∗ h + n 2 ∗ h ) O(n * d * h + n^2 * h) O(ndh+n2h)
空间复杂度: O ( n ∗ d + h ∗ n ) O(n * d + h * n) O(nd+hn)

分类和回归的loss分别是什么?

分类:
交叉熵损失(Cross Entropy Loss):在分类任务中,最常用的损失函数是交叉熵损失,也称为Softmax损失。交叉熵损失适用于多分类问题,用于衡量模型输出的类别概率分布与真实标签之间的差异。
公式:对于单个样本,交叉熵损失的计算公式为:
L ( y , y ^ ) = − ∑ c = 1 C y c ∗ l o g ( y ^ c ) L(y, \hat{y}) = -\sum_{c=1}^{C} y_c * log(\hat{y}_c) L(y,y^)=c=1Cyclog(y^c)
其中 y y y 是真实标签的概率分布, y ^ \hat{y} y^ 是模型预测的概率分布, C C C 是类别数量。
回归:
均方误差(Mean Squared Error,MSE):在回归任务中,常用的损失函数是均方误差,用于衡量模型输出与真实值之间的平方误差。
公式:均方误差的计算公式为:
L ( y , y ^ ) = ∑ i = 1 N ( y i − y ^ i ) 2 / N L(y, \hat{y}) = \sum_{i=1}^{N} (y_i - \hat{y}_i)^2 / N L(y,y^)=i=1N(yiy^i)2/N
其中 y y y 是真实值, y ^ \hat{y} y^ 是模型预测值, N N N 是样本数量。

快速排序算法?时间复杂度?

递归:

def quick_sort(arr):
    if len(arr) <= 1:
        return arr
    else:
        pivot = arr[0]
        less = [x for x in arr[1:] if x <= pivot]
        greater = [x for x in arr[1:] if x > pivot]
        return quick_sort(less) + [pivot] + quick_sort(greater)

# 测试快速排序算法
arr = [3, 6, 8, 10, 1, 2, 1]
sorted_arr = quick_sort(arr)
print("排序后的数组:", sorted_arr)

非递归:


def quick_sort(arr):
    if len(arr) <= 1:
        return arr
    
    stack = [(0, len(arr) - 1)]
    
    while stack:
        start, end = stack.pop()
        if start >= end:
            continue
        
        pivot = arr[start]
        left = start + 1
        right = end
        
        while left <= right:
            if arr[left] <= pivot:
                left += 1
            else:
                arr[left], arr[right] = arr[right], arr[left]
                right -= 1
        
        arr[start], arr[right] = arr[right], arr[start]
    
    return arr

# 测试快速排序算法
arr = [3, 6, 8, 10, 1, 2, 1]
sorted_arr = quick_sort(arr)
print("排序后的数组:", sorted_arr)

最好情况时间复杂度:O(n log n),当每次选择的基准元素是中间值时
最坏情况时间复杂度:O(n^2),当每次选择的基准元素都是最大或最小值时
平均时间复杂度:O(n log n)

二分查找算法?时间复杂度?

最坏情况时间复杂度:O(log n)
平均情况时间复杂度:O(log n)

【项目】你用bert的哪一层数据做分类?

你了解孪生网络吗?

【项目】为什么加入对比学习?

两个embedding之间的距离怎么计算?

激活函数的作用?

引入非线性

梯度爆炸和梯度消失怎么解决?

  1. 合适的激活函数:ReLU、Leaky ReLU
  2. 仅对梯度消失:ResNet
  3. Norm
  4. 仅对梯度爆炸:梯度裁剪

样本不平衡怎么办?小样本怎么办?

【来自ChatGPT】
过采样(Oversampling):对数量较少的类别样本进行复制或生成新的合成样本,使得各个类别的样本数量接近平衡。

欠采样(Undersampling):删除数量较多的类别样本,使得各个类别的样本数量相对平衡。

集成方法(Ensemble Methods):通过集成多个模型来改善模型的泛化能力,可以用不同方式处理不同类别样本的不平衡。

类别权重(Class Weights):在模型训练过程中设定不同类别的权重,让模型更关注数量较少的类别,以平衡不同类别样本对模型学习的影响。

额外损失(Additional Loss):为了加大数量较少类别的损失,可以在损失函数中增加额外的惩罚项,使得模型更加关注数量较少的类别。

生成对抗网络(GAN):可以使用生成对抗网络生成合成样本来增加数量较少类别的样本数量,提高训练数据的多样性

过拟合的解决办法?

增加数据量/正则化/Dropout

你了解什么qp任务?

BM25

BM25 ( q , d ) = ∑ i = 1 n I D F ( q i ) ⋅ T F ( q i , d ) ⋅ ( k 1 + 1 ) T F ( q i , d ) + k 1 ⋅ ( 1 − b + b ⋅ ∣ d ∣ ∣ d ∣ ˉ ) \text{BM25}(q, d) = \sum_{i=1}^{n} IDF(q_i) \cdot \frac{TF(q_i, d) \cdot (k_1 + 1)}{TF(q_i, d) + k_1 \cdot (1 - b + b \cdot \frac{|d|}{\bar{|d|}})} BM25(q,d)=i=1nIDF(qi)TF(qi,d)+k1(1b+bdˉd)TF(qi,d)(k1+1)

公式组成部分分析

  1. B M 25 ( q , d ) BM25(q, d) BM25(q,d):表示查询 ( q q q ) 与文档 ( d d d ) 之间的相关性分数。
  2. ∑ i = 1 n \sum_{i=1}^{n} i=1n:对查询中的每个词 ( q i q_i qi )(从 1 到 ( n n n ))进行求和。这里 ( n n n ) 是查询中词的总数。
  3. I D F ( q i ) IDF(q_i) IDF(qi):逆文档频率(Inverse Document Frequency),用于衡量词 ( q i q_i qi ) 的重要性。通常计算为:
    I D F ( q i ) = log ⁡ ( N − n ( q i ) + 0.5 n ( q i ) + 0.5 + 1 ) IDF(q_i) = \log\left(\frac{N - n(q_i) + 0.5}{n(q_i) + 0.5} + 1\right) IDF(qi)=log(n(qi)+0.5Nn(qi)+0.5+1)
    其中 ( N N N ) 是总文档数,( n ( q i ) n(q_i) n(qi) ) 是包含词 ( q i q_i qi ) 的文档数。IDF 的值越高,表示该词在文档集中越具有区分性,能够更好地区分相关文档与不相关文档。
  4. T F ( q i , d ) TF(q_i, d) TF(qi,d):词频(Term Frequency),表示词 ( q i q_i qi ) 在文档 ( d d d ) 中出现的次数。一般认为,词频越高,表示文档与查询的相关性越强。
  5. ∣ d ∣ |d| d:文档 ( d d d ) 的长度(即文档中的词数)。
  6. ∣ d ∣ ˉ \bar{|d|} dˉ:文档集合的平均长度,表示文档集中所有文档的平均词数。
  7. k 1 k_1 k1 b b b:调节参数
    k 1 k_1 k1通常设定在 [ 1.2 , 2.0 ] [1.2, 2.0] [1.2,2.0] 之间,用于控制词频的饱和效应。较大的 k 1 k_1 k1值会增加对高频词的影响。
    b b b 的值通常在 0 到 1 之间,用于调整文档长度对分数的影响。 b b b较高时,文档长度的影响会更显著。

公式的意义
8. 加权求和: 这个公式表示对查询中每个词的相关性进行加权求和。每个词的贡献由其 IDF 值和 TF 值的归一化决定。
9. IDF 的作用: IDF 提供了对常见词的惩罚,降低其对相关性分数的影响。常见的词(如“the”、“is”等)通常在大多数文档中都会出现,因此它们的 IDF 值较低。
10. TF 的归一化: 归一化的 TF 计算考虑了文档长度的影响,避免了长文档因词频较高而导致的偏差。通过引入 ( k 1 k_1 k1 ) 和 ( b b b ),可以控制文档长度和词频之间的平衡。

import numpy as np
from collections import Counter

class BM25:
    def __init__(self, documents, k1=1.5, b=0.75):
        self.k1 = k1
        self.b = b
        self.documents = documents
        self.doc_count = len(documents)
        self.avg_doc_len = sum(len(doc.split()) for doc in documents) / self.doc_count
        self.idf = self._compute_idf()

    def _compute_idf(self):
        idf = {}
        for doc in self.documents:
            words = set(doc.split())
            for word in words:
                if word in idf:
                    idf[word] += 1
                else:
                    idf[word] = 1
        for word, count in idf.items():
            idf[word] = np.log((self.doc_count - count + 0.5) / (count + 0.5) + 1)
        return idf

    def _get_tf(self, doc):
        tf = Counter(doc.split())
        length = len(doc.split())
        return {word: freq * (self.k1 + 1) / (freq + self.k1 * (1 - self.b + self.b * (length / self.avg_doc_len)))
                for word, freq in tf.items()}

    def score(self, query, doc):
        tf = self._get_tf(doc)
        score = 0.0
        for word in query.split():
            if word in self.idf:
                score += self.idf[word] * tf.get(word, 0)
        return score

    def get_scores(self, query):
        return [self.score(query, doc) for doc in self.documents]

# 示例文档
documents = [
    "the cat sat on the mat",
    "the dog sat on the log",
    "cats and dogs are great pets",
    "I love my pet cat",
]

# 创建 BM25 实例
bm25 = BM25(documents)

# 查询
query = "the"
scores = bm25.get_scores(query)

# 输出结果
for idx, score in enumerate(scores):
    print(f"Document {idx}: Score = {score:.4f}")

python并行怎么做?

为什么会出这种题?

哈希表的作用是什么?

这真的是算法岗的题目吗?

【场景】假如我们有一场直播的asr文本,如何快速总结出这个直播间是做什么的

【场景】用户输入一个搜索词语,需要和一篇很长的文档进行匹配,怎么做?

【项目】有哪些特征?你是如何设计的?

【场景】假如有一批图像,一大部分是正常的图像,一小部分上面包含人工添加的图像或文字,如何找到这一小部分图像呢?

SimCSE是什么?loss是什么?

在这里插入图片描述

SimCSE的dropout在哪一层?

两个embedding之间的距离?

欧氏距离(Euclidean Distance):欧氏距离是最常见的距离计算方法,它表示两个向量之间的直线距离,计算公式如下:
在这里插入图片描述

其中,a和b分别表示两个向量,n表示向量的维度。

余弦相似度(Cosine Similarity):余弦相似度是通过计算两个向量之间的夹角来度量它们之间的相似性,计算公式如下:

在这里插入图片描述

其中,a和b分别表示两个向量,a·b表示两个向量的点积,|a|和|b|分别表示两个向量的模长。

曼哈顿距离(Manhattan Distance):曼哈顿距离是通过计算两个向量对应维度之间的绝对差值的和来度量它们之间的距离,计算公式如下:

曼哈顿距离公式

对比学习Loss

NCE Loss
在这里插入图片描述

NCE Loss也是一种用于对比学习中的损失函数,是通过负采样来估计softmax函数的近似损失。
NCE Loss的计算方式是通过从一个噪声分布(通常是负样本)和一个目标分布(正样本)中采样,然后构建一个二分类损失函数。
通过最大化正样本和负样本之间的边界,NCE Loss可以有效地进行对比学习。
NCE Loss相对于Infonce Loss,虽然实现上更复杂,但在一些情况下能够提供更好的性能。

InfoNCE Loss
在这里插入图片描述
Infonce Loss是基于InfoMax原则的一种损失函数,在对比学习中较为常见。
Infonce Loss的计算方式是通过归一化的交叉熵(Normalized Cross Entropy)来度量正负样本之间的相似性。
计算样本之间的相似性分数后,通过softmax函数对这些分数进行归一化,然后再用cross entropy计算损失。
Infonce Loss关注于最大化正样本之间的相似性,同时最小化负样本之间的相似性,从而能够更好地学习特征表示。

对于每个样本,计算其与正样本和负样本之间的距离。通常使用余弦相似性或欧氏距离来度量距离。对比学习损失的目标是最小化正样本之间的距离,同时最大化负样本之间的距离。

Boosting和Bagging的区别?

Bagging(Bootstrap Aggregating):
Bagging通过并行地训练多个相互独立的弱学习器,每个弱学习器使用随机采样的训练数据集进行训练。
在Bagging中,数据集采用自助采样(Bootstrap Sampling)得到多个不同的训练集,每个样本在每次采样中有可能被多次采样,可以重复出现。
最终的预测结果是通过对所有弱学习器的预测结果进行简单投票或平均得到的。
典型的Bagging方法包括随机森林(Random Forest)。

Boosting:
Boosting是通过顺序训练多个弱学习器,每个弱学习器都在前一个学习器学习的基础上进行加权学习,重点关注前一个学习器预测错误的样本。
在Boosting中,每个样本都有一个权重,随着学习的进行,被错误分类的样本的权重会被加大,从而使得后续的学习器更加关注这些困难样本。
Boosting的思想是通过迭代地提升模型性能,不断减小残差,最终组合多个弱学习器得到一个强学习器。
典型的Boosting方法包括Adaboost(Adaptive Boosting)、Gradient Boosting等。

GBDT?

梯度提升决策树
决策树使用的是CART回归树作为GBDT的基学习器
在每一轮迭代时,我们生成一个基学习器,基学习器的拟合目标是当前模型Loss的负梯度。当训练完成后,我们将该基学习器加入至模型。
重复上述,继续训练基学习器,直至迭代次数达到目标。

XGBoost和LightGBM

XGBoost(eXtreme Gradient Boosting)和LightGBM都是基于Boosting思想的梯度提升树(Gradient Boosting Decision Tree)的算法,它们在Boosting算法的基础上进行了改进和优化,提高了模型的性能和速度。

XGBoost

  1. 目标函数:引入正则项,二阶泰勒展开
  2. 采样:加入了列采样
  3. level-wise:每一层都同时分裂所有节点
  4. 并行:特征粒度的并行,每个特征的最优切分点是并行的
  5. 防止过拟合:差值在阈值内

LightGBM
6. 基于直方图的特征离散化:根据直方图信息选择最佳分割点,减少比那里次数。
7. GOSS采样:样本不均匀导致少数类样本学习不足。计算梯度绝对值选择重要样本。重要样本按正常梯度推进,非重要样本按一定概率降采样
8. EFB特征捆绑:高度相关的特征捆绑在一起
9. leaf-wise:不是一次性生成同一层,而是选择最优的分裂,限制最深的深度。

联系和区别

  • XGBoost和LightGBM都是基于梯度提升树的算法,在思想和原理上有相似之处,都采用Boosting策略来提升模型性能。
  • XGBoost采用规则化学习策略,LightGBM采用直方图算法和特征值采样策略,两者都提高了对于大规模数据集和高维特征的处理能力。
  • XGBoost的特征分裂是通过贪心算法遍历所有可能分裂点,速度较快但计算量大;LightGBM的特征分裂是基于直方图的方法,计算速度更快。
  • LightGBM在内存占用和计算速度上更优,适合处理大规模数据集;XGBoost在泛化能力和鲁棒性上表现较好,适用于各种问题。

综上所述,XGBoost和LightGBM都是优秀的Boosting算法框架,有着各自的特点和优势,根据具体问题和数据集的特征选择合适的算法框架可以获得更好的模型性能。

协同过滤?

矩阵分解?

向量召回?

FM?

  1. 因子分解机

  2. 用于特征交叉

  3. 通过特征之间的交互关系建模改变传统线性模型,更好的捕捉特征之间的非线性

  4. 交互部分:隐向量的点积在这里插入图片描述

  5. 优点:泛化性好,能捕捉特征之间的交互关系,即使两个交叉特征的组合没有出现过;参数量也不大

  6. 缺点:只能捕捉二阶交叉信息

Wide and Deep

Wide:线性模型,稀疏侧输入,记忆性
Deep:深度神经网络,稠密侧输入,泛化性
2.
记忆性:模型直接学习并利用历史数据或物品的“共现频率”的能力。原始数据直接影响推荐结果。如果点击过A,就推荐B。
【记录了一些原始有连接的item关系?】
泛化性:模型传递特征的相关性,发觉稀疏甚至从未出现过的稀有特征与最终标签相关性的能力。矩阵分解引入隐向量,泛化能力比协同过滤更高。
降权局数据传递到稀疏物品上。
【通过已知的连接关系,推测没有连接部分的得分?】
3. 如何结合?
Wide 部分和 Deep 部分的输出会在模型的输出层(output layer) 进行加权求和,最终输出一个概率或回归值。
4.
输入通常是一个用户的全部特征,以及该用户与特定物品(item)相关的特征。模型通过学习这些特征之间的关系,最终输出用户对该物品的感兴趣程度。
输入:稀疏或连续特征
输出:CTR/停留时间/…
在这里插入图片描述

DeepFM

把Wide&Deep中的Wide部分改为FM,FM的隐向量和Deep部分embedding共享参数。
在这里插入图片描述

DIN

Deep Interest Network
在当前候选广告去历史浏览记录中查找相关的兴趣商品
在这里插入图片描述

  1. 当前ad特征与每个浏览item做attention计算得分
  2. 该得分与历史每个浏览item的embedding做带权重的pooling
    【基于attention的pooling相比于直接pooling,能够根据当前ad激活历史相关的用户信息,为每个广告都生成对应不同用户的历史行为特征】
  3. attention不使用softmax
  4. 训练技巧1:mini-batch aware redularization
    正则化技术
    传统的正则化是对整个数据集的loss函数进行正则化,而mini-batch aware regularization 是根据 mini-batch 上的特征分布和参数更新情况动态调整正则化的强度或形式。
    避免过拟合,提高模型泛化能力
  5. 训练技巧2:自适应激活函数DICE
    在pReLU的基础上做了一些改变。
    pReLU:
    在这里插入图片描述
    DICE:
    在这里插入图片描述
    根据输入数据的均值和方差自适应的调整修正点的位置

ShareBottom

  1. 多任务之间的“知识迁移”
    A任务正样本多,B任务正样本少,如果B单独训练,对底层的训练不太充分,但联合A和B,数据丰富的A可以将共享底层训练到一个较好的状态。
  2. 两种可能导致的负面影响
  • 负迁移:每个任务 比单独训练的效果更差
  • 跷跷板:一个任务效果更好,但另一个效果更差

MMOE

  1. Multi-gate Mixture-of-Experts
    多个门控对多个专家网络的输出进行加权,以得到不同任务的输出。
    不同专家专注不同任务,同时所有专家为所有任务共享
    在这里插入图片描述
  2. 极化现象:
    门控softmax一个接近为1,其他接近为0怎么办?
    在softmax输出上加入dropout,强制每个专家都参与每个任务

PLE

MMOE可以减轻负迁移现象,但还存在跷跷板现象。针对MMOE的问题针对性解决:

  1. MMOE中所有Expert是被所有任务共享的,无法捕捉人物之间更复杂的关系,从而给部分任务带来一些噪声。
    每个任务有单独的Expert,同时保留共享Expert
    在这里插入图片描述
  2. 不同Expert中没有交互,联合优化的效果有所折扣
    任务k的独享专家模块融合了上一层网络中的独享专家模块和共享专家模块
    共享专家模块融合了上一层的所有专家模块

AIGC

如何冻结模型?

来自ChatGPT,待考证。

for name, param in model.named_parameters():
    if 'unet' in name:  # 根据层名称来选择
        param.requires_grad = False  # 锁定 U-Net 的参数

clip是什么?它是如何做零样本分类的?

介绍一下contorlnet?controlnet如何控制sd?

ControlNet提供了包括canny边缘,语义分割图,关键点等多种输入条件,实现可控生成

结构
在这里插入图片描述
sd的所有参数都被锁定并克隆到ControlNet 段的可训练副本中。不直接训练是为了防止数据结果小出现过拟合。

condition压缩到隐空间之后,经过一层零卷积。将sd的encoder部分copy出来,单独做训练,copy出的encoder部分做零卷积操作,把输出连接在sd的decoder中。

零卷积是什么?
“零卷积”是 1×1 卷积,其权重和偏差都初始化为零。

为什么使用零卷积?
当 ControlNet 应用于某些神经网络块时,在进行任何优化之前,不会对深度神经特征造成任何影响。经过反向传播后,ControlNet 中的零卷积层变为非零并影响输出。【y=wx+b,y对w的偏置为x,x通常不为0,在一个梯度下降步骤后,w将更新为非0值】

训练过程
loss:
在这里插入图片描述
作为训练过程的一部分,我们随机用空字符串替换 50% 的文本提示 c t c_t ct. 这有助于 ControlNet 更好地理解输入条件图的含义,例如 Canny 边缘图或人类涂鸦。

【以上来自StableDiffusion——ControlNet超详细解释

介绍一下StableDiffusion?

网络结构:VAE + UNet + CLIP Text Encoder

VAE:对图像进行重建,包括编码器和解码器。用编码器将图片压缩到隐空间中,通过解码器还原。

将输入数据映射到潜在空间,输出潜在变量的分布参数(通常是均值和方差),再从潜在变量中重建输入数据。

loss:重建loss + KL散度

L ( x ; θ , ϕ ) = E q ( z ∣ x ) [ log ⁡ p ( x ∣ z ) ] − D K L ( q ( z ∣ x ) ∣ ∣ p ( z ) ) ] \mathcal{L}(x; \theta, \phi) = \mathbb{E}{q(z|x)}[\log p(x|z)] - D_{KL}(q(z|x) || p(z)) ] L(x;θ,ϕ)=Eq(zx)[logp(xz)]DKL(q(zx)∣∣p(z))]

Kullback-Leibler 散度:衡量编码器生成的潜在分布 q ( z ∣ x ) q(z|x) q(zx)与先验分布 p ( z ) p(z) p(z)之间的差异。通常,先验分布被设定为标准正态分布 N ( 0 , I ) \mathcal{N}(0, I) N(0,I)

UNet:包括下采样和上采样,下采样的特征会连接到上采样的同层特征中。

CLIP Text Encoder:图像文本的相似度计算。

StableDiffusion和diffusion的区别?

  1. 引入lantent
  2. 语义信息对图像的控制

sd和sdxl的区别?

  1. 网络架构
    1.1 模型架构
    VAE:同样架构,更大batchsize
    UNet:第一个Stage是普通的下采样block而不是Cross-attention block + 只用了3个Stage,SD用了4个 + Stage中使用了更多的transformer blocks
    CLIP:提取两个text encoder的倒数第二层特征 concat
    1.2 细化模型
    增加细节。网络结构类似,只使用图生图能力,text encoder只使用了一个clip
  2. 训练策略
    2.1 额外条件
    ① 图像的原始尺寸
    ② 裁剪的左上点坐标
    2.2 多尺度微调
    ① 将数据集中图像按照不同的长宽比划分到不同的buckets上,把bucket_size作为条件加入unet中
    ② 无法生成纯黑或者纯白的图像,因为SD所使用的noise scheduler其实在最后一步并没有将图像完全变成随机噪音。在训练噪声上加入offset-noise。

介绍一下lora?

  1. 原理:lora在原始的预训练模型旁边加入一个旁路:先降维后升维,模拟instinsic rank。矩阵计算从 d × d d×d d×d 变为 d × r + r × d d×r+r×d d×r+r×d ,参数量减少很多。

Aghajanyan的研究表明:预训练模型拥有极小的内在维度(instrisic dimension),即存在一个极低维度的参数,微调它和在全参数空间中微调能起到相同的效果。同时Aghajanyan发现在预训练后,越大的模型有越小的内在维度,这也解释了为何大模型都拥有很好的few-shot能力。

  1. 训练细节:固定LLM参数,只训练【降维矩阵A】和【升维矩阵B】。初始化时,A矩阵随机高斯分布初始化,B矩阵零初始化。

为什么A随机初始化,B零初始化?

A随机初始化:当每个神经元的参数初始值不同,它们在训练过程中会朝着不同的方向更新,从而能够捕捉到更丰富和多样的特征。如果所有的参数都被初始化为相同的值(例如全为零),在反向传播过程中,它们将会接收到相同的梯度更新。这意味着这些参数在训练过程中将始终保持相同的状态,无法学习到不同的特征,可能会导致训练过程缓慢或停滞

B零初始化:允许模型在微调的初期阶段专注于学习如何通过 ( A ) 来调整原始模型的权重,而不是立即影响到输出。这使得微调过程更加稳定,避免过早地引入噪声。随着训练的进行,( A ) 矩阵的参数会被更新,而 ( B ) 矩阵也会逐渐学习到应该如何影响输出。如果 ( B ) 被随机初始化,可能会在训练开始时就对原始模型的输出产生影响。这可能会导致学习过程的不稳定,尤其是在模型尚未学习到有意义的特征时。

  1. 结论:

① lora应该作用于Transformer的哪个参数矩阵?将可微调参数平均分配到 W q , W v W_q, W_v Wq,Wv 的效果最好。【见下一题】
② 更新参数矩阵Δ W \Delta WΔW可能拥有极小的‘内在秩’,在秩小到1或者2的时候,lora的仍有不错的效果。
③ 在训练过程中,低秩的适应矩阵 Δ W Δ W ΔW仅仅放大了对下游任务有用的特征,而不是预训练模型中的主要特征。

lora应该作用于Transformer的哪个参数矩阵?

在这里插入图片描述
将所有微调参数都放到attention的某一个参数矩阵的效果并不好,将可微调参数平均分配到 W q , W v W_q, W_v Wq,Wv 的效果最好。因此在实际操作中,应当将可微调参数分配到多种类型权重矩阵中,而不应该用更大的秩单独微调某种类型的权重矩阵。

【以上来自Lora的基本原理大模型微调技术——Lora

lora怎么用?

lora源码分析:从源码理解Lora微调

你了解qlora嘛?

lora对clip有更改吗?

描述diffusion模型?

马尔科夫链的作用是什么?

马尔科夫链通过转移概率描述状态之间的转换。转移概率是从一个状态转移到另一个状态的概率。

VAE, GAN, Diffusion的优缺点

在这里插入图片描述
在这里插入图片描述

GroundingDino贡献?结构?数据集是如何构建?如何训练?loss函数是什么样的?

SAM结构?数据及如何构建?如何训练?loss函数?

【项目】你们的测评标准是什么?

【项目】数据集是如何构建的?

【项目】如何对多角度图片进行标注?

DDPM和DDIM的区别

IP Adapter

VIT

DIT

其他

概率题

sql查表题

因为我说我会sql

LeetCode 92. 反转链表 II

LeetCode 76. 最小覆盖子串

LeetCode 10. 正则表达式匹配

没看懂题目什么要求,s = 'aab', p = 'c*a*b'居然是正确的?

合理怀疑面试官不想让我过。

LeetCode 44. 通配符匹配

class Solution(object):
    def isMatch(self, s, p):
        """
        :type s: str
        :type p: str
        :rtype: bool
        """
        ls, lp = len(s), len(p)
        dp = [[False] * (lp+1) for _ in range(ls+1)]
        dp[0][0] = True
        
        for j in range(1, lp+1):
            if p[j-1] == '*':
                dp[0][j] = True
            else:
                break

        for i in range(1, ls+1):
            for j in range(1, lp+1):
                if p[j-1] == '?' or s[i-1] == p[j-1]:
                    dp[i][j] = dp[i-1][j-1]
                elif p[j-1] == '*':
                    dp[i][j] = dp[i][j-1] or dp[i-1][j]

        return dp[ls][lp]

dp的形状是[ls+1, lp+1],因为 ∗ * 可以匹配空字符,所以我们从把空字符也算作一个状态。

如何理解dp[i][j] = dp[i][j-1] or dp[i-1][j]

dp[i][j-1] ∗ * 代表空字符
假设s = 'aabbcc', p = 'a*??*', i = 2, j = 2,考虑'aa''a'是否匹配, ∗ * 代表空字符

dp[i-1][j] ∗ * 代表任意字符
假设s = 'aabbcc', p = 'a*??*', i = 2, j = 2,考虑'a''a*'是否匹配, ∗ * 代表以 a a a结尾的任意字符

我一开始的解题思路是: ∗ * 代表任意字符,那么如果前面状态匹配,那j状态下所有i状态都为True,可以做但不如光放接法简单。

LeetCode 139. 单词拆分

动态规划:

class Solution(object):
    def wordBreak(self, s, wordDict):
        """
        :type s: str
        :type wordDict: List[str]
        :rtype: bool
        """
        ls = len(s)
        dp = [False] * (ls+1)
        dp[0] = True

        for i in range(ls+1):
            for word in wordDict:
                lw = len(word)
                if i >= lw and s[i-lw:i] == word:
                    dp[i] = dp[i] or dp[i-lw]

        return dp[ls]

LeetCode 110. 平衡二叉树

多次计算高度

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution(object):
    def isBalanced(self, root):
        """
        :type root: TreeNode
        :rtype: bool
        """
        def height(root):
            if not root:
                return 0
            return max(height(root.left), height(root.right)) + 1

        if not root:
            return True
        
        if abs(height(root.left) - height(root.right)) > 1:
            return False
        else:
            return self.isBalanced(root.left) and self.isBalanced(root.right)
        

只计算一次高度

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution(object):
    def isBalanced(self, root):
        """
        :type root: TreeNode
        :rtype: bool
        """
        def height(root):
            if not root:
                return 0, True
            
            left_height, left_is_balance = height(root.left)
            right_height, right_is_balance = height(root.right)

            if abs(left_height - right_height) > 1:
                is_balance = False
            else:
                is_balance = left_is_balance and right_is_balance

            return max(left_height, right_height) + 1, is_balance

        return height(root)[1]

算法题:全排列

class Solution(object):
    def permute(self, nums):
        """
        :type nums: List[int]
        :rtype: List[List[int]]
        """
        if len(nums) == 1:
            return [nums]
        res = []
        for i in range(len(nums)):
            for nn in self.permute(nums[:i]+nums[i+1:]):
                res.append([nums[i]] + nn)

        return res

[项目] convlstm是什么?和lstm的区别是什么?

lstm相比于rnn的优势是什么?

来自其他人的面经

搜推

搜推链路?

llm

旋转位置编码?

GPT

LLama网络架构设计

chatGLM

Qwen

Bert和GPT的区别

RMSNorm和LayerNorm对比

chatGLM v1和v2的对比 v3和v4的改进

multi-query attention介绍

Qwen或当前排名靠前的算法介绍

如何解决LLM的幻觉问题

RAG过程以及有哪些应用

RLHF概念及训练过程需要注意什么

LLM模型训练和加速方法

aigc

SD网络架构,参数量

IP Adapter创新点和网络架构

DIT介绍

视频生成和图像生成网络架构区别

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值