AI/机器学习(计算机视觉/NLP)方向面试复习1

1. 判断满二叉树

所有节点的度要么为0,要么为2,且所有的叶子节点都在最后一层。

#include <iostream>
using namespace std;
class TreeNode {
public:
	int val;
	TreeNode* left;
	TreeNode* right;
//创建的时候输入参数x,会把x给val,nullptr给left和right 
	TreeNode(int x) : val(x), left(nullptr), right(nullptr) {};
	TreeNode(int x, TreeNode* l, TreeNode* r) : val(x), left(l), right(r) {};
};
bool isfull(TreeNode* cur) {
	if (cur == nullptr) return true;
	if (cur->left == nullptr && cur->right != nullptr || cur->left != nullptr && cur->right == nullptr) return false;
	if (cur->left != nullptr) {
		isfull(cur->left);
		isfull(cur->right);
	}
	else {
		return true;
	}

}

int main() {
	int x = 1;
	TreeNode* left = new TreeNode(1);
	TreeNode* right = new TreeNode(1);
	TreeNode* root = new TreeNode(1, left, nullptr);
	cout << isfull(root) << endl;

}

重点在于创建一个TreeNode类,并且写出构造函数,调用构造函数创建节点。

2. 给定一个数,求该数的平方根,不用内置函数

二分法求解。递归。

float n;
float e = 0.001;
float findsquare(float left, float right) {
	float mid = (left + right) / 2;
	if (mid * mid - n >= 0 && mid * mid - n < e || mid * mid - n <= 0 && mid * mid - n >= -e) {
		return mid;
	}
	else {
		if (mid * mid > n) {
			findsquare(left, mid);
		}
		else {
			findsquare(mid, right);
		}
	}
}
int main() {
	cin >> n;
	cout<< findsquare(0, n)<<endl;

}

3. GAN model

图像生成模型。图像生成模型比较了解的两种是GAN和diffusion。

GAN的基本流程:生成器可以用任何输出二维图片的网络,例如DNN或者CNN。

Discriminator一般输入为图片,输出为real或者fake。

每一轮,将reference输入到discriminator里判别为real,Generator输出的输入到discriminator里判别为假。

Generator的损失函数和Discriminator的损失函数都是二元交叉熵,也就是评估真实数据的概率,Generator的目标是最大化二元交叉熵,也就是让假结果都为正,而Discriminator是最小化二元交叉熵,让假结果都为假。

4. Diffusion model

首先是数学知识:

条件概率公式

基于马尔科夫假设:当前概率仅与上一刻概率有关,与其他时刻无关。可以把条件概率其他项约掉。

高斯分布的KL散度公式:

参数重整化:整理出z作为网络输入,其他两个作为网络参数,可求梯度的。

多元VAE目标函数,都是根据x推理出z,用z预测x。多元VAE的z有多个。

Diffusion Model 主要是两个过程,先从目标分布中扩散,得到噪声分布,是熵增的过程;

然后是从噪声分布中预测出目标分布。训练过程就是训练好这个x,这样就能在随机生成(例如高斯分布 )的噪声中获得想要的目标分布。

扩散过程是p,逆扩散过程是q。漂移量是两者之间的差。

5. 二叉树的创建,插入和删除

这里应该是搜索二叉树,左节点小于自己,右节点大于自己。

删除先不写了不会

#include<iostream>
using namespace std;

class TreeNode {
public:
	int val;
	TreeNode* left;
	TreeNode* right;
	TreeNode(int x) :val(x), left(nullptr), right(nullptr) {};
	TreeNode(int x, TreeNode* l, TreeNode* r) : val(x), left(l), right(r) {};
};
TreeNode* insert(TreeNode* cur, int x) {
	if (cur == nullptr) {
		return new TreeNode(x);
	}
	if (x < cur->val) {
		cur->left = insert(cur->left, x);
	}
	else if (x > cur->val) {
		cur->right = insert(cur->right, x);
	}
	return cur;

}
//有点复杂,先不写了
TreeNode* deleteNode(TreeNode* cur, int val) {
	if (cur == nullptr) {
		return cur;
	}

}
int main() {
	int x = 1;
	TreeNode* root = new TreeNode(x);
	insert(root, 2);
}

6. Linux相关命令:

top 查看进程信息
df -h 查看硬盘使用情况
ps aux 查看所有进程
kill -9 pid 杀死编号为pid的进程
chmod 修改权限
grep 从文件名中找到包含某个字符串的数据
wc -l 统计行数
cut 分割一行内容
echo $PATH | cut -d ':' -f 3,5:输出PATH用:分割后第3、5列数据
find -name 查找文件
vim 浏览 
head -3 显示前三行内容
docker:
docker ps -a 查看容器
docker attach 恢复容器
docker exec 挂起容器
docker run 跑容器
vim :n 到第n行 dd 删除当前行 :q!直接退出 :wq保存退出 gg=G格式化
ssh 登录服务器 scp -r传文件

7. 快速排序

#include <bits/stdc++.h>
using namespace std;
const int N = 100001;
void quicksort(int * arr, int l, int r){
    if(l >= r) return;
    int i = l-1, j = r+1;
    int mid = (l+r) / 2;
    int x = arr[mid];
    while(i<j){
        do i++; while(arr[i] < x) ;
       do j--; while(arr[j] > x) ;
       if(i<j) swap(arr[i],arr[j]);
    }
    quicksort(arr, l,j);
    quicksort(arr, j+1, r);

}
int main(){
    int n;
    cin>>n;
    int arr[N];
    for(int i =0;i<n;i++){
        cin>>arr[i];
    }
    quicksort(arr,0,n-1);
    for(int i =0;i<n;i++){
        cout<<arr[i]<<" ";
    }cout<<endl;
}

总是会忘记的点:先do后while,i和j初始化为l-1和r+1,因为进入dowhile循环后会自增or自减。

quicksort(arr, l,j); 这里不能用i代替j,因为i是一定大于x的,j是小于等于x的。要保证左边的段是小于等于x,右边的段是大于等于x。

8. xgboost和deepfm的性能。

(1)xgboost的结构:由多个回归决策树的模型构成。每一步都加入一个新的树。(前向分布算法,用贪心的策略)逐步优化基学习器。

优化第t棵树时,前面t-1颗树的参数是确定的。每轮的目标函数是n个样本的最小损失+正则项

正则项是前t颗树的复杂度。它由叶子结点的个数和每个节点值w的平方和决定,正则项是为了防止过拟合的。叶子节点越多,越容易过拟合。节点值大,就会导致这棵树占比比较多,也容易过拟合

在机器学习中,一般通过梯度下降法优化参数。但是树模型是阶跃的,不连续的函数求不了梯度。所以xgboost是对每个叶节点求loss。 每个叶结点的loss可以用梯度来算,分别用了一阶导数和二阶导数也就是Hessian矩阵来找最优的分割点。

(2)xgboost如何用在推荐系统上?

将用户的上下文信息作为特征输入到xgboost中,预测用户的点击概率。所以xgboost是做回归的,放入到里面后

因为是回归任务,所以每轮迭代是选择叶节点的分裂点,然后根据分裂点得到一个值,这个值就是点击概率。多个数就是加权求平均。树的节点个数这些都是超参数。

(3)xgboost如何并行的?

并行时,在最优分裂点时用并行运算加快效率。它对特征进行分块,并行计算每个特征的增益,通过增益找到最佳分割点。再同步结果,选择最大的特征进行分割。

xgboost相对于梯度提升树(GBDT)有啥提升?

引入了二阶导数(Hessian),这在优化过程中比传统GBDT(只使用一阶导数信息)更为准确。

(4)Deepfm算法:

deep factorization machines 因子分解机。它对低阶特征做特征交互,另外一个DNN神经网络,做高阶特征交互。一般输出是两者的加权和。

因子分解机(FM)是什么?

FM是SVM的拓展,更适合用于处理稀疏特征。主要考虑到多维特征之间的交叉关系(就像SVM的核函数,用内积,但是却是用因子分解参数化的方式,而SVM中用的是稠密参数化的方式,这使得FM相比SVM的参数少了很多,更加容易计算)。其中参数的训练使用的矩阵分解的方法。

例如对于电影评分中的数据,用onehot向量建模,一个特征是非常稀疏的,非常长。因子分解机就是一种改进的二阶多项式模型,考虑到两个向量之间的相似性,例如喜欢这个类型电影的对另一个类型电影的喜欢。(推荐系统之FM(因子分解机)模型原理以及代码实践 - 简书

本质上是用deepfm给召回阶段的候选集合排序。所以做的仅仅只是排序,不是召回。Loss用的是adam。

(5)为什么在大规模数据集上使用deepfm?

在处理用户行为数据和隐式反馈数据时,DeepFM通过其深度部分能够捕捉到复杂的非线性关系,表现较好。在大规模推荐系统中,如广告推荐、商品推荐等,DeepFM具有优势。

适合大规模数据和自动特征学习的场景,尤其在处理高维稀疏特征时表现出色。但需要大量数据和计算资源才能充分发挥其优势。

9. 判断链表里是否有环

可以用哈希法或者快慢指针法。快慢指针要注意:判断fast的next。不然会出界,并且初始化两个指针不能相同,不然当只有一个数据时返回就不对了。

    bool hasCycle(ListNode *head) {
        if (head == nullptr) return false;
        ListNode* slow = head;
        ListNode* fast = head->next;
        while(slow != fast){
            if(fast == nullptr || fast->next == nullptr) return false;
            slow = slow->next;
            fast = fast->next->next;
        }
        return true;
    }

哈希表法:注意插入是insert

bool hasCycle(ListNode *head) {
        unordered_set<ListNode*> sets;
        ListNode * cur = head;
        while(cur!=nullptr){
            if(sets.count(cur)) return true;
            sets.insert(cur);
            cur = cur->next;
        }
        return false;
    }

10. HDFS相关基础知识

对hadoop了解的不多,主要是使用了一些hadoop的命令进行数据读取。

HDFS是hadoop distribution file system。HDFS的文件分布在服务器集群上,提供副本和容错率保证。

适用于存储特别大的文件,采用流式数据进行访问。但不适合毫秒级别的访问,是有点延时的。

我是使用了一些命令行的命令,例如:

hadoop fs -copyFromLocal // copy file
hadoop fs mkdir
hadoop fs -ls

11. Transformer相关

基础的Transformer是只用Attention,不用CNN or RNN。它相对于RNN最基础的好处是可以并行。

multi-head指的是多输出通道,self-attention指的是输入的QKV是同一个东西。基础的transformer是编解码结构,编码器和解码器都包含n个transformer块。

其中,Encoder里的transformer块里的结构是:

输入-》位置编码-》multi-head attention->add和 norm->feed forward ->add和 norm ->输出。其中,add就是残差连接。

Decoder block里多了一层Masked multi-head attention。

LayerNorm对一个batch下所有样本的各个维度分别执行标准化。例如,对某个batch所有样本的第一维度做标准化。(因为输入是一维,样本拼在一起是二维,batch是第三维,所以在网络里流通的是一个三维矩阵)

QKV是什么? Query是输入序列,key和value是attention里的,将Query和key做内积得到Query和key的相似性,将相似性作为不同value的权重,输出value的加权和就是注意力分数,再经过一个softmax计算注意力权重。

self-attention:指的是Q,K,V都来自同一个输入,关注的是输入内部的依赖关系。

Cross-Attention:Q 来自一个输入(如解码器),而 K 和 V 来自另一个输入(如编码器)。主要是用于多个序列作为输入。例如机器翻译,文本摘要,输出的结果需要考虑到多个输入序列。机器翻译用编码器-解码器结构,就是解码器关注到编码器的输出。

multi-head attention:QKV被分成多个头,独立进行运算,最后将结果拼接起来,提高模型的表达能力。

mask-head attention: 指的是多了一层掩码,只看当前帧之前的序列。掩码具体做法是在矩阵项城的时候,将那一个数置为-1e9,也就是负无穷。

12. aigc方向国内的典型研究机构以及代表性工作有哪些

AIGC AI-Generated Content 就是内容生成,包括GAN,VAE,stable diffusion(用于绘画),GPT这些。

GPT:包括预训练,fine tunning两个阶段,采用transformer的decoder作为特征抽取器。预训练是没有人工参与的,用大量文本作为训练材料。但是这样给出的结果并不是想要的,这时就需要人工指导,来引导模型输出想要的回答。这里也算是增强学习的领域。在这方面还有以下的问题:

(1)纠正错误:通过Nerual editing 修改网络,让网络不再输入错误的结果

(2)保护隐私:Machine unlearing,让网络遗忘一些学到的东西,以防泄露机密。 

国内有文心一言,

13. 两数之和

    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int,int> sets;
        for(int i =0;i<nums.size();i++){
            auto it = sets.find(target-nums[i]);
            if (it != sets.end()){
                return {it->second, i};
            }
            sets[nums[i]] = i;
        }
        return {};
    }

这题主要注意思路,用哈希表找是否存在target-当前数。unordered_map查找用find!=sets.end()

另外要考虑没有的情况,返回{}

Q. 输入的数组特别长,以文件的形式存储的,存的是txt在磁盘上,需要一个t的存储空间,是没法直接加载到内存的。这样的情况下怎么办

(1)用哈希表来维护所有数据是否出现以及次数,这样哈希表最大的空间就是0-最大值。可以分块处理,比如分成多个小块,每块小于内存容量,逐个读取,重复的数不需要读取。

(2)可以用c++的标准文件流fstream读取,每次读取一行,将一行的数据用哈希表存储。

14. PaddlePaddle和PaddleOCR

在实习中,主要是用了paddleOCR创建工作流来监控训练预测。工作流上是单独的环境。每天是用shell脚本来在服务器上运行,定时启动。

多个工作流启动的模板:

function process1(){
    echo "1"
    sleep 10s
}
process1 > /work/log/1.log 2>&1 &
pid1 = 'echo $1'

echo 等待进程 $pid1
wait $pid1

15. 列举常见的机器学习算法

可以分成无监督和有监督的模型,有监督可以分成分类与回归。

常见的分类模型有SVM,决策树,逻辑回归(用于二分类),随机森林,贝叶斯分类器等等;

回归模型有线性回归,决策树回归。

无监督的算法有k-means聚类,层次聚类,DBSCAN。

16.  如何处理用户输入特征的稀疏数据?

从数据里获取每天的观看时长、点击数量、业务类型。先用特征处理,对一些可以分类的类型(城市,片区,业务区,服务类型)进行onehot编码。对于时间特征(2022/2/18)这种做格式化,计算出观看时长并用float记录。其他项均为0.

17.  重排链表

这题主要三个步骤:先找中点,再反转链表,再合并。三个地方我都有坑。

找中点简单点,用快慢指针,注意判断条件。

反转链表主要是要初始化的问题,开始初始化时pre指向nullptr,cur指向当前。

merge简单,但是在之前一定要记得把中间节点断开!

class Solution {
public:
    ListNode * reverse(ListNode * head){
        ListNode* pre = nullptr, * cur = head, * nexth;
        while(cur != nullptr){
            nexth = cur->next;
            cur->next = pre;
            pre = cur;
            cur = nexth;
        }
        return pre;
    }
    void merge(ListNode* node1, ListNode * node2){
        ListNode* n1next, *n2next;
        while(node1!=nullptr && node2 != nullptr){
            n1next = node1->next, n2next = node2->next;
            node1->next = node2;
            node1 = n1next;
            node2->next = node1;
            node2 = n2next;
        }
    }
    ListNode * middlenode(ListNode * head){
        ListNode* slow=head, *fast=head;
        while(fast->next != nullptr && fast->next->next != nullptr){
            fast = fast->next->next;
            slow = slow->next;
        }
        return slow;
    }
    void reorderList(ListNode* head) {
        if(head == nullptr) return;
        // 找中点
        ListNode* mid = middlenode(head);
        // 逆序
        ListNode * l1 = head;
        ListNode * l2 = mid->next;
        // 一定要断开中间!
        mid->next = nullptr;
        l2 = reverse(l2);
        // 合并
        merge(l1,l2);

    }
};

18. 不同的激活函数以及特点?

(1)softmax,attention用的激活函数。能转成一个平滑的分数并总和为1.

(2)relu:只保留正区间的值并且保证线性。还能减轻梯度消失问题。但一旦输入为负就不会再激活神经元,让神经元死亡。

19. 推荐系统相关指标(主要用于排序)

(1)DCG和NDCG

DCG是会考虑到排序的分数,如果优先级比较高排到了后面,就会对结果得分打折。得分除以位置的log函数。

NDCG是对DCG进行归一化,先得到一个人工排序结果,再算出这个排序下的DCG就叫iDCG。

(2)命中率(HR)

看推荐的列表中被用户点击的概率。计算重合的项。

(3)冷门特征和热门特征。

热门特征就是所有列表中被用户点击最多的物品,而冷门特征是列表中其他用户点击非常少而该用户点击很多的特征。

20. SwinIR结构

CNN浅层特征提取+Swin transformer block深层特征提取。浅层就是几层CNN。

Swin transformer block主要是:先将图像分割成多个不重叠的patch,再进行绝对位置嵌入。经过SwinBlock后,会用一个卷积块,保证输入输出的通道数一致,方便残差连接。

最后是图像重建模块,用几层卷积上采样,最近邻差值法。

21. SwinTransformer的特点

引入了滑窗操作,包括不重叠的local window和重叠的cross window,将注意力集中在一个窗口中来节省计算量。

一个Block包括:Layer norm,window attention,MLP和shift window attention。 

22. 如何筛选流失用户?

对用户画像进行分析。用户画像包括用户的行为特征,标签化,对各个影视资源的观看时长、类别、导演、演员、语言、地区、节假日行为偏好。

用户画像来自于泛搜索、精确搜索、电机数据,形成统一格式的数据,再关联物品库得到分数据源的用户画像。

根据观看时长、点击次数找到数据衰减,将已经衰减的用户画像添加用户的其他信息,得到最终的用户画像。

23. Mysql相关基础语法

创建:

create table student(sid CHAR(6), age int); # 创建
alter table student add (classname varchar(100)); # 修改
insert into student(sid, sname) values ('100','xxr');
update student set sname = 'x', age = '20' where age > 50 and gender = 'male';
delete from student where sname = 'sname'

查询:

select * from student
where sid in ('100','200')
where sname is not null
where sname like '_i%'
# 去除重复记录
select distinct sal from emp;
select *, xxx as ddd from emp; # 添加别名
ordered by sal DESC, empno ASC # 排序,降序和升序

设置键,删除键

alter table name add primary key (cname)
alter table name drop primary key (indexname)

关联查询

# 内关联查询
SELECT * FROM table_A INNER JOIN table_B ON a_user_id = b_user_id;
# 左关联查询
SELECT * FROM table_A LEFT JOIN table_B ON a_user_id = b_user_id;
# 右关联查询
SELECT * FROM table_A RIGHT JOIN table_B ON a_user_id = b_user_id;

mysql 执行顺序:

  1. 加载 from关键词后面跟的表,计算笛卡尔积,生成虚拟表vt1。这也是sql执行的第一步:表示要从数据库中执行哪些表。

  2. 筛选关联表中满足on表达式的数据,保留主表数据,并生成虚拟表vt2。join表示要关联的表,on代表连接条件。

  3. 如果使用的是外连接,执行on的时候,会将主表中不符合on条件的数据也加载进来,作为外部行。

  4. 如果from子句中涉及多张表,则重复第一步到第三步,直至所有的表都加载完毕,更新vt3。

  5. 执行where表达式,筛选出符合条件的数据生成vt4。

  6. 执行 group by 子句进行分组。分组会把子句组合成唯一值并且每个唯一值只包含一行,生成vt5。一旦执行group by,后面的所有步骤只能操作vt5中的列(group by的子句包含的列)和聚合函数。
         温馨提示:这一步开始才可以使用select中的别名,它返回的是一个游标,而不是一张表,所以在where中不可以使用select中的别名,而having却可以。

  7. 执行聚合函数,例如sum、avg等,生成vt6。

  8. 执行having表达式,筛选vt6中的数据。having是唯一一个可以在分组后执行的条件筛选表达式,生成vt7。

  9. 执行SELECT,从vt7中筛选列,生成vt8。

  10. 执行distinct,对vt8去重,生成vt9。
         其实执行过group by后就没必要再去执行distinct,因为分组后,每组只会有一条数据,并且每条数据都不相同。

  11. 按照order_by_condition 对vt9进行排序,此处亦可以使用别名。这个过程比较耗费资源。

  12. 执行 limit 语句,取出指定条数的结果集返回给客户端。

24. 有效的括号字符串

用两个栈,一个存左括号一个存星号,思路很简单,主要是在最后!要判断一下left的索引一定是在star索引的左边,两个才能配对。

bool checkValidString(string s) {
        stack<int> left, star;
        for (int i =0;i<s.size();i++){
            char c = s[i];
            if (c == '(') left.push(i);
            else if(c == '*') star.push(i);
            else{
                if(left.empty() && star.empty()) return false;
                else if (!left.empty()) left.pop();
                else star.pop();
            }
        }
        while(!left.empty()){
            if(!star.empty()){
                // left索引一定是在star的左边,这样star才可能是右括号
                if(left.top()>star.top()) return false;
                left.pop();
                star.pop();
            }
            else{
                return false;
            }
        }
        return true;
    }

25. 链表排序

归并排序+递归 自上而下的。

class Solution {
public:
    ListNode * findmid(ListNode * head){
        ListNode * slow = head, *fast = head;
        while(fast->next != nullptr && fast->next->next != nullptr){
            fast = fast->next->next;
            slow = slow->next;
        }
        return slow;
    }
    ListNode * merge(ListNode * n1, ListNode * n2){
        ListNode * res = new ListNode();
        ListNode * cur = res;
        while(n1!= nullptr && n2!=nullptr){
            if(n1->val <= n2->val){
                cur->next = n1;
                n1 = n1->next;
            }else{
                cur->next = n2;
                n2 = n2->next;
            }
            cur = cur->next;
        }
        cur->next = n1?n1:n2;
        return res->next;
    }
    ListNode* mergesort(ListNode* head){
        if (!head || !head->next) return head;
        ListNode * mid = findmid(head);
        ListNode * n2 = mid->next, *n1 = head; 
        mid->next = nullptr;
        n1 = mergesort(n1);
        n2 = mergesort(n2);
        
        return merge(n1,n2);
    }
    ListNode* sortList(ListNode* head) {
        return mergesort(head);
    }
};

  • 11
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值