Leo Zhang

A simple man with my own ideal

2012年6月25日 #

OWL-QN算法

一、BFGS算法

      算法思想如下:

           Step1   取初始点x^{(0)} ,初始正定矩阵H_0,允许误差\epsilon>0,令k=0

           Step2   计算p^{(k)}=-H_k \nabla f(x^{(k)})

           Step3   计算\alpha_k>0,使得

                                               f(x^{(k)}+\alpha_kp^{(k)})={min}\limit_{\alpha \geq 0} f(x^{(k)}+\alpha p^{(k)})

          Step4    令x^{(k+1)}=x^{(k)}+\alpha_k p^{(k)}

          Step5    如果||\nabla f(x^{(k+1)})|| \leq \epsilon,则取x^{(k+1)}为近似最优解;否则转下一步;

          Step6    计算

                                s_k=x^{(k+1)}-x^{(k)}y_k=\nabla f(x^{(k+1)})-\nabla f(x^{(k)})

                         H_{k+1}=H_k+\frac{1}{s_k^Ty_k}(1+\frac{y_k^TH_ky_k}{s_k^Ty_k})s_ks_k^T-\frac{1}{s_k^Ty_k}(s_ky_k^TH_k+H_ky_ks_k)

          令k=k+1,转Step2.

优点:

1、不用直接计算Hessian矩阵;

2、通过迭代的方式用一个近似矩阵代替Hessian矩阵的逆矩阵。

缺点:

1、矩阵存储量为n^2,因此维度很大时内存不可接受;

2、矩阵非稀疏会导致训练速度慢。

 

二、L-BFGS算法

      针对BFGS的缺点,主要在于如何合理的估计出一个Hessian矩阵的逆矩阵,L-BFGS的基本思想是只保存最近的m次迭代信息,从而大大降低数据存储空间。对照BFGS,我重新整理一下用到的公式:

                                 \rho_k=\frac{1}{y_{k}^T s_k}     

                                 s_k=x_k-x_{k-1}            

                                 y_k=\nabla{f(x_k)}-\nabla{f(x_{k-1})}

                                 V_k=I-\rho_{k}y_{k}s_{k}^T

于是估计的Hessian矩阵逆矩阵如下:                         

                                H_k=(I-\rho_{k-1}s_{k-1}y_{k-1}^T)H_{k-1}(I-\rho_{k-1}y_{k-1}s_{k-1}^T)+s_{k-1}\rho_{k-1}s_{k-1}^T

                                       =V_{k-1}^TH_{k-1}V_{k-1}+ s_{k-1}\rho_{k-1}s_{k-1}^T

                                H_{k-1}=V_{k-2}^TH_{k-2}V_{k-2}+ s_{k-2}\rho_{k-2}s_{k-2}^T

带入上式,得:

                               H_k=V_{k-1}^TV_{k-2}^TH_{k-2}V_{k-2}V_{k-1}+ V_{k-1}^Ts_{k-2}\rho_{k-2}s_{k-2}^T V_{k-1}+s_{k-1}\rho_{k-1}s_{k-1}^T 

假设当前迭代为k,只保存最近的m次迭代信息,(即:从k-m~k-1),依次带入H,得到:

公式1:

                               H_k=(V_{k-1}^TV_{k-2}^T\ldots V_{k-m}^T) H_k^{0}(V_{k-m}\ldots V_{k-2}V_{k-1})

                                      + (V_{k-1}^TV_{k-2}^T\ldots V_{k-m+1}^T) S_{k-m}\rho_{k-m}S_{k-m}^T (V_{k-m}\ldots V_{k-2}V_{k-1})

                                      + (V_{k-1}^TV_{k-2}^T\ldots V_{k-m+2}^T) S_{k-m+1}\rho_{k-m+1}S_{k-m+1}^T (V_{k-m+1}\ldots V_{k-2}V_{k-1})

                                      +\ldots

                                      +V_{k-1}^T s_{k-2}\rho_{k-2} s_{k-2}^TV_{k-1}  

                                      +s_{k-1}\rho_{k-1}s_{k-1}^T

算法第二步表明了上面推导的最终目的:找到第k次迭代的可行方向,满足:

                                p_k=-H_k\nabla f(x_k)

为了求可行方向p,有下面的:

  two-loop recursion算法


                                  q=\nabla f(x_k)

                                  for (i=1 \ldots m) \quad \quad \quad do

                                          \alpha_i=\rho_{k-i}s_{k-i}^Tq

                                          q=q-\alpha_iy_{k-i}

                                   end \quad \quad \quad for

                                   r=H_k^{0}q

                                   for (i=m \ldots 1) \quad \quad \quad do

                                         \beta=\rho_{k-i}y_{k-i}^Tr

                                         r=r+s_{k-i}(\alpha_i-\beta)

                                    end \quad \quad \quad for

                                   return \quad \quad \quad r


该算法的正确性推导:

1、令:     q_0=\nabla f(x_k)
,递归带入q:

                                q_i=q_{i-1}-\rho_{k-i}y_{k-i}s_{k-i}^Tq_{i-1}

                                     =(I-\rho_{k-i}y_{k-i}s_{k-i}^T)q_{i-1}

                                     =V_{k-i}q_{i-1}

                                     =V_{k-i}V_{k-i+1}q_{i-2}

                                     =\ldots

                                     =V_{k-i}V_{k-i+1} \ldots V_{k-1} q_0

                                     =V_{k-i}V_{k-i+1} \ldots V_{k-1} \nabla f(x_k)

相应的:

                                \alpha_i=\rho_{k-i}s_{k-i}^Tq_{i-1}

                                     =\rho_{k-i}s_{k-i}^T V_{k-i+1}V_{k-i+2} \ldots V_{k-1} \nabla f(x_k)

2、令:r_{m+1}=H_{k-m}q=H_{k-m}V_{k-i}V_{k-i+1} \ldots V_{k-1} \nabla f(x_k)

                                r_i=r_{i+1}+s_{k-i}(\alpha_i-\beta) =r_{i+1}+s_{k-i}(\alpha_i-\rho_{k-i}y_{k-i}^Tr_{i+1})

                                     =s_{k-i}\alpha_i+(I-s_{k-i}\rho_{k-i}y_{k-i}^T)r_{i+1}

                                     =s_{k-i}\alpha_{i}+V_{k-i}^Tr_{i+1}

于是:

                                r_1=s_{k-1}\alpha_1+V_{k-1}^Tr_2 =s_{k-1}\rho_{k-1}s_{k-1}^T \nabla f(x_k)+V_{k-1}^Tr_2

                                     =s_{k-1}\rho_{k-1}s_{k-1}^T \nabla f(x_k)+V_{k-1}^T(s_{k-2}\alpha_2+V_{k-2}^Tr_3)

                                     =s_{k-1}\rho_{k-1}s_{k-1}^T \nabla f(x_k)+V_{k-1}^Ts_{k-2}\rho_{k-2}s_{k-2}^TV_{k-1}\nabla f(x_k)+V_{k-1}^T V_{k-2}^T r_3

                                     =\ldots

                                     =s_{k-1}\rho_{k-1}s_{k-1}^T \nabla f(x_k)           

                                      +V_{k-1}^T s_{k-2}\rho_{k-2} s_{k-2}^TV_{k-1}  \nabla f(x_k)

                                      +\ldots     

                                      + (V_{k-1}^TV_{k-2}^T\ldots V_{k-m+2}^T) S_{k-m+1}\rho_{k-m+1}S_{k-m+1}^T (V_{k-m+1}\ldots V_{k-2}V_{k-1}) \nabla f(x_k)

                                      + (V_{k-1}^TV_{k-2}^T\ldots V_{k-m+1}^T) S_{k-m}\rho_{k-m}S_{k-m}^T (V_{k-m}\ldots V_{k-2}V_{k-1}) \nabla f(x_k)

                                      +(V_{k-1}^TV_{k-2}^T\ldots V_{k-m}^T) H_{k-m}(V_{k-m}\ldots V_{k-2}V_{k-1}) \nabla f(x_k)

这个two-loop recursion算法的结果和公式1*初始梯度的形式完全一样,这么做的好处是:

1、只需要存储s_{k-i}y_{k-i} (i=1~m);

2、计算可行方向的时间复杂度从O(n*n)降低到了O(n*m),当m远小于n时为线性复杂度。

总结L-BFGS算法的步骤如下:

      Step1:       选初始点x_0,允许误差\epsilon >0,存储最近迭代次数m(一般取6);

      Step2:       k=0, \quad \quad \quad H_0=I ,\quad \quad \quad r=\nabla f(x_{0})

      Step3:       如果 ||\nabla f(x_{k+1})||\leq \epsilon
则返回最优解x_{k+1},否则转Step4;

      Step4:       计算本次迭代的可行方向:p_k=-r _k

      Step5:       计算步长\alpha_k>0,对下面式子进行一维搜索:

                         f(x_k+\alpha_kp_k)={min}\limits_{\alpha \geq 0} \quad \quad \quad f(x_k+\alpha p_k)

      Step6:       更新权重x:

                         x_{k+1}=x_k+\alpha_kp_k ;     

      Step7:      if k > m

                             只保留最近m次的向量对,需要删除(s
_{k-m},y_{k-m});

      Step8:       计算并保存:

                        s_k=x_{k+1}-x_k

                        y_k=\nabla f(x_{k+1})-\nabla f(x_k)

      Step9:       用two-loop recursion算法求得:

                         r_k=H_k\nabla f(x_k)

      k=k+1,转Step3。

需要注意的地方,每次迭代都需要一个H_{k-m} ,实践当中被证明比较有效的取法为:

                          H_k^0=\gamma_k I

                          \gamma_k=\frac{s_{k-1}^Ty_{k-1}}{{y_{k-1}^Ty_{k-1}}

 

三、OWL-QN算法

1、问题描述

对于类似于Logistic Regression这样的Log-Linear模型,一般可以归结为最小化下面这个问题:

                          J(x)=l(x)+r(x)

其中,第一项为loss function,用来衡量当训练出现偏差时的损失,可以是任意可微凸函数(如果是非凸函数该算法只保证找到局部最优解),后者为regularization term,用来对模型空间进行限制,从而得到一个更“简单”的模型。

        根据对模型参数所服从的概率分布的假设的不同,regularization term一般有:L2-norm(模型参数服从Gaussian分布);L1-norm(模型参数服从Laplace分布);以及其他分布或组合形式。

L2-norm的形式类似于:

                         J(x)=l(x)+C\sum\limit_i{x_i^2}

L1-norm的形式类似于:

                         J(x)=l(x)+C\sum\limit_i{|x_i|}

L1-norm和L2-norm之间的一个最大区别在于前者可以产生稀疏解,这使它同时具有了特征选择的能力,此外,稀疏的特征权重更具有解释意义。

对于损失函数的选取就不在赘述,看两幅图:

image

图1 - 红色为Laplace Prior,黑色为Gaussian Prior 

 

image

图2 直观解释稀疏性的产生

 

        对LR模型来说损失函数选取凸函数,那么L2-norm的形式也是的凸函数,根据最优化理论,最优解满足KKT条件,即有:\nabla J(x^*)=0
,但是L1-norm的regularization term显然不可微,怎么办呢?

2、Orthant-Wise Limited-memory Quasi-Newton

        OWL-QN主要是针对L1-norm不可微提出的,它是基于这样一个事实:任意给定一个维度象限,L1-norm 都是可微的,因为此时它是一个线性函数:

image

图3 任意给定一个象限后的L1-norm

OWL-QN中使用了次梯度决定搜索方向,凸函数不一定是光滑而处处可导的,但是它又符合类似梯度下降的性质,在多元函数中把这种梯度叫做次梯度,见维基百科http://en.wikipedia.org/wiki/Subderivative

举个例子:

File:Subderivative illustration.png

图4 次导数

对于定义域中的任何x0,我们总可以作出一条直线,它通过点(x0, f(x0)),并且要么接触f的图像,要么在它的下方。这条直线的斜率称为函数的次导数,推广到多元函数就叫做次梯度。

次导数及次微分:

凸函数f:IR在点x0的次导数,是实数c使得:

                        f(x)-f(x_0)\ge c(x-x_0)

对于所有I内的x。可以证明,在点x0的次导数的集合是一个非空闭区间[a, b],其中ab是单侧极限

              a=\lim_{x\to x_0^-}\frac{f(x)-f(x_0)}{x-x_0}
              b=\lim_{x\to x_0^+}\frac{f(x)-f(x_0)}{x-x_0}

它们一定存在,且满足ab。所有次导数的集合[a, b]称为函数fx0的次微分。

OWL-QN和传统L-BFGS的不同之处在于:

1)、利用次梯度的概念推广了梯度

       定义了一个符合上述原则的虚梯度,求一维搜索的可行方向时用虚梯度来代替L-BFGS中的梯度:

                      image

                        image

                       DPB1$`%~T9U0]2%YSCVQ83K

怎么理解这个虚梯度呢?见下图:

对于非光滑凸函数,那么有这么几种情况:

image

图5  \partial_i^-f(x)>0

 

image

图6  \partial_i^+f(x)<0

 

image

图7  otherwise

2)、一维搜索要求不跨越象限

       要求更新前权重与更新后权重同方向:

image

图8  OWL-QN的一次迭代

总结OWL-QN的一次迭代过程:

–Find vector of steepest descent

–Choose sectant

–Find L-BFGS quadratic approximation

–Jump to minimum

–Project back onto sectant

–Update Hessian approximation using gradient of loss alone

最后OWL-QN算法框架如下:

                                 8]O)3Y(ZOLX5C1AI9YXP72M

 

      与L-BFGS相比,第一步用虚梯度代替梯度,第二、三步要求一维搜索不跨象限,也就是迭代前的点与迭代后的点处于同一象限,第四步要求估计Hessian矩阵时依然使用loss function的梯度(因为L1-norm的存在与否不影响Hessian矩阵的估计)。

四、参考资料

1、Galen Andrew and Jianfeng Gao. 2007. 《Scalable training of L1-regularized log-linear models》. In Proceedings of ICML, pages 33–40.

2、http://freemind.pluskid.org/machine-learning/sparsity-and-some-basics-of-l1-regularization/#d20da8b6b2900b1772cb16581253a77032cec97e

3、http://research.microsoft.com/en-us/downloads/b1eb1016-1738-4bd5-83a9-370c9d498a03/default.aspx

posted @ 2012-06-25 13:08 Leo Zhang 阅读(17466) 评论(5) 推荐(5) 编辑

2012年2月24日 #

Stochastic Gradient Descent

摘要: 一、从Multinomial Logistic模型说起 阅读全文

posted @ 2012-02-24 17:13 Leo Zhang 阅读(17759) 评论(12) 推荐(4) 编辑

2012年2月10日 #

Spectral Clustering

摘要: Spectral Clustering(谱聚类)是一种基于图论的聚类方法,它能够识别任意形状的样本空间且收敛于全局最有解,其基本思想是利用样本数据的相似矩阵进行特征分解后得到的特征向量进行聚类,可见,它与样本feature无关而只与样本个数有关。 阅读全文

posted @ 2012-02-10 10:57 Leo Zhang 阅读(25542) 评论(14) 推荐(3) 编辑

2011年12月29日 #

Gradient And Karush-Kuhn-Tucker Conditions

摘要: 最近开始面试,复习当中发现自己有很多基础的东西有些模糊,借此温故而知新一下,并提醒自己基础很重要,踏踏实实、戒骄戒躁。 一、梯度是什么? 1、一个小例子 假设有单变量实值... 阅读全文

posted @ 2011-12-29 14:23 Leo Zhang 阅读(6554) 评论(7) 推荐(3) 编辑

2011年10月8日 #

Mahout学习——K-Means Clustering

摘要: K-Means这个词第一次使用是在1967,但是它的思想可以追溯到1957年,它是一种非常简单地基于距离的聚类算法,认为每个Cluster由相似的点组成而这种相似性由距离来衡量,不同Clu... 阅读全文

posted @ 2011-10-08 13:00 Leo Zhang 阅读(20493) 评论(16) 推荐(7) 编辑

2011年9月23日 #

Mahout学习——Canopy Clustering

摘要: 聚类是机器学习里很重要的一类方法,基本原则是将“性质相似”(这里就有相似的标准问题,比如是基于概率分布模型的相似性又或是基于距离的相似性)的对象尽可能的放在一个Cluster中而不同Cluster中对象尽可能不相似。对聚类算法而言,有三座大山需要爬过去:(1)、a large number of clusters,(2)、a high feature dimensionality,(3)、a large number of data points。在这三种情况下,尤其是三种情况都存在时,聚类的计算代价是非常高的,有时候聚类都无法进行下去,于是出现一种简单而又有效地方法:Canopy Method,说简单是因为它不用什么高深的理论或推导就可以理解,说有效是因为它的实际表现确实可圈可点。 阅读全文

posted @ 2011-09-23 17:30 Leo Zhang 阅读(18608) 评论(12) 推荐(4) 编辑

2011年9月20日 #

Hadoop初体验——搭建hadoop简单实现文本数据全局排序

摘要: 之前在实现一些机器学习算法时,跑数据量、feature很多的数据集往往要耗费很多时间,尤其是处理大量文本数据时候,单机跑算法的时间让我无法容忍,理论上如果合理的将大数据量分布式并行计算框架... 阅读全文

posted @ 2011-09-20 14:21 Leo Zhang 阅读(8160) 评论(6) 推荐(7) 编辑

2011年8月25日 #

SVM学习——Improvements to Platt’s SMO Algorithm

摘要: 纵观SMO算法,其核心是怎么选择每轮优化的两个拉格朗日乘子,标准的SMO算法是通过判断乘子是否违反原问题的KKT条件来选择待优化乘子的,这里可能有一个问题,回顾原问题的KKT条件: 是否违反它,与这几个因素相关:拉格朗日乘子、样本标记、偏置。的更新依赖于两个优化拉格朗日乘子,这就可能出现这种情况:拉格朗日乘子已经能使目标函数达到最优,而SMO算法本身并不能确定当前由于两个优化拉格朗日乘子计算得到的是否就是使目标函数达到最优的那个,换句话说,对一些本来不违反KKT条件的点,由于上次迭代选择了不合适的,使得它们出现违反KKT条件的情况,导致后续出现一些耗时而无用的搜索,针对标... 阅读全文

posted @ 2011-08-25 09:35 Leo Zhang 阅读(7128) 评论(15) 推荐(3) 编辑

2011年6月1日 #

SVM学习——Sequential Minimal Optimization

摘要: 1、前言 接触SVM也有一段时间了,从理论到实践都有了粗浅的认识,我认为SVM的发展可以划分为几个相对独立的部分,首先是SVM理论本身,包括寻找最大间隔分类超平面、引入核方法极大提高对非线性问题的处... 阅读全文

posted @ 2011-06-01 23:15 Leo Zhang 阅读(14488) 评论(8) 推荐(4) 编辑

2011年3月22日 #

Cholesky分解

摘要: 1、为什么要进行矩阵分解 个人认为,首先,当数据量很大时,将一个矩阵分解为若干个矩阵的乘积可以大大降低存储空间;其次,可以减少真正进行问题处理时的计算量,毕竟算法扫描的元素越少完成任务的速度越快,这... 阅读全文

posted @ 2011-03-22 15:28 Leo Zhang 阅读(21756) 评论(21) 推荐(8) 编辑

2011年1月13日 #

SVM学习——Coordinate Desent Method

摘要: 前几篇侃了侃SVM的基本原理和一些相关知识,尤其是在SVM学习——软间隔优化这一篇,提到了SVM学习器的两种形式,常被叫做L1-SVM和L2-SVM,这两种形式的区别在损失函数的形式上... 阅读全文

posted @ 2011-01-13 10:34 Leo Zhang 阅读(10024) 评论(16) 推荐(1) 编辑

2010年12月22日 #

SVM学习——软间隔优化

摘要: 回想SVM学习——线性学习器一文中提到的Rosenblatt感知器的原始形式,当时的讨论是基于输入空间线性可分的情况,包括后来的最大间隔算法,通过核函数隐式的将输入空间映射到了一个高维特征空间中了,此时的假设同样是映射后的数据线性可分,那自然就会想到如果输入空间或者由核函数映射得到的特征空间依然是线性不可分的可怎么办呀? 阅读全文

posted @ 2010-12-22 11:09 Leo Zhang 阅读(8301) 评论(9) 推荐(5) 编辑

2010年12月18日 #

SVM学习——统计学习理论

摘要: 关于统计学习的理论博大精深,想要弄明白是需要花费很大功夫的,涉及到方方面面的数学知识(比如泛函分析、高等数学、概率论、统计学…….),我这里也就是把一些基本概念、理论规整一下。 存在一个未知的系统、给定的输入样本空间和这些输入样本通过处理后的输出。机器学习的过程可以看做是这样的:利用机器学习的方法,根据和得到一个学习机(也可以叫模型),学习机在接受训练、测试样本以外的样本后得到的输出可以被认为是未知系统针对输入得到的输出的近似,所以这个学习机可以认为是对的内在规律的近似。 阅读全文

posted @ 2010-12-18 00:07 Leo Zhang 阅读(7169) 评论(6) 推荐(3) 编辑

2010年12月13日 #

SVM学习——核函数

摘要: 还记得上篇末提到的对于优化问题: 阅读全文

posted @ 2010-12-13 16:59 Leo Zhang 阅读(42570) 评论(29) 推荐(11) 编辑

2010年12月6日 #

SVM学习——求解二次规划问题

摘要: 上一篇最后提到要解决最优化问题,稍微对它做一下改动,如下: 阅读全文

posted @ 2010-12-06 14:07 Leo Zhang 阅读(18107) 评论(9) 推荐(3) 编辑

2010年12月2日 #

SVM学习——线性学习器

摘要: 变量之间存在的一次函数关系是线性关系,那么线性分类就应该是利用这样一种线性关系将输入学习器的样例按照其标记分开。一个线性函数的因变量值是连续的,而分类问题则一般是离散的,这个实值函数可以这样表示: 阅读全文

posted @ 2010-12-02 12:32 Leo Zhang 阅读(6437) 评论(7) 推荐(2) 编辑

2010年4月12日 #

EL4.1配置文件管理浅谈(1)

摘要: 一、前言 我们知道高耦合性的代码是很不方便变更的,可能会导致牵一发而动全身,为了解耦大家想了很多方法,例如依赖注入等等,常见的做法是将这种耦合外推到配置文件,那么如何能对配置文件进行很好的组织就成了一个比较重要的部分。本文以EL4.1的配置文件管理为例子,看下他们是怎么做的。 阅读全文

posted @ 2010-04-12 21:24 Leo Zhang 阅读(1280) 评论(2) 推荐(2) 编辑

2010年3月15日 #

浅析如何在ObjectBuilder2中用动态方法进行构造器注入

摘要: 一、前言 在我看来,OB2是一个用来构建和管理对象的开放性的框架,我们可以根据自己的需求去扩展它,例如扩展它可以实现依赖注入(如MS的Unity)。我认为OB2最大的亮点之一是在提供了对象创建框架的同时能够管理对象以及对象之间的依赖关系,控制对象构建和销毁过程,这样对象的创建就不是直接去使用new而对象的销毁也不仅仅只靠GC了。要说OB2所使用的设计模式,我觉得可以认为是大量使用策略(Strategy)模式并辅以责任链模式,通过责任链组织对象创建或销毁的次序及步骤。 阅读全文

posted @ 2010-03-15 18:19 Leo Zhang 阅读(2020) 评论(6) 推荐(0) 编辑

2010年3月4日 #

Castle动态代理技术初探

摘要: 假设朋友给我一个CalculatorLib.dll文件,里面包含了一个计算器接口和一个实现了该接口的计算器类,我的程序里要用到这个计算器来计算两个整数的和(仅作为简单例子,不考虑溢出处理等其他方面),计算器的实现大概如下: public interface ICalculator { Int32 AddOperation(Int32 p1, Int32 p2); } public class Calculator : ICalculator { public virtual Int32 AddOperation(Int32 p1, Int32 p2) { //① //② return p1 + p2; //③ } } 我的需求是:想在①这里为代码赋予修改输入参数和返回值的权限,在②更改参数和返回值,在③这里收回该权限,分两种情况 阅读全文

posted @ 2010-03-04 22:28 Leo Zhang 阅读(4063) 评论(18) 推荐(5) 编辑

2009年10月18日 #

Metadata探秘

摘要: 一、初探MetaData 把支持CLR的编程语言(如C++/CLI、C#、VB等)编写的源代码文件通过微软的或者自己写的编译器可以编译为一个托管模块,它实际上是一个标准的PE文件,其结构可以参见深入了解CLR的加载过程一文。Metadata(元数据)与IL代码都存在于该PE文件的Sections中,Metadata与IL是同时生成且永远同步的,本文主要讨论Metadata的内容,并以如下代码为例: 阅读全文

posted @ 2009-10-18 00:42 Leo Zhang 阅读(2519) 评论(8) 推荐(0) 编辑

导航

统计信息

点击右上角即可分享
微信分享提示