pytorch入门首选,苏黎世博士龙曲良老师手把手带你敲代码,全集150让你掌握


划分成train和test测试集的意义是:学习的成果很好可能是对于这些图片只是记忆,对于新的图片识别可能效果并不是很好
首先清零梯度optimizer.zero_grad()
然后再计算梯度loss.backword()
然后再更新梯度optimizer.step()
衡量性能好不好并不是靠loss,而是看准确度
python和pytorch的数据类型的不同

tensor的不同维度表示不同的数据类型。一个int在pytroch中是dim=0,而一个数组在pytorch中是dim=2
pytroch中如何表达string?


这几类tensor类型用的多,bytetensor是用来比较两个tensor的
tensor既可以放在cpu上也可以放在gpu上面
pytorch的数据放在不同位置类型可能不一样
randn初始化一个随机的正态分布
.type()可以返回其类型
参数合法化的检验用isinstance

数据放tensor和放cuda是两种不同的类型,如下图

标量:
用troch.tensor()方法

主要用于计算loss时候,loss计算出来一般是标量

shape和size的区别是,size是函数,需要运用()来调用它,shpae是个成员,不需要()。
数学的向量在tensor里叫张量

torch.tensor()括号里面代[],生成dim为1的
张量,其张量的长度可以为1~n。

如果要给定dim=1,size=1直接用上图的floattensor,()里2是表示dim=1,size=2
dim=1主要用于bias,linear input
dim是指维度,行列表2维,指的size长度
而size/shape表示整个形状【2,2】,tensor的具体形状
tensor指的是具体数据
dim为2
带有batch的linear的输入
dim=3
不再使用随机的正太分布,而是随机的均匀分布。
用list可以将shape直接转换成list形式
三维适合RNN的编码
dim=4
四维适合图片
【b,c,h,w】batch,channle,height,weigth
想要得到具体的所占内存的大小,用numel来表示,即number of element
A.dim()显示a的维度
如何创建 一个tensor
1,运用np作为载体

2
小写tensor接收现成的数据,大写Tensor接收shape,大写也可以接收现成的数据,数据必须要以一个list方式,第四不要用,容易混淆

使用empty函数会随机初始化数据,数据会很不规则。

以list给参数是直接喂参数,不建议。
未初始化tensor的问题
如果出现了torch.nan,或则inf,可以去看看是不是tensor未初始化。
创建完后一定要写入数据

没有设置的话,使用tensor和Tensor都是使用内置默认的
使用随机初始化
rand是指随机在0~1之间产生数值,不包括1,rand_like()是直接接收tensor,然后根据给的tensor的shape来rand一个

randint(1,10)可以取1但取不到10
有三个参数,第三个为shape
正态分布
randn均值为0方差为1

normal调节均值和方差需要将数值打平然后再reshape我们想要的形状

如果想要使得内部数据全为某个数,使用full函数【】中啥也不写则生成一个dim=0的标量,【1】则是dim=1的size=1
设置一个等差数列的tensor
第三个参数为等差数列的阶梯

range函数不建议使用

linspace的是包含右边的(1,10)是0~10包含左右边界,step是切割成几分的等差数列
而logspace是以step为底数的幂函数

全一的参数.ones
全零的参数.zeros
产生对角线为1的.eye
这三个也有_like的用法,输入一个现有的tensor,自动识别shape填充

randperm产生随机打散的数,

shuffle洗牌的意思
索引和切片
索引会从最左边开始索引
scalar标量的意思dim=1


-1:
正常排序有两种,第一种为0,1,2第二种为-3,-2,-1,所以-1到末尾只有1个通道

两个冒号表示隔行
2,用索引号进行采样index_select()
第一个参数为维度,第二个参数为所选维度的想要数据的索引号
第二个中的[0,2]list不能直接给,而是要先转化成tensor

...表示后面都取,可以省略很多的:和,

使用mask会使得数据被打平, 然后用masked_select()可以找到相应的数据,shape一下发现dim变了,变成1了

take函数也会把数据给打平,与index_select不同,index_select会先找维度,而take是打平再找
维度变换
reshape函数可以在保持tensor大小不变的情况下任意转化成一个shape
view和reshape是可以通用的
view会把额外的信息给丢失掉,所以要额外存储,不然还原的数据会被污染
view的size要和原来的一样,不然也会报错
unsqueeze如何将数据展开
unsqueeze最好不用-1来插入新维度,复数是之后插入,正数来表示已经可以满足需求了


维度变换需要变成一样的,维度变换在实际例子中应用的十分广泛
squeeze
给定一个参数,假如不给的话,会把能删减的全删掉。
只会删减那维度为1,不为1也不会报错,只会返回原来的那个
expend 不会增加数据,而repeat会增加数据
更推荐expend,expend一般不会增加数据
能使用expend的前提是dim一致
扩展是仅限于原来的维度为1,若从3->n会报错,若输入的是-1,则是保持此维度不变

repeat
repeat的参数是指在维度上拷贝的次数

.t()矩阵转置操作
转置只能适用于dim=2的
transpose
数据的维度顺序必须和存储顺序一致
因为转置之后数据之间打乱了,为了使其一致,所以使用contiguous函数,使数据重新变成连续
view会导致维度顺序关系变模糊,所以需要人为跟踪

permute
【b,h,w,c】为numpy存储图片的格式
permute只需要将原来的维度索引值输入就行

permute也会打乱数据,permute可以实现要多次tanspose的操作
broadcasting(自动扩张)
broadcasting的具体实施
1.如果没有维度就会在前面插入新的维度
2.然后再将此维度扩展成相同大小,不需要手动调用
一般将后面理解成大维度
.expend_as(A)自动扩张成a的size
broadcast作用,可以完成自动扩张,又不需要手动操作的要求
即小维度指定,大维度随意
broadcast的规则

拼接与拆分

split是在长度上的拆分,而chunk是以数量上的拆分
cat
第一个参数为指定两个tensor合并,而第二个参数指定tensor在哪个维度上合并

stack
stack回创建一个新的维度
新添加了一个维度后面的维度保持一致
split and chunk
1.根据数量来拆分chunk
2.长度split
(以下为按长度拆分)split
第一个参数为每个单元的长度,第二个参数为操作的维度

若想差分为不同的dim,则用list将想要的维度抱起来比如【3,1,1】要保证里面数加起来与dim里的shape一致

(以下为按数量拆分)chunk

这个简单
数学运算
矩阵的加减乘除
add/sub/mul/div

all 是检验所有位置是否相等
矩阵相乘:matmul

@是numpy里面的matmul形式,用的少

多维的矩阵运算
假如是四维,会使得前面两维不变,然后后面两维度相乘。
假如前面维度有出入,会自动使用boradcast保持一致

若不一样的地方没1则会报错。
sqrt为平方根
rsqrt为平方根的倒数
一般建议使用pow和**

exp函数是以e为底的函数
log默认是以e为底的即ln,若要以2为底则用log2
近似值
floor为数取小值,即3.14取3
ceil取大
trunc裁剪,裁成整数部分
frac裁剪,裁成小数部分
round为四舍五入的方法

用的多的还是clamp
梯度裁剪:gradient clipping
如果网路中出现了不稳定的时候,一定要看下梯度的模 w.grad.norm(2)一般在10左右正常
.grad(10)裁最小值,小于10的为10
或者给最大最小值,在之间不变,其余靠近变,为最大或者最小

for w in []
clamp (w.grad,10)
对w的grad进行限幅
统计属性的方法

norm 范数,用的最常见的
norm指的范数而不是normalize
batch_norm表示正则化
vector norm 和 matrix norm的区别


如果只设一个参数,则按上上图算,如果给定了维度,则在给定的维度上算,然后输入一个list
对某个维度进行norm某个维度的数据就要消掉(有点绕,建议重新看视频)
max最大,min最小,mean均值,prod累乘
sum求和
argmax是返回max的索引,argmin是。。。
min,max这些不给参数,会将其打平再返回
也可以给个维度参数

dim 和keepdim再统计里面起的作用
在max 和min输入参数为dim时候,回复返回两个tensor,一个数据一个索引,因为输入dim所在的维度时候,那个维度会消失,为了使得维度一致不丢失,则可使用keepdim参数

假如不keepdim则可以自己加个unsqueeze
top-k or k-th(kthvalue)
top-k比max能返回更多的数据,假定输入参数topk(2,dim=,largest=)则会返回概率最大的前二
假如想要最小,则将largest参数默认改成false就行
kthvalue 默认为最小的,而且只能设置为小,
会返回第k小的值和第k小的索引号
compare 比较运算的操作
对于pytorch没有t or f的类型,也没有string类型,只有0和1。
gt就是>的意思
a>0等价于torch.gt(a,0)
用eq函数进行比较会返回0和1
若用equal则会返回t or f
高阶操作
where函数
第一个参数为condition,第二个第三个为来源

假如要赋值可能要两个for语句,python语句用于cpu,很慢,所以用where语句能使得赋值语句高度并行化,快很多
gather
gather语句就是一个查表的过程
gather第一个参数是表,第二个参数是维度,在哪个维度上查表,第三个参数为需要查表的项的索引

使用gather能够快速的将相对标签转化成全局标签

什么是梯度
导数和偏微分都是标量,方向指定,长度为大小
而梯度是个向量,将所有的偏微分看成一个向量来理解
梯度表示什么呢?
向量的方向代表着梯度的方向,向量的模代表着向量的长度,代表着在这个方向增长的速率。
鞍点,是一个深度学习时候很容易可能卡住的点
对于深度学习初始化是十分重要的
激活函数及其梯度
激活函数其目的是为了模拟神经元的机制
使用sig'moid函数可能会导致梯度弥散

tanh(x)函数

relu函数

relu函数有着先天的优势,函数的梯度计算起来非常的简单,而且梯度始终是一,没有梯度弥散梯度爆炸的情况
一般都是使用relu函数
loss即其梯度
均方差:mean squared error

grad 函数,第一个参数为y,第二个参数为【x1,x2,x3】等,看你想要求导的自变量个数

想要求导,得先要更新求导得指示

还得更新一下动态图,因为pytroch是计算一步算一步
或者使用backward函数,backword函数会自动从后往前传播,会计算需要计算得grad。
w.norm返回得是一个tensor得L2norm
w.gard.norm是返回w梯度的norm

返回的是这样的一个list
softmax
满足所有属性属于0-1,然后所有属性概率和为1
适用于多分类的情况
会使得两个数据之间的拉距拉大


使用一次backward时候要跟新一下,不然会报错设置retain_graph=True

不管是用grad函数还是backward函数,输入第一个参数应该dim=1长度为一,dim为0的scalar。输入有三个量是错的,所以是p[1]
也验证了softmax的那个函数,i=j是正的,其他是负的
单层感知机

上标0表示输入层,0-n表示0层有n+1个节点
w的上标1表示第一层,橙色部分一起为第一层,下标第一个为上一层的第i个节点,第二个参数为这一层的第几号节点
又将求和设置成另外x,上标表示第一层,下标为第几号节点,然后sigmoid函数设成O,上标下标设置与求和x的意思一致

多层感知机
只跟输入节点j和输出节点k有关

链式法则

反向传播方式

假如隐含层的话,其隐含层还包含了一个激活函数,所以不是xIj而是OIj

公式前面部分只与Ok有关,设为得而塔k
简化得公式,得到了一个输入与一个项的乘积

从以上推导可以知道,只要得到这一层的地而他信息,和上一层输入信息Oi地信息,就可以得到梯度信息,一层知道了就可以推导下一次层,
2D函数的优化实例

使用这个函数就是可以直接实现下面这个作用

只要调用一次这个step就可以更新一次

做的时候不要随意初始化,初始化是十分重要的
画图可以用这个知识!
logistic regression
分类问题并不会直接优化accuracy
为什么不能最大准确率呢?
1.因为是非零即1的分类问题,当w优化使得概率朝着0.5前进时候,会使得梯度为零,0.4->0.45,其分类仍未0,所以有梯度为0得情况
2.假如概率从0.499->0.51,使得梯度突然改变,使得梯度函数不连续了,(或者为非常大得gradient)造成training不稳定,所以一般不会使用accuracy进行training。
使用了softmax函数
cross entropy交叉熵
熵就是不确定性
也是算一种惊喜度,惊喜度越大,其熵越小。
熵比较大,惊喜度就很小。
entropy衡量的是一个分布的不稳定度,而cross entropy衡量的是两个分布的不稳定度,
kl散度
假设两个分布几乎完全重合,其两个kl散度接近于零

当p=q时候,kl散度为0

当用于分类问题时one-hot,H(p)=0
H(p,q)=kl散度,所以就是优化pq就是优化pq的kl散度
kl是衡量两个分布的重叠情况
假如q(x,y)=0即下,y的kl散度为0,即xy分布很吻合,达到了优化的目的
cross entropy 更适合分类问题, sigmoid+MSE容易梯度弥散,出现sigmoid出现饱和,收敛也慢,cross entropy的梯度信息更大,收敛的更快。
虽然mes并不适合分类问题,但求其梯度很简单,在一些前沿的算法,可能有些很好的效果。

一个 crosss_entropy = softmax + log+null_loss
假如使用cross entropy 的话,第一个参数要用logits,不要用pred,因为cross entropy里含打包了sofemax和log环节,再用pred会使得数据太小
多分类问题
对于linear在pytorch中 第一个参数为 ch-out
第二个参数为ch-in,w,b都是需要梯度信息的,所以后面一定要加requires_grad=True

logits一般是没有经过激活函数的
nn.CrossEntropyLoss()和F.CrossEntropy功能效果一样。
初始化真的很重要,凯明的初始化不错,对你的算法影响很大,有些算法不是你的方法不好,可能是你的初始化不行,导致结果不太理想
torch.nn.init.kaiming_normal_()
激活函数与gpu加速
Tanh是sigmoid的放缩与平移得到的,一般用于rnn循环神经网络里面的
relu函数一般不会出现啥梯度弥散的情况,但还是也会有,比如x<0的时候
所以出现了新的函数leaky relu


使用就在relu上加个leaky就行了,默认的参数阿拉法就是0.02的样子
由于relu在0点处的地方梯度不连续,然后工程上搞了个selu函数,是两个函数的合并
一个relu函数和一个部分指数函数的合并,

softplus也是一个对relu函数的优化,使得在0
附件有个平滑的处理
用.to方法更好,不要用.cuda方法

对模块进行搬运,模块也会原地更新,对tensor进行搬运,返还值会不一样,比如data2=data.to()其data2和data不一样
产生两个tensor,一个gpu一个cpu
测试
当train的过多,dl会只记住浅层的东西,就会导致其泛化能力很弱
并不是训练时间越长越好、gpu越强越好。
数据量和架构是核心问题。
argmax是返回的最大值的索引号
如何进行test?
train一个batch做一次test或则train一个epoch做一个test
过拟合and欠拟合
Underfitting,train和test效果都不好,所以可以尝试增加trainning的层数,模型的复杂度可能不够
overfitting,层数过多,模型的复杂程度过高,导致train 的时候会过分拟合穿过每个点,会导致train的性能很好但在test时候会很不好
具体的图像大概如下

现实中主要是overfitting!
数据集有限就会overfittting
怎么检测overfitting?
将一个数据集划分成两个dataset
如果我们发现在train set 上表现很好,但在test set 上表现很差就说明是overfitting了
test主要是为了选取合适的参数,提前终止trainning,防止过拟合
一般最简单的行为是选取这个testaccrancy最高的点停止掉,选取最高点的状态

观察到训练最好的check point 保存下来,等你train发现已经overfitting了,然后即用最好的作为参数去预测
更常用的是划分成三部分,trian set、 val set、test set,这是test不是用来做模型参数的挑选了,而现在那部分功能交给了val set部分,而test set是交给客户验收时候用,防止你作弊
做research的时候会拿到一个完完整整的数据集,而做产品的时候或比赛会有一部分set不会给你
如何防止、减轻overfitting。regularization(正则化)
occam’s razor 欧卡姆的剃刀原理
如果不是必要的东西就不要使用,选最小的最可能的参数量

以上为减轻过拟合的方法


后面一项为范数
为了能够体现performence,使得低维的参数能比较模拟数据分布,高维的参数很小很小,使得本来有个7次方的,经过regularization以后迫使高维的参数接近0,使得高维退化成低维,使得得到更低复杂程度的曲线,在其他里面也叫weight decay 迫使w,b参数接近于零
最常用的是l2 regularuzation

lambda是个超参数,需要人为的调整

只需要设置weight_decay参数 这个参数即是lambda参数。(l2)
注意:设置这个参数会使网络的复杂程度降低很多,所以要在overfitting再用,没有overfitting就用,会使得性能急剧下降。
l1现在pytorch没有很好的方法

卷积
线性层就是全连接层的线性部分~
linear是没有包含激活函数得到部分
第一层叫输入层,是不计算在层数内的,而输出层是包含在内的
卷积可以大大减少参数的数量,运用的原理主要是人类的视觉处理效果(参数共享)
相乘再累加的操作就叫卷积

卷积的定义如图上
这是个连续的,而计算机是离散的,所以计算机的卷积即积分,就是一个相乘累加
使用的卷积核不同,得到的图像也不一样
卷积核理解为一个观察的视角(人)
假如你想看人脸,就会包含人脸的数据,其他数据会模糊隐去,换个视角又会不一样,形成新的feature map
paddding不需要太多,生成的feature map最多和原来大小一样就行
kernel的通道的数量是指的卷积核的数量,即视角的个数

kernel的数必须和input-channel对应起来
而这个对应起来的不叫3个kernel 是叫一个channnel 即这个channel中kernel和 input的通道数一一对应

如上图的multi-k中的16 ,是指有十六个kernel通道,3是指每个kernel通道有3个核和input的三个通道一一对应
每个kernel都带有一个偏置,所以bias=16
一般kernel的维度主要指在第一个参数那个,指的多少个视角
卷积核有几种叫法
1。filter
2.kernel
3.weight
如果不是很明确需要干什么,不推荐直接调用forward函数。
用.forward会使用不了一些pytroch自带的功能。
池化pooling
pooling:下采样,会把feature map变小的操作,与缩小操作不太一样
upsample:上采样,与放大操作很像
relu
maxpooling:在一个卷积核里中取最大值

avg ppooling:求和然后取平均值
uosample:简单复制最近元素的数值
函数为F.interpolate
一般不再使用F.upsample了

第二个参数scale_factor 为放大的倍数,第三个参数为模式,具体看一下api文档
channel
卷积神经网络最基本的单元为:
conv2d----batch normalization-----maxpooling----relu
后面三个的顺序可以变换,颠倒也是没有关系,看你自己了

如果将inplace设置成ture的话,就会将x‘的内存空间用x的,将会节省很多内存。
batch normlazation
在sigmoid函数的输入范围不在-4~4之内的话,会使得梯度十分十分小,很长时间参数得不到更新,出现梯度弥散的情况
所以就想找有没有一个方式使得输入的参数在0附近,而且不大,这就是batch normlaztion的作用。

就如上图,如果w1,w2比较相近,就可以在初始化时候,能够平均的更快捷稳定的到达极值
image normalization
对于彩色图片,对于rgb的通道进行normalization,将其均值为0,先是统计rgb的平均,在将其均值弄成0,具体操作如下

主要有四种,看你在哪个维度操作了

经典的卷积神经网络
AlexNet
2012的图像识别的冠军, AlexNet引进了max pooling,还引进了relu这个激活函数,引用了dropout
VGG
vgg有6个版本,包含vgg11,16,19等等
1×1的convolution实现了维度的改变
GoogLe net
2014 年 22层,有多个kernel,每个kernel代表的视野不同
并不能简单堆叠更多的层,层越多,training的难度变大。网络变深结构变复杂
深度残差网络Resnet
一般网络结构堆叠增加的时候,网络性能并未提升,而training、test性能会降低,
由于网络的特性是一层一层传递的嘛,其导致误差积累,导致梯度弥散和爆炸,一般是梯度弥散,导致后面的梯度接近于0,导致train半天还没啥效果

增加了一个shortcut的连接,短路连接,是隔几个加一个shortcut,分成很多个小单元

以前是不停的堆叠这个网络来形成更深层的网络

resnet多用上述单元
即网络有一个选择权,可以选择退化成更简单的层,在其他层训练完了,会考虑将shortcut那部分加入进去,看能不能训练的更好
加了shortcut会使得损失函数曲面更光滑使得train的更好更快
vgg的计算量很大
densenet
后面的每一层都有机会和前面一层的接触
而densenet并不是简单的相加,而是concat,会使得channel不断地加大,所以要设计的十分巧妙,以至于不会使得最后cahnel过多
nn.Module
所有网络层的父类,要去继承

第一层关系的才是children,其他统称modules(包括children)
to(device)

net的值返回与不返回都是一致的。而不像一个tensor那样,一个在cpu一个在gpu
save and load

返回状态然后保存至ckpt.mdl文件夹

开始train时候要检查有没有ckpoint,这样就可以不用从头开始train了
train/test 方便的切换状态
对于dropout、batchnormlization 来说,其train和test的方式不一样、
需要对nn的状态进行切换,假如每个类都是继承nn的话,用下面操作可以直接转入
test没有test方法,只有eval方法

nn.sequential里面只能放类,不能放函数
所以要自己定义一些类 reshape,flatten

那个flatten可以直接抄上面的,这样就可以写在一个seq里面 不用分两个了

按道理要写requ的,初始化定义torch tenseor 是不会加到nn.parameter。如果不自动加,调用nn.parameter函数就不能得到那个参数的了,会使得优化不方便
nn自带了一个nn.Parameter函数可以封装数据,包装tensor自动的加入到nn.parameter上去,自动的被SGD优化。
上面没写requ是因为nn.Parameter能够自动的将梯度信息设置为ture
假如此处只用了torch.randn(),其w。b参数不会传入nn.parameter里面去,就不能将数据传入optimizer
注意parameter的大小写

此处的mylinear类和liner类功能一模一样,实现了自己的底层 操作
数据增强
big data
可以很好的预防过拟合
数据增强:即对数据进行多样化处理,然后得到更多的数据

数据少的时候要减轻网络的参数量(capacity)
regularization:正则化,迫使网络一些高阶参数接近于0
data argumentation:意思是对于原来的一些数据进行增强,对照片进行变换,比如旋转、裁剪、调色、加噪声。效果比真实的同数量的差很多,但是一般比原来没有变换的情况好很多

四种比较常用的方法
flip:翻转的意思

具体操作如上。random是随机的一i是,即这一步有可能做有可能不做
rotate:旋转操作

主要有两个方式,第一个为给定最大的旋转角度,(15)即旋转的角度为-15---15,或者如第二种,给定几个固定的旋转角度【0,90,180,270】
scale:缩放
缩放是从中心点往外缩放,

使用resize函数

里面参数为【32,32】是一个参数
crop part

随机裁剪的操作
noise:用的比较小,加入些高斯白噪声啥的
使用数据增强技术可能会使得样本数量趋于很大,但是对于训练来说帮助有,但没想的那么多,variance太小。
神经网络实战
与sigmoid函数之前的叫logits

CELoss包含了softmax和loss的计算

用于告诉torch需要backprop,不会打乱计算图
stride 对于图片维度的降低有着很好的效果
设计时候,一般取得hw越来越小,ch越来越大效果会比较好
embedding:嵌入
时间序列表示方法
one-hot 是非常稀疏的,占用了大量的空间
特性是:稀疏的,高纬度的,语义相似性
导致其用的很少
rnn原理
使用weight sharing的方式来实现上下语境的共享
类似kernel
使用一个新的存储单元consistent memory,用来连接前后语境
rnn和cnn的区别就是cnn只会一股脑往前传,而rnn的话会有一个核心变量ht,一个类似于语境信息,会自我跟新
ht(memery)
RNN主要用的激活函数为tanh
rnn中使用了权值共享,所以rnn的参数量并不大

rnn layer
不仅实现了权值的共享,还实现了语境的贯穿,考虑了上一步的输入。
有个h的单元,类似于记忆,会把语句的信息存储下来
rnn 的layer使用

h是最后一个时间的所有memery的状态
out是指所有时间点上最后一个memery的状态
84 时间序列预测实战
为什么会出现梯度弥散和梯度爆炸的现象呢?、
原因是求梯度时候有个whh的k次方,当层数变高时候,就会出现此现象。

梯度爆炸:loss突然上升。
解决方法:做梯度的clipping(不是weight的)
梯度弥散:lstm方法
long short—trem memory 长短时记忆
short-term memory 是指rnn的h只是短时的语境记忆,现在加个long,加长记忆
rnn的模式

lstm的模式


lstm为什么能有效的解决梯度弥散的问题,主要是由rnn的连乘,换成了四项的加法,对于rnn出现的很多的很大很小梯度的现象有着制约的能力

有效避免了wr的k次方,还有个类似于retnet的shortcut的通道,也是一种解释。
数据预处理的基本流程

106 迁移学习
简单来说就是用以前学习的知识来弄现在的模型~不是从一个随机初始化的角度开始的,从良好的性能开始