2021年6月9日,CVTE NLP算法岗面试题9道

882 阅读6分钟

1.讲一下改进的tf-idf

TF-IDF中的IDF是一种试图抑制噪声的加权,单纯的以为文本频率小的单词就越重要,文本频率越大的单词就越无用,这一方式会在同类语料库中存在巨大弊端,一些同类文本的关键词容易被掩盖。例如:语料库 D 中教育类文章偏多,而文本 j 是一篇属于教育类的文化在那个,那么教育类相关词语的IDF值就会较小,使提取本文关键词的召回率更低。

改进方法:TF−IWF (Term Frequency-Inverse Word Frequency)

AI充电季,开宝箱免费学正价课!

更有200本纸质书等,来就送!>>>AI充电季,开宝箱免费学正价课,200本纸质书等包邮送!-七月在线

2.讲一下k-means与谱聚类

聚类算法属于无监督的机器学习算法,即没有类别标签y,需要根据数据特征将相似的数据分为一组。K-means聚类算法即随机选取k个点作为聚类中心,计算其他点与中心点的距离,选择距离最近的中心并归类,归类完成后计算每类的新中心点,重新计算每个点与中心点的聚类并选择距离最近的归类,重复此过程,直到中心点不再变化。

谱聚类的思想是将样本看作顶点,样本间的相似度看作带权的边,从而将聚类问题转为图分割问题:找到一种图分割的方法使得连接不同组的边的权重尽可能低(这意味着组间相似度要尽可能低),组内的边的权重尽可能高(这意味着组内相似度要尽可能高),从而达到聚类的目的。

3.蒸馏的思想,为什么要蒸馏?

知识蒸馏就是将已经训练好的模型包含的知识,蒸馏到另一个模型中去。具体来说,知识蒸馏,可以将一个网络的知识转移到另一个网络,两个网络可以是同构或者异构。做法是先训练一个teacher网络,然后使用这个teacher网络的输出和数据的真实标签去训练student网络。

在训练过程中,我们需要使用复杂的模型,大量的计算资源,以便从非常大、高度冗余的数据集中提取出信息。在实验中,效果最好的模型往往规模很大,甚至由多个模型集成得到。而大模型不方便部署到服务中去,常见的瓶颈如下:

  • 推断速度慢
  • 对部署资源要求高(内存,显存等),在部署时,我们对延迟以及计算资源都有着严格的限制。

因此,模型压缩(在保证性能的前提下减少模型的参数量)成为了一个重要的问题。而”模型蒸馏“属于模型压缩的一种方法。

4.有哪些蒸馏方式? 蒸馏中的学生模型是?

以Bert模型举例:

Logit Distillation

Beyond Logit Distillation:TinyBert

Curriculum Distillation:

Dynamic Early Exit:FastBert。

5.python在内存上做了哪些优化?

python通过内存池来减少内存碎片化,提高执行效率。主要通过引用计数来完成垃圾回收,通过标记-清除解决容器对象循环引用造成的问题,通过分代回收提高垃圾回收的效率。

6.怎么节省内存?

  • 手动回收不需要用的变量;
  • 将数值型数据转化为32位或16位(对数据类型进行限制)

代码示例如下:

def reduce_mem_usage(props):
    # 计算当前内存
    start_mem_usg = props.memory_usage().sum() / 1024 ** 2
    print("Memory usage of the dataframe is :", start_mem_usg, "MB")
    
    # 哪些列包含空值,空值用-999填充。why:因为np.nan当做float处理
    NAlist = []
    for col in props.columns:
        # 这里只过滤了object格式,如果你的代码中还包含其他类型,请一并过滤
        if (props[col].dtypes != object):
            
            print("**************************")
            print("columns: ", col)
            print("dtype before", props[col].dtype)
            
            # 判断是否是int类型
            isInt = False
            mmax = props[col].max()
            mmin = props[col].min()
            
            # Integer does not support NA, therefore Na needs to be filled
            if not np.isfinite(props[col]).all():
                NAlist.append(col)
                props[col].fillna(-999, inplace=True) # 用-999填充
                
            # test if column can be converted to an integer
            asint = props[col].fillna(0).astype(np.int64)
            result = np.fabs(props[col] - asint)
            result = result.sum()
            if result < 0.01: # 绝对误差和小于0.01认为可以转换的,要根据task修改
                isInt = True
            
            # make interger / unsigned Integer datatypes
            if isInt:
                if mmin >= 0: # 最小值大于0,转换成无符号整型
                    if mmax <= 255:
                        props[col] = props[col].astype(np.uint8)
                    elif mmax <= 65535:
                        props[col] = props[col].astype(np.uint16)
                    elif mmax <= 4294967295:
                        props[col] = props[col].astype(np.uint32)
                    else:
                        props[col] = props[col].astype(np.uint64)
                else: # 转换成有符号整型
                    if mmin > np.iinfo(np.int8).min and mmax < np.iinfo(np.int8).max:
                        props[col] = props[col].astype(np.int8)
                    elif mmin > np.iinfo(np.int16).min and mmax < np.iinfo(np.int16).max:
                        props[col] = props[col].astype(np.int16)
                    elif mmin > np.iinfo(np.int32).min and mmax < np.iinfo(np.int32).max:
                        props[col] = props[col].astype(np.int32)
                    elif mmin > np.iinfo(np.int64).min and mmax < np.iinfo(np.int64).max:
                        props[col] = props[col].astype(np.int64)  
            else: # 注意:这里对于float都转换成float16,需要根据你的情况自己更改
                props[col] = props[col].astype(np.float16)
            
            print("dtype after", props[col].dtype)
            print("********************************")
    print("___MEMORY USAGE AFTER COMPLETION:___")
    mem_usg = props.memory_usage().sum() / 1024**2 
    print("Memory usage is: ",mem_usg," MB")
    print("This is ",100*mem_usg/start_mem_usg,"% of the initial size")

7.pandas库怎么读取超大型文件?

可以采取分块读取数据的方式。

代码示例如下:

data_path = r'E:\python\Study\BiGData\demo.csv'
def read_bigfile(path):
    # 分块,每一块是一个chunk,之后将chunk进行拼接
    df = pd.read_csv(path, engine='python', encoding='gbk', iterator=True)  
    loop = True
    chunkSize = 10000
    chunks = []
    while loop:
        try:
            chunk = df.get_chunk(chunkSize)
            chunks.append(chunk)
        except StopIteration:
            loop = False
            print("Iteration is stopped.")
    df = pd.concat(chunks, ignore_index=True)
after_df = read_bigfile(path = data_path)

8.无重复字符的最长子串

该题为leetcode-3,难度:【中等】

方法:双指针 + sliding window

定义两个指针 start 和 end 得到 sliding window

start 初始为0,用end线性遍历每个字符,用 recod 记录下每个字母最新出现的下标

两种情况:一种是新字符没有在 record 中出现过,表示没有重复,一种是新字符 char 在 record 中出现过,说明 start 需要更新,取 start 和 record[char]+1 中的最大值作为新的 start。

需要注意的是:两种情况都要对record进行更新,因为是新字符没在record出现过的时候需要添加到record中,而对于出现过的情况,也需要把record中对应的value值更新为新的下标。

代码:

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        record = {}
        start,res = 0,0
        for end in range(len(s)):
            if s[end] in record:
                start = max(start, record[s[end]] + 1)
            record[s[end]] = end
            res = max(res, end - start + 1)
        return res

时间复杂度:O(n)

空间复杂度:O(1)

9.判断链表是否有环、链表环的入口

判断链表是否有环为leetcode-141题

提供两种解题方法,如下:

方法一:哈希表

遍历所有节点,每次遍历一个节点时,判断该节点此前是否被访问过。

如果被访问过,说明该链表是环形链表,并返回True,如果没有,则将该节点加入到哈希表中,遍历完成即可。

代码如下:

class Solution:
    def hasCycle(self, head: ListNode) -> bool:
        seen = set()
        while head:
            if head in seen:
                return True
            seen.add(head)
            head = head.next
        return False

时间复杂度:O(n)

空间复杂度:O(n)

方法二:快慢指针

定义两个指针,一快一慢,满指针每一移动一步,快指针每次移动两步,由于快指针比慢指针慢,如果链表有环,则快指针一定会和慢指针相遇。

代码如下:

class Solution:
    def hasCycle(self, head: ListNode) -> bool:
        fast = slow = head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
            if fast == slow:
                return True
        return False

时间复杂度:O(N)

空间复杂度:O(1)

链表环的入口 为leetcode-142题

解题思路:使用快慢指针

首先,我们要做以下设定,设链表共有 a + b个节点,其中链表头部到链表入口(不计链表入口节点)有 a 个节点,链表环有 b 个节点,同时设快指针走了 f 步,慢指针走了 s 步;

显然,由于快指针是慢指针的两倍,因此有:f = 2s

如果两个指针第一次相遇,则满足:f = s + nb(n表示n个环),也就是 fast 比 slow 多走了 n 个环的长度;

由上可得:当两个指针第一次相遇,有s = nb, f = 2nb

现在需要解决的问题,我们如何知道 a 的大小?

假设现在两指针第一次相遇:

对慢指针来说,如果让慢指针走到链表入口节点,需要 a + nb步,而在相遇时,已经走了 nb 步了,因此,再让慢指针走 a 步就可以走到链表入口节点了;

对快指针来说,如果让快指针从头指针开始走 a 步,快指针也正好走到链表入口节点;

由此可见,两指针发生第二次相遇的时候,刚好都在链表入口处。

代码如下:

class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        fast = slow = head
        while True:
            if not (fast and fast.next):return
            fast = fast.next.next
            slow = slow.next
            if fast == slow:break
        fast = head
        while fast != slow:
            fast = fast.next
            slow = slow.next
        return fast

时间复杂度 O(N)

空间复杂度 O(1)


AI充电季,开宝箱免费学正价课!

更有200本纸质书等,来就送! >>>AI充电季,开宝箱免费学正价课,200本纸质书等包邮送!-七月在线

发布于刚刚