测试开发面试题

测试开发面试题
问1. 链表和数组的区别,和各自应用场景。
数组:我们知道不管是一维数组还是二维数组培训它们在内存里面的地址都 必须是连续的
优点:既然地址是连续的那么必然给我查找数据提供了极大的方便,让我们很容易的就能根据下标找到你需要的数据,提高了我们的效率
缺点:我们刚刚在它的优点中只讲到提高我们查找数据的效率,因为毕竟我们对数组的运用不只是查找数据,还有增加数据,插入数据,删除数据等一些操作;由于数组的特殊性,使得我们在进行这些操作的时候不能对其进行直接操作,还要重新开辟一个新的数组并使其长度增加来存放数据,这样的工作模式给我们带来了很大的不便,影响了效率
链表:链表的地址不是连续的,可以随意存放,删除等操作,通过引用来关联数据,链表可以分为单向的和双向的
优点:链表和数组的差别就在于链表在对数据进行插入和删除的时候可以不用开辟新的空间,只需要找到需要操作的结点就可以了,大大提高了人我们的效率
缺点:数组利于了查找,那链表就不利于查找咯
数组应用场景:数据比较少;经常做的运算是按序号访问数据元素;数组更容易实现,任何高级语言都支持;构建的线性表较稳定。
  链表应用场景:对线性表的长度或者规模难以估计;频繁做插入删除操作;构建动态性比较强的线性表。

问2. 如何不免堆内推的碎片化。
答自己提前申请一大片内存,自己定义上面的内存结构。
问3. 决策树算法。
决策树算法是一种逼近离散函数值的方法。它是一种典型的分类方法,首先对数据进行处理,利用归纳算法生成可读的规则和决策树,然后使用决策对新数据进行分析。本质上决策树是通过一系列规则对数据进行分类的过程。
问1. 链表和数组的区别,和各自应用场景。
数组:我们知道不管是一维数组还是二维数组培训它们在内存里面的地址都 必须是连续的
优点:既然地址是连续的那么必然给我查找数据提供了极大的方便,让我们很容易的就能根据下标找到你需要的数据,提高了我们的效率
缺点:我们刚刚在它的优点中只讲到提高我们查找数据的效率,因为毕竟我们对数组的运用不只是查找数据,还有增加数据,插入数据,删除数据等一些操作;由于数组的特殊性,使得我们在进行这些操作的时候不能对其进行直接操作,还要重新开辟一个新的数组并使其长度增加来存放数据,这样的工作模式给我们带来了很大的不便,影响了效率
链表:链表的地址不是连续的,可以随意存放,删除等操作,通过引用来关联数据,链表可以分为单向的和双向的
优点:链表和数组的差别就在于链表在对数据进行插入和删除的时候可以不用开辟新的空间,只需要找到需要操作的结点就可以了,大大提高了人我们的效率
缺点:数组利于了查找,那链表就不利于查找咯
数组应用场景:数据比较少;经常做的运算是按序号访问数据元素;数组更容易实现,任何高级语言都支持;构建的线性表较稳定。
  链表应用场景:对线性表的长度或者规模难以估计;频繁做插入删除操作;构建动态性比较强的线性表。

问2. 如何不免堆内推的碎片化。
答自己提前申请一大片内存,自己定义上面的内存结构。
问3. 决策树算法。
决策树算法是一种逼近离散函数值的方法。它是一种典型的分类方法,首先对数据进行处理,利用归纳算法生成可读的规则和决策树,然后使用决策对新数据进行分析。本质上决策树是通过一系列规则对数据进行分类的过程。
关于深度学习
深度学习项目,怎么训练的,网络如何设计?数据集是什么?
Input:一个人脸数据库,15个人,每人20个样本(左右)。
Output:人脸检测,并识别出每张检测到的人脸。
深度学习网络设计流程
Step 1:定义问题收集数据
Step 2:选择衡量成功的指标
Step 3:确定评估方法
Step 4:准备数据
Step 5:开发比基准更好的模型
Step 6:扩大模型规模:开发过拟合的模型
Step 7:模型正则化与调节超参数
Step 1:定义问题收集数据
确定问题是什么:即输入与预测分别是什么;
确定分类问题:是二分类问题、多分类问题、标量回归问题、向量回归问题, 还是多分类、多标签、聚类、生成、强化学习等;
假设输出是可以根据输入进行预测的(排除不可预测问题,Eg: 根据夏装销量预测冬装销量的非平稳问题;)
假设数据包含足够多信息,足以学习出输入和输出的关系;
Step 2:选择衡量成功的指标
观测是否成功的标志:
平衡分类问题:精度(accuracy)和接收者操作特征曲线下面积(ROC AUC)?
类别不平衡问题:准确率(precision)和召回率(recall)
排序或多标签分类:平均准确率均值(Mean Average Precision)
Step 3:确定评估方法
衡量当前进展(只选其一):
留出验证集:数据量大时可以采用;
折交叉验证:用于流出验证样本量太少;
重复的K折验证:如果可用的数据很少,同时模型评估又需要非常准确;
Step 4:准备数据
知道训练什么、要优化什么以及评估方法基础上,格式化数据使其可以输入到模型:
将数据处理为张量;
张量数值保持在较小范围,比如 (0,1) (-1,1);
不同的特征具有不同的取值范围(异质数据),那么应该做数据标准化;
对小数据问题,需要做特征工程;
Step 5:开发比基准更好的模型
开发一个小模型,打败纯随机基准(dumb baseline),获得统计功效(Statistical power):
假设输出是可以根据输入进行预测的;
假设数据包含足够多的信息,足以学习输入和输出之间的关系;
构建
最后一层激活函数:对网络的输出进行限制;
损失函数:匹配要解决的问题的类型;
优化配置:使用的 优化器、学习率 【一般使用rmsprop与默认的学习率】
Step 6:扩大模型规模:开发过拟合的模型
获得统计功效模型,判断模型是否足够强大,增加模型规模;
(1)添加更多层;
(2)让每一层变得更大;
(3)训练更多的轮次;
出现过拟合后,准备正则化和调节模型;
Step 7:模型正则化与调节超参数
尝试以下几项,达到模型最佳性能:
(1)添加 Dropout;
(2)尝试不同的架构:增加或减少层数;
(3)添加 L1 和/或 L2 正则化;
(4)尝试不同的超参数(比如每层的单元个数或优化器的学习率),以找到最佳配置;
(可选)反复做特征工程:添加新特征或删除没有信息量的特征;
【验证过程中,使用同一数据验证模型效果,会出现模型对验证过程过拟合,降低验证过程的可靠性;】
最后开发出满意的模型,在所有可用数据(训练数据+验证数据)训练最终模型,并用测试数据评估;若结果不理想,表明验证流程不可靠,可能需要更换可靠的评估方法,如重复的K折验证;

数据集的拆分:
首先要准备好已经处理好的数据集(注意数据集要满足独立同分布),分为训练集、验证集、测试集。可按80%,10%,10%分割。
训练集用来整个模型的训练。
验证集在训练过程中验证是否过拟合。
测试集切记只用在最终判断模型的质量的,切记变成根据测试集调参了,这样测试集没意义。
训练的关键:
在输入数据做迭代训练时的关键要关注模型在训练集(绿线)和验证集(紫线)所画出的误差曲线之间关系(或准确度曲线,曲线任选其一,误差越小越好或准确度越高越好)
欠拟合及应对方法:
如果训练集和验证集的误差均较高,则说明训练还不够,处于模型欠拟合状态,需要继续迭代训练,当然如果迭代多久都无法降低误差,则考虑所用的模型是否太小了,导致模型学不到东西。
过拟合及应对方法:
如果训练集和验证集的两者之间的误差差别较大,训练集的误差较低(训练集的误差永远是越来越低的,因为模型就是在不断拟合训练集的),而验证集的误差相对较高,则模型已经处于过拟合状态了。因为模型已经训练的过头,倾向于死记硬背的记住训练集,不再具有泛化性,而在验证集上的表现就很差。此时可以考虑用正则化的方法,如L1正则化或L2正则化,也可以使用Dropout(随机丢弃神经元的)的方法,防止过拟合。
最佳拟合:
当然,欠拟合和过拟合的中间状态就是刚好拟合,是最佳的训练效果,这里用到的方法叫做Early Stopping,就是当你的验证集的误差曲线不断降低,但是验证集的误差开始有向上反弹的趋势时就可以停止训练的迭代了,此时模型的参数就是拟合的较好的情况。
人脸识别过程中输入的是视频流,在识别过程中视频流速率多少?是否有丢帧情况?是每一帧都都识别吗?
这个跟嵌入式端程序运行速度有关,如果能运行到30ms以内 就是每一帧都识别,如果60ms 就是跳一帧, 90ms就跳两帧 相当三帧识别一帧,所以要求帧率越高越好 也就是是程序运行越快越好,识别的帧率只跟运行的速度有关。

电影模糊是比特率低,视频播放卡是帧速率低。视频的比特率和帧速率的区别:
1、定义的区别
比特率是指每秒传送的比特(bit)数。比特率越高,传送数据速度越快。
帧速率是帧的位图图像连续出现在显示器上的频率(速率)。
2、单位的区别
比特率单位为 bps(Bit Per Second)。
帧速率单位为赫兹(Hz)。
3、表示的对象的区别
比特率是单位时间播放连续的媒体如压缩后的音频或视频的比特数量。在这个意义上讲,它相当于术语数字带宽消耗量,或吞吐量。
解释jvm原理

在人脸识别中,我们通常采用欧氏距离和余弦距离来衡量人脸特征的相似度,判别是否为同一个人。
欧氏距离
欧氏距离比较简单,采用欧氏公式直接计算两个点之间的距离,如下:

diff = np.subtract(feature1, feature2)
dist = np.sqrt(np.sum(np.square(diff)))
余弦距离

余弦距离,也称为余弦相似度,是用向量空间中两个向量夹角的余弦值作为衡量两个个体间差异的大小的度量。
当两个向量直接的夹角趋向0时,两个向量越接近,差异就越小。此时 = 1,即越接近1值时,说明人脸越相似。
区别
欧氏距离计算的是空间中两个点的绝对距离,距离dist越小,特征越相似;
余弦距离衡量的是空间中两个向量的夹角,夹角越靠近0,即dist越接近1,特征越相似。
从上面的代码,可以看到其实我们在实际的人脸识别中,可以对标准的欧氏距离和余弦距离做适当的放大,这样在更有利于阈值的比较,更精准。那么阈值我们怎么选值呢?在上面第二段代码中可以找到答案,可以看到最佳阈值是可以算出来的,代码 thresholds = np.arange(0, 4, 0.001) ,最佳阈值在(0, 4)之间进行查找,在acc_train 最高时的threshold来当中最佳阈值。

识别率99.8是怎么评估的?实际环境中识别效果也很好吗?

给定两个有序数组a,b以及一个数n,求这两个数组中第n小的数。并用测试的角度增加健壮性。有没有优化思路?如何进行优化?

A = [‘a’, ‘b’, ‘c’, ‘d’, ‘e’] # 出现的元素
B = [1, 5, 0, 45, 78] # 元素出现的次数(统计结果)
Z = zip(B, A) # 对AB进行封装,把频率放在前面
Z = sorted(Z, reverse=True) # 进行逆序排列
B, A = zip(*Z) # 进行解压,其中的AB已经按照频率排好
for i,j in zip(B, A):
print(i,’\t’,j)

a=[1,4,6,8,90,30]
b=[3,5,78,90,76,54]
c=a+b;
d=sorted©
print(d)
print(d[3])

1.http请求都有哪些?有什么区别?
HTTP协议中共定义了八种方法或者叫“动作”来表明对Request-URI指定的资源的不同操作方式,具体介绍如下:

OPTIONS:返回服务器针对特定资源所支持的HTTP请求方法。也可以利用向Web服务器发送’*'的请求来测试服务器的功能性。
HEAD:向服务器索要与GET请求相一致的响应,只不过响应体将不会被返回。这一方法可以在不必传输整个响应内容的情况下,就可以获取包含在响应消息头中的元信息。
GET:向特定的资源发出请求。
POST:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的创建和/或已有资源的修改。
PUT:向指定资源位置上传其最新内容。
DELETE:请求服务器删除Request-URI所标识的资源。
TRACE:回显服务器收到的请求,主要用于测试或诊断。
影响一个 HTTP 网络请求的因素主要有两个:带宽和延迟。
带宽:如果说我们还停留在拨号上网的阶段,带宽可能会成为一个比较严重影响请求的问题,但是现在网络基础建设已经使得带宽得到极大的提升,我们不再会担心由带宽而影响网速,那么就只剩下延迟了。
延迟:
o浏览器阻塞(HOL blocking):浏览器会因为一些原因阻塞请求。浏览器对于同一个域名,同时只能有 4 个连接(这个根据浏览器内核不同可能会有所差异),超过浏览器最大连接数限制,后续请求就会被阻塞。
oDNS 查询(DNS Lookup):浏览器需要知道目标服务器的 IP 才能建立连接。将域名解析为 IP 的这个系统就是 DNS。这个通常可以利用DNS缓存结果来达到减少这个时间的目的。
o建立连接(Initial connection):HTTP 是基于 TCP 协议的,浏览器最快也要在第三次握手时才能捎带 HTTP 请求报文,达到真正的建立连接,但是这些连接无法复用会导致每次请求都经历三次握手和慢启动。三次握手在高延迟的场景下影响较明显,慢启动则对文件类大请求影响较大
HTTP1.0和HTTP1.1的一些区别
HTTP1.0最早在网页中使用是在1996年,那个时候只是使用一些较为简单的网页上和网络请求上,而HTTP1.1则在1999年才开始广泛应用于现在的各大浏览器网络请求中,同时HTTP1.1也是当前使用最为广泛的HTTP协议。 主要区别主要体现在:
1.缓存处理,在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。
2.带宽优化及网络连接的使用,HTTP1.0中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1则在请求头引入了range头域,它允许只请求资源的某个部分,即返回码是206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。
3.错误通知的管理,在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
4.Host头处理,在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP1.1的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request)。
5.长连接,HTTP 1.1支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,在HTTP1.1中默认开启Connection: keep-alive,一定程度上弥补了HTTP1.0每次请求都要创建连接的缺点。

四、HTTPS与HTTP的一些区别
HTTPS协议需要到CA申请证书,一般免费证书很少,需要交费。
HTTP协议运行在TCP之上,所有传输的内容都是明文,HTTPS运行在SSL/TLS之上,SSL/TLS运行在TCP之上,所有传输的内容都经过加密的。
HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
HTTPS可以有效的防止运营商劫持,解决了防劫持的一个大问题。

2.对服务器命令熟悉?
3.数据库查询怎么实现的?

4.你项目中的亮点?
5.numpy或者矩阵运算为什么比一般循环快?
numpy,他能把简单好用的python和高性能的c语言合并在一起,当调用numpy功能的时候,其实调用了很多的c语言而不是纯的python
其实numpy就是c的逻辑,创建存储容器’array’的时候在c上是寻找到一连串区域来存放,但是python存放的时候则不是连续的区域,这使得
python在索引这个容器里的数据时不是那么的有效率,numpy只需要在这块固定的连续区域前后来回走动就能拿到数据了
在运用numpy的时候我们通常不是一个一维的array来存放数据,而是用二维和三维的快来存放,因为numpy的快速矩阵相乘的运算,能将乘法分配
到计算机中的多个核,让运算并行,从而实现多线程/多进程,这种并行的计算大大加快了运算的速度

6.有如下代码,请问运行结果是?
还是来做一下题吧:假设有一个有序数组,给定一个数target,求数组中对数和等于target的对数,比如数组是1,2,3,4,5 target=6,则[1,5]就是一对数答案,请将所有答案存放在二维数组里面,时间复杂度O(n).
假设数组无序,请保证时间复杂度O(n)的情况下解决上述问题
def func(lis,target) :
for i in range(0,len(lis)-1):
for j in range(i+1,len(lis)-1):
if lis[i]+lis[j]==target:
b=[lis[i],lis[j]]
print(b)
if name == ‘main’:
lis=[1,2,3,4,5,6,7,8,9,11,12,13,14,20,21,25,26,27,28,29,30,31,32]
target=30
func(lis,target)
如何评测一个跟踪算法的好坏?

问如何判断一个链表里有环?
方法一、穷举遍历
方法一:首先从头节点开始,依次遍历单链表的每一个节点。每遍历到一个新节点,就从头节点重新遍历新节点之前的所有节点,用新节点ID和此节点之前所有节点ID依次作比较。如果发现新节点之前的所有节点当中存在相同节点ID,则说明该节点被遍历过两次,链表有环;如果之前的所有节点当中不存在相同的节点,就继续遍历下一个新节点,继续重复刚才的操作。
例如这样的链表:A->B->C->D->B->C->D, 当遍历到节点D的时候,我们需要比较的是之前的节点A、B、C,不存在相同节点。这时候要遍历的下一个新节点是B,B之前的节点A、B、C、D中恰好也存在B,因此B出现了两次,判断出链表有环。
假设从链表头节点到入环点的距离是D,链表的环长是S。那么算法的时间复杂度是0+1+2+3+…+(D+S-1) = (D+S-1)(D+S)/2 , 可以简单地理解成 O(NN)。而此算法没有创建额外存储空间,空间复杂度可以简单地理解成为O(1)。
方法二、哈希表缓存
***首先创建一个以节点ID为键的HashSet集合,用来存储曾经遍历过的节点。然后同样是从头节点开始,依次遍历单链表的每一个节点。每遍历到一个新节点,就用新节点和HashSet集合当中存储的节点作比较,如果发现HashSet当中存在相同节点ID,则说明链表有环,如果HashSet当中不存在相同的节点ID,就把这个新节点ID存入HashSet,之后进入下一节点,继续重复刚才的操作。
这个方法在流程上和方法一类似,本质的区别是使用了HashSet作为额外的缓存。
假设从链表头节点到入环点的距离是D,链表的环长是S。而每一次HashSet查找元素的时间复杂度是O(1), 所以总体的时间复杂度是1
(D+S)=D+S,可以简单理解为O(N)。而算法的空间复杂度还是D+S-1,可以简单地理解成O(N)。
方法三、快慢指针
首先创建两个指针1和2(在java里就是两个对象引用),同时指向这个链表的头节点。然后开始一个大循环,在循环体中,让指针1每次向下移动一个节点,让指针2每次向下移动两个节点,然后比较两个指针指向的节点是否相同。如果相同,则判断出链表有环,如果不同,则继续下一次循环。
例如链表A->B->C->D->B->C->D,两个指针最初都指向节点A,进入第一轮循环,指针1移动到了节点B,指针2移动到了C。第二轮循环,指针1移动到了节点C,指针2移动到了节点B。第三轮循环,指针1移动到了节点D,指针2移动到了节点D,此时两指针指向同一节点,判断出链表有环。
此方法也可以用一个更生动的例子来形容:在一个环形跑道上,两个运动员在同一地点起跑,一个运动员速度快,一个运动员速度慢。当两人跑了一段时间,速度快的运动员必然会从速度慢的运动员身后再次追上并超过,原因很简单,因为跑道是环形的。

涉及字符串的分割和反转和合并等方法
split()通过指定分隔符对字符串进行切片,如果参数num 有指定值,则仅分隔 num 个子字符串
语法
split()方法语法:
str.split(str="", num=string.count(str))
利用re模块分割含有多种分割符的字符串:
import re
a=‘Beautiful, is; better*than\nugly’

四个分隔符为:, ; * \n

x= re.split(’,|; |*|\n’,a)
print(x)

python中字符串拆分与合并——split()、join()、strip()和replace()

算法coding:各种排序法
接口测试相关,主要是接口协议、类型;自动化接口测试方法、工具
1.1测试需求及范围
目前提供的接口多为Rest 规范的接口,需要使用Postman 进行接口测试。也可以尝试考虑使用JMeter 工具进行接口测试和压测,核对接口入参及返回报文格式、内容的正确性。
1.2测试方法
根据开发人员提供的接口访问地址、入参格式、请求格式,进行接口请求数据拼接,并查看返回结果及返回报文、响应时间,检查返回Json内容是否符合接口定义规范,是否符合预期的返回结果。
1.3测试工具
Postman/Jemeter

1.4工具使用示例
根据接口设计文档,获取访问url 、接口访问方式、参数、参数值,分别录入工具中,请求后,查看返回的Body 体 显示 接口返回的json串信息,测试操作参考如下图:

1.5测试思路
首先根据接口设计的技术架构方案,了解清楚被测接口对应的公共入参、入参、出参及返回数据的Json 结构规范,根据测试场景进行测试。
1、理解接口参数,熟悉接口参数的输入要求、输入值范围、必填项等;
2、理解接口输出,熟悉返回json的结构构成、返回值类别、返回值范围、返回data的不同类型等。
3、理解接口的逻辑、接口的业务关联,熟悉技术方案中的接口相互关联、依赖的关系,接口与接口之间的数据传递等。
4、寻找测试点,根据输入(参数名、取值范围)、输出(参数名、返回值范围)、关联关系,进行测试点分析,具体分析方法可参考1.6 通用测试场景、1.7 逻辑测试场景。
1.6通用测试场景
对于接口测试的入参需考虑以下几个方面,设计测试用例时需要考虑交叉的情况:
1、测试参数名称的正确性
1.参数名缺失,比如参数名错误,导致参数不存在;必填参数缺失,选填参数缺失;参数名必须匹配大小写时,考虑英文大小写的传参测试;分别考虑参数名称错误的情况,参数名包括特殊字符:如中文,空格,数字,特殊符等;

2、测试参数值的正确性
1.长度限制,考虑输入参数值是否字数限制;非空判断,是否可为空(不输入,即””),是否为null,是否为空格;输入值是否在正常范围内(如int 类型参数 是-32768~32767,输入区域外的数值);参数传入的类型错误,如要求传入int类型,结果传入String类型;输入参数值个数不正确;必填项参数值,传入为空;参数值传入特殊字符等;参数值模糊匹配查询;

1.7逻辑场景
逻辑场景考虑是对功能测试的补充,对于接口的业务逻辑场景,需要根据需求方案、技术架构中的设计的接口业务逻辑进行用例设计,例如:分页查询接口(分页查询接口涉及数据分页共5页,传入-1 表示查询全部数据),设计场景如下:
1.传入查询页码为空,提示缺少查询页码参数;传入页码为-1,查询返回全部数据;传入页码为1,查询返回第一页数据;传入页码3,查询返回第三页数据;传入页码为5,查询返回第5页数据;传入页码为6,提示查询无数据;传入页码为0,提示查询页码参数值不正确;传入页码为01,03,05 ,可查询出1、3、5页数据;传入页码为all ,提示查询页码数据不正确;

1.8测试结果检查
对接口测试返回的结果进行核对,验证返回JSON数据的结构是否正确、返回数据值是否正确、返回数据值是否有缺失等,比如
用户注册接口的接口返回值:
接口返回结果
1 2 3 4 5 6 7 8 9 10 11 12 {“error_code”:“0”, “msg”:“注册成功”, “redirect”:"", “data”:{ “rsid”:“xxxx”,//session会话主键 “userinfo”:{ “userid”:“xxxxxxxxx”,//用户主键 “username”:“12345678”, … “create_time”: 14221221124,//创建时间,int型,时间戳, } }
需要验证如下:
1、返回参数error_code ,msg,redirct ,data 是否正确返回 2、msg,error_code对应的value 不为空; 3、error_code的错误码在错误码列表范围内; 4、验证data返回的数据结构正确性、返回的参数值是否全面完整;
自动化测试框架:框架里包含哪些模块;工具;脚本语言
案例设计:比如如何测试一个字符串是ip地址
算法-判断一个字符串是否是ip地址?

如何判断一个IP是否是合法的IP,如输入:192.168.1.0,输出:合法;输入192.168.1.1222,输出:非法。

首先明确IP的格式:(1255).(0255).(0255).(0255)

下面使用两种不同的方式进行验证:方案一为字符串处理,方案二为正则表达式处理

  • (BOOL)ipIsValidity1:(NSString *)ip {
    // (1255).(0255).(0255).(0255)
    if (!ip || ip.length < 7 || ip.length > 15) {
    return NO;
    }

    //首末字符判断,如果是".“则是非法IP
    if ([[ip substringToIndex:1] isEqualToString:@”."]) {
    return NO;
    }
    if ([[ip substringFromIndex:ip.length - 1] isEqualToString:@"."]) {
    return NO;
    }

    NSArray <NSString *> *subIPArray = [ip componentsSeparatedByString:@"."];
    if (subIPArray.count != 4) {
    return NO;
    }

    for (NSInteger i = 0; i < 4; i++) {
    NSString *subIP = subIPArray[i];

      if (subIP.length > 1 && [[subIP substringToIndex:1] isEqualToString:@"0"]) {
          //避免出现 01.  011.
          return NO;
      }
      for (NSInteger j = 0; j < subIP.length; j ++) {
          char temp = [subIP characterAtIndex:j];
          if (temp < '0' || temp > '9') {
              //避免出现 11a.19b.s.s
              return NO;
          }
      }
    
      NSInteger subIPInteger = subIP.integerValue;
      if (i == 0) {
          if (subIPInteger < 1 || subIPInteger > 255) {
              return NO;
          }
      }else{
          if (subIPInteger < 0 || subIPInteger > 255) {
              return NO;
          }
      }
    

    }
    return YES;
    }
    正则
    验证IP是否合法的正则表达式:

  • //String ipRegEx = “^([1-9]|([1-9][0-9])|(1[0-9][0-9])|(2[0-4][0-9])|(25[0-5]))(\.([0-9]|([1-9][0-9])|(1[0-9][0-9])|(2[0-4][0-9])|(25[0-5]))){3}$”
  • //String ipRegEx = “^([1-9]|([1-9]\d)|(1\d{2})|(2[0-4]\d)|(25[0-5]))(\.(\d|([1-9]\d)|(1\d{2})|(2[0-4]\d)|(25[0-5]))){3}$”
  • //String ipRegEx = “^(([1-9]\d?)|(1\d{2})|(2[0-4]\d)|(25[0-5]))(\.(0|([1-9]\d?)|(1\d{2})|(2[0-4]\d)|(25[0-5]))){3}$”
    接口的组成:
    a、接口说明
    b、调用url
    c、请求方法(get\post)
    d、请求参数、参数类型、请求参数说明
    e、返回参数说明

1.python的闭包
闭包: 在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。一般情况下,在我们认知当中,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。
2.python装饰器
(1)装饰器就是对闭包的使用;
(2)装饰器用来装饰函数;
(3)返回一个函数对象,被装饰的函数接收;
(4)被装饰函数标识符指向返回的函数对象。
装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

3.冒泡排序(快速排序)
def bubble_sort3(arr):
for j in range(len(arr)-1, 0, -1):
count = 0
for i in range(0, j):
if arr[i] > arr[i + 1]:
arr[i], arr[i + 1] = arr[i + 1], arr[i]
count += 1
if count == 0:
return
bubble_sort3(arr)
print(arr) # [1, 3, 4, 7, 8, 34, 67]
冒泡排序还是一种稳定性的算法,如果序列中出现两个相同的值的时候,无论选取最大值,还是最小值进行排序,最后两个相同值的前后位置都是不变的。
排序算法可以分为内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。常见的内部排序算法有:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序等。用一张图概括:
关于稳定性:
稳定的排序算法:冒泡排序、插入排序、归并排序和基数排序。
不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序。

4.Python下多线程的限制以及多线程的传递方式、多线程的实现
什么情况使用多线程–编写程序过程中需要使用某些阻塞过程时,我们才使用多线程,或者更进一步讲,使用多线程的目的是对阻塞过程中的实际阻塞的抽象提取。
多线程的限制----1 什么是资源限制资源限制是指在进行并发编程时,程序的执行速度受限于计算机硬件资源或软件资源。
例如,服务器的带宽只有2Mb/s,某个资源的下载速度是1Mb/s每秒,系统启动10个线程下载资源,下载速度不会变成10Mb/s,所以在进行并发编程时,要考虑这些资源的限制。
硬件资源限制有带宽的上传/下载速度、硬盘读写速度和CPU的处理速度。
软件资源限制有数据库的连接数和socket连接数等。
(2)资源限制引发的问题
在并发编程中,将代码执行速度加快的原则是将代码中串行执行的部分变成并发执行,但是如果将某段串行的代码并发执行,因为受限于资源,仍然在串行执行,这时候程序不仅不会加快执行,反而会更慢,因为增加了上下文切换和资源调度的时间。
例如,之前看到一段程序使用多线程在办公网并发地下载和处理数据时,导致CPU利用率达到100%,几个小时都不能运行完成任务,后来修改成单线程,一个小时就执行完成了。
(3) 解决资源限制的问题
对于硬件资源限制,可以考虑使用集群并行执行程序。既然单机的资源有限制,那么就让程序在多机上运行。
对于软件资源限制,可以考虑使用资源池将资源复用。比如使用连接池将数据库和Socket连接复用,或者在调用对方webservice接口获取数据时,只建立一个连接。
(4) 在资源限制情况下进行并发编程
如何在资源限制的情况下,让程序执行得更快呢?
方法就是,根据不同的资源限制调整程序的并发度
比如下载文件程序依赖于两个资源——带宽和硬盘读写速度。有数据库操作时,涉及数据库连接数,如果SQL语句执行非常快,而线程的数量比数据库连接数大很多,则某些线程会被阻塞,等待数据库连接。
Python唯一支持的参数传递方式是『共享传参』(call by sharing)
多数面向对象语言都采用这一模式,包括Ruby、Smalltalk和Java(Java的引用类型是这样,基本类型按值传递)
共享传参是指函数的各个形式参数获得实参中各个引用的副本;也就是说,函数内部的形参是实参的别名(alias)
python多线程有个全局解释器锁(global interpreter lock),这个锁的意思是任一时间只能有一个线程使用解释器,跟单cpu跑多个程序一个意思,大家都是轮着用的,这叫“并发”,不是“并行”。
多进程间共享数据,可以使用 multiprocessing.Value 和 multiprocessing.Array
多线程类似于同时执行多个不同程序,多线程运行有如下优点:
使用线程可以把占据长时间的程序中的任务放到后台去处理。
用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度
程序的运行速度可能加快
在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。
线程在执行过程中与进程还是有区别的。每个独立的进程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。
指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存。
线程可以被抢占(中断)。
在其他线程正在运行时,线程可以暂时搁置(也称为睡眠) – 这就是线程的退让。
Python多线程编程中常用方法:
1、join()方法:如果一个线程或者在函数执行的过程中调用另一个线程,并且希望待其完成操作后才能执行,那么在调用线程的时就可以使用被调线程的join方法join([timeout]) timeout:可选参数,线程运行的最长时间
2、isAlive()方法:查看线程是否还在运行
3、getName()方法:获得线程名
4、setDaemon()方法:主线程退出时,需要子线程随主线程退出,则设置子线程的setDaemon()
5.了解mutiprocessing模块和进程池的实现?进程池pool
仔细说来,multiprocess不是一个模块而是python中一个操作、管理进程的包。 之所以叫multi是取自multiple的多功能的意思,在这个包中几乎包含了和进程有关的所有子模块。由于提供的子模块非常多,为了方便大家归类记忆,我将这部分大致分为四个部分:创建进程部分,进程同步部分,进程池部分,进程之间数据共享。
当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态成生多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法。初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来执行,
6.Python是如何进行内存管理的,什么阶段释放内存对象?通过什么释放内存对象
python内部使用引用计数,来保持追踪内存中的对象,Python内部记录了对象有多少个引用,即引用计数,当对象被创建时就创建了一个引用计数,当对象不再需要时,这个对象的引用计数为0时,它被垃圾回收。
垃圾回收1、当内存中有不再使用的部分时,垃圾收集器就会把他们清理掉。它会去检查那些引用计数为0的对象,然后清除其在内存的空间。当然除了引用计数为0的会被清除,还有一种情况也会被垃圾收集器清掉:当两个对象相互引用时,他们本身其他的引用已经为0了。
垃圾回收机制还有一个循环垃圾回收器, 确保释放循环引用对象(a引用b, b引用a, 导致其引用计数永远不为0)。
在Python中,许多时候申请的内存都是小块的内存,这些小块内存在申请后,很快又会被释放,由于这些内存的申请并不是为了创建对象,所以并没有对象一级的内存池机制。这就意味着Python在运行期间会大量地执行malloc和free的操作,频繁地在用户态和核心态之间进行切换,这将严重影响Python的执行效率。为了加速Python的执行效率,Python引入了一个内存池机制,用于管理对小块内存的申请和释放。
内存池机制----Python提供了对内存的垃圾收集机制,但是它将不用的内存放到内存池而不是返回给操作系统。
Python中所有小于256个字节的对象都使用pymalloc实现的分配器,而大的对象则使用系统的 malloc。另外Python对象,如整数,浮点数和List,都有其独立的私有内存池,对象间不共享他们的内存池。也就是说如果你分配又释放了大量的整数,用于缓存这些整数的内存就不能再分配给浮点数
7.Lambda函数?它有什么好处?使用lambda机制,写一段fibonacci数列
lambda函数有如下特性:lambda函数是匿名的:所谓匿名函数,通俗地说就是没有名字的函数。lambda函数没有名字。lambda函数有输入和输出:输入是传入到参数列表argument_list的值,输出是根据表达式expression计算得到的值。lambda函数一般功能简单:单行expression决定了lambda函数不可能完成复杂的逻辑,只能完成非常简单的功能。由于其实现的功能一目了然,甚至不需要专门的名字来说明。
fib = lambda n : n if n <= 2 else fib(n-1)+fib(n-2)
不用的情况下:
def fib(n):
if n <= 2:
return n
else:
return fib(n-1)+fib(n-2)
8.日常环境一般需要部署多少个应用,使用什么样的手段维护和搭建环境

9.工厂模式的实现与原理
简单工厂模式适用于需创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂的情况下,而且用户只关心那种类型的实例被创建,并不关心其初始化过程时,比如多种数据库(MySQL/MongoDB)的实例,多种格式文件的解析器(XML/JSON)等。
 工厂方法模式继承了简单工厂模式的优点又有所改进,其不再通过一个工厂类来负责所有产品的创建,而是将具体创建工作交给相应的子类去做,这使得工厂方法模式可以允许系统能够更高效的扩展。实际应用中可以用来实现系统的日志系统等,比如具体的程序运行日志,网络日志,数据库日志等都可以用具体的工厂类来创建。
 抽象工厂模式在工厂方法基础上扩展了工厂对多个产品创建的支持,更适合一些大型系统,比如系统中有多于一个的产品族,且这些产品族类的产品需实现同样的接口,像很多软件系统界面中不同主题下不同的按钮、文本框、字体等等。
10.Spring的扩展点

11.大文件的单词数据统计(编码)
https://blog.csdn.net/jaket5219999/article/details/70669917
(多线程统计)
python2.7 运用multiprocessing模块的Pool 异步进程池,分段读取文件(文件编码为gbk),并统计词频,代码如下:
12.计算日期差距(编码)
import datetime
starttime = datetime.datetime(2009, 3, 23,10,28,30)
#long running
endtime = datetime.datetime(2009, 4, 23,12,58,50)
print (endtime - starttime)
13.防资损常用手段
14.列表合并
在应用append()时,发现列表是以一个元素的形式追加到列表上的,最后查询后用的是extend()方法,下面是区别
append() 向列表尾部追加一个新元素,列表只占一个索引位,在原有列表上增加
extend() 向列表尾部追加一个列表,将列表中的每个元素都追加进来,在原有列表上增加

  • 直接用+号看上去与用extend()一样的效果,但是实际上是生成了一个新的列表存这两个列表的和,只能用在两个列表相加上
    += 效果与extend()一样,向原列表追加一个新元素,在原有列表上增加
    15.内存 cpu 电量性能测试的内部机制

16.QA在加入新项目后首先要做什么

17.劲舞团创建房间的测试点(关注点异步操作、过程中断、并发性能相关)

18.统计一个字符串中单词词频的code
import numpy as np
def most_frequent(s):
word_list = s.split(’ ')
print(word_list)
new_list = []
count = []
for w in word_list:
if w not in new_list:
new_list.append(w)
print(new_list)
new_list = np.sort(new_list)
print(new_list)
for w in new_list:
count.append(word_list.count(w))
print(count)
inx = count.index(max(count))
print(new_list[inx])
return new_list[inx]
s = ‘I scream you scream we all scream for ice cream’
most_frequent(s)
19.Shell—如何批量修改4台主机名的脚本

20.库存尾单并发问题如何处理 库存争抢的幂

21.资源缓存的调度策略和安全策略

  1. android 系统的基础知识、android四大组件 activity生命周期

23.http请求的完整过程--------一次完整的HTTP请求过程
当我们在web浏览器的地址栏中输入: www.baidu.com,然后回车,到底发生了什么过程概览
(1).对www.baidu.com这个网址进行DNS域名解析,得到对应的IP地址
(2).根据这个IP,找到对应的服务器,发起TCP的三次握手
(3).建立TCP连接后发起HTTP请求
(4).服务器响应HTTP请求,浏览器得到html代码
(5).浏览器解析html代码,并请求html代码中的资源(如js、css图片等)(先得到html代码,才能去找这些资源)
(6)浏览器对页面进行渲染呈现给用
注:1.DNS域名解析采用的是递归查询的方式,过程是,先去找DNS缓存->缓存找不到就去找根域名服务器->根域名又会去找下一级,这样递归查找之后,找到了,给我们的web浏览器
2.为什么HTTP协议要基于TCP来实现? TCP是一个端到端的可靠的面相连接的协议,HTTP基于传输层TCP协议不用担心数据传输的各种问题(当发生错误时,会重传)
3.最后一步浏览器是如何对页面进行渲染的? a)解析html文件构成 DOM树,b)解析CSS文件构成渲染树, c)边解析,边渲染 , d)JS 单线程运行,JS有可能修改DOM结构,意味着JS执行完成前,后续所有资源的下载是没有必要的,所以JS是单线程,会阻塞后续资源下载
24.订购系统的APA如何进行功能和性能测试

27.一行代码实现对列表a中的偶数位置的元素进行加3后求和
map( lambda i: i+3, list( filter( lambda y:y%2 == 0, l ) ) )
第二种方法:------ reduce的参数 -函数-必须接受两个参数。
#!usr/bin python

-- coding: utf-8 --

from functools import reduce
l = [ 1, 2, 3, 4, 5 , 6 ]
l2 = reduce( lambda x, y : x + y, map( lambda i: i+3, list( filter( lambda y:y%2 == 0, l ) ) ) )
l3 = sum( list( map( lambda x: x + 3, list( filter( lambda y:y%2 == 0, l ) ) ) ) )
print( l2 )
print( l3 )
25.将列表A的元素顺序打乱,再对A进行排序得到列表B,然后把A和B按元素顺序构造一个字典d

-- coding: utf-8 --

keys = [‘a’, ‘b’, ‘c’]
values = [1, 2, 3]
dictionary = dict(zip(keys, values))
print dictionary
“”"
输出:
{‘a’: 1, ‘c’: 3, ‘b’: 2}
“”"
26.Python中pass的作用
做占位符或创建占位程序------保证格式完整------保证语义完整
27.Python中match()和search()的区别
import re
print(re.match(“func”, “function”))

打印结果 <_sre.SRE_Match object; span=(0, 4), match=‘func’>

print(re.match(“func”, “function”).span())

打印结果 (0, 4)

print(re.match(“func1”, “function”))

打印结果 None

注意:print(re.match(“func1”, “function”).span())会报错,因为取不到span
match()函数只检测字符串开头位置是否匹配,匹配成功才会返回结果,否则返回None
search()函数会在整个字符串内查找模式匹配,只到找到第一个匹配然后返回一个包含匹配信息的对象,该对象可以通过调用group()方法得到匹配的字符串,如果字符串没有匹配,则返回None。
28.线程安全的单列模式
单例模式是一种常见的设计模式,该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。
比如,服务器的配置信息写在一个文件中online.conf中,客户端通过一个 Config 的类来读取配置文件的内容。如果在程序运行期间,有很多地方都需要使用配置文件的内容,那么每个调用配置文件的地方都会创建 Config的实例,这就导致系统中存在多个Config 的实例对象,在配置文件内容很多的情况下,我们就浪费了大量的内存做了同样的事。事实上,对于Config类我们在程序运行期间时只需要一个实例对象即可,这时单例模式就是最好的选择。
单例模式是指,该对象创建后,在其生命周期内内存中始终只有一个对象, 如果被再次调用时,还是返回该对象。
这样做的好处是,可以节约内存,缺点是不可以根据不同的应用场景创建不同的对象。
多线程环境下,由于单例模式总是会去判断 实例是否被创建,但是多个线程有可能会拿到相同的结果,这样就无法实现单例模式了,因此遇到多线程的环境时,需要加锁。
29.时间的格式化方法
ython 提供了一个 time 和 calendar 模块可以用于格式化日期和时间。时间间隔是以秒为单位的浮点小数。

30.定时器是用什么做的
Timer: 隔一定时间调用一个函数,如果想实现每隔一段时间就调用一个函数的话,就要在Timer调用的函数中,再次设置Timer。Timer是Thread的一个派生类
#引入库 threading
import threading
#定义函数
def fun_timer():
  print(‘hello timer’) #打印输出
  global timer #定义变量
  timer = threading.Timer(60,fun_timer) #60秒调用一次函数
  #定时器构造函数主要有2个参数,第一个参数为时间,第二个参数为函数名
  timer.start() #启用定时器
timer = threading.Timer(1,fun_timer) #首次启动
timer.start()
31.线程如何退出结束---------一、全局锁
(1)、在Python中,Python代码的执行由Python虚拟机来控制,而在Python虚拟机中,同一时刻只有一个线程在执行,就像单CPU的系统中运行多个进程那样,内存中可以存放多个程序,但在任意时刻,只有一个程序在CPU中运行。同样的,在Python解释器中可以“运行”多个线程,但在任意时刻,只有一个线程在Python解释器中运行。
(2)、对Python虚拟机的访问由全局解释器锁【GIL】来控制,正是这个锁能保证同一时刻只有一个线程在运行。
(3)、多线程环境中,Python虚拟机的执行方式为:

  1. 设置GIL--------2. 切换到一个线程去运行
  2. 运行:a. 指定数量的字节码指令,或者
    b. 线程主动让出控制(可以调用time.sleep(0))
  3. 把线程设置为睡眠状态5. 解锁GIL
  4. 再次重复以上所有步骤
    我们都知道python中可以是threading模块实现多线程, 但是模块并没有提供暂停, 恢复和停止线程的方法, 一旦线程对象调用start方法后, 只能等到对应的方法函数运行完毕. 也就是说一旦start后, 线程就属于失控状态. 不过, 我们可以自己实现这些. 一般的方法就是循环地判断一个标志位, 一旦标志位到达到预定的值, 就退出循环. 这样就能做到退出线程了. 但暂停和恢复线程就有点难了, 我一直也不清除有什么好的方法, 直到我看到threading中Event对象的wait方法的描述时.
    利用wait的阻塞机制, 就能够实现暂停和恢复了, 再配合循环判断标识位, 就能实现退出了, 下面是代码示例:
    #!/usr/bin/env python

coding: utf-8

import threading
import time

class Job(threading.Thread):
def init(self, *args, **kwargs):
super(Job, self).init(args, **kwargs)
self.__flag = threading.Event() # 用于暂停线程的标识
self.__flag.set() # 设置为True
self.__running = threading.Event() # 用于停止线程的标识
self.__running.set() # 将running设置为True
def run(self):
while self.__running.isSet():
self.__flag.wait() # 为True时立即返回, 为False时阻塞直到内部的标识位为True后返回
print time.time()
time.sleep(1)
def pause(self):
self.__flag.clear() # 设置为False, 让线程阻塞
def resume(self):
self.__flag.set() # 设置为True, 让线程停止阻塞
def stop(self):
self.__flag.set() # 将线程从暂停状态恢复, 如何已经暂停的话
self.__running.clear() # 设置为False
a = Job()
a.start()
time.sleep(3)
a.pause()
time.sleep(3)
a.resume()
time.sleep(3)
a.pause()
time.sleep(2)
a.stop()
这完成了暂停, 恢复和停止的功能. 但是这里有一个缺点: 无论是暂停还是停止, 都不是瞬时的, 必须等待run函数内部的运行到达标志位判断时才有效. 也就是说操作会滞后一次.
  但是这有时也不一定是坏事. 如果run函数中涉及了文件操作或数据库操作等, 完整地运行一次后再退出, 反而能够执行剩余的资源释放操作的代码(例如各种close). 不会出现程序的文件操作符超出上限, 数据库连接未释放等尴尬的情况.
32.Linux的系统日志在哪里看
系统所有的日志都在 /var/log 下面自己看(具体用途可以自己查,附录列出一些常用的日志)
cat /var/log/syslog 等
cat /var/log/
.log
tail -f
如果日志在更新,如何实时查看 tail -f /var/log/messages
还可以使用 watch -d -n 1 cat /var/log/messages
-d表示高亮不同的地方,-n表示多少秒刷新一次。
该指令,不会直接返回命令行,而是实时打印日志文件中新增加的内容,
这一特性,对于查看日志是非常有效的。如果想终止输出,按 Ctrl+C 即可。
linux日志文件说明
/var/log/message 系统启动后的信息和错误日志,是Red Hat Linux中最常用的日志之一
/var/log/secure 与安全相关的日志信息
/var/log/maillog 与邮件相关的日志信息
/var/log/cron 与定时任务相关的日志信息
/var/log/spooler 与UUCP和news设备相关的日志信息
/var/log/boot.log 守护进程启动和停止相关的日志消息
/var/log/wtmp 该日志文件永久记录每个用户登录、注销及系统的启动、停机的事件
33.如何查看网络进程

链表翻转
class Solution:
# 返回ListNode
def ReverseList(self, pHead):
# write code here
if not pHead or not pHead.next:
return pHead
NewHead = self.ReverseList(pHead.next)
pHead.next.next = pHead
pHead.next = None
return NewHead
二叉树遍历

AB测试

物理内存和虚拟内存
物理内存:真实的硬件设备(内存条)
虚拟内存:利用磁盘空间虚拟出的一块逻辑内存,用作虚拟内存的磁盘空间被称为交换空间(Swap Space)。(为了满足物理内存的不足而提出的策略)
linux会在物理内存不足时,使用交换分区的虚拟内存。
内核会将暂时不用的内存块信息写到交换空间,这样以来,物理内存得到了释放,这块内存就可以用于其它目的,当需要用到原始的内容时,这些信息会被重新从交换空间读入物理内存。
linux的内存管理采取的是分页存取机制,Linux系统会不时的进行页面交换操作,以保持尽可能多的空闲物理内存,即使并没有什么事情需要内存,Linux也会交换出暂时不用的内存页面。这可以避免等待交换所需的时间。
( 注1 :linux进行页面交换是有条件的,不是所有页面在不用时都交换到虚拟内存,linux内核根据”最近最经常使用“算法,仅仅将一些不经常使用的页面文件交换到虚拟内存,有时我们会看到这么一个现象:linux物理内存还有很多,但是交换空间也使用了很多。其实,这并不奇怪,例如,一个占用很大内存的进程运行时,需要耗费很多内存资源,此时就会有一些不常用页面文件被交换到虚拟内存中,但后来这个占用很多内存资源的进程结束并释放了很多内存时,刚才被交换出去的页面文件并不会自动的交换进物理内存,除非有这个必要,那么此刻系统物理内存就会空闲很多,同时交换空间也在被使用,这是正常现象。)
( 注2:交换空间的页面在使用时会首先被交换到物理内存,如果此时没有足够的物理内存来容纳这些页面,它们又会被马上交换出去,如此以来,虚拟内存中可能没有足够空间来存储这些交换页面,最终会导致linux出现假死机、服务异常等问题,linux虽然可以在一段时间内自行恢复,但是恢复后的系统已经基本不可用了。
因此,合理规划和设计linux内存的使用,是非常重要的。)
34分片的实现

33.覆盖率的解释
模型评估是模型中关键部分,一方面通过模型评估可以对模型进行进一步的优化,使模型性能够更准确;另一方面,通过模型评估可以看模型实际运行效果,对采取的维系策略的有效性进行评价。
模型评估主要通过对低稳定度用户的流失率进行验证,观察低稳定度用户在后续月份的流失情况。
模型准确性评估。评估模型本身的准确性,通过两个重要指标。
1)命中率。描述模型预测准确性性指标。
命中率:=预测用户中流失用户数/预测用户数100%。
2) 覆盖率。描述模型预测结果与实际结果对比情况指标。
覆盖率:=预测用户中流失用户数/当月实际流失用户数
100%。

34.如何去除文本行末的回车
line = line.strip(’\n’)
35.均方根误差
均方根误差,它是观测值与真值偏差的平方和观测次数n比值的平方根,在实际测量中,观测次数n总是有限的,真值只能用最可信赖(最佳)值来代替.方根误差对一组测量中的特大或特小误差反映非常敏感,所以,均方根误差能够很好地反映出测量的精密度。均方根误差,当对某一量进行甚多次的测量时,取这一测量列真误差的均方根差(真误差平方的算术平均值再开方),称为标准偏差,以σ表示。σ反映了测量数据偏离真实值的程度,σ越小,表示测量精度越高,因此可用σ作为评定这一测量过程精度的标准。
36.如果训练集有限,不能外出扩充,要怎么做

37.如何反复点击图片中某个位置的按钮

38.如何查看CPU利用率
CPU使用率----例如每1秒采集一次CPU使用率,共采集5次。
[root@Peter ~]# sar -u 1 5
和top一样,可以看到所有cpu的使用情况。如果需要查看某颗cpu的使用可以用-P参数。例如指定显示0号cpu 的使用情况。
%us:表示用户空间程序的cpu使用率(没有通过nice调度)
%sy:表示系统空间的cpu使用率,主要是内核程序。
%ni:表示用户空间且通过nice调度过的程序的cpu使用率。
%id:空闲cpu
%wa:cpu运行时在等待io的时间
%hi:cpu处理硬中断的数量
%si:cpu处理软中断的数量
%st:被虚拟机偷走的cpu
[root@Peter ~]# sar -P 0 -u 1 5
看cpu信息,型号,几核 [root@f3 ~]# cat /proc/cpuinfo | grep name | cut -f2 -d:| uniq -c 16 Intel® Xeon® CPU E5520 @ 2.27GHz
39.如何判断内存泄漏
用户空间查看内存泄漏和解决都相对简单。定位问题的方法和工具也很多相对容易.我们来看看.
1. 查看内存信息
cat /proc/meminfo、free、cat /proc/slabinfo等
2. 查看进程的状态信息
top、ps、cat /proc/pid/maps/status/fd等
我们只需要关注几项就ok. buffers/cache/slab/active/anonpages
Active= Active(anon) + Active(file) (同样Inactive)
AnonPages: Non-file backed pages mapped into userspace page tables
buffers和cache的区别注释说的很清楚了.
有时候不是内存泄露,同样也会让系统崩溃,比如cache、buffers等占用的太多,打开太多文件,而等待系统自动回收是一个非常漫长的过程.
从proc目录下的meminfo文件了解到当前系统内存的使用情况汇总,其中可用的物理内存=memfree+buffers+cached,当memfree不够时,内核会通过
回写机制(pdflush线程)把cached和buffered内存回写到后备存储器,从而释放相关内存供进程使用,或者通过手动方式显式释放cache内存

40.对管道的理解

41.写过的shell脚本

42.linux如何实时监控日志
Linux日志文件在/var/log目录下,可以通过命令查看日志文件。
1,cat messages可以查看某个日志文件。
2,要达到实时更新,可以通过tail命令查看更新的数据,例如tail -f messages。
3,tail命令参数:
-f 循环读取
-q 不显示处理信息
-v 显示详细的处理信息
-c<数目> 显示的字节数
-n<行数> 显示行数
–pid=PID 与-f合用,表示在进程ID,PID死掉之后结束.
-q, --quiet, --silent 从不输出给出文件名的首部
-s, --sleep-interval=S 与-f合用,表示在每次反复的间隔休眠S秒

43.linux如果要将一个文本的第一行改为 a b c d应该怎么做

44.列表 元组 字典区别是什么,什么的查找速度最快
列表是最常用的Python数据类型,它可以作为一个方括号内的逗号分隔值出现。
列表的数据项不需要具有相同的类型
创建一个列表,只要把逗号分隔的不同的数据项使用方括号括起来即可。
元组也是存一组数据,只是一旦创建,便不能修改,所以又叫只读列表。元组创建很简单,只需要在括号中添加元素,并使用逗号隔开即可。
元组只有两个方法:count和index
不可变的tuple有什么意义?因为tuple不可变,所以代码更安全。如果可能,能用tuple代替list就尽量用tuple。
字典是另一种可变容器模型,且可存储任意类型对象。
字典的每个键值对()用冒号(:)分割,每个对之间用逗号(,)分割,整个字典包括在花括号({})中 ,格式如下所示:
键必须是唯一的,但值则不必。值可以取任何数据类型,但键必须是不可变的,如字符串,数字或元组。
为什么dict查找速度这么快?
因为dict的实现原理和查字典是一样的。假设字典包含了1万个汉字,我们要查某一个字,一个办法是把字典从第一页往后翻,直到找到我们想要的字为止,这种方法就是在list中查找元素的方法,list越大,查找越慢。
第二种方法是先在字典的索引表里(比如部首表)查这个字对应的页码,然后直接翻到该页,找到这个字。无论找哪个字,这种查找速度都非常快,不会随着字典大小的增加而变慢。dict就是第二种实现方式。
和list比较,dict有以下几个特点:
无序
查找和插入的速度极快,不会随着key的增加而变慢;
需要占用大量的内存,内存浪费多。
而list相反:
查找和插入的时间随着元素的增加而增加;
占用空间小,浪费内存很少。
所以,dict是用空间来换取时间的一种方法。
dict可以用在需要高速查找的很多地方,在Python代码中几乎无处不在,正确使用dict非常重要,需要牢记的第一条就是dict的key必须是不可变对象。这是因为dict根据key来计算value的存储位置,如果每次计算相同的key得出的结果不同,那dict内部就完全混乱了。这个通过key计算位置的算法称为哈希算法(Hash)。
要保证hash的正确性,作为key的对象就不能变。在Python中,字符串、整数等都是不可变的,因此,可以放心地作为key。而list是可变的,就不能作为key:
集合(set)是一个无序的不重复元素序列。
可以使用大括号 { } 或者 set() 函数创建集合,注意:创建一个空集合必须用 set() 而不是 { },因为 { } 是用来创建一个空字典。

算法测试分为三种测试场景:1算法准确性校验2算法性能校验3算法稳定性校验
准确性校验---------(1)检验率(召回率) (2)误识率 (3)准确率
算法性能校验
(1)单路延迟时间-----IPC取流,跑一个就是单路,测取视频流的时间,测上传单张图片的时间(可以测度张取平均值)
(2)多路并发抓拍延迟时间
性能测试的方法:获取线上一小时的抓拍人脸数据,主要是抓拍人脸存储地址,将地址存储成CSV文件,记录时间,最后输出结果。

算法的抽象度高:
算法工程由数据模型、筛选器、语义分析模块等高抽象模块组成,每个的属入输出都是抽象的代码接口,没有像前端工程一样有个UI操作界面进行输入,并且能直接通过界面的方式查看输出结果。所以算法工程测试以白盒的接口测试为主,通常使用基于junit测试框架的接口用例对输出数据进行验证。
准确性验证
最新模型代码----对应模型接口数据拉取-----数据执行—准确率对比
其中接口测试数据需要在线上调用时进行记录保存,建议使用标准的json格式,便于反序列化。选择的测试数据最好以一天或者一周为一个周期,便于以后对准确率数据进行横向对比。
覆盖率验证:每个模型都有对应的业务对象,最理想的情况是100%覆盖,但是实际覆盖率测试会根据项目阶段的不同要求不一样。项目进展中,都是在保证一定准确率的情况下分阶段逐渐提高覆盖率
交叉测试:在完成迭代版本的准确率测试后,可以另外取套与之前不同的测试数据集做准确度校验,用以测试该版本模型是否出现过度拟合的问题(一般偏差5%是标准)
算法内部规律测试:在算法的结果输出中,通常会根据先后顺序输出不同纬度的算法结果,在维度1结果确认后,可以通过算法维度1的结果判定出其他部分维度结果的值区间,以这个标准来测试算法结果的一个基本正确性。
算法工程不论从模型初期的训练数据学习,到后期的模型验证数据,到最后测试介入的测试数据,数据是一直贯穿整个算法的重要资源,数据质量的好坏直接关系着整个算法工程的准确度。对应的,我们测试过程中,除了以往的业务用例脚本维护,更需要增加对算法测试数据的统一管理和维护。
1测试数据管理算法测试数据维护,采用json标准格式进行持久化,并且加以标签进行管理。
标签组成—对应模型 (对应需求 对应bug), 时间, 迭代版本号,(训练、验证、测试)数据,这样可以保证测试过程是可复现的,以便后期进行对比测试。
2 测试数据优化:因为每个测试集的单个case量都很大,难免有影响判断的噪点,这些噪点需要我们每次在使用的时候逐步进行清理,同时根据场景的实际情况,按比例调整测试集中各case的类型比列,以达到测试数据与场景实际情况的吻合,保证测试的准确度

算法测试的重要评价指标
精确率 召回率 f1值 mean-iou
对于数据测试结果有下面4种情况:
TP: 预测为正,实际为正
FP: 预测为正,实际为负
TN:预测为负,实际为负
FN: 预测为负,实际为正
精确率、准确率:Accuracy=(TP+TN)/(TP+TN+FN+FP)
Recall: R = TP/ (TP+FN)
Missing Rate:M=1-R
精准率、查准率: P = TP/ (TP+FP)
召回率、查全率: R = TP/ (TP+FN)
真正例率(同召回率、查全率):TPR = TP/ (TP+FN)
假正例率(False positive rate):FPR =FP/ (FP+TN)
F1-score: 2TP/(2TP + FP + FN)

F-Measure是Precision(准确率)和Recall加权调和平均:

当参数α=1时,就是最常见的F1。因此,F1综合了P和R的结果,当F1较高时则能说明试验方法比较有效。
总体map
多标签图像分类(Multi-label Image Classification)任务中图片的标签不止一个,因此评价不能用普通单标签图像分类的标准,即mean accuracy,该任务采用的是和信息检索中类似的方法—mAP(mean Average Precision)。mAP虽然字面意思和mean accuracy看起来差不多,但是计算方法要繁琐得多,以下是mAP的计算方法:
首先用训练好的模型得到所有测试样本的confidence score,每一类(如car)的confidence score保存到一个文件中(如comp1_cls_test_car.txt)。假设共有20个测试样本,每个的id,confidence score和ground truth label如下:​​

接下来对confidence score排序,得到:

然后计算precision和recall,这两个标准的定义如下:

上图比较直观,圆圈内(true positives + false positives)是我们选出的元素,它对应于分类任务中我们取出的结果,比如对测试样本在训练好的car模型上分类,我们想得到top-5的结果,即:

在这个例子中,true positives就是指第4和第2张图片,false positives就是指第13,19,6张图片。方框内圆圈外的元素(false negatives和true negatives)是相对于方框内的元素而言,在这个例子中,是指confidence score排在top-5之外的元素,即:

其中,false negatives是指第9,16,7,20张图片,true negatives是指第1,18,5,15,10,17,12,14,8,11,3张图片。
那么,这个例子中Precision=2/5=40%,意思是对于car这一类别,我们选定了5个样本,其中正确的有2个,即准确率为40%;Recall=2/6=30%,意思是在所有测试样本中,共有6个car,但是因为我们只召回了2个,所以召回率为30%。
实际多类别分类任务中,我们通常不满足只通过top-5来衡量一个模型的好坏,而是需要知道从top-1到top-N(N是所有测试样本个数,本文中为20)对应的precision和recall。显然随着我们选定的样本越来也多,recall一定会越来越高,而precision整体上会呈下降趋势。把recall当成横坐标,precision当成纵坐标,即可得到常用的precision-recall曲线。这个例子的precision-recall曲线如下:

接下来说说AP的计算,此处参考的是PASCAL VOC CHALLENGE的计算方法。首先设定一组阈值,[0, 0.1, 0.2, …, 1]。然后对于recall大于每一个阈值(比如recall>0.3),我们都会得到一个对应的最大precision。这样,我们就计算出了11个precision。AP即为这11个precision的平均值。这种方法英文叫做11-point interpolated average precision。​
当然PASCAL VOC CHALLENGE自2010年后就换了另一种计算方法。新的计算方法假设这N个样本中有M个正例,那么我们会得到M个recall值(1/M, 2/M, …, M/M),对于每个recall值r,我们可以计算出对应(r’ > r)的最大precision,然后对这M个precision值取平均即得到最后的AP值。计算方法如下:​

相应的Precision-Recall曲线(这条曲线是单调递减的)如下:​

AP衡量的是学出来的模型在每个类别上的好坏,mAP衡量的是学出的模型在所有类别上的好坏,得到AP后mAP的计算就变得很简单了,就是取所有AP的平均值。

模型的检测尺寸 模型的网格尺寸 非极大值抑制的iou阈值
non-max suppression(非最大值抑制)
该算法的原理:
先假设有6个矩形框,根据分类器的类别分类概率大小排序,假设从小到大属于车辆(被检测的目标)的概率分别问:A、B、C、D、E、F
(1)从最大概率 矩形框F开始,分别判断A~E与F的重叠度IOU是否大于某个指定的阀值;
(2)假设B、D与F的重叠度大于指定的阀值,则丢弃B、D,并标记第一个矩形框 F,使我们要保留的
(3)从剩下的矩形框A、C、E中,选择最大概率,假设为E,然后判断A、C与E的重叠度是否大于指定的阀值,假如大于就丢弃A、C,并标记E,是我们保留下来的第二个矩形框
一直重复上述过程,找到所有被保留的矩形框。
框面积占比区间可以用来评测特定范围内的物体检测指标
是否使用soft non max suppression需评测是否启用
计算map时的iou阈值
从列联表引入两个新名词。其一是真正类率(true positive rate ,TPR), 计算公式为
TPR = TP / (TP + FN)
刻画的是分类器所识别出的 正实例占所有正实例的比例。
  另外一个是负正类率(false positive rate, FPR),计算公式为
FPR = FP / (FP + TN)
计算的是分类器错认为正类的负实例占所有负实例的比例。
  还有一个真负类率(True Negative Rate,TNR),也称为specificity,计算公式为
TNR = TN /(FP + TN) = 1 - FPR
ROC(Receiver Operating Characteristic)翻译为"接受者操作特性曲线"。曲线由两个变量1-specificity 和 Sensitivity绘制. 1-specificity=FPR,即负正类率。Sensitivity即是真正类率,TPR(True positive rate),反映了正类覆盖程度。这个组合以1-specificity对sensitivity,即是以代价(costs)对收益(benefits)。
此外,ROC曲线还可以用来计算“均值平均精度”(mean average precision),这是当你通过改变阈值来选择最好的结果时所得到的平均精度(PPV)。
  为了更好地理解ROC曲线,我们使用具体的实例来说明:
  如在医学诊断中,判断有病的样本。那么尽量把有病的揪出来是主要任务,也就是第一个指标TPR,要越高越好。而把没病的样本误诊为有病的,也就是第二个指标FPR,要越低越好。
  不难发现,这两个指标之间是相互制约的。如果某个医生对于有病的症状比较敏感,稍微的小症状都判断为有病,那么他的第一个指标应该会很高,但是第二个指标也就相应地变高。最极端的情况下,他把所有的样本都看做有病,那么第一个指标达到1,第二个指标也为1。
  我们以FPR为横轴,TPR为纵轴,得到如下ROC空间。

我们可以看出,左上角的点(TPR=1,FPR=0),为完美分类,也就是这个医生医术高明,诊断全对。点A(TPR>FPR),医生A的判断大体是正确的。中线上的点B(TPR=FPR),也就是医生B全都是蒙的,蒙对一半,蒙错一半;下半平面的点C(TPR<FPR),这个医生说你有病,那么你很可能没有病,医生C的话我们要反着听,为真庸医。上图中一个阈值,得到一个点。现在我们需要一个独立于阈值的评价指标来衡量这个医生的医术如何,也就是遍历所有的阈值,得到ROC曲线。
  还是一开始的那幅图,假设如下就是某个医生的诊断统计图,直线代表阈值。我们遍历所有的阈值,能够在ROC平面上得到如下的ROC曲线。

曲线距离左上角越近,证明分类器效果越好。

如上,是三条ROC曲线,在0.23处取一条直线。那么,在同样的低FPR=0.23的情况下,红色分类器得到更高的PTR。也就表明,ROC越往上,分类器效果越好。我们用一个标量值AUC来量化它。
AUC值为ROC曲线所覆盖的区域面积,显然,AUC越大,分类器分类效果越好。
  AUC = 1,是完美分类器,采用这个预测模型时,不管设定什么阈值都能得出完美预测。绝大多数预测的场合,不存在完美分类器。
  0.5 < AUC < 1,优于随机猜测。这个分类器(模型)妥善设定阈值的话,能有预测价值。
  AUC = 0.5,跟随机猜测一样(例:丢铜板),模型没有预测价值。
  AUC < 0.5,比随机猜测还差;但只要总是反预测而行,就优于随机猜测。
  AUC的物理意义:假设分类器的输出是样本属于正类的socre(置信度),则AUC的物理意义为,任取一对(正、负)样本,正样本的score大于负样本的score的概率。
第一种方法:AUC为ROC曲线下的面积,那我们直接计算面积可得。面积为一个个小的梯形面积之和。计算的精度与阈值的精度有关。
  第二种方法:根据AUC的物理意义,我们计算正样本score大于负样本的score的概率。取NM(N为正样本数,M为负样本数)个二元组,比较score,最后得到AUC。时间复杂度为O(NM)。
  第三种方法:与第二种方法相似,直接计算正样本score大于负样本的概率。我们首先把所有样本按照score排序,依次用rank表示他们,如最大score的样本,rank=n(n=N+M),其次为n-1。那么对于正样本中rank最大的样本,rank_max,有M-1个其他正样本比他score小,那么就有(rank_max-1)-(M-1)个负样本比他score小。其次为(rank_second-1)-(M-2)。最后我们得到正样本大于负样本的概率为

时间复杂度为O(N+M)。
关于分类和回归模型的各种评估方法
2018-09-24 阅读量 192
常见的机器学习模型分为两种,一种是处理分类任务的模型,一种是预测回归的模型,这两种模型的评估方法也不完全一致,下面就来分别介绍一下两种类型模型的各种评估手段。
一、分类
在介绍方法开始首先了解一下四个概念:
TP即True Positive:预测正确的正样本个数。
TN即True Negative:预测正确的负样本个数。
FP即False Positive:将负样本错误的预测成为正样本的个数。
FN即False Negative:将正样本错误的预测成为负样本的个数。
1.Accuracy(准确率):
简单粗暴的方法,直接用 预测正确的个数/总数 x 100% 即(TP+FP)/(TP+FN+FP+TN),但这个评估方法只适用于平衡数据集,即正负样本个数大致相同,若用于非平衡数据集效果会很差。
2.Precision(精确率):
精确率可以告诉我们正样本预测中正确的预测的百分比,即TP/(TP+FP)
3.Recall(召回率):
召回率可以告诉我们预测正确的正样本个数占总正样本个数的百分比,即TP/(TP+FN),通常,精确率和召回率成反比。
4.F score:
Fscore很好的将精确率P和召回率R结合在一起,计算方式:F1 = 2*(PR/(P+R)),当F1=1时情况最好。
5.ROC曲线:
ROC曲线是根据一系列不同的二分类方式(分界值或决定阈),以真阳性率(灵敏度)为纵坐标,假阳性率(1-特异度)为横坐标绘制的曲线。
其中真阳性率TPR=TP/(TP+FN),假阳性率FPR=FP/(FP+TN)。
6.AUC(area under the curve):
AUC可以说是用单个数字总结模型性能的最好方法,其实就是ROC曲线下方的面积大小。使用AUC值作为评价标准是因为很多时候ROC曲线并不能清晰的说明哪个分类器的效果更好,而作为一个数值,对应AUC更大的分类器效果更好。
二、回归
1.MAE(mean of the absolute value of the errors):
平均绝对误差,是所有单个观测值与算术平均值的偏差的绝对值的平均。平均绝对误差可以避免误差相互抵消的问题,因而可以准确反映实际预测误差的大小。
MAE可以表示预测与实际结果的接近程度。
计算方法:
2.MSE(the mean of the squared value of the errors):
均方误差测量数据点的预测值与实际值之差的平方和。由于平方的关系,负值不会抵消正值,还会放大误差的影响。
计算方法:
3.RMSE(the square root of the mean of the squared errors):
均方根误差更积极地惩罚大错误而不是小错误。这意味着当大的错误特别不受欢迎时,RMSE应该更有用。
计算方法:
4.MAPE(mean absolute percent error):
MAPE(平均绝对误差百分比)以百分比来衡量误差的大小。
计算方法:
5.R^2:
介绍决定系数前首先介绍一下皮尔逊相关系数:
皮尔逊相关系数是用于度量两个变量X和Y之间的相关(线性相关),其值介于-1与1之间。
计算方法:
而决定系数代表可以由自变量X表示的因变量Y的差异(有多少百分比的Y可以被X所解释)。
计算方法:,其中,
R^2 的缺陷:当我们人为的向系统中添加过多的自变量,SSE会减少,从而R^2变大。因此我们采用下面的校正R方,惩罚了过多无意义的自变量。
6.Adjusted R^2:
计算方法:,其中n是样本个数,k是模型中解释变量的总数(不包括常数项)。
首先需要知道的是,机器学习想要能够解决业务中的问题,简单可以分为两个阶段:离线和线上(离线指的是模型未部署到生产环境之前,线上是指模型部署到生产环境之后)。在离线阶段,首先需要先训练一个模型,然后对训练好的模型进行离线评估来了解下模型的性能情况。
我们知道,模型训练的时候使用的数据集是训练集,模型在测试集上的误差近似为泛化误差,而我们更关注的就是泛化误差,所以在离线阶段我们需要解决一个问题,那就是如何将一个数据集 D 划分成训练集 S 和测试集 T ?实际上,离线评估的时候有多种方法可以实现上面的要求,这里介绍一些常用的方法。
留出法(hold-out)是指将数据集 D 划分成两份互斥的数据集,一份作为训练集 S,一份作为测试集 T,在 S 上训练模型,在 T 上评估模型效果。
留出法的优点是简单好实现,但是也会有一些明显的缺点。比如说划分后的训练集和测试集的大小会严重影响模型最终的评估结果。如果说训练集 S 比较大,测试集 T 比较小,那么评估结果的不够稳定准确,可信度较低;如果说训练集 S 比较小,测试集 T 比较大,那么得到的模型很可能与全量数据集 D 得到的模型有很大的差别,这就降低了评估结果的真实性。通常的做法是,将全量数据集的 2/3~4/5的样本作为训练集,剩余样本作为测试集。
除了划分得到的训练集 S 和测试集 T 的数据量会影响评估结果外,它们的数据分布也会影响评估结果,尽量保证训练集 S 和测试集 T 的数据分布一致,避免由于数据划分引入额外的偏差而对最终结果产生影响。举个具体的例子来说明,有一个包含了 1500 条正样本和 1500 条负样本的数据集,现在使用二分类模型来进行自动分类,假设将 1/3 的数据作为测试集,应该使得测试集正负样本均在 500 条左右;如果测试集由 50 条正样本和 950 条负样本组成,那么评估结果将会因为样本分布差异而导致很大的偏差。
因此,考虑到单次留出法的结果往往不够稳定可靠,我们一般会进行多次留出法实验,每次随机划分,最终将多次得到的实验结论进行平均。
但是在实际工作中,如果不分场景,任何时候都是用随机划分数据集可能会导致一些其他的问题,比如说数据泄露(穿越问题)等。
交叉验证法(cross validation)先将数据集 D 划分成 k 分互斥的数据子集,即

,一般每个数据子集的个数基本相近、数据分布基本一致。然后每次用一份数据子集作为测试集,其余的 k-1 份数据子集作为训练集,迭代 k 轮得到 k 个模型,最后将将 k 次的评估结果汇总求平均值得到最终的评估结果。上面说的有点抽象,来看一个 k 为 10 的时候的交叉验证示意图。

很明显,交叉验证评估结果的稳定性和保真性在很大程度上取决于 k 值的大小,所以交叉验证也叫做k 折交叉验证(k-fold cross validation)。k 常用的取值有 5、10 和 20。
假定数据集 D 中包含了 m 个样本,若令 k=m,则得到了交叉验证法中的一个特例:留一法(leave-one-out,简称 LOO)。留一法的优缺点都很明显。训练 m 个模型,每个模型基本上用到了全部的数据,得到的模型与全部数据集 D 得到的模型更接近,并且不再受随机样本划分方式的影响。但是当样本太多时,即 m 很大时,计算成本非常高。
知道了留一法之后,我们再来看下一个新的评估方法:留P法(leave-p-out,简称LPO)。留P法是每次留下 p 个样本作为测试集,而从 m 个元素中选择 p 个元素有中可能,因此它的计算开销远远高于留一法。
由于存在不平衡分类问题(分类问题中样本里包含的标签的数量失衡,比如二分类中样本量包含的正负样本比例为10:1),基于此,存在一种叫做分层 k 折交叉验证法(stratified-k-fold)。这种方法对每个类别进行 k 折划分,使得每份数据子集中各类别的数据分布与完整数据集分布相一致。比如二分类中进行分层5折交叉验证,正样本有 300 条,负样本有 30 条,将数据划分成 5 分,并且每份数据中有 60 条正样本,6 条负样本。
自助法
自助法(bootstrapping)以自主采样(bootstrap sampling)为基础,使用有放回的重复采样的方式进行训练集、测试集的构建。比如为了构建 m 条样本的训练集,每次从数据集 D 中采样放入训练集,然后有放回重新采样,重复 m 次得到 m 条样本的训练集,然后将将没有出现过的样本作为测试集。
很明显,有一些样本在会被重复采样,多次出现在训练集中,而另外一些样本在训练集从未出现。我们可以计算样本从未在训练集中出现的概率。在每次采样
时,每条样本经过 m 次始终没有被采到的概率是

,当 m 无穷大时,取极限可得到。

这也就意味着,当数据量很大时,大约有 36.8% 的样本不会出现在训练集中,也就是这些样本都会作为测试集。
留出法和交叉验证法在训练模型时用的数据都只是整个数据集 D 的一个自己,得到的模型会因为训练集大小不一致导致一定的偏差。而自助法能够更好地解决这个问题。但自助法改变了初始数据集的分布,会引入估计偏差,所以在数据量足够时,一般采用留出法和交叉验证法。
一:经验误差与过拟合 1.无论什么时候,我们都希望得到一个泛化误差(在新样本上的误差)小的学习器。但有时候会出现将训练数据训练的太好,很可能已经把训练样本本身的一些特点当作了所有潜在样本都会具有的一般性质。这就成为过拟合,与其相对的还有欠拟合。 2.导致过拟合最常见的情况是由于学习能力过强大。欠拟合则通常是由于学习能力低下而造成的。过拟合是无法彻底避免的。 3.欠拟合比较容易克服,在决策树学习中扩展分支、在神经网络学习中增加训练轮数。二:评估方法 1.留出法:直接将数据D划分为两个互斥的集合,其中一个作为训练集S,另一个作为测试集T,在S上训练出模型后,用T来评估其测试误差,作为对泛化误差的估计。 (1)需要注意的是:训练/测试集的划分要尽可能保持数据分布的一致性,避免因数据划分过程引入额外的偏差而对最终结果产生影响。 如果从采样的角度来看待数据集的划分过程,则保留类别比例的采样方式通常称为“分层采样”。 (2)另一个需要注意的是,即便在给定训练/测试集的样本比例后,仍存在多种划分方式对初始数据集D进行分割。单次使用留出法得到的估计结果往往不够稳定可靠,在使用留出法的时,一般要采用若干次随机划分、重复进行实验评估后取平均值作为留出法的评估结果。 (3)我们希望评估的是用D训练出的模型的性能,但留出法需划分训练/测试集,就会导致一个问题,如果训练集包含绝大多数样本,这样训练出来可能很接近D,但T比较少,这样评估结果就可能不够稳定准确;若T多一些,这样S与D差别更大,这样训练出来的模型可能有更大差别,从而降低了评估结果的保真性。 (4)结局上一问题没有完美的方案,常见做法是将大约2/3 - 3/4的样本用来训练,剩余样本用来测试。 2.交叉验证法:先将数据集D划分为K个大小相似的互斥之集,每个之集Di都尽可能保持数据分布的一致性,即从D中通过分层采样得到,然后每次用k-1个之集的并集作为训练集,雨下的那个之集作为测试集,这样会得到K组数据。 (1)交叉验证法评估结果的稳定性和保真性在很大程度上取决于k的取值,通常称为K折价差验证,K常取10. (2)若数据集D中有m个样本,若K = m,则得到一个特例:留一法。留一法不受随机样本划分方式的影响。留一法的评估结果往往被认为比较准确,然而,也有缺陷:在数据集比较大时,训练m个模型的计算开销可能难以忍受。 3.自助法:它直接以自助采样发法为基础。给定m个样本的数据集D,我们对它进行采样产生数据集D:每次随机从D中挑选一个样本,拷贝放入D,然后在将样本放回初始数据集D中,使该样本下次还可以被采集到,这个过程重复m次,我们就得到包含m个样本的数据集D`,这就是自助采样的结果。 (1)自助采集法在数据集较小、难以有效划分训练/测试集时很有用,但是,自助法产生的数据集改变了初始数据集的分布,这会引入估计偏差。
从五个方面讲了模型的评估与选择:
  1、经验误差与过拟合
  2、评估方法
  3、性能度量
  4、比较检验
  5、偏差与方差
一、经验误差与过拟合

在分类模型中把分类错误样本数占总样本的比例成为:错误率(error rate)
M个样本、a个样本分类错误,错误率为:E=a/m,精度=1-错误率*100%
在实际的模型中,不可能存在分类错误率为0、分类精度为100%,一旦出现就可以说该模型过拟合了;
  过拟合为NP问题,在训练模型时要注意好过拟合与欠拟合之间的尺度;
二、评估方法

在对模型进行泛化误差评估时通常使用一个测试集(testing set)来测试模型对新样本的判别能力;
通常我们只有个训练集,这时需要把训练集分为训练记与测试集两个部分,并且要保证测试集与训练集尽可能互斥,使得测试样本尽可能未在训练中使用过,从而保证能够真实的评估出模型的泛化能力;
有M个样例数据集:
  怎么把数据集分为训练集S与测试集T呢,下面介绍几种常用的方法:
  1、留出法(hold-out)
留出法:直接将数据集D划分为两个互斥的集合,训练集S与测试集T;

用S训练出模型,用T评估测试误差作为泛化误差的估计;
通常使用2/3~4/5的样本用于训练;
  2、交叉验证法(cross validation):
  交叉验证法:将数据集划分为k个大小相似互斥子集,
  
  每个子集尽可能保持数据分布一致性,每次使用k-1个子集作为训练集,剩下一个作为测试集,这可取得k组训练、测试集进行k次训练与测试取k个测试结果的均值作为模型的评估结果;交叉验证法又称k折交叉验证;
  3、自助法(bootstrapping):
  留出法与交叉验证法由于保留部分样本用于测试,使得实际评估模型使用训练集比D小,这时可能会由于样本规模小导致偏差,从而存在欠拟合;自助法正是不错的解决方案;
  自助法以自助采样为基础,在给定m个样本的数据集D,进行采样产生数据集D1;
  产生方法为:每次随机从D中挑选一个样本,拷贝放入D1中,再将样本放回数据集D,该样本在下次仍然可能被采样到,重复m次得到包含m个样本的数据集D1;
样本在m次采样中不被采样到的概率为:

使用自助法初始数据集中仍然有36.8%样本未被使用过,实际评估模型与期望评估的模型都使用m个训练样本;自助法在训练集小规模不够时比较适合;
三、性能度量

在之前的线性回归中说过梯度下降法、最小二乘法都是求出方差最小时的参数值;
样本集:,其中y为样本的真实值,评估模型性能就是拿模型预测值f(x)与真实的样本值y进行比较;
  均方误差(mean squared error)

错误率:

精度:

1、查准率、查全率、F1
  错误率只能评估模型多多少样本预测错了,如模型是判别垃圾邮件,错误率只能评估模型有多少邮件被判别错了,如要想知道所有垃圾邮件中多少比例被判别出来了,错误率就不能评估了;
  在使用逻辑回归进行二分类预测时,模型预测值与真实的类别组合可分为:
真正例(true positive)、假正例(false positive)、真反例(true negative)、假反例(false negative),分别用TP、FP、TN、FN表示相应样例数,样例总数为:TP+FP+TN+FN;分类结果混淆矩阵;

通常查准率高时查全率低,查全率高时查准率低;
  可以通过P-R曲线来取两者的平衡值,但更常用的使用F1来衡量查准率与查全率;
  F1基于查准率与查全率的调和平均;

四、比较检验

统计假设检验(hypothesis test):对模型进行性能比较时基于假设检验可以推断出测试集上观察到的A比B好则A的泛化性能是否在统计意义上优于B;
  假设检验:假设是对模型泛化错误率分布的某种判断与猜想,实际中并不知道泛化错误率,通常使用测试错误率推出泛化错误率分布;
  Friedman检验与Nemenyi后续检验:交叉验证、McNamar验证为在一个数据集上对模型进行比较,Friedman校验在一组数据集上对多个模型进行比较
五、偏差与方差

偏差-方差分解(bias-variance decomposition)为解释模型泛化性能的工具;为什么模型具有如此泛化性能;
  期望输出与真实标记的差别称为偏差(bias),期望值与实际值的偏离程度;
  方差(variance):衡量相同样本数目,不同训练集 预测值的变化程度离期望值越远方差越大;
  泛化误差可分解为:偏差、方差、噪声之和
  偏差较大导致欠拟合 、过于接近训练集,不够泛化,方差过大导致过拟合;

二、算法测试的痛点
目前我负责的是广告算法的质量保障工作,测试开发比将近1:20,按目前的情况来看,开发数量还有增大的趋势。整个团队并行在推进的项目可能在10个左右,总体来说测试资源非常紧张。
一方面,作为一个测试开发同学,在兼顾业务保障的同时,往往还需要保障多个组内外平台及工具的开发或者其他流程沟通推动的相关工作。另一方面,我们的开发同学其实都有较高的质量意识,有强烈的自测诉求,但是缺乏高效的自测手段。
因此,如何将项目测试过程固化沉淀、形成自动化保障体系,赋能开发自测,释放测试自己的资源,是当前不得不进行的一个事情。
三、算法业务特点

  1. 与网站应用/工程测试的比较
    算法因为其自身的特殊性,大部分广告算法实际上是对已有算法的改进和结合,涉及的技术包括点击率预估、搜索、推荐、图、NLP、图像等。以搜索广告为例,在流量变现方面,它的主要优化方向集中在两个方面:排序(点击率预估、出价优化)和匹配(召回、相关性)。从工程的角度来看,大部分项目对工程结果的影响是广告商品的排序不同或是召回商品的内容和数量变化。不同于传统的网站应用/工程测试(可能会有不断的新功能的产出),相对来说,如果抛开算法效果测试来看,在功能层面,算法的工程结果是比较稳定的。
  2. 与传统接口测试的比较
    算法的开发主要包含两部分:离线任务开发和在线工程开发。离线任务开发包括数据分析、业务建模、特征提取、模型训练、模型调参等。在线工程开发主要是将算法模型服务化,以接口的形式提供给外部去调用。从集成测试的角度来看,算法的功能测试可以归为“接口测试”的范畴。
    传统的接口测试,除了校验接口的返回状态码是否正确外,比较容易根据业务的处理逻辑对返回的结果字段进行校验。比如查询类的get接口,我们可以通过接口的返回结果与数据库的结果进行比较;比如提交类的post接口,我们也可以通过调用接口,根据传入的参数的不同,结合对系统的不同处理逻辑的了解以及数据库得到一个确定的预期返回。
    与之相对的,算法的接口没有一个确定的预期。同一个接口,传入完全相同的参数,在不同的时间段调用可能会返回不同的结果。因为很多时候,算法的接口返回是一个概率值,如“某个商品被某个用户点击的概率”,影响算法返回结果的因素,除了服务代码之外,还包括索引的版本、特征词典的版本。特征词典的版本往往是日更新,而索引包含日级别的全量更新和实时的增量更新。虽然算法的功能相对稳定,但是算法的不断调整,实际上是对同一批接口的不断调整。
  3. 当前的主要测试手段
    目前算法工程质量的保障,主要还是集中在在线部分的测试。离线部分因为其时效性,往往会有T-1、T-2的延迟,更多的采用监控保证其质量。而对于在线部分的测试,主要的保障方式包括:
    CodeReview
    功能验证
    线上请求日志回放
    性能测试
    稳定性测试
    线上请求日志回放主要是基于线上的access_log日志,分别在新版本的环境和base环境进行请求回放,比较返回结果各个字段值的差异。是一种对功能验证的补充,避免人为地构造数据可能无法覆盖线上的所有场景。
    四、算法自动化测试的挑战
    前面说了,当前算法自动化测试的目标主要是想解决工程测试方面的效率问题,释放测试在工程质量保障方面的人力投入,因此本文主要是针对算法的“工程”测试自动化方面的一些思考。
    理想的自动化测试链路应该是:
    代码提交
    静态代码检查
    单元测试
    代码打包
    词典更新
    测试环境部署
    索引更新
    功能冒烟回归
    线上日志回放
    变更代码覆盖率统计
    模块压测
    集成压测
    测试报告
    整条链路的回归时间控制在一个小时以内,过程中既有中间测试结果的即时透出,也有最终报告的展现。下面是对于自动化测试链路的各个环节存在的一些挑战的思考:
  4. 代码提交和管理
    目前的开发代码大部分都是用git去管理,但是并没有一个明确且严格遵守的代码分支管理策略,且没有打Tag的习惯。算法和引擎开发同学有不同的开发习惯:
    算法的同学开发习惯是在线上分支master上拉一个feature分支,用于新版本的开发,然后待feature全量发布上线后再将feature合回master。
    引擎的同学开发习惯是直接将代码合到master上提测。
    从测试的角度来看,这两种都存在一些问题:
    第一种方式,虽然保证了master一直是稳定可用的版本,但是当有多个同学并行在此项目上开发多个feature时候,在多个feature前后相继需要发布的时候,可能都是基于旧的master分支拉出的代码,如何相互合并是一个问题。且当feature分支上线后,再合并到master,如果出现conflict,无法保证解决conflict后的master代码与线上的代码一致。
    第二种方式,能保证提测的代码是合并conflict后的最新代码,同时也是测试通过后上线的真实代码。但是会影响到master分支的稳定性,当线上出现一些问题,需要紧急回滚的时候可能会出现问题。
    在分支管理上不妨可以参考一些优秀开源项目(如 apache/spark、apache/flink、tensorflow/tensorflow等)的普遍做法:
    对于只有一个大版本的代码,只维护一条master分支。通常大部分项目只有一个大版本,一些特殊的项目可能会存在多个大版本,如SP有3.0版本和2.0版本在线上同时使用,则相应维护两个分支。
    开发时,从master拉代码到本地进行开发,本地自测完成后合代码进master,及时解决冲突。
    提测时,以tag的方式提测。比如目前要上线的RS服务版本为v1.0,则第一次提测在master分支上打tag名v1.0.1。如果测试不通过,修改代码后重新提测,打tag名v1.0.2,依次类推。
    最终测试通过后,打一个没有后缀的tag:v1.0,表明是测试通过的可用版本。
    最终上线基于tag:v1.0进行发布。
    当线上验证通过,且运行稳定后,可以在上线一周后将v1.0.1、v1.0.2……等过程中的提测tag删除,只保留最后上线的稳定版本tag:v1.0。
    用同样的办法上线v2.0时,如果发生线上问题,需要回滚,可以立即回滚到tag:v1.0。
  5. 静态代码检查
    静态代码检查是一项低投入高回报的工程,从集团推出Java开发规约可见其重要性。“低投入”是因为只要一次配置,可以无限次的检查。“高回报”是它不仅可以帮我们纠正代码书写规范的一些问题,还可以发现一些诸如“空指针异常”、“I/O及数据库连接资源使用后未关闭”、“冗余变量”等一些重大的工程风险。
    对于Java,有集团规约插件、PMD、CheckStyle、FindBugs等可以很方便地使用,而算法的开发则大多是使用C++,相应的静态代码检查工具还需要调研。目前了解到的有cppcheck、clint等。
  6. 单元测试
    目前RS服务已经使用了Google C++单元测试框架“Gtest”进行单元测试的编写,但是还处于刚刚起步状态,这部分需要去推动开发好好地补充测试用例,给出单元测试的覆盖情况统计,不让单测仅仅是流于形式。后续需要将单测的通过率和覆盖率数据透出到报告中。
  7. 算法打包部署
    不同于Java工程,基于Maven或Gradle,可以通过命令行很方便地实现整个工程“一键编译打包”,而且目前集团的Java应用大都已经可以基于Aone平台很方便地发布。而算法的发布因为其索引和词典的特殊存在,无法通过Aone很好地集成。算法包的发布包含三块内容:so文件(代码编译后的结果)、conf(配置)、data(词典),过程通常包含RPM打包和词典更新两个步骤,其中RPM包会包含so文件、conf和部分data。目前不同算法模块发布的平台各异,包括suez、drogo、autoweb等等,如何去规整并实现测试环境的自动发布是一项挑战。同时,在环境部署完毕后,我们还要保证新版本环境和base环境索引的一致性,或者当索引的构建也发生改变时,需要保证构建索引的商品库的一致性。
  8. 功能冒烟回归
    这一块需要对每个算法服务的使用场景进行具体的分析和细致的梳理,比如RS服务,它有来自bp、rsproxy、dump、rspe等不同的调用方的请求,每个调用方的可能的使用场景都需要一一梳理,从而提取出必要的冒烟接口测试用例。前面提到过算法接口每次调用的返回值可能会变化,
    因此这部分用例的意义在于检测服务接口是否针对各个场景有正常的返回,返回结果的各个字段的值是否在一个合理范围内。
  9. 线上日志回放
    相比人为地构造数据,和AB切流实验,线上日志回放是一项相对覆盖面广且高效低成本的方式,通过自动化地回放大量的日志请求,我们可以发现新的算法版本是否对某一些不该产生变化的结果字段产生了影响,也可以发现一部分算法的效果问题:如出价优化时,整体价格是调高了还是调低了。
  10. (变更)代码覆盖率统计
    Java中有成熟的Jacoco可以用于工程覆盖率(深入到行、方法、类等不同级别)的统计,结合git的diff日志,还可以去挖掘工程两个不同版本间变化的代码的覆盖情况。而C++的覆盖率统计方式仍需要调研。
  11. 性能压测
    性能压测包括模块压测和集成压测。模块压测指的是如RS这样单个服务的压测,而集成压测是从ha3端或者SP端去看整体性能的情况。通常,性能的测试都是需要有一定经验的测试人员基于测试分析来制定详细的压测方案并予以执行和结构分析。但是,之前提过,算法的项目发布有很大的共性,正是基于这些共性,使得自动化压测成为了可能。要实现自动化地压测,除了要编写通用的压测脚本,还要有稳定的施压环境作为持续集成的节点机器。同时还需要去实现自动化地采集被压环境的系统性能变化数据(如cpu、内存、磁盘i/o、网卡性能等)。
  12. 报告生成
    要实现最终报告的自动化生成,需要涉及到整条链路的各个环节数据的自动采集,以及各项数据呈现和报表的展示,透出和提炼出对研发过程有帮助的数据,给出相应地测试结论。
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页