“点评:单例模式是指让一个类只能创建出唯一的实例,这个题目在面试中出现的频率极高,因为它考察的不仅仅是单例模式,更是对Python语言到底掌握到何种程度,建议大家用装饰器和元类这两种方式来实现单例模式,因为这两种方式的通用性最强,而且也可以顺便展示自己对装饰器和元类中两个关键知识点的理解。 ”
方法一:使用装饰器实现单例模式。
“扩展:装饰器是Python中非常有特色的语法,用一个函数去装饰另一个函数或类,为其添加额外的能力。通常通过装饰来实现的功能都属横切关注功能,也就是跟正常的业务逻辑没有必然联系,可以动态添加或移除的功能。装饰器可以为代码提供缓存、代理、上下文环境等服务,它是对设计模式中代理模式的践行。在写装饰器的时候,带装饰功能的函数(上面代码中的
wrapper
函数)通常都会用functools
模块中的wraps
再加以装饰,这个装饰器最重要的作用是给被装饰的类或函数动态添加一个__wrapped__
属性,这个属性会将被装饰之前的类或函数保留下来,这样在我们不需要装饰功能的时候,可以通过它来取消装饰器,例如可以使用President pile
函数创建正则表达式对象,这样会减少频繁编译同一个正则表达式所造成的开销。
match
方法是从字符串的起始位置进行正则表达式匹配,返回Match
对象或None。search
方法会扫描整个字符串来找寻匹配的模式,同样也是返回Match对象或None。
上面代码的运行结果很容易被误判为[0, 100, 200,
300]
。首先需要注意的是multiply
函数用生成式语法返回了一个列表,列表中保存了4个Lambda函数,这4个Lambda函数会返回传入的参数乘以i
的结果。需要注意的是这里有闭包(closure)现象,multiply
函数中的局部变量i
的生命周期被延展了,由于i
最终的值是3
,所以通过m(100)
调列表中的Lambda函数时会返回300
,而且4个调用都是如此。
方法一:使用生成器,让函数获得i
的当前值。
方法二:使用偏函数,彻底避开闭包。
“点评:C++、Java、C#等诸多编程语言都支持函数重载,所谓函数重载指的是在同一个作用域中有多个同名函数,它们拥有不同的参数列表(参数个数不同或参数类型不同或二者皆不同),可以相互区分。重载也是一种多态性,因为通常是在编译时通过参数的个数和类型来确定到底调用哪个重载函数,所以也被称为编译时多态性或者叫前绑定。这个问题的潜台词其实是问面试者是否有其他编程语言的经验,是否理解Python是动态类型语言,是否知道Python中函数的可变参数、关键字参数这些概念。
首先Python是解释型语言,函数重载现象通常出现在编译型语言中。其次Python是动态类型语言,函数的参数没有类型约束,也就无法根据参数类型来区分重载。再者Python中函数的参数可以有默认值,可以使用可变参数和关键字参数,因此即便没有函数重载,也要可以让一个函数根据调用者传入的参数产生不同的行为。
“点评:这个题目看似简单,但实际上还是比较考察面试者的功底。因为Python内置的
max
函数既可以传入可迭代对象找出最大,又可以传入两个或多个参数找出最大;最为关键的是还可以通过命名关键字参数key
来指定一个用于元素比较的函数,还可以通过default
命名关键字参数来指定当可迭代对象为空时返回的默认值。 ”
“点评:送人头的题目,不解释。 ”
'b2.txt', 'b10.txt', 'b19.txt']。提示一下,可以通过字符串替换的方式为文件名补位,根据补位后的文件名用
sorted
函数来排序,大家可以思考下这个问题如何解决。 ”
剖析代码性能可以使用Python标准库中的cProfile
和pstats
模块,cProfile
的run
函数可以执行代码并收集统计信息,创建出Stats
对象并打印简单的剖析报告。Stats
是pstats
模块中的类,它是一个统计对象。当然,也可以使用三方工具line_profiler
和memory_profiler
来剖析每一行代码耗费的时间和内存,这两个三方工具都会用非常友好的方式输出剖析结构。如果使用PyCharm,可以利用“Run”菜单的“Profile”菜单项对代码进行性能分析,PyCharm中可以用表格或者调用图(Call
Graph)的方式来显示性能剖析的结果。
下面是使用cProfile
剖析代码性能的例子。
如果使用line_profiler
三方工具,可以直接剖析is_prime
函数每行代码的性能,需要给is_prime
函数添加一个profiler
装饰器,代码如下所示。
random
模块生成随机数、实现随机乱序和随机抽样?
“点评:送人头的题目,因为Python标准库中的常用模块应该是Python开发者都比较熟悉的内容,这个问题回如果答不上来,整个面试基本也就砸锅了。 ”
random.choice(seq)
函数可以从非空序列中取出一个随机元素。
random.choices(population, weights=None, *, cum_weights=None, k=1)
函数可以从总体中随机抽取(有放回抽样)出容量为k
的样本并返回样本的列表,可以通过参数指定个体的权重,如果没有指定权重,个体被选中的概率均等。
random.sample(population, k)
函数可以从总体中随机抽取(无放回抽样)出容量为k
的样本并返回样本的列表。
“扩展:
random
模块提供的函数除了生成均匀分布的随机数外,还可以生成其他分布的随机数,例如random.gauss(mu,
题目37:解释一下线程池的工作原理。
“点评:池化技术就是一种典型空间换时间的策略,我们使用的数据库连接池、线程池等都是池化技术的应用,Python标准库
currrent.futures
模块的ThreadPoolExecutor
就是线程池的实现,如果要弄清楚它的工作原理,可以参考下面的内容。 ”
线程池是一种用于减少线程本身创建和销毁造成的开销的技术,属于典型的空间换时间操作。如果应用程序需要频繁的将任务派发到线程中执行,线程池就是必选项,因为创建和释放线程涉及到大量的系统底层操作,开销较大,如果能够在应用程序工作期间,将创建和释放线程的操作变成预创建和借还操作,将大大减少底层开销。线程池在应用程序启动后,立即创建一定数量的线程,放入空闲队列中。这些线程最开始都处于阻塞状态,不会消耗CPU资源,但会占用少量的内存空间。当任务到来后,从队列中取出一个空闲线程,把任务派发到这个线程中运行,并将该线程标记为已占用。当线程池中所有的线程都被占用后,可以选择自动创建一定数量的新线程,用于处理更多的任务,也可以选择让任务排队等待直到有空闲的线程可用。在任务执行完毕后,线程并不退出结束,而是继续保持在池中等待下一次的任务。当系统比较空闲时,大部分线程长时间处于闲置状态时,线程池可以自动销毁一部分线程,回收系统资源。基于这种预创建技术,线程池将线程创建和销毁本身所带来的开销分摊到了各个具体的任务上,执行次数越多,每个任务所分担到的线程本身开销则越小。
一般线程池都必须具备下面几个组成部分:
举一个简单的例子,变量a
是一个字典,执行int(a['x'])
这个操作就有可能引发上述三种类型的异常。如果字典中没有键x
,会引发KeyError
;如果键x
对应的值不是str
、float
、int
、bool
以及bytes-like
类型,在调用int
函数构造int
类型的对象时,会引发TypeError
;如果a[x]
是一个字符串或者字节串,而对应的内容又无法处理成int
时,将引发ValueError
。
“点评:Python函数在定义的时候,默认参数
items
的值就被计算出来了,即[]
。因为默认参数items
引用了对象[]
,每次调用该函数,如果对items
引用的列表进行了操作,下次调用时,默认参数还是引用之前的那个列表而不是重新赋值为[]
,所以列表中会有之前添加的元素。如果通过传参的方式为items
重新赋值,那么items
将引用到新的列表对象,而不再引用默认的那个列表对象。这个题在面试中经常被问到,通常不建议使用容器类型的默认参数,像PyLint这样的代码检查工具也会对这种代码提出质疑和警告。
很显然4G内存要一次性的加载大小为8G的文件是不现实的,遇到这种情况必须要考虑多次读取和分批次处理。在Python中读取文件可以先通过open
函数获取文件对象,在读取文件时,可以通过read
方法的size
参数指定读取的大小,也可以通过seek
方法的offset
参数指定读取的位置,这样就可以控制单次读取数据的字节数和总字节数。除此之外,可以使用内置函数iter
将文件对象处理成迭代器对象,每次只读取少量的数据进行处理,代码大致写法如下所示。
在Linux系统上,可以通过split
命令将大文件切割为小片,然后通过读取切割后的小文件对数据进行处理。例如下面的命令将名为filename
的大文件切割为大小为512M的多个文件。
如果愿意, 也可以将名为filename
的文件切割为10个文件,命令如下所示。
“扩展:外部排序跟上述的情况非常类似,由于处理的数据不能一次装入内存,只能放在读写较慢的外存储器(通常是硬盘)上。“排序-归并算法”就是一种常用的外部排序策略。在排序阶段,先读入能放在内存中的数据量,将其排序输出到一个临时文件,依此进行,将待排序数据组织为多个有序的临时文件,然后在归并阶段将这些临时文件组合为一个大的有序文件,这个大的有序文件就是排序的结果。 ”
每个Python文件就是一个模块,而保存这些文件的文件夹就是一个包,但是这个作为Python包的文件夹必须要有一个名为__init__.py
的文件,否则无法导入这个包。通常一个文件夹下还可以有子文件夹,这也就意味着一个包下还可以有子包,子包中的__init__.py
并不是必须的。模块和包解决了Python中命名冲突的问题,不同的包下可以有同名的模块,不同的模块下可以有同名的变量、函数或类。在Python中可以使用import
或from
... import ...
来导入包和模块,在导入的时候还可以使用as
关键字对包、模块、类、函数、变量等进行别名,从而彻底解决编程中尤其是多人协作团队开发时的命名冲突问题。
“点评:企业的Python编码规范基本上是参照PEP-8或谷歌开源项目风格指南来制定的,后者还提到了可以使用Lint工具来检查代码的规范程度,面试的时候遇到这类问题,可以先说下这两个参照标准,然后挑重点说一下Python编码的注意事项。 ”
self
以表示对象自身。
cls
以表示该类自身。
if a is not b
就比if not a is b
更容易让人理解。
None
或者没有元素,应该用if not x
这样的写法来检查它。
if
分支、for
循环、except
异常捕获等中只有一行代码,也不要将代码和if
、for
、except
等写在一起,分开写才会让代码更清晰。
import
语句总是放在文件开头的地方。
import
语句,应该将其分为三部分,从上到下分别是Python标准模块、第三方模块和自定义模块,每个部分内部应该按照模块名称的字母表顺序来排列。
“点评:这道题有两个考察点,一个考察点是对
_
和__
开头的对象属性访问权限以及@property
装饰器的了解,另外一个考察的点是对动态语言的理解,不需要过多的解释。 ”
“扩展:如果不希望代码运行时动态的给对象添加新属性,可以在定义类时使用
__slots__
魔法。例如,我们可以在上面的A
中添加一行__slots__ = ('__value', )
,再次运行上面的代码,将会在原来的第10行处产生AttributeError
错误。 ”
“点评:
sorted
函数的高阶用法在面试的时候经常出现,key
参数可以传入一个函数名或一个Lambda函数,该函数的返回值代表了在排序时比较元素的依据。 ”
namedtuple
的用法和作用。
“点评:Python标准库的
collections
模块提供了很多有用的数据结构,这些内容并不是每个开发者都清楚,就比如题目问到的namedtuple
,在我参加过的面试中,90%的面试者都不能准确的说出它的作用和应用场景。此外,deque
也是一个非常有用但又经常被忽视的类,还有Counter
、OrderedDict
在使用面向对象编程语言的时候,定义类是最常见的一件事情,有的时候,我们会用到只有属性没有方法的类,这种类的对象通常只用于组织数据,并不能接收消息,所以我们把这种类称为数据类或者退化的类,就像C语言中的结构体那样。我们并不建议使用这种退化的类,在Python中可以用namedtuple
(命名元组)来替代这种类。
命名元组与普通元组一样是不可变容器,一旦将数据存储在namedtuple
的顶层属性中,数据就不能再修改了,也就意味着对象上的所有属性都遵循“一次写入,多次读取”的原则。和普通元组不同的是,命名元组中的数据有访问名称,可以通过名称而不是索引来获取保存的数据,不仅在操作上更加简单,代码的可读性也会更好。
命名元组的本质就是一个类,所以它还可以作为父类创建子类。除此之外,命名元组内置了一系列的方法,例如,可以通过_asdict
方法将命名元组处理成字典,也可以通过_replace
方法创建命名元组对象的浅拷贝。
总而言之,命名元组能更好的组织数据结构,让代码更加清晰和可读,在很多场景下是元组、字典和数据类的替代品。在需要创建占用空间更少的不可变类时,命名元组就是很好的选择。
“要求:写一个函数,传入一个有若干个整数的列表,该列表中某个元素出现的次数超过了50%,返回这个元素。 ”
“点评:LeetCode上的题目,在Python面试中出现过,利用元素出现次数超过了50%这一特征,出现和
temp
相同的元素就将计数值加1,出现和temp
不同的元素就将计数值减1。如果计数值为0
,说明之前出现的元素已经对最终的结果没有影响,用temp
记下当前元素并将计数值置为1
。最终,出现次数超过了50%的这个元素一定会被赋值给变量temp
。
“要求:写一个函数,传入的参数是一个列表(列表中的元素可能也是一个列表),返回该列表最大的嵌套深度。例如:列表
[1, 2, 3]
的嵌套深度为1
,列表[[1], [2, [3]]]
的嵌套深度为3
。 ”
“点评:看到题目应该能够比较自然的想到使用递归的方式检查列表中的每个元素。 ”
“要求:有一个通过网络获取数据的函数(可能会因为网络原因出现异常),写一个装饰器让这个函数在出现指定异常时可以重试指定的次数,并在每次重试之前随机延迟一段时间,最长延迟时间可以通过参数进行控制。 ”
“点评:我们不止一次强调过,装饰器几乎是Python面试必问内容,这个题目比之前的题目稍微复杂一些,它需要的是一个参数化的装饰器。 ”
“点评:烂大街的题目,基本上算是送人头的题目。 ”
“扩展:这些方法其实都是大同小异的,面试的时候能够给出几种有代表性的就足够了。给大家留一个思考题,上面这些方法,哪些做法的性能较好呢?我们之前提到过剖析代码性能的方法,大家可以用这些方法来检验下你给出的答案是否正确。 ”
“要求:列表中有
1000000
个元素,取值范围是[)
,设计一个函数找出列表中的重复元素。 ”
“点评:这道题的解法和计数排序的原理一致,虽然元素的数量非常多,但是取值范围
[1000, 10000)
并不是很大,只有9000个可能的取值,所以可以用一个能够保存9000个元素的dups
列表来记录每个元素出现的次数,dups
列表所有元素的初始值都是0
,通过对items
列表中元素的遍历,当出现某个元素时,将dups
列表对应位置的值加1,最后dups
列表中值大于1的元素对应的就是items
列表中重复出现过的元素。
数据:工资和年龄(2个特征)
目标:预测银行会贷款多少钱(标签)
考虑:工资和年龄都会影响最终银行贷款的结果,那么它们各自有多大的影响?(参数)
通过图表可以看出随着工资和年龄的增长,贷款额度也随之增长。X1和X2的数量级是不同的,因此需要增加两个因子:θ1x1+θ2x2=y ,在已知x1,x2,y的情况下建立回归方程。方程的目标就是求出最合适的θ1、θ2,这样就知道工资和年龄对贷款额度到底有多大的影响。
X1、X2就是我们的两个特征(年龄、工资),Y是银行最终会借给我们多少钱。
找到最合适的一条线(想象一个高维)来最好的拟合我们的数据点。(无法满足所有,满足尽可能多的点)
图中红点是样本数据,想根据给定的数据集拟合一个平面,使得各个样本数据到达平面的误差最小。
这个图就是机器如何进行预测的(回归)它会根据贷款的历史数据(年龄和工资分别对应于X1与X2)找出来最好的拟合线(面)来进行预测,这样新的数据来了之后直接带入进去就可以得出来该给多少钱了。
整合是把偏置项和权重参数项放到了一起(加了个θ0让其都等于1)。
一个传统的神经网络就可以看成多个逻辑回归模型的输出作为另一个逻辑回归模型的输入的“组合模型”。
因此,讨论神经网络中的偏置项b的作用,就近似等价于讨论逻辑回归模型中的偏置项b的作用。
逻辑回归模型本质:利用 y = WX + b 这个函数画决策面,其中W为模型参数,也是函数的斜率;b为函数的截距。
一维情况:W=[1],b=2,y=WX+b得到一个截距为2,斜率为1的直线如下所示:
二维情况:W=[1 1],b=2,则 y=WX+b得到一个截距为2,斜率为[1 1]的平面如下所示:
显然y=WX+b这个函数,就是2维/3维/更高维空间的直线/平面/超平面。如果没有偏置项b,则只能在空间里画过原点的直线/平面/超平面。
因此对于逻辑回归必须加上偏置项b,才能保证分类器可以在空间任何位置画决策面。
同理,对于多个逻辑回归组成的神经网络,更要加上偏置项b。
如果隐层有3个节点,那就相当于有3个逻辑回归分类器。这三个分类器各画各的决策面,那一般情况下它们的偏置项b也会各不相同。
复杂决策边界由三个隐层节点的神经网络画出如下:
如何机智的为三个分类器(隐节点)分配不同的b呢?或者说如果让模型在训练的过程中,动态的调整三个分类器的b以画出各自最佳的决策面呢?
那就是先在X的前面加个1,作为偏置项的基底,(此时X就从n维向量变成了n+1维向量,即变成 [1, x1,x2…] ),然后,让每个分类器去训练自己的偏置项权重,所以每个分类器的权重就也变成了n+1维,即[w0,w1,…],其中,w0就是偏置项的权重,所以1*w0就是本分类器的偏置/截距啦。这样,就让截距b这个看似与斜率W不同的参数,都统一到了一个框架下,使得模型在训练的过程中不断调整参数w0,从而达到调整b的目的。
所以,如果在写神经网络的代码的时候,把偏置项给漏掉了,那么神经网络很有可能变得很差,收敛很慢而且精度差,甚至可能陷入“僵死”状态无法收敛。
银行的目标得让误差越小越好,这样才能够使得我们的结果是越准确的。
独立同分布(iid,independently identically distribution)在概率统计理论中,指随机过程中,任何时刻的取值都为随机变量,如果这些随机变量服从同一分布,并且互相独立,那么这些随机变量是独立同分布。
误差在0附近浮动的可能性较大,正负差距较大的可能性越来越小。符合概率统计中现实分布情况。
将(1)式转化为:ε(i) = y(i) - θTx(i) ,即:误差=实际值-预测值,然后带入高斯分布函数(2)式,就将误差项都替换为了x,y。
p(x;θ)代表:在给定θ的情况下x的取值;
p(y|x;θ)代表:在给定x的情况下,还给定某种参数θ的情况下,y的概率密度函数。
由于x和θ是一个定值,所以θTx(i) 可以理解为一个定值C。
似然函数是一种关于模型中参数的函数,用来表示模型参数中的似然性。
已知样本数据x(x1,x2,...,xn)组合,要使用什么样的参数θ和样本数据组合后,可以恰好得到真实值?
要让误差项越小越好,则要让似然函数越大越好,由此将问题转为求L(θ)的最大值。
引入似然函数如下:(Π从...到...的积)
连续型变量相互独立的充要条件是联合概率密度等于边缘概率密度的乘积。因此变量符合独立同分布前提下,联合概率密度等于边缘概率密度的乘积成立。
p(y(i) | x(i);θ):什么样的x和θ组合完后,能成为y的可能性越大越好。m项的乘积非常难解,难以估计,因此要想办法转为加法。
对数似然:乘法难解,加法相对容易,对数里面乘法可以转换成加法,因此对式子左右两边取对数。
首先,取对数不影响函数的单调性,保证输入对应的概率的最大最小值对应似然函数的最值。
其次,减少计算量,比如联合概率的连乘会变成加法问题,指数亦可。
最后,概率的连乘将会变成一个很小的值,可能会引起浮点数下溢,尤其是当数据集很大的时候,联合概率会趋向于0,非常不利于之后的计算。依据ln曲线可知,很小的概率(越接近0)经过对数转换会转变为较大的负数,解决下溢问题。
取对数虽然会改变极值,但不会改变极值点。任务依然是求极值,因此L(θ)和logL(θ)两者是等价的。
这里要求解是的 θ,因此其他的都可以看作是常数项。 因此可以把看作是m倍的常数项:。
再观察另一个部分:,exp:高等数学里以自然常数e为底的指数函数,它同时又是航模名词,全称Exponential(指数曲线)。由于给对数取不同的底数只会影响极值,但不会影响极值点。
将这一部分底数取e,则与exp(x)的以e为底的指数发生抵消,再将常数项提取出来,可以将公司转成这种累加形式:
公式到这里就不能继续化简了,毕竟每个人的年龄(x)和每个有多少钱(y)是不同的,因此,必须从第一个样本迭代到第m个样本。最终简化为:
之前的目标:x和θ组合完后,成为y的可能性越大越好。因此现在要求得极大值点。A是一个恒为正的常数,B中包含平分因此也是一个正数。因此是两个正数间的减法。
如要求值越大越好,因此B:必须越小越好。
现在就将目标转换为求解最小二乘法。
从上面的推导可以得出结论:要求让似然函数越大越好,可转化为求θ取某个值时使J(θ)最小的问题。
求解最小二乘法的方法一般为两种:矩阵式、梯度下降法。
数据集含有m个样本,每个样本有n个特征时:
矩阵式的推导如下所示:
让J(θ)对θ求偏导,当偏导等于零时,则这个θ就是极值点。XT代表X矩阵的转置,XT与X的乘积一定会得到一个对称阵。
另外存在公式: θTXTXθ 等于 2XTXθ。
XTX的逆矩阵为:(XTX)-1 ,将这个逆矩阵分别乘到偏导结果等式两边,左边期望是零,推导得到:
这种方法存在的问题:不存在学习的过程;矩阵求逆不是一个必然成功的行为(存在不可逆);
对于多元线性回归来说,拟合函数为:
由于目标函数是由m个样本累加得到的,因此可以求一个平均得到损失函数:
1)对损失函数求偏导数,批量梯度下降:
容易得到最优解,但是每次考虑所有样本,执行速度很慢。
2)每次只用一个样本,随机梯度下降:
去除累加操作,每次抽样一个样本来计算,速度快,结果不准。
3)每次更新选择一部分数据,小批量梯度下降法:
当得到一个目标函数时,通常是不能直接求解的,线性回归能求出结果在机器学习中是一个特例。
机器学习常规套路:交给机器一堆数据,然后告诉它使用什么样的学习方式(目标函数),然后它朝着这个方向去学习。
算法优化:一步步完成迭代,每次优化一点点,积累起来就能获得大成功。
在一元函数中叫做求导,在多元函数中就叫做求梯度。梯度下降是一个最优化算法,通俗的来讲也就是沿着梯度下降的方向来求出一个函数的极小值。比如一元函数中,加速度减少的方向,总会找到一个点使速度达到最小。
通常情况下,数据不可能完全符合我们的要求,所以很难用矩阵去求解,所以机器学习就应该用学习的方法,因此我们采用梯度下降,不断迭代,沿着梯度下降的方向来移动,求出极小值。
梯度下降法包括批量梯度下降法和随机梯度下降法(SGD)以及二者的结合mini批量下降法(通常与SGD认为是同一种,常用于深度学习中)。
对于梯度下降,我们可以形象地理解为一个人下山的过程。假设现在有一个人在山上,现在他想要走下山,但是他不知道山底在哪个方向,怎么办呢?显然我们可以想到的是,一定要沿着山高度下降的地方走,不然就不是下山而是上山了。山高度下降的方向有很多,选哪个方向呢?这个人比较有冒险精神,他选择最陡峭的方向,即山高度下降最快的方向。现在确定了方向,就要开始下山了。
又有一个问题来了,在下山的过程中,最开始选定的方向并不总是高度下降最快的地方。这个人比较聪明,他每次都选定一段距离,每走一段距离之后,就重新确定当前所在位置的高度下降最快的地方。这样,这个人每次下山的方向都可以近似看作是每个距离段内高度下降最快的地方。
现在我们将这个思想引入线性回归,在线性回归中,我们要找到参数矩阵 使得损失函数 最小。如果把损失函数 看作是这座山,山底不就是损失函数最小的地方吗,那我们求解参数矩阵 的过程,就是人走到山底的过程。
如图所示,这是一元线性回归(即假设函数 )中的损失函数图像,一开始我们选定一个起始点(通常是 ),然后沿着这个起始点开始,沿着这一点处损失函数下降最快的方向(即该点的梯度负方向)走一小步,走完一步之后,到达第二个点,然后我们又沿着第二个点的梯度负方向走一小步,到达第三个点,以此类推,直到我们到底局部最低点。为什么是局部最低点呢?因为我们到达的这个点的梯度为 0 向量(通常是和 0 向量相差在某一个可接受的范围内),这说明这个点是损失函数的极小值点,并不一定是最小值点。
从梯度下降法的思想,我们可以看到,最后得到的局部最低点与我们选定的起始点有关。通常情况下,如果起始点不同,最后得到的局部最低点也会不一样。
每次更新参数的操作:
其中α为学习率(步长),对结果会产生巨大的影响,调节学习率这个超参数是建模中的重要内容。
选择方法:从小的开始,不行再小。
批处理数量:32、64、128比较常用,很多时候还要考虑内存和效率。
因为J(θ)是凸函数,所以GD求出的最优解是全局最优解。批量梯度下降法是求出整个数据集的梯度,再去更新θ,所以每次迭代都是在求全局最优解。
写一个prepare_for_training
函数,对数据进行函数变换、标准化等操作。最后返回处理过的数据,以及均值和标准差。
写一个LinearRegression类,包含线性回归相关的方法。
对 LinearRegression类进行建模、预测、计算损失等。
开始时的损失 463505
训练后的损失 26.332
多特征建模,观察与单特征建模效果对比。
Plotly 是一款用来做数据分析和可视化的在线平台,功能非常强大,可以在线绘制很多图形比如条形图、散点图、饼图、直方图等等。而且还是支持在线编辑,以及多种语言python、javascript、matlab、R等许多API。使用Plotly可以画出很多媲美Tableau的高质量图:
把mode设置为markers就是散点图,然后marker里面设置一组参数,比如颜色的随机范围,散点的大小,还有图例等等。