小姐姐的Python隱藏技巧合集,推特2400贊,代碼可以直接跑
小姐姐的Python隱藏技巧合集,推特2400贊,代碼可以直接跑">
本文經(jīng)AI新媒體量子位(公眾號ID:QbitAI)授權(quán)轉(zhuǎn)載,轉(zhuǎn)載請聯(lián)系出處。
常常發(fā)資源的英偉達(dá)工程師小姐姐Chip Huyen,又發(fā)了一套Python隱藏功能合集。
里面都是她“從前沒發(fā)現(xiàn),或者從前不太敢用”的機(jī)器學(xué)習(xí)技巧,有notebook可以直接跑。
合集名叫python-is-cool,推特宣布之后不到半天,已經(jīng)收獲了2400+贊。
小姐姐的Python隱藏技巧合集,推特2400贊,代碼可以直接跑">那么,這份令人奔走相告的資源,到底長什么樣子?
隱藏技巧五大類
就像開頭提到的:這里的功能,要么是小姐姐花了很久才找到的,要么是曾經(jīng)讓她瑟瑟發(fā)抖到不敢嘗試的。
不過現(xiàn)在,她的技巧已經(jīng)成功支配了這些功能,于是分享了出來。
目前一共有5個版塊,專注機(jī)器學(xué)習(xí),日后還會持續(xù)更新:
1、Lambda、Map、Filter、Reduce函數(shù)
lambda 關(guān)鍵字,是用來創(chuàng)建內(nèi)聯(lián)函數(shù) (Inline Functions) 的。square_fn 和 square_ld 函數(shù),在這里是一樣的。
- 1def square_fn(x):
 - 2 return x * x
 - 3
 - 4square_ld = lambda x : x * x
 - 5
 - 6for i in range(10):
 - 7 assert square_fn(i) == square_ld(i)
 
lambda 函數(shù)可以快速聲明,所以拿來當(dāng)回調(diào) (Callbacks) 函數(shù)是非常理想的:就是作為參數(shù) (Arguments) 傳遞給其他函數(shù)用的,那種函數(shù)。
和 map、filter 和 reduce 這樣的函數(shù)搭配使用,尤其有效。
map(fn,iterable) 會把 fn 應(yīng)用在 iterable 的所有元素上,返回一個map object。
- 1nums = [1/3, 333/7, 2323/2230, 40/34, 2/3]
 - 2nums_squared = [num * num for num in nums]
 - 3print(nums_squared)
 - 4
 - 5==> [0.1111111, 2263.04081632, 1.085147, 1.384083, 0.44444444]
 
這樣調(diào)用,跟用有回調(diào)函數(shù)的 map 來調(diào)用,是一樣的。
- 1nums_squared_1 = map(square_fn, nums)
 - 2nums_squared_2 = map(lambda x : x * x, nums)
 - 3print(list(nums_squared_1))
 - 4
 - 5==> [0.1111111, 2263.04081632, 1.085147, 1.384083, 0.44444444]
 
map 也可以有不止一個 iterable。
比如,你要想計算一個簡單線性函數(shù) f(x)=ax+b 的均方誤差 (MSE) ,兩種方法就是等同的。
- 1a, b = 3, -0.5
 - 2xs = [2, 3, 4, 5]
 - 3labels = [6.4, 8.9, 10.9, 15.3]
 - 4
 - 5# Method 1: using a loop
 - 6errors = []
 - 7for i, x in enumerate(xs):
 - 8 errors.append((a * x + b - labels[i]) ** 2)
 - 9result1 = sum(errors) ** 0.5 / len(xs)
 - 10
 - 11# Method 2: using map
 - 12diffs = map(lambda x, y: (a * x + b - y) ** 2, xs, labels)
 - 13result2 = sum(diffs) ** 0.5 / len(xs)
 - 14
 - 15print(result1, result2)
 - 16
 - 17==> 0.35089172119045514 0.35089172119045514
 
要注意的是,map 和 filter 返回的是迭代器 (Iterator) ,這就是說它們的值不是存儲的,是按需生成的。
當(dāng)你調(diào)用了sum(diffs) 之后,diffs 就空了。如果你想要保留 diffs 里面所有的元素,就用 list(diffs) 把它轉(zhuǎn)換成一個列表。
filter(fn,iterable) 也是和 map 一樣道理,只不過 fn 返回的是一個布爾值,filter 返回的是,iterable 里面所有 fn 返回True的元素。
- 1bad_preds = filter(lambda x: x > 0.5, errors)
 - 2print(list(bad_preds))
 - 3
 - 4==> [0.8100000000000006, 0.6400000000000011]
 
reduce(fn,iterable,initializer) 是用來給列表里的所有元素,迭代地應(yīng)用某一個算子。比如,想要算出列表里所有元素的乘積:
- 1product = 1
 - 2for num in nums:
 - 3 product *= num
 - 4print(product)
 - 5
 - 6==> 12.95564683272412
 
上面這串代碼,和下面這串代碼是等同的:
- 1from functools import reduce
 - 2product = reduce(lambda x, y: x * y, nums)
 - 3print(product)
 - 4
 - 5==> 12.95564683272412
 
2、列表操作
小姐姐說,Python的列表太炫酷了。
2.1、解包 (Unpacking)
想把一個列表解包成一個一個元素,就這樣:
- 1elems = [1, 2, 3, 4]
 - 2a, b, c, d = elems
 - 3print(a, b, c, d)
 - 4
 - 5==> 1 2 3 4
 
也可以這樣:
- 1elems = [1, 2, 3, 4]
 - 2a, b, c, d = elems
 - 3print(a, b, c, d)
 - 4
 - 5==> 1 2 3 4
 
2.2、切片 (Slicing)
大家可能知道,如果想把一個列表反過來排,就用 [::-1] 。
- 1elems = list(range(10))
 - 2print(elems)
 - 3
 - 4==> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 - 5
 - 6print(elems[::-1])
 - 7
 - 8==> [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
 
而 [x:y:z] 這種語法的意思是,從索引x到索引y,每z個元素取一個。
如果z是負(fù)數(shù),就是反向取了。
如果x不特別指定,就默認(rèn)是在遍歷列表的方向上,遇到的第一個元素。
如果y不特別指定,就默認(rèn)是列表最后一個元素。
所以,我們要從一個列表里面,每兩個取一個的話,就是 [::2] 。
- 1evens = elems[::2]
 - 2print(evens)
 - 3
 - 4reversed_evens = elems[-2::-2]
 - 5print(reversed_evens)
 - 6
 - 7==> [0, 2, 4, 6, 8]
 - 8 [8, 6, 4, 2, 0]
 
也可以用這種方法,把一個列表里的偶數(shù)都刪掉,只留奇數(shù):
- 1del elems[::2]
 - 2print(elems)
 - 3
 - 4==> [1, 3, 5, 7, 9]
 
2.3、插入 (Insertion)
把列表里的其中一個元素的值,換成另一個值。
- 1elems = list(range(10))
 - 2elems[1] = 10
 - 3print(elems)
 - 4
 - 5==> [0, 10, 2, 3, 4, 5, 6, 7, 8, 9]
 
如果想把某個索引處的一個元素,替換成多個元素,比如把 1 換成 20, 30, 40 :
- 1elems = list(range(10))
 - 2elems[1:2] = [20, 30, 40]
 - 3print(elems)
 - 4
 - 5==> [0, 20, 30, 40, 2, 3, 4, 5, 6, 7, 8, 9]
 
如果想把3個值 0.2, 0.3, 0.5 插在索引0和索引1之間:
- 1elems = list(range(10))
 - 2elems[1:1] = [0.2, 0.3, 0.5]
 - 3print(elems)
 - 4
 - 5==> [0, 0.2, 0.3, 0.5, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 
2.4、拉平 (Flattening)
如果,一個列表里的每個元素都是個列表,可以用sum把它拉平:
- 1list_of_lists = [[1], [2, 3], [4, 5, 6]]
 - 2sum(list_of_lists, [])
 - 3
 - 4==> [1, 2, 3, 4, 5, 6]
 
如果是嵌套列表 (Nested List) 的話,就可以用遞歸的方法把它拉平。這也是lambda函數(shù)又一種優(yōu)美的使用方法:在創(chuàng)建函數(shù)的同一行,就能用上這個函數(shù)。
- 1nested_lists = [[1, 2], [[3, 4], [5, 6], [[7, 8], [9, 10], [[11, [12, 13]]]]]]
 - 2flatten = lambda x: [y for l in x for y in flatten(l)] if type(x) is list else [x]
 - 3flatten(nested_lists)
 - 4
 - 5# This line of code is from
 - 6# https://github.com/sahands/python-by-example/blob/master/python-by-example.rst#flattening-lists
 
2.5、列表vs生成器
要想知道列表和生成器的區(qū)別在哪,看個例子:從token列表里面創(chuàng)建n-grams。
一種方法是用滑窗來創(chuàng)建:
- 1tokens = ['i', 'want', 'to', 'go', 'to', 'school']
 - 2
 - 3def ngrams(tokens, n):
 - 4 length = len(tokens)
 - 5 grams = []
 - 6 for i in range(length - n + 1):
 - 7 grams.append(tokens[i:i+n])
 - 8 return grams
 - 9
 - 10print(ngrams(tokens, 3))
 - 11
 - 12==> [['i', 'want', 'to'],
 - 13 ['want', 'to', 'go'],
 - 14 ['to', 'go', 'to'],
 - 15 ['go', 'to', 'school']]
 
上面這個例子,是需要把所有n-gram同時儲存起來的。如果文本里有m個token,內(nèi)存需求就是 O(nm) 。m值太大的話,存儲就可能成問題。
所以,不一定要用一個列表儲存所有n-gram??梢杂靡粋€生成器,在收到指令的時候,生成下一個n-gram,這叫做惰性計算 (Lazy Evaluation) 。
只要讓 ngrams 函數(shù),用 yield 關(guān)鍵字返回一個生成器,然后內(nèi)存需求就變成 O(n) 了。
- 1def ngrams(tokens, n):
 - 2 length = len(tokens)
 - 3 for i in range(length - n + 1):
 - 4 yield tokens[i:i+n]
 - 5
 - 6ngrams_generator = ngrams(tokens, 3)
 - 7print(ngrams_generator)
 - 8
 - 9==> <generator object ngrams at 0x1069b26d0>
 - 10
 - 11for ngram in ngrams_generator:
 - 12 print(ngram)
 - 13
 - 14==> ['i', 'want', 'to']
 - 15 ['want', 'to', 'go']
 - 16 ['to', 'go', 'to']
 - 17 ['go', 'to', 'school']
 
還有一種生成n-grams的方法,是用切片來創(chuàng)建列表:[0, 1, …, -n], [1, 2, …, -n+1], …, [n-1, n, …, -1],然后把它們zip到一起。
- 1def ngrams(tokens, n):
 - 2 length = len(tokens)
 - 3 slices = (tokens[i:length-n+i+1] for i in range(n))
 - 4 return zip(*slices)
 - 5
 - 6ngrams_generator = ngrams(tokens, 3)
 - 7print(ngrams_generator)
 - 8
 - 9==> <zip object at 0x1069a7dc8> # zip objects are generators
 - 10
 - 11for ngram in ngrams_generator:
 - 12 print(ngram)
 - 13
 - 14==> ('i', 'want', 'to')
 - 15 ('want', 'to', 'go')
 - 16 ('to', 'go', 'to')
 - 17 ('go', 'to', 'school')
 
注意,創(chuàng)建切片用的是 (tokens[…] for i in range(n)) ,不是 [tokens[…] for i in range(n)] 。
[] 返回的是列表,() 返回的是生成器。
3、類,以及魔術(shù)方法
在Python里面,魔術(shù)方法 (Magic Methods) 是用雙下劃線,作為前綴后綴的。
其中,最知名的可能就是 _init_ 了。
- 1class Node:
 - 2 """ A struct to denote the node of a binary tree.
 - 3 It contains a value and pointers to left and right children.
 - 4 """
 - 5 def __init__(self, value, left=None, right=None):
 - 6 self.value = value
 - 7 self.left = left
 - 8 self.right = right
 
不過,如果想輸出 (Print) 一個節(jié)點 (Node) ,就不是很容易了。
- 1root = Node(5)
 - 2print(root) # <__main__.Node object at 0x1069c4518>
 
理想情況,應(yīng)該是輸出它的值,如果它有子節(jié)點的話,也輸出子節(jié)點的值。
所以,要用魔術(shù)方法 _repr_ ,它必須返回一個可輸出的object,如字符串。
- 1class Node:
 - 2 """ A struct to denote the node of a binary tree.
 - 3 It contains a value and pointers to left and right children.
 - 4 """
 - 5 def __init__(self, value, left=None, right=None):
 - 6 self.value = value
 - 7 self.left = left
 - 8 self.right = right
 - 9
 - 10 def __repr__(self):
 - 11 strings = [f'value: {self.value}']
 - 12 strings.append(f'left: {self.left.value}' if self.left else 'left: None')
 - 13 strings.append(f'right: {self.right.value}' if self.right else 'right: None')
 - 14 return ', '.join(strings)
 - 15
 - 16left = Node(4)
 - 17root = Node(5, left)
 - 18print(root) # value: 5, left: 4, right: None
 
如果想對比兩個節(jié)點 (的各種值) ,就用 _eq_ 來重載 == 運(yùn)算符,用 _lt_ 來重載 < 運(yùn)算符,用 _ge_ 來重載 >= 。
- 1class Node:
 - 2 """ A struct to denote the node of a binary tree.
 - 3 It contains a value and pointers to left and right children.
 - 4 """
 - 5 def __init__(self, value, left=None, right=None):
 - 6 self.value = value
 - 7 self.left = left
 - 8 self.right = right
 - 9
 - 10 def __eq__(self, other):
 - 11 return self.value == other.value
 - 12
 - 13 def __lt__(self, other):
 - 14 return self.value < other.value
 - 15
 - 16 def __ge__(self, other):
 - 17 return self.value >= other.value
 - 18
 - 19
 - 20left = Node(4)
 - 21root = Node(5, left)
 - 22print(left == root) # False
 - 23print(left < root) # True
 - 24print(left >= root) # False
 
想要了解更多魔術(shù)方法,請前往:
https://www.tutorialsteacher.com/python/magic-methods-in-python
或者使用官方文檔,只是有一點點難讀:
https://docs.python.org/3/reference/datamodel.html#special-method-names
這里,還要重點安利幾種魔術(shù)方法:
一是 _len_ :重載 len() 函數(shù)用的。
二是 _str_:重載 str() 函數(shù)用的。
三是 _iter_:想讓object變成迭代器,就用這個。有了它,還可以在object上調(diào)用 next() 函數(shù)。
對于像節(jié)點這樣的類,我們已經(jīng)知道了它支持的所有屬性 (Attributes) :value、left和right,那就可以用 _slots_ 來表示這些值。這樣有助于提升性能,節(jié)省內(nèi)存。
- 1class Node:
 - 2 """ A struct to denote the node of a binary tree.
 - 3 It contains a value and pointers to left and right children.
 - 4 """
 - 5 __slots__ = ('value', 'left', 'right')
 - 6 def __init__(self, value, left=None, right=None):
 - 7 self.value = value
 - 8 self.left = left
 - 9 self.right = right
 
想要全面了解 _slots_ 的優(yōu)點和缺點,可以看看Aaron Hall的精彩回答:
https://stackoverflow.com/a/28059785/5029595
4、局部命名空間,對象的屬性
locals() 函數(shù),返回的是一個字典 (Dictionary) ,它包含了局部命名空間 (Local Namespace) 里定義的變量。l
- 1class Model1:
 - 2 def __init__(self, hidden_size=100, num_layers=3, learning_rate=3e-4):
 - 3 print(locals())
 - 4 self.hidden_size = hidden_size
 - 5 self.num_layers = num_layers
 - 6 self.learning_rate = learning_rate
 - 7
 - 8model1 = Model1()
 - 9
 - 10==> {'learning_rate': 0.0003, 'num_layers': 3, 'hidden_size': 100, 'self': <__main__.Model1 object at 0x1069b1470>}
 
一個object的所有屬性,都存在 _dict_ 里面。
- 1print(model1.__dict__)
 - 2
 - 3==> {'hidden_size': 100, 'num_layers': 3, 'learning_rate': 0.0003}
 
注意,當(dāng)參數(shù)列表 (List of Arguments) 很大的時候,手動把每個參數(shù)值分配給一個屬性會很累。
想簡單一點的話,可以直接把整個參數(shù)列表分配給 _dict_ 。
- 1class Model2:
 - 2 def __init__(self, hidden_size=100, num_layers=3, learning_rate=3e-4):
 - 3 params = locals()
 - 4 del params['self']
 - 5 self.__dict__ = params
 - 6
 - 7model2 = Model2()
 - 8print(model2.__dict__)
 - 9
 - 10==> {'learning_rate': 0.0003, 'num_layers': 3, 'hidden_size': 100}
 
當(dāng)object是用 kwargs** 初始化的時候,這種做法尤其方便 (雖然 kwargs** 還是盡量少用為好) :
- 1class Model3:
 - 2 def __init__(self, **kwargs):
 - 3 self.__dict__ = kwargs
 - 4
 - 5model3 = Model3(hidden_size=100, num_layers=3, learning_rate=3e-4)
 - 6print(model3.__dict__)
 - 7
 - 8==> {'hidden_size': 100, 'num_layers': 3, 'learning_rate': 0.0003}
 
前4個版塊就到這里了,至于第5個版塊傳授了怎樣的技巧,先不介紹,大家可以從傳送門前往觀察:
https://github.com/chiphuyen/python-is-cool
寶藏小姐姐
貢獻(xiàn)資源的Chip Huyen小姐姐,現(xiàn)在是英偉達(dá)的高級深度學(xué)習(xí)工程師了。
但在2015年進(jìn)入斯坦福讀書之前,她還是個沒接觸過深度學(xué)習(xí)的作家,旅行路上的故事已經(jīng)出版了兩本書。
小姐姐的Python隱藏技巧合集,推特2400贊,代碼可以直接跑">
△ 對,是個越南小姐姐
原本想讀英文專業(yè),卻在選了一門計算機(jī)課之后,走上了深度學(xué)習(xí)的不歸路。
畢業(yè)前,她在Netflix實習(xí)過;畢業(yè)后,她在斯坦福教過TensorFlow,課號CS20;一年前離開學(xué)校,進(jìn)入英偉達(dá)。
正式選擇了機(jī)器學(xué)習(xí)的她,依然像旅行的時候一樣,喜歡和大家分享經(jīng)歷。
這位小姐姐產(chǎn)出的各式資源和感悟,量子位也介紹過不止一次。
爬網(wǎng)頁、洗數(shù)據(jù)、創(chuàng)建海量數(shù)據(jù)集一條龍:
https://mp.weixin.qq.com/s/rOXKglzYLRqTJkyLEZqJ6A
免費(fèi)機(jī)器學(xué)習(xí)課程,從概率統(tǒng)計到全棧深度學(xué)習(xí):
https://mp.weixin.qq.com/s/Jk8YuQuP5e64Q0ak-WJUKg
AI從業(yè)者要不要讀博,要不要自己創(chuàng)業(yè):
https://mp.weixin.qq.com/s/MTpS6RwCTLIxwP8iDbZotQ
加上今天的Python隱藏技巧,(至少) 是第四次了:
傳送門
如果你想更順滑地使用Python,快馬克這些方法吧。
項目傳送門:
https://github.com/chiphuyen/python-is-cool
Notebook傳送門:
https://github.com/chiphuyen/python-is-cool/blob/master/cool-python-tips.ipynb















 
 
 








 
 
 
 