求職季,真的會讓一個人變得有些不一樣吧,比如我,對於一個菜鳥來說,最近一段時間焦慮不安外加有點自閉...
前段時間在校內網上看到了陌陌科技內推計算機視覺算法工程師和機器學習算法工程師的消息,抱着試試的心態整理了一份簡歷按照提供的郵箱投出去了,我想這次應該又是石沉大海了吧,誰想在一周前悶熱的一天在嘈雜的餐廳接到了陌陌科技HR的電話,一周后的周五下午4點在西安的一家咖啡館參加面試。我問清了時間地點並道謝了HR后並掛了電話繼續吃飯。
好吧,這周每天都有各個公司的筆試,外加這周周五上午的組會輪到我做組會匯報,我心里預估了一下時間安排,確實沒時間來准備陌陌的面試,心想,就這樣吧,面掛了就當積累經驗吧...
時間很快就來到了周四晚上,當我9點做完招商銀行的網上筆試后,打來之前沒有寫完的明天組會匯報的ppt接着寫了起來,前兩天已經連續凌晨2點回宿舍了,今晚不知何時能回。我主要給大家匯報一下近期的工作以及一篇臨時看的發表在ICB2018上的使用GAN來完成從熱紅外到可見光的跨頻譜人臉匹配的文獻。時間來到11點半,ppt算是寫得差不多了,但是文獻中還是有很多細節問題因為時間關系沒有搞懂,這篇文獻里最大創新點也就是所提出的損失函數理解得雲里霧里,我是繼續加班搞懂才回宿舍呢,還是就這樣將這個問題放在組會上大家一起討論,可是明天還得早起啊。有那么一瞬間,我感覺呼吸不太順暢,身體超負荷運轉已經吃不消了。我選擇回宿舍休息,就這樣吧...
昨晚還是沒有睡好,已經很久沒有睡好覺了,不過相對於前兩天,已經很不錯了,9點組會開始,疲倦始終圍繞着我,不出所料,這次組會因為各種因素我算是搞砸了...
組會完后,回到實驗室給手機充電,打印了一份簡歷,吃完午飯回來打開百度地圖搜索那家咖啡館的地理位置並做好時間路線規划,等待手機充滿電后我便出發了,下午3點我提前一個小時到了那家咖啡館,我微信上給HR發消息說我到了進門之后怎么走,那位帥氣的HR帶我進了咖啡館,問了下我的姓名和求職崗位,帶我去簽完到后給我找一個位置並了給了我一張A4紙,然后就是用自己熟悉的語言實現兩道算法題,我周圍的人都是今天來面試陌陌的,他們都在認真的低着頭寫代碼。
第一題是實現輸出一個長度為n的無序數組中的前k個最小值:
我能想到的就是先通過各種排序算法將數組排序,然后輸出前k個最小值就行了,但是這並不是最好的方式,會造成復雜度比較高,因為只需要輸出前k個最小值,剩下的n-k個數值不需要考慮。那么通過排序算法只需要排前k個數值ok了,不同的排序算法時間復雜度都是不一樣的,比如比較容易實現的選擇排序和冒泡排序平均情況下都是O(n2),若只需要找到前面的k個值復雜度也要O(n*k),若使用快速排序,復雜度近似O(n),如果使用堆排序,復雜度近似O(nlogk),下面基於堆排序給出解題思路以及python3代碼:
方法是維護k個元素的最大堆,即用容量為k的最大堆存儲最先遍歷到的k個數,並假設它們即是最小的k個數,建堆費時O(k)后,有k1<k2<...<kmax(kmax設為大頂堆中最大元素)。繼續遍歷數列,每次遍歷一個元素x,與堆頂元素比較,x<kmax,更新堆(用時logk),否則不更新堆。這樣下來,總費時O(k+(n-k)*logk)=O(n*logk)。此方法得益於在堆中,查找等各項操作時間復雜度均為logk,python3代碼如下:
1 # -*- coding: utf-8 -*- 2 """ 3 Created on Sun Sep 2 17:16:36 2018 4 5 @author: aoanng 6 """ 7 8 def create_heap(lyst): 9 #創建數組中前k個數的最大堆 10 for start in range((len(lyst) - 2) // 2, -1, -1): 11 sift_down(lyst, start, len(lyst) - 1) 12 13 14 return lyst 15 16 # 堆排序,對於本問題用不着 17 def heapSort(lyst): 18 # 堆排序 19 for end in range(len(lyst) - 1, 0, -1): 20 lyst[0], lyst[end] = lyst[end], lyst[0] 21 sift_down(lyst, 0, end - 1) 22 return lyst 23 24 # 最大堆調整 25 def sift_down(lst, start, end): 26 root = start 27 while True: 28 child = 2 * root + 1 29 if child > end: 30 break 31 if child + 1 <= end and lst[child] < lst[child + 1]: 32 child += 1 33 if lst[root] < lst[child]: 34 lst[root], lst[child] = lst[child], lst[root] 35 root = child 36 else: 37 break 38 39 40 #測試 41 if __name__ == '__main__': 42 list1 = [50, 45, 40, 20, 25, 35, 30, 10, 15] 43 k = 4 #設置需要輸出的前k個最小值 44 list_n_k = list1[k:] 45 heap_k = create_heap(list1[:k]) #將數組前k個數創建最大堆,並假設它們是最小的k個數 46 for i in range(len(list_n_k)): 47 if list_n_k[i]<heap_k[0]: 48 heap_k[0] = list_n_k[i] 49 heap_k = create_heap(heap_k) #更新堆 50 print(heap_k) #輸出前k個未排序的最小值 51 52 #若需要,則可以對堆進行排序 53 heap_k_sort = heapSort(heap_k) 54 print(heap_k_sort)
第二題是給出一個n*n的矩陣,將其逆時針旋轉90度,但是不能開辟新的內存空間:
這題的前提是必須在原數組上進行旋轉操作,只要搞清楚矩陣中元素旋轉的規律就容易求解了,那就是當前元素ai,j經過逆時針旋轉90度后有ai,j=aj,(n-i)的關系。
寫完這兩道算法題后,我檢查了兩遍並在那兒扣手機,hr看見我寫完之后過來收走了我的作業,讓我等待一會兒,大概20分鍾后一位技術面試官帶着我的簡歷以及之前寫好的算法題過來找我,第一輪技術面試便開始了。
我先簡單的自我介紹后,面試官看着我的寫的那兩道算法題聊了起來,讓我說說我的思路,我說第一題其實就是一個排序算法問題,然后輸出前k個值就好,看見我用的選擇排序算法,面試官指出了疑問,說這樣時間復雜度會比較高,然后問我有沒有其他的思路,我說可以只需要排前k個值將時間復雜度降到O(n*k),面試官最后逐步的引導我,說用最大堆會比較好,總之面試官人很nice,問到我不會的,總是在引導我。
然后就是介紹我簡歷中做過的幾個項目,項目當中有用到深度學習平台tensorflow和CNN網絡架構以及一些機器學習算法。面試官逐個問我,比如在ttensorflow中怎樣構建一個cnn網絡,防止過擬合的一些tips,Dropout是怎樣工作的等等,然后讓我手寫在tensorflow中怎樣保存模型和加載模型,tf.get_variable和tf.Variable的區別,tf.variable_scope和tf.name_scope的用法和區別,這些其實我平時項目中也有用到,平時也關注過這個問題,只是沒有上心,當時沒有回答上來,然后面試官大致的給我講了一下原理就跳過這個問題了,回來后我又在網上查了一下資料,總結如下:
tf.variable_scope和tf.name_scope的用法:
tf.variable_scope可以讓變量有相同的命名,包括tf.get_variable得到的變量,還有tf.Variable的變量
tf.name_scope可以讓變量有相同的命名,只是限於tf.Variable的變量
例如:
1 import tensorflow as tf; 2 3 with tf.variable_scope('V1'): 4 a1 = tf.get_variable(name='a1', shape=[1], initializer=tf.constant_initializer(1)) 5 a2 = tf.Variable(tf.random_normal(shape=[2,3], mean=0, stddev=1), name='a2') 6 with tf.variable_scope('V2'): 7 a3 = tf.get_variable(name='a1', shape=[1], initializer=tf.constant_initializer(1)) 8 a4 = tf.Variable(tf.random_normal(shape=[2,3], mean=0, stddev=1), name='a2') 9 10 with tf.Session() as sess: 11 sess.run(tf.global_variables_initializer()) 12 print (a1.name) 13 print (a2.name) 14 print (a3.name) 15 print (a4.name) 16 17 #輸出: 18 ''' 19 V1/a1:0 20 V1/a2:0 21 V2/a1:0 22 V2/a2:0 23 '''
如果將上邊的tf.variable_scope換成tf.name_scope將會報錯:
Variable a1 already exists, disallowed. Did you mean to set reuse=True or reuse=tf.AUTO_REUSE in VarScope?...
改成如下這樣就ok了:
1 import tensorflow as tf 2 3 with tf.name_scope('V1'): 4 # a1 = tf.get_variable(name='a1', shape=[1], initializer=tf.constant_initializer(1)) 5 a2 = tf.Variable(tf.random_normal(shape=[2,3], mean=0, stddev=1), name='a2') 6 with tf.name_scope('V2'): 7 # a3 = tf.get_variable(name='a1', shape=[1], initializer=tf.constant_initializer(1)) 8 a4 = tf.Variable(tf.random_normal(shape=[2,3], mean=0, stddev=1), name='a2') 9 10 with tf.Session() as sess: 11 sess.run(tf.global_variables_initializer()) 12 # print (a1.name) 13 print (a2.name) 14 # print (a3.name) 15 print (a4.name) 16 17 #輸出: 18 ''' 19 V1/a2:0 20 V2/a2:0 21 '''
接下來看看tf.Variable和tf.get_variable()的區別
在tensorflow中,tf.Variable和tf.get_variable()兩個op分別用來創建變量。
tf.Variable()總是創建新的變量,返回一個variable,可以定義名字相同的變量,若給出的name已經存在,會自動修改name,生成個新的:
1 import tensorflow as tf 2 w_1 = tf.Variable(3,name="w_1") 3 w_2 = tf.Variable(1,name="w_1") 4 print (w_1.name) 5 print (w_2.name) 6 #輸出 7 #w_1:0 8 #w_1_1:0
tf.get_variable()不可以定義名字相同的變量,tf.get_variable函數擁有一個變量檢查機制,會檢測已經存在的變量是否設置為共享變量,如果已經存在的變量沒有設置為共享變量,TensorFlow 運行到第二個擁有相同名字的變量的時候,就會報錯。
不同的變量之間不能有相同的名字,除非你定義了variable_scope,這樣才可以有相同的名字。
1 import tensorflow as tf 2 3 w_1 = tf.get_variable(name="w_1",initializer=1) 4 w_2 = tf.get_variable(name="w_1",initializer=2) 5 #錯誤信息 6 #ValueError: Variable w_1 already exists, disallowed. Did 7 #you mean to set reuse=True in VarScope?
tf.get_variable一般和tf.variable_scope配合使用,用於在同一個的變量域中共享同一個變量。
如何在tensorflow中保存和加載模型呢?
構建網絡中加入:saver = tf.train.Saver()
然后在session會話中:saver.save(sess, "./model/model.ckpt")
加載模型:
構建網絡中需要和之前一樣,然后在session會話中加載模型:
saver.restore(sess, "./model/model.ckpt")
然后和面試官討論一些機器學習算法的問題,諸如LR和SVM的區別,隨機森林和GBDT區別,xgboost以及最優化算法的原理等等,很快一個多小時就過去啦,感覺自己表現得不是太好,但是和面試官還是挺聊得來的,面試的最后,面試官問我對一些經典的數據結構熟悉不?我說還可以,然后他讓我現場寫一個單鏈表的逆序,很簡單的問題,我卻沒寫出來,我曾經看過java版本和c版本的數據結構與算法,前不久也看過用python實現的數據結構與算法,但是這個時候我卻卡住了。10分鍾后面試官看我連這個簡單的問題都沒寫出來,笑着對我說該不該給我第二輪技術面的機會,然后第一輪技術面就這樣結束了,讓我在旁邊的椅子上稍等一下...
等的過程中,我拿出手機百度了下單鏈表的逆序如何實現,恍然大悟的同時也有點懊惱,參考網上的答案,實現如下:
循環反轉單鏈表:
1 #定義一個單鏈表節點 2 class ListNode: 3 def __init__(self,x): 4 self.data = x 5 self.next = None 6 7 def nonrecurse(head): #循環的方法反轉鏈表 8 if head is None or head.next is None: 9 return head 10 pre=None 11 cur=head 12 h=head 13 while cur: 14 h=cur 15 tmp=cur.next 16 cur.next=pre 17 pre=cur 18 cur=tmp 19 return h 20 21 head=ListNode(1) #測試代碼 22 p1=ListNode(2) #建立鏈表1->2->3->4->None; 23 p2=ListNode(3) #head->p1->p2->p3->None 24 p3=ListNode(4) 25 head.next=p1 26 p1.next=p2 27 p2.next=p3 28 29 p=nonrecurse(head) #輸出鏈表 4->3->2->1->None 30 while p: 31 print (p.data) 32 p=p.next
遞歸實現單鏈表反轉:
1 class ListNode: 2 def __init__(self,x): 3 self.val=x; 4 self.next=None; 5 6 7 def recurse(head,newhead): #遞歸,head為原鏈表的頭結點,newhead為反轉后鏈表的頭結點 8 if head is None: 9 return ; 10 if head.next is None: 11 newhead=head; 12 else : 13 newhead=recurse(head.next,newhead); 14 head.next.next=head; 15 head.next=None; 16 return newhead; 17 18 head=ListNode(1); #測試代碼 19 p1=ListNode(2); # 建立鏈表1->2->3->4->None 20 p2=ListNode(3); 21 p3=ListNode(4); 22 head.next=p1; 23 p1.next=p2; 24 p2.next=p3; 25 newhead=None; 26 p=recurse(head,newhead); #輸出鏈表4->3->2->1->None 27 while p: 28 print (p.val) 29 p=p.next;
接下來就是技術第二面了,不說了,說多了都是淚...
參考:
- 窺探算法之美妙---尋找數組中最小的K個數&python中巧用最大堆
- 程序員編程藝術:第三章、尋找最小的k個數
- tf.variable_scope和tf.name_scope的用法
- tf.Variable()與tf.get_variable()與不同之處
- TensorFlow模型保存和加載方法
- 單鏈表反轉python實現