小姐姐的Python隐藏技巧合集,推特2400赞,代码可以直接跑

新闻 前端
常常发资源的英伟达工程师小姐姐Chip Huyen,又发了一套Python隐藏功能合集。里面都是她“从前没发现,或者从前不太敢用”的机器学习技巧,有notebook可以直接跑。

 [[280131]]小姐姐的Python隐藏技巧合集,推特2400赞,代码可以直接跑">

本文经AI新媒体量子位(公众号ID:QbitAI)授权转载,转载请联系出处。

常常发资源的英伟达工程师小姐姐Chip Huyen,又发了一套Python隐藏功能合集

里面都是她“从前没发现,或者从前不太敢用”的机器学习技巧,有notebook可以直接跑。

合集名叫python-is-cool,推特宣布之后不到半天,已经收获了2400+赞。

英伟达<span><span><span><i style=小姐姐的Python隐藏技巧合集,推特2400赞,代码可以直接跑">

那么,这份令人奔走相告的资源,到底长什么样子?

隐藏技巧五大类

就像开头提到的:这里的功能,要么是小姐姐花了很久才找到的,要么是曾经让她瑟瑟发抖到不敢尝试的。

不过现在,她的技巧已经成功支配了这些功能,于是分享了出来。

目前一共有5个版块,专注机器学习,日后还会持续更新:

1、Lambda、Map、Filter、Reduce函数

lambda 关键字,是用来创建内联函数 (Inline Functions) 的。square_fn 和 square_ld 函数,在这里是一样的。

  1. 1def square_fn(x): 
  2. 2 return x * x 
  3. 3 
  4. 4square_ld = lambda x : x * x 
  5. 5 
  6. 6for i in range(10): 
  7. 7 assert square_fn(i) == square_ld(i) 

lambda 函数可以快速声明,所以拿来当回调 (Callbacks) 函数是非常理想的:就是作为参数 (Arguments) 传递给其他函数用的,那种函数。

和 map、filter 和 reduce 这样的函数搭配使用,尤其有效。

map(fn,iterable) 会把 fn 应用在 iterable 的所有元素上,返回一个map object。

  1. 1nums = [1/3333/72323/223040/342/3
  2. 2nums_squared = [num * num for num in nums] 
  3. 3print(nums_squared) 
  4. 4 
  5. 5==> [0.11111112263.040816321.0851471.3840830.44444444

这样调用,跟用有回调函数的 map 来调用,是一样的。

  1. 1nums_squared_1 = map(square_fn, nums) 
  2. 2nums_squared_2 = map(lambda x : x * x, nums) 
  3. 3print(list(nums_squared_1)) 
  4. 4 
  5. 5==> [0.11111112263.040816321.0851471.3840830.44444444

map 也可以有不止一个 iterable。

比如,你要想计算一个简单线性函数 f(x)=ax+b 的均方误差 (MSE) ,两种方法就是等同的。

  1.  1a, b = 3, -0.5 
  2.  2xs = [2345
  3.  3labels = [6.48.910.915.3
  4.  4 
  5.  5# Method 1: using a loop 
  6.  6errors = [] 
  7.  7for i, x in enumerate(xs): 
  8.  8 errors.append((a * x + b - labels[i]) ** 2
  9.  9result1 = sum(errors) ** 0.5 / len(xs) 
  10. 10 
  11. 11# Method 2: using map 
  12. 12diffs = map(lambda x, y: (a * x + b - y) ** 2, xs, labels) 
  13. 13result2 = sum(diffs) ** 0.5 / len(xs) 
  14. 14 
  15. 15print(result1, result2) 
  16. 16 
  17. 17==> 0.35089172119045514 0.35089172119045514 

要注意的是,map 和 filter 返回的是迭代器 (Iterator) ,这就是说它们的值不是存储的,是按需生成的。

当你调用了sum(diffs) 之后,diffs 就空了。如果你想要保留 diffs 里面所有的元素,就用 list(diffs) 把它转换成一个列表。

filter(fn,iterable) 也是和 map 一样道理,只不过 fn 返回的是一个布尔值,filter 返回的是,iterable 里面所有 fn 返回True的元素。

  1. 1bad_preds = filter(lambda x: x > 0.5, errors) 
  2. 2print(list(bad_preds)) 
  3. 3 
  4. 4==> [0.81000000000000060.6400000000000011

reduce(fn,iterable,initializer) 是用来给列表里的所有元素,迭代地应用某一个算子。比如,想要算出列表里所有元素的乘积:

  1. 1product = 1 
  2. 2for num in nums: 
  3. 3 product *= num 
  4. 4print(product) 
  5. 5 
  6. 6==> 12.95564683272412 

上面这串代码,和下面这串代码是等同的:

  1. 1from functools import reduce 
  2. 2product = reduce(lambda x, y: x * y, nums) 
  3. 3print(product) 
  4. 4 
  5. 5==> 12.95564683272412 

2、列表操作

小姐姐说,Python的列表太炫酷了。

2.1、解包 (Unpacking)

想把一个列表解包成一个一个元素,就这样:

  1. 1elems = [1234
  2. 2a, b, c, d = elems 
  3. 3print(a, b, c, d) 
  4. 4 
  5. 5==> 1 2 3 4 

也可以这样:

  1. 1elems = [1234
  2. 2a, b, c, d = elems 
  3. 3print(a, b, c, d) 
  4. 4 
  5. 5==> 1 2 3 4 

2.2、切片 (Slicing)

大家可能知道,如果想把一个列表反过来排,就用 [::-1] 。

  1. 1elems = list(range(10)) 
  2. 2print(elems) 
  3. 3 
  4. 4==> [0123456789
  5. 5 
  6. 6print(elems[::-1]) 
  7. 7 
  8. 8==> [9876543210

而 [x:y:z] 这种语法的意思是,从索引x到索引y,每z个元素取一个。

如果z是负数,就是反向取了。

如果x不特别指定,就默认是在遍历列表的方向上,遇到的第一个元素。

如果y不特别指定,就默认是列表最后一个元素。

所以,我们要从一个列表里面,每两个取一个的话,就是 [::2] 。

  1. 1evens = elems[::2
  2. 2print(evens) 
  3. 3 
  4. 4reversed_evens = elems[-2::-2
  5. 5print(reversed_evens) 
  6. 6 
  7. 7==> [02468
  8. 8 [86420

也可以用这种方法,把一个列表里的偶数都删掉,只留奇数:

  1. 1del elems[::2
  2. 2print(elems) 
  3. 3 
  4. 4==> [13579

2.3、插入 (Insertion)

把列表里的其中一个元素的值,换成另一个值。

  1. 1elems = list(range(10)) 
  2. 2elems[1] = 10 
  3. 3print(elems) 
  4. 4 
  5. 5==> [01023456789

如果想把某个索引处的一个元素,替换成多个元素,比如把 1 换成 20, 30, 40 :

  1. 1elems = list(range(10)) 
  2. 2elems[1:2] = [203040
  3. 3print(elems) 
  4. 4 
  5. 5==> [020304023456789

如果想把3个值 0.2, 0.3, 0.5 插在索引0和索引1之间:

  1. 1elems = list(range(10)) 
  2. 2elems[1:1] = [0.20.30.5
  3. 3print(elems) 
  4. 4 
  5. 5==> [00.20.30.5123456789

2.4、拉平 (Flattening)

如果,一个列表里的每个元素都是个列表,可以用sum把它拉平:

  1. 1list_of_lists = [[1], [23], [456]] 
  2. 2sum(list_of_lists, []) 
  3. 3 
  4. 4==> [123456

如果是嵌套列表 (Nested List) 的话,就可以用递归的方法把它拉平。这也是lambda函数又一种优美的使用方法:在创建函数的同一行,就能用上这个函数。

  1. 1nested_lists = [[12], [[34], [56], [[78], [910], [[11, [1213]]]]]] 
  2. 2flatten = lambda x: [y for l in x for y in flatten(l)] if type(x) is list else [x] 
  3. 3flatten(nested_lists) 
  4. 4 
  5. 5# This line of code is from 
  6. 6# https://github.com/sahands/python-by-example/blob/master/python-by-example.rst#flattening-lists 

2.5、列表vs生成器

要想知道列表和生成器的区别在哪,看个例子:从token列表里面创建n-grams。

一种方法是用滑窗来创建:

  1. 1tokens = ['i''want''to''go''to''school'
  2.  2 
  3.  3def ngrams(tokens, n): 
  4.  4 length = len(tokens) 
  5.  5 grams = [] 
  6.  6 for i in range(length - n + 1): 
  7.  7 grams.append(tokens[i:i+n]) 
  8.  8 return grams 
  9.  9 
  10. 10print(ngrams(tokens, 3)) 
  11. 11 
  12. 12==> [['i''want''to'], 
  13. 13 ['want''to''go'], 
  14. 14 ['to''go''to'], 
  15. 15 ['go''to''school']] 

上面这个例子,是需要把所有n-gram同时储存起来的。如果文本里有m个token,内存需求就是 O(nm) 。m值太大的话,存储就可能成问题。

所以,不一定要用一个列表储存所有n-gram。可以用一个生成器,在收到指令的时候,生成下一个n-gram,这叫做惰性计算 (Lazy Evaluation) 。

只要让 ngrams 函数,用 yield 关键字返回一个生成器,然后内存需求就变成 O(n) 了。

  1.  1def ngrams(tokens, n): 
  2.  2 length = len(tokens) 
  3.  3 for i in range(length - n + 1): 
  4.  4 yield tokens[i:i+n] 
  5.  5 
  6.  6ngrams_generator = ngrams(tokens, 3
  7.  7print(ngrams_generator) 
  8.  8 
  9.  9==> <generator object ngrams at 0x1069b26d0
  10. 10 
  11. 11for ngram in ngrams_generator: 
  12. 12 print(ngram) 
  13. 13 
  14. 14==> ['i''want''to'
  15. 15 ['want''to''go'
  16. 16 ['to''go''to'
  17. 17 ['go''to''school'

还有一种生成n-grams的方法,是用切片来创建列表:[0, 1, …, -n], [1, 2, …, -n+1], …, [n-1, n, …, -1],然后把它们zip到一起。

  1. 1def ngrams(tokens, n): 
  2.  2 length = len(tokens) 
  3.  3 slices = (tokens[i:length-n+i+1for i in range(n)) 
  4.  4 return zip(*slices) 
  5.  5 
  6.  6ngrams_generator = ngrams(tokens, 3
  7.  7print(ngrams_generator) 
  8.  8 
  9.  9==> <zip object at 0x1069a7dc8> # zip objects are generators 
  10. 10 
  11. 11for ngram in ngrams_generator: 
  12. 12 print(ngram) 
  13. 13 
  14. 14==> ('i''want''to'
  15. 15 ('want''to''go'
  16. 16 ('to''go''to'
  17. 17 ('go''to''school'

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

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

3、类,以及魔术方法

在Python里面,魔术方法 (Magic Methods) 是用双下划线,作为前缀后缀的。

其中,最知名的可能就是 _init_ 了。

  1. 1class Node: 
  2. 2 """ A struct to denote the node of a binary tree. 
  3. 3 It contains a value and pointers to left and right children. 
  4. 4 ""
  5. 5 def __init__(self, value, left=None, right=None): 
  6. 6 self.value = value 
  7. 7 self.left = left 
  8. 8 self.right = right 

不过,如果想输出 (Print) 一个节点 (Node) ,就不是很容易了。

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

理想情况,应该是输出它的值,如果它有子节点的话,也输出子节点的值。

所以,要用魔术方法 _repr_ ,它必须返回一个可输出的object,如字符串。

  1. 1class Node: 
  2.  2 """ A struct to denote the node of a binary tree. 
  3.  3 It contains a value and pointers to left and right children. 
  4.  4 ""
  5.  5 def __init__(self, value, left=None, right=None): 
  6.  6 self.value = value 
  7.  7 self.left = left 
  8.  8 self.right = right 
  9.  9 
  10. 10 def __repr__(self):  
  11. 11 strings = [f'value: {self.value}'
  12. 12 strings.append(f'left: {self.left.value}' if self.left else 'left: None'
  13. 13 strings.append(f'right: {self.right.value}' if self.right else 'right: None'
  14. 14 return ', '.join(strings) 
  15. 15 
  16. 16left = Node(4
  17. 17root = Node(5, left) 
  18. 18print(root) # value: 5, left: 4, right: None 

如果想对比两个节点 (的各种值) ,就用 _eq_ 来重载 == 运算符,用 _lt_ 来重载 < 运算符,用 _ge_ 来重载 >= 。

  1.  1class Node: 
  2.  2 """ A struct to denote the node of a binary tree. 
  3.  3 It contains a value and pointers to left and right children. 
  4.  4 ""
  5.  5 def __init__(self, value, left=None, right=None): 
  6.  6 self.value = value 
  7.  7 self.left = left 
  8.  8 self.right = right 
  9.  9 
  10. 10 def __eq__(self, other): 
  11. 11 return self.value == other.value 
  12. 12 
  13. 13 def __lt__(self, other): 
  14. 14 return self.value < other.value 
  15. 15 
  16. 16 def __ge__(self, other): 
  17. 17 return self.value >= other.value 
  18. 18 
  19. 19 
  20. 20left = Node(4
  21. 21root = Node(5, left) 
  22. 22print(left == root) # False 
  23. 23print(left < root) # True 
  24. 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) :value、left和right,那就可以用 _slots_ 来表示这些值。这样有助于提升性能,节省内存。

  1. 1class Node: 
  2. 2 """ A struct to denote the node of a binary tree. 
  3. 3 It contains a value and pointers to left and right children. 
  4. 4 ""
  5. 5 __slots__ = ('value''left''right'
  6. 6 def __init__(self, value, left=None, right=None): 
  7. 7 self.value = value 
  8. 8 self.left = left 
  9. 9 self.right = right 

想要全面了解 _slots_ 的优点和缺点,可以看看Aaron Hall的精彩回答:

https://stackoverflow.com/a/28059785/5029595

4、局部命名空间,对象的属性

locals() 函数,返回的是一个字典 (Dictionary) ,它包含了局部命名空间 (Local Namespace) 里定义的变量。l

  1. 1class Model1: 
  2.  2 def __init__(self, hidden_size=100, num_layers=3, learning_rate=3e-4): 
  3.  3 print(locals()) 
  4.  4 self.hidden_size = hidden_size 
  5.  5 self.num_layers = num_layers 
  6.  6 self.learning_rate = learning_rate 
  7.  7 
  8.  8model1 = Model1() 
  9.  9 
  10. 10==> {'learning_rate'0.0003'num_layers'3'hidden_size'100'self': <__main__.Model1 object at 0x1069b1470>} 

一个object的所有属性,都存在 _dict_ 里面。

  1. 1print(model1.__dict__) 
  2. 2 
  3. 3==> {'hidden_size'100'num_layers'3'learning_rate'0.0003

注意,当参数列表 (List of Arguments) 很大的时候,手动把每个参数值分配给一个属性会很累。

想简单一点的话,可以直接把整个参数列表分配给 _dict_ 。

  1. 1class Model2: 
  2.  2 def __init__(self, hidden_size=100, num_layers=3, learning_rate=3e-4): 
  3.  3 params = locals() 
  4.  4 del params['self'
  5.  5 self.__dict__ = params 
  6.  6 
  7.  7model2 = Model2() 
  8.  8print(model2.__dict__) 
  9.  9 
  10. 10==> {'learning_rate'0.0003'num_layers'3'hidden_size'100

当object是用 kwargs** 初始化的时候,这种做法尤其方便 (虽然 kwargs** 还是尽量少用为好) :

  1. 1class Model3: 
  2. 2 def __init__(self, **kwargs): 
  3. 3 self.__dict__ = kwargs 
  4. 4 
  5. 5model3 = Model3(hidden_size=100, num_layers=3, learning_rate=3e-4
  6. 6print(model3.__dict__) 
  7. 7 
  8. 8==> {'hidden_size'100'num_layers'3'learning_rate'0.0003

前4个版块就到这里了,至于第5个版块传授了怎样的技巧,先不介绍,大家可以从传送门前往观察:

https://github.com/chiphuyen/python-is-cool

宝藏小姐姐

贡献资源的Chip Huyen小姐姐,现在是英伟达的高级深度学习工程师了。

但在2015年进入斯坦福读书之前,她还是个没接触过深度学习的作家,旅行路上的故事已经出版了两本书。

英伟达<span><span><span><i style=小姐姐的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

责任编辑:张燕妮 来源: 量子位
相关推荐

2021-04-14 14:28:14

Python点攒抖音

2019-10-21 10:01:58

Python素描技术

2019-05-17 10:38:23

SpringBoot开发技术

2023-07-07 08:24:53

Python爬虫Flask

2021-10-05 21:03:54

BeautifulSo 爬虫

2021-10-25 20:02:00

gRPC订阅模式超时控制

2020-05-26 10:20:56

Python开发工具

2020-09-29 09:09:03

数据库程序运行

2012-08-20 09:41:05

开源技术推特

2020-01-18 15:10:57

机器人人工智能系统

2020-10-26 14:11:13

特朗普漏洞推特密码

2019-07-08 10:08:35

人工智能AI自动化

2021-05-06 10:46:40

JS代码文字烟花

2018-02-05 15:52:06

硬盘故障损坏

2021-02-05 22:47:01

物联网IOT物联网技术

2019-11-26 14:47:59

机器学习人工智能计算机

2021-08-03 22:52:52

Python编程语言开发

2020-03-16 10:07:02

代码开发工具

2022-02-23 14:21:20

Chrome插件浏览器

2023-12-05 13:49:00

AI模型
点赞
收藏

51CTO技术栈公众号