游戏电视苹果数码历史美丽
投稿投诉
美丽时装
彩妆资讯
历史明星
乐活安卓
数码常识
驾车健康
苹果问答
网络发型
电视车载
室内电影
游戏科学
音乐整形

手把手教你如何自己设计实现一个深度学习框架(附代码实现)

  作者丨王桂波知乎(已授权)
  来源丨https:zhuanlan。zhihu。comp78713744
  编辑丨极市平台
  当前深度学习框架越来越成熟,对于使用者而言封装程度越来越高,好处就是现在可以非常快速地将这些框架作为工具使用,用非常少的代码就可以构建模型进行实验,坏处就是可能背后地实现都被隐藏起来了。在这篇文章里笔者将设计和实现一个、轻量级的(约200行)、易于扩展的深度学习框架tinynn(基于Python和Numpy实现),希望对大家了解深度学习的基本组件、框架的设计和实现有一定的帮助。
  本文首先会从深度学习的流程开始分析,对神经网络中的关键组件抽象,确定基本框架;然后再对框架里各个组件进行代码实现;最后基于这个框架实现了一个MNIST分类的示例,并与Tensorflow做了简单的对比验证。目录组件抽象组件实现整体结构MNIST例子总结附录参考组件抽象
  首先考虑神经网络运算的流程,神经网络运算主要包含训练training和预测predict(或inference)两个阶段,训练的基本流程是:输入数据网络层前向传播计算损失网络层反向传播梯度更新参数,预测的基本流程是输入数据网络层前向传播输出结果。从运算的角度看,主要可以分为三种类型的计算:数据在网络层之间的流动:前向传播和反向传播可以看做是张量Tensor(多维数组)在网络层之间的流动(前向传播流动的是输入输出,反向传播流动的是梯度),每个网络层会进行一定的运算,然后将结果输入给下一层计算损失:衔接前向和反向传播的中间过程,定义了模型的输出与真实值之间的差异,用来后续提供反向传播所需的信息参数更新:使用计算得到的梯度对网络参数进行更新的一类计算
  基于这个三种类型,我们可以对网络的基本组件做一个抽象tensor张量,这个是神经网络中数据的基本单位layer网络层,负责接收上一层的输入,进行该层的运算,将结果输出给下一层,由于tensor的流动有前向和反向两个方向,因此对于每种类型网络层我们都需要同时实现forward和backward两种运算loss损失,在给定模型预测值与真实值之后,该组件输出损失值以及关于最后一层的梯度(用于梯度回传)optimizer优化器,负责使用梯度更新模型的参数
  然后我们还需要一些组件把上面这个4种基本组件整合到一起,形成一个pipelinenet组件负责管理tensor在layers之间的前向和反向传播,同时能提供获取参数、设置参数、获取梯度的接口model组件负责整合所有组件,形成整个pipeline。即net组件进行前向传播losses组件计算损失和梯度net组件将梯度反向传播optimizer组件将梯度更新到参数。
  基本的框架图如下图
  组件实现
  按照上面的抽象,我们可以写出整个流程代码如下。definemodelnetNet(〔layer1,layer2,。。。〕)modelModel(net,lossfn,optimizer)trainingpredmodel。forward(trainX)loss,gradsmodel。backward(pred,trainY)model。applygrad(grads)inferencetestpredmodel。forward(testX)
  首先定义net,net的输入是多个网络层,然后将net、loss、optimizer一起传给model。model实现了forward、backward和applygrad三个接口分别对应前向传播、反向传播和参数更新三个功能。接下来我们看这里边各个部分分别如何实现。tensor
  tensor张量是神经网络中基本的数据单位,我们这里直接使用numpy。ndarray类作为tensor类的实现layer
  上面流程代码中model进行forward和backward,其实底层都是网络层在进行实际运算,因此网络层需要有提供forward和backward接口进行对应的运算。同时还应该将该层的参数和梯度记录下来。先实现一个基类如下layer。pyclassLayer(object):definit(self,name):self。namenameself。params,self。gradsNone,Nonedefforward(self,inputs):raiseNotImplementedErrordefbackward(self,grad):raiseNotImplementedError
  最基础的一种网络层是全连接网络层,实现如下。forward方法接收上层的输入inputs,实现的运算;backward的方法接收来自上层的梯度,计算关于参数和输入的梯度,然后返回关于输入的梯度。这三个梯度的推导可以见附录,这里直接给出实现。winit和binit分别是参数和的初始化器,这个我们在另外的一个实现初始化器中文件initializer。py去实现,这部分不是核心部件,所以在这里不展开介绍。layer。pyclassDense(Layer):definit(self,numin,numout,winitXavierUniformInit(),binitZerosInit()):super()。init(Linear)self。params{w:winit(〔numin,numout〕),b:binit(〔1,numout〕)}self。inputsNonedefforward(self,inputs):self。inputsinputsreturninputsself。params〔w〕self。params〔b〕defbackward(self,grad):self。grads〔w〕self。inputs。Tgradself。grads〔b〕np。sum(grad,axis0)returngradself。params〔w〕。T
  同时神经网络中的另一个重要的部分是激活函数。激活函数可以看做是一种网络层,同样需要实现forward和backward方法。我们通过继承Layer类实现激活函数类,这里实现了最常用的ReLU激活函数。func和derivationfunc方法分别实现对应激活函数的正向计算和梯度计算。layer。pyclassActivation(Layer):Baseactivationlayerdefinit(self,name):super()。init(name)self。inputsNonedefforward(self,inputs):self。inputsinputsreturnself。func(inputs)defbackward(self,grad):returnself。derivativefunc(self。inputs)graddeffunc(self,x):raiseNotImplementedErrordefderivativefunc(self,x):raiseNotImplementedErrorclassReLU(Activation):ReLUactivationfunctiondefinit(self):super()。init(ReLU)deffunc(self,x):returnnp。maximum(x,0。0)defderivativefunc(self,x):returnx0。0net
  上文提到net类负责管理tensor在layers之间的前向和反向传播。forward方法很简单,按顺序遍历所有层,每层计算的输出作为下一层的输入;backward则逆序遍历所有层,将每层的梯度作为下一层的输入。这里我们还将每个网络层参数的梯度保存下来返回,后面参数更新需要用到。另外net类还实现了获取参数、设置参数、获取梯度的接口,也是后面参数更新时需要用到net。pyclassNet(object):definit(self,layers):self。layerslayersdefforward(self,inputs):forlayerinself。layers:inputslayer。forward(inputs)returninputsdefbackward(self,grad):allgrads〔〕forlayerinreversed(self。layers):gradlayer。backward(grad)allgrads。append(layer。grads)returnallgrads〔::1〕defgetparamsandgrads(self):forlayerinself。layers:yieldlayer。params,layer。gradsdefgetparameters(self):return〔layer。paramsforlayerinself。layers〕defsetparameters(self,params):fori,layerinenumerate(self。layers):forkeyinlayer。params。keys():layer。params〔key〕params〔i〕〔key〕losses
  上文我们提到losses组件需要做两件事情,给定了预测值和真实值,需要计算损失值和关于预测值的梯度。我们分别实现为loss和grad两个方法,这里我们实现多分类回归常用的SoftmaxCrossEntropyLoss损失。这个的损失loss和梯度grad的计算公式推导进文末附录,这里直接给出结果:多分类softmax交叉熵的损失为
  梯度稍微复杂一点,目标类别和非目标类别的计算公式不同。对于目标类别维度,其梯度为对应维度模型输出概率减一,对于非目标类别维度,其梯度为对应维度输出概率本身。
  代码实现如下loss。pyclassBaseLoss(object):defloss(self,predicted,actual):raiseNotImplementedErrordefgrad(self,predicted,actual):raiseNotImplementedErrorclassCrossEntropyLoss(BaseLoss):defloss(self,predicted,actual):mpredicted。shape〔0〕expsnp。exp(predictednp。max(predicted,axis1,keepdimsTrue))pexpsnp。sum(exps,axis1,keepdimsTrue)nllnp。log(np。sum(pactual,axis1))returnnp。sum(nll)mdefgrad(self,predicted,actual):mpredicted。shape〔0〕gradnp。copy(predicted)gradactualreturngradmoptimizer
  optimizer主要实现一个接口computestep,这个方法根据当前的梯度,计算返回实际优化时每个参数改变的步长。我们在这里实现常用的Adam优化器。optimizer。pyclassBaseOptimizer(object):definit(self,lr,weightdecay):self。lrlrself。weightdecayweightdecaydefcomputestep(self,grads,params):steplist()flattenallgradientsflattengradsnp。concatenate(〔np。ravel(v)forgradingradsforvingrad。values()〕)computestepflattenstepself。computestep(flattengrads)reshapegradientsp0forparaminparams:layerdict()fork,vinparam。items():blocknp。prod(v。shape)stepflattenstep〔p:pblock〕。reshape(v。shape)stepself。weightdecayvlayer〔k〕steppblockstep。append(layer)returnstepdefcomputestep(self,grad):raiseNotImplementedErrorclassAdam(BaseOptimizer):definit(self,lr0。001,beta10。9,beta20。999,eps1e8,weightdecay0。0):super()。init(lr,weightdecay)self。b1,self。b2beta1,beta2self。epsepsself。t0self。m,self。v0,0defcomputestep(self,grad):self。t1self。mself。b1self。m(1self。b1)gradself。vself。b2self。v(1self。b2)(grad2)biascorrectionmself。m(1self。b1self。t)vself。v(1self。b2self。t)returnself。lrm(v0。5self。eps)model
  最后model类实现了我们一开始设计的三个接口forward、backward和applygrad,forward直接调用net的forward,backward中把net、loss、optimizer串起来,先计算损失loss,然后反向传播得到梯度,然后optimizer计算步长,最后由applygrad对参数进行更新model。pyclassModel(object):definit(self,net,loss,optimizer):self。netnetself。losslossself。optimizeroptimizerdefforward(self,inputs):returnself。net。forward(inputs)defbackward(self,preds,targets):lossself。loss。loss(preds,targets)gradself。loss。grad(preds,targets)gradsself。net。backward(grad)paramsself。net。getparameters()stepself。optimizer。computestep(grads,params)returnloss,stepdefapplygrad(self,grads):forgrad,(param,)inzip(grads,self。net。getparamsandgrads()):fork,vinparam。items():param〔k〕grad〔k〕整体结构
  最后我们实现出来核心代码部分文件结构如下tinynncoreinitializer。pylayer。pyloss。pymodel。pynet。pyoptimizer。py
  其中initializer。py这个模块上面没有展开讲,主要实现了常见的参数初始化方法(零初始化、Xavier初始化、He初始化等),用于给网络层初始化参数。MNIST例子
  框架基本搭起来后,我们找一个例子来用tinynn这个框架run起来。这个例子的基本一些配置如下数据集:MNIST任务类型:多分类网络结构:三层全连接INPUT(784)FC(400)FC(100)OUTPUT(10),这个网络接收的输入,其中是每次输入的样本数,784是每张的图像展平后的向量,输出维度为,其中是样本数,10是对应图片在10个类别上的概率激活函数:ReLU损失函数:SoftmaxCrossEntropyoptimizer:Adam(lr1e3)batchsize:128Numepochs:20
  这里我们忽略数据载入、预处理等一些准备代码,只把核心的网络结构定义和训练的代码贴出来如下examplemnistrun。pynetNet(〔Dense(784,400),ReLU(),Dense(400,100),ReLU(),Dense(100,10)〕)modelModel(netnet,lossSoftmaxCrossEntropyLoss(),optimizerAdam(lrargs。lr))iteratorBatchIterator(batchsizeargs。batchsize)evaluatorAccEvaluator()forepochinrange(numep):forbatchiniterator(trainx,trainy):trainingpredmodel。forward(batch。inputs)loss,gradsmodel。backward(pred,batch。targets)model。applygrad(grads)evaluateeveryepochtestpredmodel。forward(testx)testpredidxnp。argmax(testpred,axis1)testyidxnp。asarray(testy)resevaluator。evaluate(testpredidx,testyidx)print(res)
  运行结果如下tinynnEpoch0{totalnum:10000,hitnum:9658,accuracy:0。9658}Epoch1{totalnum:10000,hitnum:9740,accuracy:0。974}Epoch2{totalnum:10000,hitnum:9783,accuracy:0。9783}Epoch3{totalnum:10000,hitnum:9799,accuracy:0。9799}Epoch4{totalnum:10000,hitnum:9805,accuracy:0。9805}Epoch5{totalnum:10000,hitnum:9826,accuracy:0。9826}Epoch6{totalnum:10000,hitnum:9823,accuracy:0。9823}Epoch7{totalnum:10000,hitnum:9819,accuracy:0。9819}Epoch8{totalnum:10000,hitnum:9820,accuracy:0。982}Epoch9{totalnum:10000,hitnum:9838,accuracy:0。9838}Epoch10{totalnum:10000,hitnum:9825,accuracy:0。9825}Epoch11{totalnum:10000,hitnum:9810,accuracy:0。981}Epoch12{totalnum:10000,hitnum:9845,accuracy:0。9845}Epoch13{totalnum:10000,hitnum:9845,accuracy:0。9845}Epoch14{totalnum:10000,hitnum:9835,accuracy:0。9835}Epoch15{totalnum:10000,hitnum:9817,accuracy:0。9817}Epoch16{totalnum:10000,hitnum:9815,accuracy:0。9815}Epoch17{totalnum:10000,hitnum:9835,accuracy:0。9835}Epoch18{totalnum:10000,hitnum:9826,accuracy:0。9826}Epoch19{totalnum:10000,hitnum:9819,accuracy:0。9819}
  可以看到测试集accuracy随着训练进行在慢慢提升,这说明数据在框架中确实按照正确的方式进行流动和计算,参数得到正确的更新。为了对比下效果,我用Tensorflow1。13实现了相同的网络结构、采用相同的采数初始化方法、优化器配置等等,得到的结果如下Tensorflow1。13。1Epoch0{totalnum:10000,hitnum:9591,accuracy:0。9591}Epoch1{totalnum:10000,hitnum:9734,accuracy:0。9734}Epoch2{totalnum:10000,hitnum:9706,accuracy:0。9706}Epoch3{totalnum:10000,hitnum:9756,accuracy:0。9756}Epoch4{totalnum:10000,hitnum:9722,accuracy:0。9722}Epoch5{totalnum:10000,hitnum:9772,accuracy:0。9772}Epoch6{totalnum:10000,hitnum:9774,accuracy:0。9774}Epoch7{totalnum:10000,hitnum:9789,accuracy:0。9789}Epoch8{totalnum:10000,hitnum:9766,accuracy:0。9766}Epoch9{totalnum:10000,hitnum:9763,accuracy:0。9763}Epoch10{totalnum:10000,hitnum:9791,accuracy:0。9791}Epoch11{totalnum:10000,hitnum:9773,accuracy:0。9773}Epoch12{totalnum:10000,hitnum:9804,accuracy:0。9804}Epoch13{totalnum:10000,hitnum:9782,accuracy:0。9782}Epoch14{totalnum:10000,hitnum:9800,accuracy:0。98}Epoch15{totalnum:10000,hitnum:9837,accuracy:0。9837}Epoch16{totalnum:10000,hitnum:9811,accuracy:0。9811}Epoch17{totalnum:10000,hitnum:9793,accuracy:0。9793}Epoch18{totalnum:10000,hitnum:9818,accuracy:0。9818}Epoch19{totalnum:10000,hitnum:9811,accuracy:0。9811}
  可以看到两者效果上大差不差,测试集准确率都收敛到0。982左右,就单次的实验看比Tensorflow稍微好一点点。总结
  tinynn相关的源代码在这个repo里。目前支持:layer:全连接层、2D卷积层、2D反卷积层、MaxPooling层、Dropout层、BatchNormalization层、RNN层以及ReLU、Sigmoid、Tanh、LeakyReLU、SoftPlus等激活函数loss:SigmoidCrossEntropy、SoftmaxCrossEntroy、MSE、MAE、Huberoptimizer:RAam、Adam、SGD、RMSProp、Momentum等优化器,并且增加了动态调节学习率LRScheduler实现了mnist(分类)、nnpaint(回归)、DQN(强化学习)、AutoEncoder和DCGAN(无监督)等常见模型。见tinynnexamples
  tinynn还有很多可以继续完善的地方受限于时间还没有完成,笔者在空闲时间会进行维护和更新。
  当然tinynn只是一个玩具版本的深度学习框架,一个成熟的深度学习框架至少还需要:支持自动求导、高运算效率(静态语言加速、支持GPU加速)、提供丰富的算法实现、提供易用的接口和详细的文档等等。这个小项目的出发点更多地是学习,在设计和实现tinynn的过程中笔者个人学习确实到了很多东西,包括如何抽象、如何设计组件接口、如何更效率的实现、算法的具体细节等等。对笔者而言写这个小框架除了了解深度学习框架的设计与实现之外还有一个好处:后续可以在这个框架上快速地实现一些新的算法,新的参数初始化方法,新的优化算法,新的网络结构设计,都可以快速地在这个小框架上进行实验。如果你对自己设计实现一个深度学习框架也感兴趣,希望看完这篇文章会对你有所帮助,也欢迎大家提PR一起贡献代码附录:Softmax交叉熵损失和梯度推导
  多分类下交叉熵损失如下式:
  其中分别是真实值和模型预测值,是样本数,是类别个数。由于真实值一般为一个onehot向量(除了真实类别维度为1其他均为0),因此上式可以化简为
  其中是代表真实类别,代表第个样本类的预测概率。即我们需要计算的是每个样本在真实类别上的预测概率的对数的和,然后再取负就是交叉熵损失。接下来推导如何求解该损失关于模型输出的梯度,用表示模型输出,在多分类中通常最后会使用Softmax将网络的输出归一化为一个概率分布,则Softmax后的输出为
  代入上面的损失函数
  求解关于输出向量的梯度,可以将分为目标类别所在维度和非目标类别维度。首先看目标类别所在维度
  再看非目标类别所在维度
  可以看到对于目标类别维度,其梯度为对应维度模型输出概率减一,对于非目标类别维度,其梯度为对应维度输出概率真身。
  参考DeepLearning,Goodfellow,etal。(2016)JoelGrusLivecodingMadnessLetsBuildaDeepLearningLibraryTensorFlowDocumentationPyTorchDocumentation

每日一学党史学习教育(一百九十八)党史知识问答1919年5月,中国最早的马克思主义者()在《新青年》上发表《我的马克思主义观》,系统介绍了马克思主义。A。陈独秀B。李大钊C。周恩来……宫井洞事件朴槿惠之父朴正熙之死朴正熙1917年11月14日出生于庆尚北道龟尾市,幼时家境贫寒,兄弟姐妹众多,据说他的母亲怀上他的时候,并不想再要一个孩子,试了各种土办法想流产,包括喝滚烫的酱油,从山上滚下来……特伦斯戴维斯福克斯绝对是全明星,入选全明星是他应得的国王以133128击败独行侠。赛后,国王球员特伦斯戴维斯接受媒体采访,谈到了队友达龙福克斯。这就是达龙福克斯应有的表现。戴维斯在采访中表示。他绝对是全明星,入……三皇五帝是谁,神话故事里的人真的存在吗?中国有5000年的历史,从秦始皇开始,中国有407位皇帝,但我们所说的三位皇帝和五位皇帝不在407位以内。那么这三位皇帝和五位皇帝呢?既然不在皇帝之列,为什么要称之为三皇五帝?……谁是三国第一小人?我认为是吕布,张飞经常骂吕布为三姓家奴!吕布见利忘义,首根丁原,认丁原为父,董卓进京后,专横跋扈,上欺天子,下压群臣,无人敢惹。丁原看不过董卓的骄横,起兵伐卓,吕布……卫星通信企业超2。5万家竞逐万亿级大市场本报记者李玉洋李正豪上海报道虽然卫星通信早已不是新鲜事,华为和苹果的新尝试却赋予消费者一份踏实感,让这种捅破天的功能攥在消费者的智能手机里。就在华为常务董事、终端事……一文了解清朝科举制选拔考试从破蒙到状元科举在漫长的历史发展中,科举制形成了完备的考试选拔体系,最大程度地实现了文官任用机会上的平等化,其公正的选拔方式成为世界上当前最广泛的选拔方式。第一步:开笔破蒙古代科举制……小米平板5和联想小新PadPro2022怎么选择?如果只是用来视频娱乐、游戏,那么价格更便宜的小米平板5就足够了。而且小米的软件系统、手机联动,都会比联想好一些,当然只是稍稍好一点而已。看看详细参数配置对比:……韦唯向美国人普及中国春节挂红灯笼,派利是,用西式餐具吃川菜作为一个经常出现在中国乐坛的歌手,韦唯因为嫁给美国人,并且生了三个儿子,如今还定居美国,估计早就不是中国国籍了。但从韦唯分享的中国除夕夜安排来看,虽然现在美国跟中国有时差,韦唯……他是邓小平的得力助手,年逾80时,致信中央,要求停播一部电视在革命年代,有无数的先辈和烈士,为了赶走侵略者,为了建立一个新中国,抛头颅洒热血,即便是付出自己的性命也在所不惜。也正是因为他们不怕苦不怕难的精神,才能有如今的新中国。他……世界上有没有龙,东方龙和西方龙的差距在哪中国的龙文化几乎有近万年的历史,是中国文化的重要的符号。也是我国的十二生肖之一,与其它生肖不同的是龙的形象对我们来说是虚无缥缈的,只能从一些文献来拼凑其原型。出现在我国古代一些……罕见一幕!继男子3米跳板后世锦赛再次出现0分跳水,裁判羞愧捂2022年FINA世锦赛,女子3米板预赛再现0分跳水。巴西选手罗德里格斯在第四轮完成107C时出现失误,结果0分。中国队表现出色,陈榛和常占据前两名,齐头并进!这次比赛出现了尴……
俄国人当初扩张领土,为什么不往南方扩张,而一直往北方严寒地区13世纪末,欧洲东部诞生了一个国家:莫斯科大公国。当时在欧洲它还是个不起眼的国家,领土还没有北京市大。谁也没有想到就是这样一个默默无闻的小国日后那么疯狂成为叫全世界恐惧的国家。……詹姆斯比之杜兰特又高下如何?老詹命中率不及后者职业荣誉却优胜近日NBA2K公布了2K23游戏中能力值最高的11名球员,字母哥一骑绝尘以97分领跑榜首,第二位置则就显得热闹许多,杜兰特、詹姆斯、库里、约基奇、恩比德五人并列第二,以96分仅……病人不忌嘴,医生跑断腿!生什么病忌什么口!这份清单请收好俗话说,病从口入,其实,饮食与疾病之间虽然并不存在直接的因果关系,但如果一个正常的健康人,长期保持着高脂、高糖以及高盐饮食等不良饮食习惯,的确会大大增加高血压、高血脂、糖尿病等……诸葛亮北伐失败的原因诸葛亮北伐是相当有名的一个历史大事件,由于当时蜀国的实力接近枯竭贻尽,丢失荆州以后,蜀国只能故步自封,要想改变现状,不被温水煮青蛙,就必须要扩大地盘。为了让国民能生存下去,诸葛……苏联解体的原因贡献与展望1991年12月26日解体,红色巨人轰然倒下,令人唏嘘。本文就剖析背后的原因,历史贡献和今朝展望。时间回到1924年,斯大林上台执政近三十年,1953年斯大林谢幕,苏联也……亚洲唯一的触角沙滩〔提示〕:沙滩、栈道、木船、礁石、海带、贝壳是特色位于晋江金井镇。一个阳光暖暖微风缓缓的下午,来到了很小众的触角沙滩。因为这里是塘东村,所以也叫塘东沙堤。触角沙滩整……2023年春季日系叠穿只要叠穿的内搭不选择版型过于宽松、材质过厚的单品,不管什么身型都是能驾驭叠穿滴,穿出即美观大方又修饰身材的视觉效果。下面是15款常见的日系叠穿。喜欢的收藏哈。〔笑〕……第七批国家级制造业单项冠军公示,这些鲁企入选制造业单项冠军是指长期专注于制造业某些细分产品市场,生产技术或工艺国际领先,单项产品市场占有率位居全球或国内前列的企业,代表全球制造业细分领域最高发展水平、最强市场实力。单项冠……中国雪景界天花板自驾路线!穿越中俄边境,赏世界级冰雪奇景这里是最值得一去的地方之一。30万平方公里的黑土地上,冬日有许多超乎你想象的美好体验。每到十一月,这里就变成被冰雪覆盖的白色童话。(去年中俄边境游……深圳市大与文体度假中心深圳市大与文体度假中心总占地面积为10万平米,坐落于横岗中心区,群山环绕,风景宜人,是都市人的世外桃源。中心总建筑面积约为6万平米,主要经营范围有:羽毛球、篮网足、高尔夫……东汉帝陵光武帝原陵景区(刘秀坟)(西安路过)洛阳北去孟津白鹤镇不过20多公里,驱车也就一个来小时就到,一路市镇比陕西显得要繁华一些,必竟河南人口众多也是实际情况。当然洛阳也不愧为中国人口最密集的城市之一,洛阳作为古都,文……从拼多多6万人砍价说起且不说这消息的真假,可能真没有60000人那么多,但是最终人数肯定不会少。对于多数人来说,或许只是成了一个谈资,或许很多人有亲身经历。但从当初的砍一刀,到今天的砍6000……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网