简 介: 对于在深度学习中的两个常见的函数SoftMax,交叉熵进行的探讨。在利用paddle平台中的反向求微分进行验证的过程中,发现结果 与数学定义有差别。具体原因还需要之后进行查找。

关键词交叉熵SoftMax

#mermaid-svg-gWapA5v3Gts4krSl {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-gWapA5v3Gts4krSl .error-icon{fill:#552222;}#mermaid-svg-gWapA5v3Gts4krSl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gWapA5v3Gts4krSl .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-gWapA5v3Gts4krSl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gWapA5v3Gts4krSl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gWapA5v3Gts4krSl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gWapA5v3Gts4krSl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gWapA5v3Gts4krSl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gWapA5v3Gts4krSl .marker.cross{stroke:#333333;}#mermaid-svg-gWapA5v3Gts4krSl svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gWapA5v3Gts4krSl .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-gWapA5v3Gts4krSl .cluster-label text{fill:#333;}#mermaid-svg-gWapA5v3Gts4krSl .cluster-label span{color:#333;}#mermaid-svg-gWapA5v3Gts4krSl .label text,#mermaid-svg-gWapA5v3Gts4krSl span{fill:#333;color:#333;}#mermaid-svg-gWapA5v3Gts4krSl .node rect,#mermaid-svg-gWapA5v3Gts4krSl .node circle,#mermaid-svg-gWapA5v3Gts4krSl .node ellipse,#mermaid-svg-gWapA5v3Gts4krSl .node polygon,#mermaid-svg-gWapA5v3Gts4krSl .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gWapA5v3Gts4krSl .node .label{text-align:center;}#mermaid-svg-gWapA5v3Gts4krSl .node.clickable{cursor:pointer;}#mermaid-svg-gWapA5v3Gts4krSl .arrowheadPath{fill:#333333;}#mermaid-svg-gWapA5v3Gts4krSl .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-gWapA5v3Gts4krSl .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-gWapA5v3Gts4krSl .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-gWapA5v3Gts4krSl .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-gWapA5v3Gts4krSl .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-gWapA5v3Gts4krSl .cluster text{fill:#333;}#mermaid-svg-gWapA5v3Gts4krSl .cluster span{color:#333;}#mermaid-svg-gWapA5v3Gts4krSl div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-gWapA5v3Gts4krSl :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}

定 义
目 录
Contents
函数
交叉熵
求 导
梯度求解
对比MSE
对比Sigmoid
实验验证
对比SoftMax与MSE
总 结

  在深度学习网络中,SoftMax 以及Cross Entropy(交叉熵)是最常见到神经网络输出级的数据处理以及计算损失函数。

§01 定  义


1.1 函数

  SoftMax函数往往使用在分类神经网络的最后一层的处理中,它将分类神经网络的输出转换成网络对于输入样本所属类别的概率。

1.1.1 类别概率

  如果神经网络N(φ;X)N\left( {\varphi ;X} \right)N(φ;X)在输入样品xix_ixi​的作用下,输出输出向量为[z1,z2,⋯,zM]\left[ {z_1 ,z_2 , \cdots ,z_M } \right][z1​,z2​,⋯,zM​]。分别表示该样本属于 1 ~ M 个类别的概率。但属于概率必须满足:

  • zi≥0,i=1,2,⋯,Mz_i \ge 0,\,\,i = 1,2, \cdots ,Mzi​≥0,i=1,2,⋯,M
  • ∑i=1Mzi=1\sum\limits_{i = 1}^M {z_i } = 1i=1∑M​zi​=1

  为了达到概率条件,需要进行处理。SoftMax处理方式是其中一种。

▲ 图1.1.1 神经网络输入输出示意图

1.1.2 SoftMax

  在 详解softmax函数以及相关求导过程 中,给出了SoftMax的定义:

yi=ezi∑i=1Meziy_i = {{e^{z_i } } \over {\sum\limits_{i = 1}^M {e^{z_i } } }}yi​=i=1∑M​ezi​ezi​​

  数据计算过程中的流程图参见下图:

▲ 图1.1.2 SoftMax结构示意图

  可以看到输出的yi,i=1,2,⋯,My_i ,i = 1,2, \cdots ,Myi​,i=1,2,⋯,M满足:

  • yi≥0,i=1,2,⋯,My_i \ge 0,\,\,i = 1,2, \cdots ,Myi​≥0,i=1,2,⋯,M
  • ∑i=1Myi=1\sum\limits_{i = 1}^M {y_i } = 1i=1∑M​yi​=1

(1)举例

a = [0.1,0.2,0.3,-1,5]
b = sum([exp(aa) for aa in a])
c = [exp(aa)/b for aa in a]
print("a: {}".format(a), "b: {:0.2f}".format(b), "c: {}".format(c))
a: [0.1, 0.2, 0.3, -1, 5]
b: 152.46
c: [0.007249044016189045, 0.008011432630542426, 0.008854002355397775, 0.0024129971374439265, 0.9734725238604267]

1.2 交叉熵

  根据 一文搞懂熵(Entropy),交叉熵(Cross-Entropy)中的定义,对于一组样本所出现的概率分布:pi,i=1,2,⋯,Mp_i ,\,\,i = 1,2, \cdots ,Mpi​,i=1,2,⋯,M,所对应的信息熵为:
Eentropy(P)=E(P)=−∑i=1Mpilog⁡(pi)E_{entropy} \left( P \right) = E\left( P \right) = - \sum\limits_{i = 1}^M {p_i \log \left( {p_i } \right)}Eentropy​(P)=E(P)=−i=1∑M​pi​log(pi​)

1.2.1 交叉熵定义

  如果有两个相同长度的概率分布:P={p1,p2,⋯,pM}P = \left\{ {p_1 ,p_2 , \cdots ,p_M } \right\}P={p1​,p2​,⋯,pM​}Q={q1,q2,⋯,qM}Q = \left\{ {q_1 ,q_2 , \cdots ,q_M } \right\}Q={q1​,q2​,⋯,qM​}。他们两者之间的交叉熵有两种定义:

  在神经网络应用中,P,Q两个概率通常情况下:一个是网络输出SoftMax对应的样本类别概率;另外一个是样本期望类别概率。

  从上面定义来看,对于两个概率分布,这两个交叉熵的取值是不同的,除非两个概率分布是相同的,或者相差一个系数。

1.2.2 举例

  比如对于MNIST分类问题,期望值为:

P: [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]

  网络输出为:

Q: [0.29213776 0.03122202 0.03394387 0.01968607 0.13676823 0.005762330.32953807 0.02688782 0.07919548 0.04485835]

H(P,Q)H\left( {P,Q} \right)H(P,Q)对应的交叉熵为:

H(P,Q)=−∑i=09p(i)⋅log⁡[q(i)]=−log⁡(0.03394)=3.383H\left( {P,Q} \right) = - \sum\limits_{i = 0}^9 {p\left( i \right) \cdot \log \left[ {q\left( i \right)} \right] = - \log \left( {0.03394} \right) = 3.383}H(P,Q)=−i=0∑9​p(i)⋅log[q(i)]=−log(0.03394)=3.383

  从这里可以看到,由于 log 函数要求输入 x 必须是大于0,所以在计算上面的交叉熵的时候,往往使用 H(Q,P)H\left( {Q,P} \right)H(Q,P)的定义,而不是反过来。

  这个交叉熵只是反应了网络输出中,对应样本正确类别的神经元输出距离“1”还相差多少,至于其他网络输出对于交叉熵是没有影响的。

§02 求  导


  通常情况下,对于用于分类神经网络的最后一层的输出往往采用线性传递函数,再加上SoftMax,通过交叉熵计算损失函数Loss。在进行损失函数反向传播的过程中,需要计算反向传播的梯度方向。

▲ 图2.2 网络输出对应的交叉熵

Loss(zi,ti)=−∑i=Mtilog⁡(ezi∑j=MMezj)Loss\left( {z_i ,t_i } \right) = - \sum\limits_{i = }^M {t_i \log \left( {{{e^{z_i } } \over {\sum\limits_{j = M}^M {e^{z_j } } }}} \right)}Loss(zi​,ti​)=−i=∑M​ti​log⎝⎜⎜⎜⎛​j=M∑M​ezj​ezi​​⎠⎟⎟⎟⎞​

2.1 梯度求解

  为了方便梯度求解,假设样本属于第 c 个种类,也就是:tC=1,tm=0,m≠ct_C = 1,\,\,t_m = 0,m \ne ctC​=1,tm​=0,m​=c。那么此时交叉熵为:
Loss(zi,ti)=−log⁡ezc∑j=1Mezj=log⁡(∑j=1Mezj)−zcLoss\left( {z_i ,t_i } \right) = - \log {{e^{z_c } } \over {\sum\limits_{j = 1}^M {e^{z_j } } }} = \log \left( {\sum\limits_{j = 1}^M {e^{z_j } } } \right) - z_cLoss(zi​,ti​)=−logj=1∑M​ezj​ezc​​=log(j=1∑M​ezj​)−zc​

  求此时损失函数 LossLossLoss对于网络输出ziz_izi​ 的梯度下降方向:

−∂Loss∂zi=dzcdzi−1∑j=1Mezj⋅ezi=dzcdzi−yi- {{\partial Loss} \over {\partial z_i }} = {{dz_c } \over {dz_i }} - {1 \over {\sum\limits_{j = 1}^M {e^{z_j } } }} \cdot e^{z_i } = {{dz_c } \over {dz_i }} - y_i−∂zi​∂Loss​=dzi​dzc​​−j=1∑M​ezj​1​⋅ezi​=dzi​dzc​​−yi​

  情况分为两种:

  第一种情况: i=ci = ci=c

−∂Loss∂zc=1−yc-{{\partial Loss} \over {\partial z_c }} = 1 - y_c−∂zc​∂Loss​=1−yc​

  第二种情况: i≠ci \ne ci​=c
−∂Loss∂zi=−yi-{{\partial Loss} \over {\partial z_i }} = - y_i−∂zi​∂Loss​=−yi​

2.2 对比MSE

  如果损失函数使用MSE,即:

Loss=1M∑i=1M(ti−zi)2Loss = {1 \over M}\sum\limits_{i = 1}^M {\left( {t_i - z_i } \right)^2 }Loss=M1​i=1∑M​(ti​−zi​)2

  那么它针对于所有输出的负梯度方向:

−∂Loss∂zi=(ti−zi)M- {{\partial Loss} \over {\partial z_i }} = {{\left( {t_i - z_i } \right)} \over M}−∂zi​∂Loss​=M(ti​−zi​)​

  对比前面对于SoftMax以及交叉熵求解来看,除了相差一个常量系数 1/M1/M1/M之外,梯度的方向是相同的。

▲ 图2.2.1 交叉熵在分类问题

2.3 对比Sigmoid

  如果网络最后一层传递函数采用Sigmoid函数:

f(x)=11+e−xf\left( x \right) = {1 \over {1 + e^{ - x} }}f(x)=1+e−x1​

  Sigmoid函数曲线:

▲ 图2.3.1 Sigmoid函数曲线

x = linspace(-10, 10, 200)
smt = 1/(1+exp(-x))plt.figure(figsize=(10,5))
plt.plot(t, smt)
plt.xlabel("x")
plt.ylabel("sigmoid(x)")
plt.grid(True)
plt.title("Sigmoid Active Function")
plt.tight_layout()
plt.show()

  这个函数虽然不能够保证网络的输出zi,i=1,2,⋯,Mz_i ,i = 1,2, \cdots ,Mzi​,i=1,2,⋯,M满足概率分布归一化要求,但可以保证输出值zi>0,i=1,2,⋯,Mz_i > 0,\,\,i = 1,2, \cdots ,Mzi​>0,i=1,2,⋯,M。这种情况也称为 logistic回归。

▲ 图2.3.2 最后一级采用Sigmoid函数输出

  如果在此基础上仍然利用交叉熵作为损失函数,那么交叉熵对于zi,i=1,2,⋯,Mz_i ,i = 1,2, \cdots ,Mzi​,i=1,2,⋯,M的负梯度为:

−∂Loss∂zi=−∑i=1Mtilog⁡(11+e−zi)- {{\partial Loss} \over {\partial z_i }} = - \sum\limits_{i = 1}^M {t_i \log \left( {{1 \over {1 + e^{ - z_i } }}} \right)}−∂zi​∂Loss​=−i=1∑M​ti​log(1+e−zi​1​)

  同样,假设当前样本对应第ccc类,也就是:tc=1,ti=0,i≠ct_c = 1,\,\,t_i = 0,\,i \ne ctc​=1,ti​=0,i​=c。

  第一种情况: i=ci = ci=c

−∂Loss∂zc=∂∂zclog⁡(1+e−zi)=−e−zc1+e−zc=(1−yc)- {{\partial Loss} \over {\partial z_c }} = {\partial \over {\partial z_c }}\log \left( {1 + e^{ - z_i } } \right) = {{ - e^{ - z_c } } \over {1 + e^{ - z_c } }} = \left( {1 - y_c } \right)−∂zc​∂Loss​=∂zc​∂​log(1+e−zi​)=1+e−zc​−e−zc​​=(1−yc​)

  第二种情况: i≠ci \ne ci​=c

−∂Loss∂zi=0- {{\partial Loss} \over {\partial z_i }} = 0−∂zi​∂Loss​=0

  通过上面对比,可以看到使用Sigmoid函数作为网络的最后一级输出的传递函数,然后使用交叉熵来作为损失函数的时候存在的问题:

  • 它只对于该样本对应的类别所属的神经元反向有梯度,对于其它输出神经元反向没有梯度;
  • 当神经元输出接近于1的时候,反向梯度数值会下降;

  上面会使得网络在收敛的时候速度变慢。

§03 实验验证


3.1 对比SoftMax与MSE

  根据前面推导,可以看到在网络输出传递函数为线性函数的时候,通过SoftMax以及交叉熵对应的损失函数,与MSE对应的损失函数所对网络输出节点的损失函数负梯度数值是一样的。下面在paddle平台下,测试一下这方面的内容。

3.1.1 建立softmax网络

  搭建一个仅仅由softmax组成的网络。

class netmodel(paddle.nn.Layer):def __init__(self, ):super(netmodel, self).__init__()def forward(self, x):x = paddle.nn.functional.softmax(x)return x

3.1.2 求解SoftMax梯度

(1)求SoftMax输出

 Ⅰ.使用net求解SoftMax输出

  定义一个向量z,代表网络的实际输出。然后经过上面定义的SoftMax,获得实际输出y。

net = netmodel()
z = TT(array([0,0.1,-2,3])[newaxis,:], dtype='float64')
z.stop_gradient = False
y = paddle.nn.functional.softmax(z)
print("z: {}".format(z), "y: {}".format(y))
z: Tensor(shape=[1, 4], dtype=float64, place=CPUPlace, stop_gradient=False,[[ 0.       , 0.10000000, -2.       ,  3.       ]])
y: Tensor(shape=[1, 4], dtype=float64, place=CPUPlace, stop_gradient=False,[[0.04479074, 0.04950142, 0.00606177, 0.89964607]])
 Ⅱ.使用numpy求SoftMax

  通过Numpy函数计算SoftMax,对比paddle中的 SoftMax函数数值:

zz = z.numpy()
yy = exp(zz)/sum(exp(zz))
print("zz: {}".format(zz), "yy: {}".format(yy))
zz: [[ 0.   0.1 -2.   3. ]]
yy: [[0.04479074 0.04950142 0.00606177 0.89964607]]

  对比一下,上面的数值是一样的。

(2)反向梯度计算

  上面利用paddle中的loss求解机制,自动完成对z的梯度求解。

net = netmodel()
optimizer = paddle.optimizer.SGD(learning_rate=0.1, parameters=net.parameters())z = TT(array([0,0.1,-2,3])[newaxis,:], dtype='float64')
z.stop_gradient = False
y = paddle.nn.functional.softmax(z)t = TT(array([[1]]))
loss = paddle.nn.functional.cross_entropy(y, t)
loss.backward()print("loss.numpy(): {}".format(loss.numpy()), "y: {}".format(y), "y.grad: {}".format(y.grad), "z.grad: {}".format(z.grad))
loss.numpy(): [1.66634288]
y: Tensor(shape=[1, 4], dtype=float64, place=CPUPlace, stop_gradient=False,[[0.04479074, 0.04950142, 0.00606177, 0.89964607]])
y.grad: Tensor(shape=[1, 4], dtype=float64, place=CPUPlace, stop_gradient=False,[[ 0.18804884, -0.81106323,  0.18090513,  0.44210926]])
z.grad: Tensor(shape=[1, 4], dtype=float64, place=CPUPlace, stop_gradient=False,[[-0.00802040, -0.05832138, -0.00112875,  0.06747052]])

  观察 z 变量的反向梯度,下面进行验证一下。
  首先,关于 Loss的计算数值,它应该等于:

Loss=−log⁡(y1)=−log⁡(0.0495)=3.006Loss = - \log \left( {y_1 } \right) = - \log \left( {0.0495} \right) = 3.006Loss=−log(y1​)=−log(0.0495)=3.006

  但上面给出的数值:

print("loss.numpy(): {}".format(loss.numpy()))
loss.numpy(): [1.66634288]

  这与定义不同?那么问题出现在哪里了呢?

  再检查y[1]的梯度:

print(1+log(0.0495)*y[0][1])
Tensor(shape=[1], dtype=float64, place=CPUPlace, stop_gradient=False,[0.85120948])

  可以看到这与上面计算出的数值也不一样。

※ 总  结 ※


  对于在深度学习中的两个常见的函数SoftMax,交叉熵进行的探讨。在利用paddle平台中的反向求微分进行验证的过程中,发现结果 与数学定义有差别。具体原因还需要之后进行查找。


■ 相关文献链接:

  • 详解softmax函数以及相关求导过程
  • 一文搞懂熵(Entropy),交叉熵(Cross-Entropy)

● 相关图表链接:

  • 图1.1.1 神经网络输入输出示意图
  • 图1.1.2 SoftMax结构示意图
  • 图2.2 网络输出对应的交叉熵
  • 图2.2.1 交叉熵在分类问题
  • 图2.3.1 Sigmoid函数曲线
  • 图2.3.2 最后一级采用Sigmoid函数输出

神经网络学习中的SoftMax与交叉熵相关推荐

  1. 神经网络适用于分类问题的最后一层-Softmax和交叉熵损失介绍及梯度推导

    前言 传统机器学习中两大经典任务就是回归与分类.分类在深度学习中也很常见,令我印象最深的是图像分类.当然,在NLP中,分类也无处不在.从RNN与其变体,到Transformer.Bert等预训练模型, ...

  2. pytoch人工神经网络基础:最简单的分类(softmax回归+交叉熵分类)

    softmax回归分类原理 对于回归问题,可以用模型预测值与真实值比较,用均方误差这样的损失函数表示误差,迭代使误差最小训练模型. 那么分类问题是否可以用线性回归模型预测呢.最简单的方法就是用soft ...

  3. 度量学习(Metric learning)—— 基于分类损失函数(softmax、交叉熵、cosface、arcface)

    概述 首先,我们把loss归为两类:一类是本篇讲述的基于softmax的,一类是基于pair对的(如对比损失.三元损失等). 基于pair对的,参考我的另一篇博客: https://blog.csdn ...

  4. 人脸识别-Loss-2010:Softmax Loss(Softmax激活函数 + “交叉熵损失函数”)【样本3真实标签为c_5,则样本3的损失:loss_3=-log(\hat{y}_5^3)】

    一般一个CNN网络主要包含卷积层,池化层(pooling),全连接层,损失层等. 全连接层:等号左边部分就是全连接层做的事, W W W 是全连接层的参数,我们也称为权值, X X X 是全连接层的输 ...

  5. 深度学习基础入门篇[五]:交叉熵损失函数、MSE、CTC损失适用于字识别语音等序列问题、Balanced L1 Loss适用于目标检测

    [深度学习入门到进阶]必看系列,含激活函数.优化策略.损失函数.模型调优.归一化算法.卷积模型.序列模型.预训练模型.对抗神经网络等 专栏详细介绍:[深度学习入门到进阶]必看系列,含激活函数.优化策略 ...

  6. softmax与交叉熵损失

    softmax基本形式 softmax是一个将实数域上取值的N维向量转化为[0,1]范围内的函数,常用用于分类问题中,接在神经网络最后一层,将输出值映射到概率区间上,其基本形式如下所示 pi=eai∑ ...

  7. 图示Softmax及交叉熵损失函数

    Softmax函数 Softmax是将神经网络得到的多个值,进行归一化处理,使得到的值在之间,让结果变得可解释.即可以将结果看作是概率,某个类别概率越大,将样本归为该类别的可能性也就越高.Softma ...

  8. tensoflow随笔——softmax和交叉熵

    softmax函数 softmax函数接收一个N维向量作为输入,然后把每一维的值转换到(0, 1)之间的一个实数.假设模型全连接网络输出为a,有C个类别,则输出为a1,a2,...,aC,对于每个样本 ...

  9. 动手学深度学习——softmax回归之OneHot、softmax与交叉熵

    目录 一.从回归到多类分类 1. 回归估计一个连续值 2. 分类预测一个离散类别 二.独热编码OneHot 三.校验比例--激活函数softmax 四.损失函数--交叉熵 五.总结 回归可以用于预测多 ...

最新文章

  1. 【高性价比】AIO-3288C四核高性能主板
  2. Wallop下蛋送邀请。
  3. 解决在使用numpy.polyfit 时出现的 raise LinAlgError(“SVD did not converge in Linear Least Squares“)错误
  4. nacos 怎么配置 里的配置ip_Nacos-服务注册地址为内网IP的解决办法
  5. Azkaban安装部署,配置文件配置,启动等
  6. java lock代码写法_java Lock接口详解及实例代码
  7. 通信系统的同步技术归纳
  8. python 文件加密_python实现文件快照加密保护的方法
  9. 左移右移位运算_计算机硬件技术基础M2——计算机运算基础(二)
  10. Azure IoT Edge on Windows 10 IoT Core
  11. iOS 拼音 Swift K3Pinyin
  12. 生成对抗网络发展及其主要工程应用综述
  13. 现代控制理论6——能控、能观及其对偶原理、线性变换、结构分解
  14. 实现人生梦想,共同创造人生辉煌!
  15. 微软中国裁员计划曝光:年底前裁完!补偿不变!
  16. 室内定位导航系统源码php,UWB室内定位系统方案_室内精确定位DEMO搭建
  17. Mono及MonoDevelop安装
  18. 软件测试究竟发挥什么作用?
  19. VM15 虚拟机下载及安装CentOS7.6
  20. linux+scp+失败_Linux scp 命令卡住的原因

热门文章

  1. 【码云周刊第 68 期】数据可视化:商业智能的未来!
  2. Banner长得丑,网站没朋友
  3. 看看C# 6.0中那些语法糖都干了些什么(终结篇)
  4. 安卓v7支持包下的ListView替代品————RecyclerView
  5. 20145129 课程总结
  6. 新一轮光伏电站产能过剩隐忧初显
  7. 《CCNP ROUTE 300-101学习指南》——2.6节复习题
  8. Picasso fit() centerCrip() centerInside()
  9. nodejs使用带用户和密码的Mongo示例
  10. CLLocationCoordinate2D 用法 和一个最隐蔽的错误