oppo哲库-音频算法工程师面经
面试内容要求保密,所以除编码题不会涉及具体内容,整体面试体验不是很好,大部分原因是我菜。从交流过程中发现他们想要的是可以做底层开发的人,偏向工业落地,希望应聘者既可以做理论研究,同时也要能进行工业化部署,而不是像我这种浮于表面研究而不考虑落地的研究生(身边大多数人的毛病,只看效果,不考虑实施,学界和工业界的割裂)。
先说好的地方,本人提前半小时上线(个人习惯),面试官提前十五分钟上线,好评。面试开始是自我介绍,其后聊我提到的论文,中间有讨论,面试官提出了很多见解,有思想的碰撞,真心话,受益匪浅,对自己课题研究有了新的思考。
然后不好的地方,从其举止上感受到不被尊重,坐姿有些懒散,脸上无表情,说话声音时大时小,而且多数时候否定我的观点而不举证,让人有一种被训话的感觉,当然可能是工作很忙,加班之类的比较累导致精神状态不好。
最后聊不下去了出了道编程题:
# 用python实现一个二维卷积,其中输入和输出均为四维tensor y=x*h # x: [batch, channel_in, height, width] # h: [channel_in, channel_out, k_height, k_width] # y: [batch, channel_out, height, width]
我开始以为是写个小算法,但看到题之后愣了一下,然后问面试官是否是要求我使用python在不依赖第三方库的情况下实现二维卷积,得到了肯定答复。
我笑了一下,告诉面试官这题我不会做(其实有思路,但不能熟练的写出来,也不能保证正确,尤其是特征通道处理部分没有看过详解)。面试官说难道不尝试一下吗?我以能力有限为由拒绝了。
反问环节就项目内容和公司发展问了两个问题,面试官耐心的做了解答,而且给了一些建议,很受用。
当天晚上有事,第二天对这次笔试做了复盘,并且做了反思。
首先是对编码题进行了尝试,写了一个半小时,才写出来,没有用实例测试,代码如下,希望可以得到大佬指导。采用将高维矩阵展开成一维相乘的形式,输入通道方面的处理使用的是取平均的方法,这个地方需要看看原理。代码比较冗长,因为很多功能list实现起来很麻烦,如果能用numpy的话可以精简很多。代码风格有些混乱,将就。
其次,自己对这次面试的处理并不好,最后心态发生了变化,发现对方岗位与自己不适合之后想立即结束面试,并且拒绝做编程题目,这是面试时不应该出现的,至少应该与面试官沟通编码思路,并表示无法在有限时间完成编码,希望朋友们引以为戒。
import random from typing import List class conv2d(object): def __init__(self, in_channals: int, out_channals: int, kernel_size: int, stride: int = 1, dialtion: int = 0): # 没实现padding,这个比较简单 self.in_channals = in_channals self.out_channals = out_channals self.kernel_size = kernel_size self.stride = stride self.dialtion = dialtion self.kernel_ = (self.dialtion + 1) * self.kernel_size - self.dialtion self.kernel = [[self._make_kernel() for k in range(in_channals)] for l in range(out_channals)] # 核大小是[out_channals, in_channals, kernel_size, kernel_size] def _make_kernel(self): kernel = [] for j in range(self.kernel_size): rows = [] for i in range(self.kernel_size): rows.append(random.random()) if i < self.kernel_size-1 and self.dialtion != 0: zeros = [0 for l in range(self.dialtion)] rows += zeros kernel.append(rows) if j < self.kernel_size-1 and self.dialtion != 0: for k in range(self.dialtion): zeros = [0 for k in range(self.kernel_)] kernel.append(zeros) return kernel def _make_kernel_mat(self, i, j, feature_map_size): kernel_mat = [] for col_ in range((feature_map_size - self.kernel_)//self.stride+1): kernel_head = [0 for _ in range(col_*feature_map_size)] for row_ in range((feature_map_size - self.kernel_)//self.stride+1): kernel = kernel_head[:] for kernel_row in self.kernel[i][j]: kernel += [0 for _ in range(row_*self.stride)] + kernel_row + [0 for _ in range(feature_map_size - row_*self.stride - self.kernel_)] kernel += [0 for _ in range(feature_map_size**2 - len(kernel))] kernel_mat.append(kernel) return kernel_mat def _matrix_mul(self, feature_maps, kernel_mats): result = [] in_dim = len(feature_maps) for i in range(in_dim): feature_map = [] for maps in feature_maps[i]: feature_map += maps for idx_, kernel in enumerate(kernel_mats[i]): res_ = 0 for a, b in zip(feature_map, kernel): res_ += (a*b) if i == 0: result.append(res_) else: result[idx_] += res_ for i in range(len(result)): result[i] /= in_dim out_dim = int(len(result) ** 0.5) result = [result[:out_dim], result[out_dim:]] return result def forward(self, inputs: List[List[List[List[int]]]]): # 没有写维度检验 batch_size = len(inputs) in_channel = len(inputs[0]) feature_map_size = [len(inputs[0][0]), len(inputs[0][0][0])] # 写了要求特征图是方形,是为了方便写原理,可以随意,需要修改循环的中止条件 assert self.in_channals == in_channel, 'in_channals must be equal' assert feature_map_size[0] == feature_map_size[1] and feature_map_size[0] >= self.kernel_, 'feature_map size must larger than (dialtion * kernel_size - dialtion)' kernel_mats = [[self._make_kernel_mat(i, j, feature_map_size[0]) for j in range(self.in_channals)] for i in range(self.out_channals)] outputs = [] for i in range(batch_size): output = [] for j in range(self.out_channals): output.append(self._matrix_mul(inputs[i], kernel_mats[j])) outputs.append(output) return outputs net = conv2d(in_channals=1, out_channals=1, kernel_size=3, dialtion=0) inputs = [[[[random.random() for _ in range(4)] for _ in range(4)] for _ in range(1)] for _ in range(3)] outputs = net.forward(inputs)