深度学习(三)theano入门学习

来源:互联网 发布:费雪淘宝旗舰店 编辑:程序博客网 时间:2024/05/20 11:24

theano入门学习

原文地址:http://blog.csdn.net/hjimce/article/details/46806923

作者:hjimce

本篇博文主要是在我刚入门学theano的一个学习历程的回顾,记录了自己深度学习学习征程的第一站。

一、初识theano

1、theano.tensor常用数据类型

学习theano,首先要学的就是theano.tensor使用,其是基础数据结构,功能类似于python.numpy,教程网站为:http://deeplearning.net/software/theano/library/tensor/basic.html

在theano.tensor数据类型中,有double、int、uchar、float等各种类型,不过我们最常用到的是int和float类型,float是因为GPU一般是float32类型,所以在编写程序的时候,我们很少用到double,常用的数据类型如下:

数值:iscalar(int类型的变量)、fscalar(float类型的变量)

一维向量:ivector(int 类型的向量)、fvector(float类型的向量)、

二维矩阵:fmatrix(float类型矩阵)、imatrix(int类型的矩阵)

三维float类型矩阵:ftensor3  

四维float类型矩阵:ftensor4

其它类型只要把首字母变一下就可以了,更多类型请参考:http://deeplearning.net/software/theano/library/tensor/basic.html#theano.tensor.TensorVariable 中的数据类型表格

2、theano编程风格

例1:记得刚接触theano的时候,觉得它的编程风格很神奇,与我们之前所接触到的编程方式大不相同。在c++或者java等语言中,比如我们要计算“2的3次方”的时候,我们一般是:

[cpp] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. int x=2;  
  2. int y=power(x,3);  
也就是以前的编程方法中,我们一般先为自变量赋值,然后再把这个自变量作为函数的输入,进行计算因变量。然而在theano中,我们一般是先声明自变量x(不需要赋值),然后编写函数方程结束后;最后在为自变量赋值,计算出函数的输出值y,比如上面的代码在theano中,一般这么写:

[python] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. import theano  
  2. x=theano.tensor.iscalar('x')#声明一个int类型的变量x  
  3. y=theano.tensor.pow(x,3)#定义y=x^3  
  4. f=theano.function([x],y)#定义函数的自变量为x(输入),因变量为y(输出)  
  5. print f(2)#计算当x=2的时候,函数f(x)的值  
  6. print f(4)#计算当x=4时,函数f(x)=x^3的值  

一旦我们定义了f(x)=x^3,这个时候,我们就可以输入我们想要的x值,然后计算出x的三次方了。因此个人感觉theano的编程方式,跟我们数学思路一样,数学上一般是给定一个自变量x,定义一个函数(因变量),然后根据我们实际的x值,对因变量进行赋值。在深度学习中,每个样本就是一个x的不同赋值。

例2:S函数示例。再看一个例子,S函数的实现:


[python] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. import theano  
  2. x =theano.tensor.fscalar('x')#定义一个float类型的变量x  
  3. y= 1 / (1 + theano.tensor.exp(-x))#定义变量y  
  4. f= theano.function([x],y)#定义函数f,输入为x,输出为y  
  5. print f(3)#计算当x=3的时候,y的值  

上面的例子中,我们学到了一个非常重要的函数:theano.function,这个函数用于定义一个函数的自变量、因变量。

function网站为:http://deeplearning.net/software/theano/library/compile/function.html。函数的参数:function(inputs, outputs, mode=None, updates=None, givens=None, no_default_updates=False, accept_inplace=False, name=None,rebuild_strict=True, allow_input_downcast=None, profile=None, on_unused_input='raise'),参数看起来一大堆,不过我们一般只用到三个,inputs表示自变量、outputs表示函数的因变量(也就是函数的返回值),还有另外一个比较常用的是updates这个参数,这个一般用于神经网络共享变量参数更新,这个参数后面讲解。

例3:function使用示例

我们再看一个多个自变量、同时又有多个因变量的函数定义例子:

[python] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. #coding=utf-8  
  2. import theano  
  3. x, y =theano.tensor.fscalars('x''y')  
  4. z1= x + y  
  5. z2=x*y  
  6. f =theano.function([x,y],[z1,z2])#定义x、y为自变量,z1、z2为函数返回值(因变量)  
  7. print f(2,3)#返回当x=2,y=3的时候,函数f的因变量z1,z2的值  

二、必学函数

例1、求偏导数

theano有个很好用的函数,就是求函数的偏导数theano.grad(),比如上面的S函数,我们要求当x=3的时候,s函数的导数,代码如下:

[python] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. #coding=utf-8  
  2. import theano  
  3. x =theano.tensor.fscalar('x')#定义一个float类型的变量x  
  4. y= 1 / (1 + theano.tensor.exp(-x))#定义变量y  
  5. dx=theano.grad(y,x)#偏导数函数  
  6. f= theano.function([x],dx)#定义函数f,输入为x,输出为s函数的偏导数  
  7. print f(3)#计算当x=3的时候,函数y的偏导数  

例2、共享变量

共享变量是多线程编程中的一个名词,故名思议就是各线程,公共拥有的变量,这个是为了多线程高效计算、访问而使用的变量。因为深度学习中,我们整个计算过程基本上是多线程计算的,于是就需要用到共享变量。在程序中,我们一般把神经网络的参数W、b等定义为共享变量,因为网络的参数,基本上是每个线程都需要访问的。

[python] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. #coding=utf-8  
  2. import theano  
  3. import numpy  
  4. A=numpy.random.randn(3,4);#随机生成一个矩阵  
  5. x = theano.shared(A)#从A,创建共享变量x  
  6. print x.get_value()  
通过get_value()、set_value()可以查看、设置共享变量的数值。

例3、共享变量参数更新

前面我们提到theano.function函数,有个非常重要的参数updates,updates是一个包含两个元素的列表或tuple,updates=[old_w,new_w],当函数被调用的时候,这个会用new_w替换old_w,具体看一下下面例子:

[python] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. #coding=utf-8  
  2. import theano  
  3. w= theano.shared(1)#定义一个共享变量w,其初始值为1  
  4. x=theano.tensor.iscalar('x')  
  5. f=theano.function([x], w, updates=[[w, w+x]])#定义函数自变量为x,因变量为w,当函数执行完毕后,更新参数w=w+x  
  6. print f(3)#函数输出为w  
  7. print w.get_value()#这个时候可以看到w=w+x为4  

这个主要用于梯度下降的时候,要用到。比如updates=[w,w-α*(dT/dw)],其中dT/dw就是我们梯度下降的时候,损失函数对参数w的偏导数,α是学习率。

OK,下面开始进入实战阶段,实战阶段的源码主要参考自网站:http://deeplearning.net/tutorial/

三、实战阶段1—逻辑回归实现

打好了基础的扎马步阶段,接着我们就要开始进入学习实战招式了,先学一招最简答的招式,逻辑回归的实现:

[python] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. #coding=utf-8  
  2. import numpy  
  3. import theano  
  4. import theano.tensor as T  
  5. rng = numpy.random  
  6.   
  7. N = 10     # 我们为了测试,自己生成10个样本,每个样本是3维的向量,然后用于训练  
  8. feats = 3  
  9. D = (rng.randn(N, feats).astype(numpy.float32), rng.randint(size=N, low=0, high=2).astype(numpy.float32))  
  10.   
  11.   
  12. # 声明自变量x、以及每个样本对应的标签y(训练标签)  
  13. x = T.matrix("x")  
  14. y = T.vector("y")  
  15.   
  16. #随机初始化参数w、b=0,为共享变量  
  17. w = theano.shared(rng.randn(feats), name="w")  
  18. b = theano.shared(0., name="b")  
  19.   
  20. #构造损失函数  
  21. p_1 = 1 / (1 + T.exp(-T.dot(x, w) - b))   # s激活函数  
  22. xent = -y * T.log(p_1) - (1-y) * T.log(1-p_1) # 交叉商损失函数  
  23. cost = xent.mean() + 0.01 * (w ** 2).sum()# 损失函数的平均值+L2正则项,其中权重衰减系数为0.01  
  24. gw, gb = T.grad(cost, [w, b])             #对总损失函数求参数的偏导数  
  25.   
  26. prediction = p_1 > 0.5                    # 预测  
  27.   
  28. train = theano.function(inputs=[x,y],outputs=[prediction, xent],updates=((w, w - 0.1 * gw), (b, b - 0.1 * gb)))#训练所需函数  
  29. predict = theano.function(inputs=[x], outputs=prediction)#测试阶段函数  
  30.   
  31. #训练  
  32. training_steps = 1000  
  33. for i in range(training_steps):  
  34.     pred, err = train(D[0], D[1])  
  35.     print err.mean()#查看损失函数下降变化过程  
四、实战阶段2—mlp实现

接着学一个稍微牛逼一点,也就三层神经网络模型的实现,然后用于“手写字体识别”训练:



网络输出层的计算公式就是:


具体源码如下:

[python] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. #encoding:utf-8  
  2. import os  
  3. import sys  
  4. import timeit  
  5. import numpy  
  6. import theano  
  7. import theano.tensor as T  
  8. from logistic_sgd import LogisticRegression, load_data  
  9.   
  10.   
  11. class HiddenLayer(object):  
  12.     def __init__(self, rng, input, n_in, n_out, W=None, b=None,  
  13.                  activation=T.tanh):  
  14.         """ 
  15.         2、rng: numpy.random.RandomState是随机数生成器用于初始化W 
  16.         3、input: 类型为theano.tensor.dmatrix,是一个二维的矩阵(n_examples, n_in) 
  17.         第一维表示训练样本的个数,第二维表示特征维数,比如:input(i,j)表示第i个样本的第j个特征值 
  18.         4、n_in: 输入特征数,也就是输入神经元的个数 
  19.         5、n_out: 输出神经元的个数 
  20.         6、W如果有输入,那么为(n_in,n_out)大小的矩阵 
  21.         7、b如果有输入,那么为(n_out,)的向量 
  22.         8、activation活激活函数选项 
  23.  
  24.         """  
  25.         self.input = input  
  26.   
  27.   
  28.         '''''W的初始化选择[-a,a]进行均匀分布采样,其中如果激活函数选择tanh,则a=sqrt(6./(本层输入神经元数+本层输出神经元数)) 
  29.         如果激活函数是选择sigmoid,那么a=4*sqrt(6./(本层输入神经元数+本层输出神经元数)) 
  30.         dtype类型需要设置成theano.config.floatX,这样GPU才能调用'''  
  31.         if W is None:#如果外部没有输入W,那么创建W  
  32.             W_values = numpy.asarray(  
  33.                 rng.uniform(  
  34.                     low=-numpy.sqrt(6. / (n_in + n_out)),  
  35.                     high=numpy.sqrt(6. / (n_in + n_out)),  
  36.                     size=(n_in, n_out)  
  37.                 ),  
  38.                 dtype=theano.config.floatX  
  39.             )  
  40.             if activation == theano.tensor.nnet.sigmoid:  
  41.                 W_values *= 4  
  42.   
  43.             W = theano.shared(value=W_values, name='W', borrow=True)  
  44.   
  45.         if b is None:#如果外部没有输入b,那么创建b  
  46.             b_values = numpy.zeros((n_out,), dtype=theano.config.floatX)  
  47.             b = theano.shared(value=b_values, name='b', borrow=True)  
  48.   
  49.         self.W = W  
  50.         self.b = b  
  51.         #激活函数映射  
  52.         lin_output = T.dot(input, self.W) + self.b  
  53.         self.output = (  
  54.             lin_output if activation is None  
  55.             else activation(lin_output)  
  56.         )  
  57.         # 模型参数  
  58.         self.params = [self.W, self.b]  
  59.   
  60.   
  61. # MLP类是三层神经网络:输入,隐层,输出,第一层为简单的人工神经网络,第二层为逻辑回归层  
  62. class MLP(object):  
  63.   
  64.     def __init__(self, rng, input, n_in, n_hidden, n_out):  
  65.         """ 
  66.         n_in: 输入层神经元个数 
  67.  
  68.         n_hidden: 隐层神经元个数 
  69.  
  70.         n_out:输出层神经元个数 
  71.  
  72.         """  
  73.   
  74.         # 创建隐藏层  
  75.         self.hiddenLayer = HiddenLayer(  
  76.             rng=rng,  
  77.             input=input,  
  78.             n_in=n_in,  
  79.             n_out=n_hidden,  
  80.             activation=T.tanh  
  81.         )  
  82.   
  83.         # 创建逻辑回归层  
  84.         self.logRegressionLayer = LogisticRegression(  
  85.             input=self.hiddenLayer.output,  
  86.             n_in=n_hidden,  
  87.             n_out=n_out  
  88.         )  
  89.   
  90.         # 整个网络的L1正则项,也就是使得所有的链接权值W的绝对值总和最小化  
  91.         self.L1 = (  
  92.             abs(self.hiddenLayer.W).sum()  
  93.             + abs(self.logRegressionLayer.W).sum()  
  94.         )  
  95.   
  96.         # 整个网络的L2正则项,也就是使得所有的链接权值的平方和最小化  
  97.         self.L2_sqr = (  
  98.             (self.hiddenLayer.W ** 2).sum()  
  99.             + (self.logRegressionLayer.W ** 2).sum()  
  100.         )  
  101.   
  102.         #  
  103.         self.negative_log_likelihood = (  
  104.             self.logRegressionLayer.negative_log_likelihood  
  105.         )  
  106.         # same holds for the function computing the number of errors  
  107.         self.errors = self.logRegressionLayer.errors  
  108.   
  109.         # 把所有的参数保存到同一个列表中,这样后面可以直接求导  
  110.         self.params = self.hiddenLayer.params + self.logRegressionLayer.params  
  111.   
  112.         self.input = input  
  113.   
  114. #手写数字识别测试,BP算法  
  115. def test_mlp(learning_rate=0.01, L1_reg=0.00, L2_reg=0.0001, n_epochs=1000,  
  116.              dataset='mnist.pkl.gz', batch_size=20, n_hidden=500):  
  117.     """ 
  118.     learning_rate: 梯度下降法的学习率 
  119.  
  120.     L1_reg: L1正则项的权值 
  121.  
  122.     L2_reg:L2正则项的权值 
  123.  
  124.     n_epochs:最大迭代次数 
  125.      
  126.     dataset:里面的数据是28*28的手写图片数据 
  127.  
  128.    """  
  129.     datasets = load_data(dataset)  
  130.   
  131.     train_set_x, train_set_y = datasets[0]  
  132.     valid_set_x, valid_set_y = datasets[1]  
  133.     test_set_x, test_set_y = datasets[2]  
  134.   
  135.     # 批量训练,计算总共有多少批  
  136.     n_train_batches = train_set_x.get_value(borrow=True).shape[0] / batch_size  
  137.     n_valid_batches = valid_set_x.get_value(borrow=True).shape[0] / batch_size  
  138.     n_test_batches = test_set_x.get_value(borrow=True).shape[0] / batch_size  
  139.   
  140.   
  141.   
  142.     # 分配符号变量  
  143.     index = T.lscalar()  # index to a [mini]batch  
  144.     x = T.matrix('x')  # 训练数据  
  145.     y = T.ivector('y')  # 训练数据的标签  
  146.   
  147.     rng = numpy.random.RandomState(1234)  
  148.   
  149.     # 构建三层神经网络  
  150.     classifier = MLP(  
  151.         rng=rng,  
  152.         input=x,  
  153.         n_in=28 * 28,  
  154.         n_hidden=n_hidden,  
  155.         n_out=10  
  156.     )  
  157.   
  158.     # 计算损失函数  
  159.     cost = (  
  160.         classifier.negative_log_likelihood(y)  
  161.         + L1_reg * classifier.L1  
  162.         + L2_reg * classifier.L2_sqr  
  163.     )  
  164.     #损失函数求解偏导数  
  165.     gparams = [T.grad(cost, param) for param in classifier.params]  
  166.     # 梯度下降法参数更新  
  167.     updates = [  
  168.         (param, param - learning_rate * gparam)  
  169.         for param, gparam in zip(classifier.params, gparams)  
  170.     ]  
  171.   
  172.   
  173.     #定义训练函数  
  174.     train_model = theano.function(  
  175.         inputs=[index],  
  176.         outputs=cost,  
  177.         updates=updates,  
  178.         givens={  
  179.             x: train_set_x[index * batch_size: (index + 1) * batch_size],  
  180.             y: train_set_y[index * batch_size: (index + 1) * batch_size]  
  181.         }  
  182.     )  
  183.   
  184.     #跑起来,训练起来,搞起  
  185.     epoch=0  
  186.     while (epoch <10):  
  187.         cost=0  
  188.         for minibatch_index in xrange(n_train_batches):  
  189.             cost+= train_model(minibatch_index)  
  190.         print 'epoch:',epoch,'    error:',cost/n_train_batches  
  191.         epoch = epoch + 1  
  192.   
  193.   
  194. #跑起来,网络train起来  
  195. test_mlp()  

五、实战阶段3—最简单的卷积神经网络实现

最后再学一招最牛逼的,也就是卷积神经网络的实现,下面是一个手写字体lenet5的实现:

[python] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. #-*-coding:utf-8-*-  
  2. import theano  
  3. import numpy as np  
  4. import matplotlib.pyplot as plt       
  5. from loaddata import loadmnist  
  6. import theano.tensor as T  
  7.   
  8.   
  9. #softmax函数  
  10. class softmax:  
  11.     #outdata为我们标注的输出,hiddata网络输出层的输入,nin,nout为输入、输出神经元个数  
  12.     def __init__(self,hiddata,outdata,nin,nout):  
  13.   
  14.         self.w=theano.shared(value=np.zeros((nin,nout),dtype=theano.config.floatX),name='w');  
  15.         self.b=theano.shared(value=np.zeros((nout,),dtype=theano.config.floatX),name='b')  
  16.   
  17.         prey=T.nnet.softmax(T.dot(hiddata,self.w)+self.b)#通过softmax函数,得到输出层每个神经元数值(概率)  
  18.         self.loss=-T.mean(T.log(prey)[T.arange(outdata.shape[0]),outdata])#损失函数  
  19.         self.para=[self.w,self.b]  
  20.         self.predict=T.argmax(prey,axis=1)  
  21.         self.error=T.mean(T.neq(T.argmax(prey,axis=1),outdata))  
  22.           
  23.   
  24.           
  25.   
  26. #输入层到隐藏层  
  27. class HiddenLayer:  
  28.     def __init__(self,inputx,nin,nout):  
  29.          a=np.sqrt(6./(nin+nout))  
  30.          ranmatrix=np.random.uniform(-a,a,(nin,nout));  
  31.          self.w=theano.shared(value=np.asarray(ranmatrix,dtype=theano.config.floatX),name='w')  
  32.          self.b=theano.shared(value=np.zeros((nout,),dtype=theano.config.floatX),name='b')  
  33.          self.out=T.tanh(T.dot(inputx,self.w)+self.b)  
  34.          self.para=[self.w,self.b]  
  35. #传统三层感知器      
  36. class mlp:  
  37.     def __init__(self,nin,nhid,nout):  
  38.         x=T.fmatrix('x')  
  39.         y=T.ivector('y')  
  40.         #前向  
  41.         hlayer=HiddenLayer(x,nin,nhid)  
  42.         olayer=softmax(hlayer.out,y,nhid,nout)  
  43.         #反向  
  44.         paras=hlayer.para+olayer.para  
  45.         dparas=T.grad(olayer.loss,paras)  
  46.         updates=[(para,para-0.1*dpara) for para,dpara in zip(paras,dparas)]  
  47.         self.trainfunction=theano.function(inputs=[x,y],outputs=olayer.loss,updates=updates)  
  48.   
  49.           
  50.     def train(self,trainx,trainy):  
  51.         return self.trainfunction(trainx,trainy)  
  52. #卷积神经网络的每一层,包括卷积、池化、激活映射操作  
  53. #img_shape为输入特征图,img_shape=(batch_size,特征图个数,图片宽、高)  
  54. #filter_shape为卷积操作相关参数,filter_shape=(输入特征图个数、输出特征图个数、卷积核的宽、卷积核的高)  
  55. #,这样总共filter的个数为:输入特征图个数*输出特征图个数*卷积核的宽*卷积核的高  
  56. class LeNetConvPoolLayer:  
  57.     def __init__(self,inputx,img_shape,filter_shape,poolsize=(2,2)):  
  58.         #参数初始化  
  59.         assert img_shape[1]==filter_shape[1]  
  60.         a=np.sqrt(6./(filter_shape[0]+filter_shape[1]))  
  61.   
  62.         v=np.random.uniform(low=-a,high=a,size=filter_shape)  
  63.   
  64.         wvalue=np.asarray(v,dtype=theano.config.floatX)  
  65.         self.w=theano.shared(value=wvalue,name='w')  
  66.         bvalue=np.zeros((filter_shape[0],),dtype=theano.config.floatX)  
  67.         self.b=theano.shared(value=bvalue,name='b')  
  68.           
  69.         covout=T.nnet.conv2d(inputx,self.w)#卷积操作  
  70.           
  71.         covpool=T.signal.downsample.max_pool_2d(covout,poolsize)#池化操作  
  72.           
  73.         self.out=T.tanh(covpool+self.b.dimshuffle('x'0'x''x'))  
  74.           
  75.         self.para=[self.w,self.b]  
  76.       
  77.           
  78.            
  79. #读取手写字体数据  
  80. trainx,trainy=loadmnist()  
  81. trainx=trainx.reshape(-1,1,28,28)  
  82. batch_size=30  
  83. m=trainx.shape[0]  
  84. ne=m/batch_size  
  85.   
  86.   
  87.   
  88. batchx=T.tensor4(name='batchx',dtype=theano.config.floatX)#定义网络的输入x  
  89. batchy=T.ivector('batchy')#定义输出y  
  90.   
  91. #第一层卷积层  
  92. cov1_layer=LeNetConvPoolLayer(inputx=batchx,img_shape=(batch_size,1,28,28),filter_shape=(20,1,5,5))  
  93. cov2_layer=LeNetConvPoolLayer(inputx=cov1_layer.out,img_shape=(batch_size,20,12,12),filter_shape=(50,20,5,5))#第二层卷积层  
  94. cov2out=cov2_layer.out.flatten(2)#从卷积层到全连接层,把二维拉成一维向量  
  95. hlayer=HiddenLayer(cov2out,4*4*50,500)#隐藏层  
  96. olayer=softmax(hlayer.out,batchy,500,10)#  
  97.   
  98. paras=cov1_layer.para+cov2_layer.para+hlayer.para+olayer.para#网络的所有参数,把它们写在同一个列表里  
  99. dparas=T.grad(olayer.loss,paras)#损失函数,梯度求导  
  100. updates=[(para,para-0.1*dpara) for para,dpara in zip(paras,dparas)]#梯度下降更新公式  
  101.   
  102. train_function=theano.function(inputs=[batchx,batchy],outputs=olayer.loss,updates=updates)#定义输出变量、输出变量  
  103. test_function=theano.function(inputs=[batchx,batchy],outputs=[olayer.error,olayer.predict])  
  104.   
  105. testx,testy=loadmnist(True)  
  106. testx=testx.reshape(-1,1,28,28)  
  107.   
  108. train_history=[]  
  109. test_history=[]  
  110.   
  111. for it in range(20):  
  112.     sum=0  
  113.     for i in range(ne):  
  114.         a=trainx[i*batch_size:(i+1)*batch_size]  
  115.         loss_train=train_function(trainx[i*batch_size:(i+1)*batch_size],trainy[i*batch_size:(i+1)*batch_size])  
  116.         sum=sum+loss_train  
  117.     sum=sum/ne   
  118.     print 'train_loss:',sum  
  119.     test_error,predict=test_function(testx,testy)  
  120.     print 'test_error:',test_error  
  121.       
  122.     train_history=train_history+[sum]  
  123.     test_history=test_history+[test_error]  
  124. n=len(train_history)  
  125. fig1=plt.subplot(111)  
  126. fig1.set_ylim(0.001,0.2)  
  127. fig1.plot(np.arange(n),train_history,'-')  

参考文献:

1、http://deeplearning.net/software/theano/tutorial/index.html#tutorial

*************作者:hjimce     联系qq:1393852684   更多资源请关注我的博客:http://blog.csdn.net/hjimce                原创文章,转载请保留本行信息*********************

0 0
原创粉丝点击