Twitter涨赞平台 --Twitter写评论
红豆 丽翔 凹非寺 物理位 报导 | 社会公众号 QbitAI
经常发天然资源的英伟达技师上恩Chip Huyen,又升空了两套Python暗藏机能选集。
里头都是她萨德基没辨认出,或是萨德基不太敢用的机器学习基本功,有notebook能间接跑。
选集名为python-is-cool,twitter正式宣布后不出一会,早已斩获了2400+赞。
所以,这本更让人TNUMBERKC的天然资源,究竟长甚么模样?
暗藏基本功五类
就像结尾提及的:这儿的机能,或是是上恩花了很久才找出的,或是是曾让她科亮颤抖到不肯试著的。
但是那时,她的基本功早已获得成功主宰了那些机能,只好撷取了出。
现阶段总共有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) 传递给其他函数用的,那种函数。
和 map、filter 和 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.rstflattening-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==>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 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 = [fvalue: {self.value}] 12 strings.append(fleft: {self.left.value} if self.left else left: None) 13 strings.append(fright: {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.htmlspecial-method-names
这儿,还要重点安利几种魔术方法:
一是 _len_ :重载 len() 函数用的。
二是 _str_:重载 str() 函数用的。
三是 _iter_:想让object变成迭代器,就用这个。有了它,还能在object上调用 next() 函数。
对于像节点这样的类,我们早已知道了它支持的所有属性 (Attributes) :value、left和right,那就能用 _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年进入斯坦福读书之前,她还是个没接触过深度学习的作家,旅行路上的故事早已出版了两本书。
△ 对,是个越南上恩
原本想读英文专业,却在选了一门计算机课后,走上了深度学习的不归路。
毕业前,她在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
— 完 —
诚挚招聘
物理位正在招募编辑/记者,工作地点在北京中关村。期待有才气、有热情的同学加入我们!相关细节,请在物理位社会公众号(QbitAI)对话界面,回复招聘两个字。
物理位 QbitAI · 头条号签约作者
վᴗ ի 追踪AI技术和产品新动态