英偉達小姐姐的Python隱藏技巧合集,推特2400贊,程式碼可以直接跑

視學演算法發表於2020-04-06
本文經AI新媒體量子位(ID:QbitAI)授權轉載,轉載請聯絡出處

英偉達小姐姐的Python隱藏技巧合集,推特2400贊,程式碼可以直接跑

常常發資源的英偉達工程師小姐姐Chip Huyen,又發射了一套Python隱藏功能合集

裡面都是她“從前沒發現,或者從前不太敢用”的機器學習技巧,有notebook可以直接跑。

合集名叫python-is-cool,推特宣佈之後不到半天,已經收穫了2400+贊。

英偉達小姐姐的Python隱藏技巧合集,推特2400贊,程式碼可以直接跑

那麼,這份令人奔走相告的資源,到底長什麼樣子?

隱藏技巧五大類

就像開頭提到的:這裡的功能,要麼是小姐姐花了很久才找到的,要麼是曾經讓她瑟瑟發抖到不敢嘗試的。

不過現在,她的技巧已經成功支配了這些功能,於是分享了出來。

目前一共有5個版塊,專注機器學習,日後還會持續更新:

1、Lambda、Map、Filter、Reduce函式

lambda 關鍵字,是用來建立行內函數 (Inline Functions) 的。square_fn  square_ld 函式,在這裡是一樣的。

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 函式可以快速宣告,所以拿來當回撥 (Callbacks) 函式是非常理想的:就是作為引數 (Arguments) 傳遞給其他函式用的,那種函式。

 mapfilter  reduce 這樣的函式搭配使用,尤其有效。

map(fn,iterable) 會把 fn 應用在 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]

這樣呼叫,跟用有回撥函式的 map 來呼叫,是一樣的。

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

比如,你要想計算一個簡單線性函式 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) ,這就是說它們的值不是儲存的,是按需生成的。

當你呼叫了sum(diffs) 之後,diffs 就空了。如果你想要保留 diffs 裡面所有的元素,就用 list(diffs) 把它轉換成一個列表。

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) 是用來給列表裡的所有元素,迭代地應用某一個運算元。比如,想要算出列表裡所有元素的乘積:

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是負數,就是反向取了。

如果x不特別指定,就預設是在遍歷列表的方向上,遇到的第一個元素。

如果y不特別指定,就預設是列表最後一個元素。

所以,我們要從一個列表裡面,每兩個取一個的話,就是 [::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]

也可以用這種方法,把一個列表裡的偶數都刪掉,只留奇數:

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函式又一種優美的使用方法:在建立函式的同一行,就能用上這個函式。

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生成器

要想知道列表和生成器的區別在哪,看個例子:從token列表裡面建立n-grams。

一種方法是用滑窗來建立:

 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,記憶體需求就是 O(nm)m值太大的話,儲存就可能成問題。

所以,不一定要用一個列表儲存所有n-gram。可以用一個生成器,在收到指令的時候,生成下一個n-gram,這叫做惰性計算 (Lazy Evaluation) 。

只要讓 ngrams 函式,用 yield 關鍵字返回一個生成器,然後記憶體需求就變成 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的方法,是用切片來建立列表:[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')

注意,建立切片用的是 (tokens[…] for i in range(n)) ,不是 [tokens[…] for i in range(n)]

[] 返回的是列表,() 返回的是生成器。

3、類,以及魔術方法

在Python裡面,魔術方法 (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) 一個節點 (Node) ,就不是很容易了。

1root = Node(5)
2print(root) # <__main__.Node object at 0x1069c4518>

理想情況,應該是輸出它的值,如果它有子節點的話,也輸出子節點的值。

所以,要用魔術方法 _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

如果想對比兩個節點 (的各種值) ,就用 _eq_ 來過載 == 運算子,用 _lt_ 來過載 < 運算子,用 _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

想要了解更多魔術方法,請前往:
https://www.tutorialsteacher.com/python/magic-methods-in-python

或者使用官方文件,只是有一點點難讀:
https://docs.python.org/3/reference/datamodel.html#special-method-names

這裡,還要重點安利幾種魔術方法:

一是 _len_ 過載 len() 函式用的。
二是 _str_過載 str() 函式用的。
三是 _iter_想讓object變成迭代器,就用這個。有了它,還可以在object上呼叫 next() 函式。

對於像節點這樣的類,我們已經知道了它支援的所有屬性 (Attributes) :valueleftright,那就可以用 _slots_ 來表示這些值。這樣有助於提升效能,節省記憶體。

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_ 的優點和缺點,可以看看Aaron Hall的精彩回答:
https://stackoverflow.com/a/28059785/5029595

4、區域性名稱空間,物件的屬性

locals() 函式,返回的是一個字典 (Dictionary) ,它包含了區域性名稱空間 (Local Namespace) 裡定義的變數。

 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}

注意,當引數列表 (List of Arguments) 很大的時候,手動把每個引數值分配給一個屬性會很累。

想簡單一點的話,可以直接把整個引數列表分配給 _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}

當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

寶藏小姐姐

貢獻資源的Chip Huyen小姐姐,現在是英偉達的高階深度學習工程師了。

但在2015年進入史丹佛讀書之前,她還是個沒接觸過深度學習的作家,旅行路上的故事已經出版了兩本書。

英偉達小姐姐的Python隱藏技巧合集,推特2400贊,程式碼可以直接跑

 對,是個越南小姐姐

原本想讀英文專業,卻在選了一門計算機課之後,走上了深度學習的不歸路。

畢業前,她在Netflix實習過;畢業後,她在史丹佛教過TensorFlow,課號CS20;一年前離開學校,進入英偉達。

正式選擇了機器學習的她,依然像旅行的時候一樣,喜歡和大家分享經歷。

這位小姐姐產出的各式資源和感悟,量子位也介紹過不止一次。

爬網頁、洗資料、建立海量資料集一條龍:
https://mp.weixin.qq.com/s/rOXKglzYLRqTJkyLEZqJ6A

免費機器學習課程,從概率統計到全棧深度學習:
https://mp.weixin.qq.com/s/Jk8YuQuP5e64Q0ak-WJUKg

AI從業者要不要讀博,要不要自己創業:
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

- END -

如果看到這裡,說明你喜歡這篇文章,請轉發、點贊掃描下方二維碼或者微信搜尋「perfect_iscas」,新增好友後即可獲得10套程式設計師全棧課程+1000套PPT和簡歷模板向我私聊「進群」二字即可進入高質量交流群。

送幾本書《Pytorch深度學習入門》《演算法交易員》等

英偉達小姐姐的Python隱藏技巧合集,推特2400贊,程式碼可以直接跑

掃描二維碼進群↓

英偉達小姐姐的Python隱藏技巧合集,推特2400贊,程式碼可以直接跑

英偉達小姐姐的Python隱藏技巧合集,推特2400贊,程式碼可以直接跑

英偉達小姐姐的Python隱藏技巧合集,推特2400贊,程式碼可以直接跑

在看 英偉達小姐姐的Python隱藏技巧合集,推特2400贊,程式碼可以直接跑

相關文章