当前位置:   article > 正文

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

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

目录

1. GNN graph neural network

2. 0-1背包问题

3. 0-1背包问题(一维dp)

4. 螺旋矩阵 按顺时针顺序返回所有数

5. fasttext与glove


1. GNN graph neural network

(1)图的基本定义

GNN的Roadmap:其中用的最常见的还是GAT和GCN这两个model。

一般用到的tasks:

(1)半监督Node 分类

(2)回归

(3)图分类

(4)图表示学习

(5)链接预测

例子:NN4G的结构。先做embedding,再conv两次,最后readout也就是得到一个值,这个值就用来做分类。

李宏毅的课不知道在说什么。找了下其他的课件。图神经网络是一种图嵌入的方法,图嵌入除了图神经网络,还有矩阵分解和随机游走。常见模型是DeepWalk, Node2Vec。但是直接嵌入的方法缺乏泛化能力,无法处理动态图或者泛化到新的图。

2. 0-1背包问题

平时只会考01背包和完全背包,但重点还是写出状态转移方程。

01背包:有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

这里要构造二维数组。dp[i][j]表示的是:当物品为i个,最大重量为j时的最大价值。

状态转移方程为: dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i]) 分别表示放这个物品和不放这个物品

dp初始化:第一列都为0,因为重量为0放不了东西,第一行从重量能包含第一个物品开始,价值都为物品1的价值。因为物品1只有一个,放进去了就是它的价值。

遍历顺序先行后列,先列后行都可以。

写完代码发现坑还是很多。dp的维度,长为n,包含0到n-1个物品,宽为weight+1,包含0到weight个重量选择。

在每次迭代之前,要判断j的重量是不是小于这个物品重量,小于就肯定放不了。

  1. int main(){
  2. int M,N;
  3. //m:物品数量,n:行李空间
  4. cin>>M>>N;
  5. vector<int> weights(M), value(M);
  6. for(int i =0;i<M;i++) cin>>weights[i];
  7. for(int i =0;i<M;i++) cin>>value[i];
  8. vector<vector<int>> dp(M,vector<int>(N+1,0));
  9. // init
  10. for(int j=weights[0];j<=N;j++){
  11. dp[0][j] = value[0];
  12. }
  13. // iter
  14. for(int i =1; i<M;i++){
  15. for(int j = 0;j<=N;j++){
  16. if (j<weights[i]) dp[i][j] = dp[i-1][j];
  17. else dp[i][j] = max(dp[i-1][j], dp[i-1][j-weights[i]] + value[i]);
  18. }
  19. }
  20. cout<<dp[M-1][N] << endl;
  21. }

3. 0-1背包问题(一维dp)

dp[i][j] = dp[i-1][j] + dp[i-1][j-weight[i]] + value[i]

简化为:

dp[j] = dp[j] + dp[j-weight[i]] + value[i]

初始化:整个数组初始化为0

要注意!一个是j是从大到小遍历的,另一点是j的限制条件,必须大于weights[i] 才可能把物品i放进去

  1. int main(){
  2. int m,n;
  3. cin>>m>>n;
  4. vector<int> weights(m), values(m);
  5. for(int i=0;i<m;i++){
  6. cin>>weights[i];
  7. }
  8. for(int i=0;i<m;i++){
  9. cin>>values[i];
  10. }
  11. vector<int> dp(n+1,0);
  12. for(int i = 0;i<m;i++){
  13. for(int j = n;j>=weights[i];j--){
  14. dp[j] = max(dp[j], dp[j-weights[i]]+values[i]);
  15. }
  16. }
  17. cout<<dp[n]<<endl;
  18. return 0;
  19. }

4. 螺旋矩阵 按顺时针顺序返回所有数

  1. vector<int> spiralOrder(vector<vector<int>>& matrix) {
  2. int n = matrix.size(), m = matrix[0].size();
  3. int top = 0,bot = n-1,left = 0, right = m-1;
  4. vector<int> res;
  5. while(true){
  6. for(int i = left;i<=right;i++ ) res.push_back(matrix[top][i]);
  7. top++;
  8. if(top > bot) break;
  9. for(int i = top;i<=bot;i++) res.push_back(matrix[i][right]);
  10. right--;
  11. if(right < left) break;
  12. for(int i = right;i>=left;i--) res.push_back(matrix[bot][i]);
  13. bot--;
  14. if(bot < top) break;
  15. for(int i = bot;i>=top ;i--) res.push_back(matrix[i][left]);
  16. left++;
  17. if(left > right) break;
  18. }
  19. return res;
  20. }

这题挺难搞,最好直接背这个思路,不然边界条件太麻烦了。

思路就是,设置上下左右边界,遍历一遍后更新边界,并判断边界是否有交错,有交错就说明到结尾了。

5. fasttext与glove

这两个是除了word embedding以外其他的词转向量的算法。

(1)fasttext

fastText是一个快速文本分类算法。非神经网络,用的应该是机器学习算法。和CBOW很像。但fastText预测标签,而CBOW预测的是中间词。用上下文来预测连接词。输入的是x的n-gram形式,输出的是类别。

它使用了分层softmax,也就是根据类别频率的霍夫曼树来代替softmax。原本是对所有类别做归一化,复杂度就是N,构造树后复杂度就是Log(N)

霍夫曼树就是把所有节点按权重排列

n-gram的具体实现,是文本内容按照子节顺序进行大小为N的窗口滑动操作,最终形成窗口为N的字节片段序列。

从上面来看,使用n-gram有如下优点:
1、为罕见的单词生成更好的单词向量:根据上面的字符级别的n-gram来说,即是这个单词出现的次数很少,但是组成单词的字符和其他单词有共享的部分,因此这一点可以优化生成的单词向量
2、在词汇单词中,即使单词没有出现在训练语料库中,仍然可以从字符级n-gram中构造单词的词向量
3、n-gram可以让模型学习到局部单词顺序的部分信息, 如果不考虑n-gram则便是取每个单词,这样无法考虑到词序所包含的信息,即也可理解为上下文信息,因此通过n-gram的方式关联相邻的几个词,这样会让模型在训练的时候保持词序信息

但正如上面提到过,随着语料库的增加,内存需求也会不断增加,严重影响模型构建速度,针对这个有以下几种解决方案:
1、过滤掉出现次数少的单词
2、使用hash存储
3、由采用字粒度变化为采用词粒度

(2)glove 带有全局向量的词嵌入

是一种无监督学习算法,用聚合全局词-词共现统计数据

所以fasttext用的是ngram,glove用的是共现矩阵。

GloVe的优点在于,与Word2vec不同,GloVe不仅依赖于局部统计信息(单词的本地上下文信息),而是结合全局统计信息(单词共现)来获得单词向量。

GloVe可以从共现矩阵(co-occurrence probabilities matrix)中派生单词之间了解语义关系。

共现矩阵是一个方阵,第i行j列表示单词i与单词j之间的关系。判断方式是看这两个词是否出现在上下文中,也就是同一个句子或者同一个窗口。

6. 只出现一次的数组

给一个数组,判断哪个数只出现一次,保证其余的数都出现两次。

做法就是异或,相同的数异或为0,都消掉了,不同的数异或结果就等于他本身。注意异或符号是^。(python也是)

  1. int singleNumber(vector<int>& nums) {
  2. int res = 0;
  3. for(int num:nums){
  4. res =res ^ num;
  5. }
  6. return res;
  7. }

7. 分割等和子集

我这辈子都想不到这题会用01背包做。反正题解解释的思路大概也能懂,但是换个题我肯定就不会了。。

从一个数组里找到一些数加起来能刚好等于总和/2

做法就是01背包,让weights=values=数。重点在于,如果刚好有一组数加起来能等于target,那么dp[target]=target. 否则,dp[target]肯定小于dptarget。

状态转移方程和01背包一模一样,没啥好说的

  1. bool canPartition(vector<int>& nums) {
  2. int sum = 0;
  3. for(int n:nums){
  4. sum += n;
  5. }
  6. if (sum%2 != 0) return false;
  7. int total = int(sum/2);
  8. cout<<total<<endl;
  9. vector<int>dp(total+1,0);
  10. for(int i =0;i<nums.size();i++){
  11. for(int j = total;j>=nums[i];j--){
  12. dp[j] = max(dp[j],dp[j-nums[i]]+nums[i]);
  13. }
  14. }
  15. if (dp[total] == total) return true;
  16. return false;
  17. }

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号