本文将说明如何使用迁移学习来解决图像分类问题,相关代码示例将使用 Python + Keras 进行实现。

深度学习在最近几年发展迅速,并且已经在人工只能领域占据了极其重要的一席之地(LeCun,2015)。在业界,深度学习也应用广泛,例如在计算机视觉、自然语言处理和语音识别等领域,深度学习都取得了不错的效果。

在深度学习中,图像分类(Rawat&Wang 2017)是一个经典问题。图像分类的目标是根据一组特定图片进行分类,一个非常常见的图像分类例子就是是在识别猫和狗。

事实上,图像分类还可以通过迁移学习来建模,并且,在最近一些图像分类问题中取得良好表现的几个解决方案(Krizhevsky等人2012,Simonyan&Zisserman 2014,He等人2016),都是基于迁移学习实现的。如果对论文感兴趣,可以参考:Pan&Yang(2010)。

本文将着重介绍如何使用迁移学习进行图像分类。看完本文后,相信你能够快速轻松地解决任何图像分类问题。

文章结构如下:

1. 迁移学习

2. 卷积神经网络

3. 如何利用预训练模型

4. 迁移学习流程

5. CNN 中的分类器

6. 代码示例

7. 总结

8. 参考文献


一、迁移学习

迁移学习是计算机视觉中的一种很受欢迎的方法,通过它我们可以花更少的时间建立更准确的模型(Rawat&Wang 2017)。迁移学习可以利用其他模型在学习其他模型已经训练得到的模式和知识,并应用到当前你要解决的问题中。因此,迁移学习并不需要从头开始学习和训练模型,迁移学习的这一优秀特征被称为“站在巨人的肩膀上”。

进行迁移学习之前,我们需要基本的预训练模型。在这里,预训练模型可以是一些公开的预训练模型,这些模型通常基于一些公开的大型数据集进行建模,效果都还不错。但是由于这些数据和模型的训练目标和我们需要解决的问题不一样,因此我们无法直接使用那些模型。当然,也不是什么模型都能够用于迁移学习,对于图片分类的迁移学习任务,我们当然无法使用自然语言处理的预训练模型,通常情况下,我们会选择一些训练目标类似的预训练模型用于迁移学习。

二、卷积神经网络

目前看来,迁移学习用到的大多数预训练模型基本都是基于卷积神经网络 (CNN)(Voulodimos等人,2018)训练的。一个很重要的原因是,CNN在各种计算机视觉任务中都表现出色(Bengio 2009),并且入门门槛较低便于普及。

一个经典的 CNN 模型有两部分:

1. 卷积+池化(Convolutional Base):由一堆卷积层和池化层组成。该层的主要目标是从原始数据中生成特征。有关卷积和池化层的直观解释,请参阅Chollet(2017)。

2. 分类器(Classifier):通常由全连接层组成。分类器的主要目标是基于得到的特征对图像进行分类。

下图展示了一个基本的 CNN 模型结构:

图 1. 基本的 CNN 模型结构

深度学习模型的一个重要特点就是:它们可以自动学习分层特征表示。这也就意味着:第一层计算的特征是通用的,在不同的问题中它可以被重用,而最后一层计算的特征是特定的,极大地取决于所选的数据集和任务。根据( Yosinski,2014)文中所说 :“如果深度模型的第一层特征是通用的,最后一层特征是特定的,那么在网路中一定存在某一层,是从通用到特定的过渡”。对于 CNN 而言,靠近输入的低层特征通常是可以重用的特征,而靠近输出的分类器部分,则是一些和任务及数据相关的特定特征。

三、如何利用预训练模型

当你想要利用预训练的模型时,首先要做的就是删除原始分类器,然后添加合适的新分类器,最后再根据如下三种策略中的任意一种对模型进行微调:

1. 训练整个模型。在这种情况下,新模型将需要数据进行端到端的训练。为了有比较好的效果,通常需要一个大型数据集(以及大量的计算能力)进行整体建模。

2. 训练部分层,冻结剩余层。前面提到,较低层是指一般特征(与问题无关,通常特征),而较高层是指特定特征(取决于问题类型及数据)。因此,在这里我们可以设定不同网络层的训练权重程度(冻结层在训练期间不会改变)来实现部分层的训练。一般情况下,如果你只有一个小数据集,但模型又有大量参数时,你需要保留更多的冻结层以避免过拟合。当然,如果你的数据集很大,你可以冻结较少的层,毕竟在大量数据集的情况下,过拟合通常不会发生。

3. 冻结卷积+池化层。这种方法是第二种办法的一种极端操作。它的主要思想是保留卷积层+池化层,并使用其输出来训练分类器。预训练模型中的特征抽取方法将作为固定的特征抽取方式,如果你的计算能力不足,数据集又很小,这种方法可以较好的解决你的问题。

下图展示了这三种策略的区别

图 2. 三种微调策略

三种策略中,策略3最为简单,策略1和策略2则需要谨慎的设置学习率。学习率是一个超参,可以控制调整网络的权重。通常情况下,当我们使用基于CNN的预训练模型时,我们会选择较小的学习率。因为过大的学习率会增加丢失先前知识的风险。理想情况下,假设预训练模型本身训练得当,保持较小的学习率将确保新模型不会过早或过多地扭曲 CNN 各层的权重。

四、迁移学习流程

工程实现中,迁移学习的整体流程可归纳如下:

1. 选择预训练模型。网上有很多可用的预训练模型,并且不乏大公司发布的效果良好的模型。例如,如果您正在使用Keras,在 github 上你可以找到系列模型,如:VGG(Simonyan和Zisserman 2014)、InceptionV3(Szegedy,2015)和ResNet5(He,2015)等。

2. 确定你的数据和任务所处的象限。我画了一个简单的图(图3)来帮助大家确定自己的任务在所处的象限。该图根据数据集的大小和数据集的相似性将迁移学习任务划分到四个象限。

第一个象限代表:大数据集+与预训练目标有较大不同;

第二个象限代表:大数据集+与预训练目标相似;

第三个象限代表:小数据集+与预训练目标有较大不同;

第四个象限代表:小数据集+与预训练目标相似。

一般经验是,如果每个类别的数据少于1000个,则认为数据集很小。 关于数据集相似性,我们则只有通过常识判断。 比如,如果你的任务是识别猫和狗,ImageNet 将是一个类似的数据集,因为它有猫和狗的图像。 但是,如果你的任务是识别癌细胞,则最好不要将 ImageNet 视为类似的数据集。

图 3. 尺寸相似矩阵

3. 制定微调策略。前面我们已经提到了模型微调的几种方法,并且在第二步中我们确定了迁移学习任务所处的象限,现在就让我们根据图4来确定迁移学习模型的微调策略。该图中的象限与图三中的象限一一对应。

图 4. 微调策略决策

处于象限1时:由于你有大型数据集,因此可以全量训练模型。虽然数据集与预训练模型的数据集不相似,但在工程实践中我们发现,使用预训练模型初始化模型的架构和权重作为迁移模型的初始化架构和参数仍然是非常有用的。

处于象限2时:由于任务类型相似,所以我们可以冻结一部分输入层。另外,由于又有一个大型数据集,所以我们可以尽可能多的训练一些中间层和分类器,并且也不用担心过拟合问题。所以我们选择的策略是,训练尾部的卷积+池化层、以及分类器。

处于象限3时:数据集较小,并且与预训练模型的目标不同。这是最糟糕的情况了,由于数据集太小我们不能全量训练,又由于训练目标不同我们不得不尽可能多的训练中间层。因此唯一的办法就是如图四中所示,训练卷积+池化的中部及尾部层,并且还要训练分类器。当然,如果有任何方法增加你的数据集,也是一种不错的选择。

处于象限4时:小数据集,但与预训练模型的目标类似。这种情况下,你只需要重新训练分类器即可,避免过拟合的同时又能确保你的目标得到了训练。

五、CNN 中的分类器

前面我们提到,CNN 主要由卷积+池化层、分类器两大部分构成。在第四节提到的迁移学习策略中,无论那种方案,分类器都是需要被重新训练的部分。

因此,在本节中,我们将着重关注分类器。一些常见的分类器构造办法是:

1. DNN 全链接:DNN 是一个很基本的神经网络,通常由几层全连接层构成,在末尾接上一层 softmax 激活层(Krizhevsky,2012,Simonyan和Zisserman,2014,Zeiler和Fergus,2014)。softmax层输出数据在每个类别上的概率分布,然后我们就可以根据概率来对数据进行分类。

2. 全局平均池化层:这是由 Lin 在 2013 年提出来的方法。这种方法中,我们不需要添加DNN全连接层,而是添加全局平均池化层,并直接将其输出作为softmax激活层的输入,从而得到分类结果。

3. SVM 支持向量机:线性支持向量机(SVM)是另一种常用的方法Tang(2013)。我们可以通过将卷积+池化得到的特征作为输入传入 SVM,利用 SVM 进行分类,从而得到分类结果。

六、代码示例

本节中,我将给出一些代码示例。这些例子中,将比较每一种迁移学习中不同的分类器效果。这些代码主要基于 Python + Keras 对图像进行分类。

1. 准备数据

为了使这些数据能较快的在本地运行,我门将使用较小的原始数据集,从而保证计算能力有限的机器也能运行。

为了构建较小版本的数据集,我们可以调整Chollet(2017)提供的代码(数据集为对猫和狗的图片进行分类的数据集),如下所示:

# Create smaller dataset for Dogs vs. Catsimport os, shutiloriginal_dataset_dir = '/Users/macbook/dogs_cats_dataset/train/'base_dir = '/Users/macbook/book/dogs_cats/data'if not os.path.exists(base_dir): os.mkdir(base_dir)# Create directoriestrain_dir = os.path.join(base_dir,'train')if not os.path.exists(train_dir): os.mkdir(train_dir)validation_dir = os.path.join(base_dir,'validation')if not os.path.exists(validation_dir): os.mkdir(validation_dir)test_dir = os.path.join(base_dir,'test')if not os.path.exists(test_dir): os.mkdir(test_dir)train_cats_dir = os.path.join(train_dir,'cats')if not os.path.exists(train_cats_dir): os.mkdir(train_cats_dir)train_dogs_dir = os.path.join(train_dir,'dogs')if not os.path.exists(train_dogs_dir): os.mkdir(train_dogs_dir)validation_cats_dir = os.path.join(validation_dir,'cats')if not os.path.exists(validation_cats_dir): os.mkdir(validation_cats_dir)validation_dogs_dir = os.path.join(validation_dir, 'dogs')if not os.path.exists(validation_dogs_dir): os.mkdir(validation_dogs_dir)test_cats_dir = os.path.join(test_dir, 'cats') if not os.path.exists(test_cats_dir): os.mkdir(test_cats_dir)test_dogs_dir = os.path.join(test_dir, 'dogs')if not os.path.exists(test_dogs_dir): os.mkdir(test_dogs_dir)# Copy first 1000 cat images to train_cats_dirfnames = ['cat.{}.jpg'.format(i) for i in range(100)]for fname in fnames: src = os.path.join(original_dataset_dir, fname) dst = os.path.join(train_cats_dir, fname) shutil.copyfile(src, dst) # Copy next 500 cat images to validation_cats_dirfnames = ['cat.{}.jpg'.format(i) for i in range(200, 250)]for fname in fnames: src = os.path.join(original_dataset_dir, fname) dst = os.path.join(validation_cats_dir, fname) shutil.copyfile(src, dst) # Copy next 500 cat images to test_cats_dirfnames = ['cat.{}.jpg'.format(i) for i in range(250,300)]for fname in fnames: src = os.path.join(original_dataset_dir, fname) dst = os.path.join(test_cats_dir, fname) shutil.copyfile(src, dst) # Copy first 1000 dog images to train_dogs_dirfnames = ['dog.{}.jpg'.format(i) for i in range(100)]for fname in fnames: src = os.path.join(original_dataset_dir, fname) dst = os.path.join(train_dogs_dir, fname) shutil.copyfile(src, dst) # Copy next 500 dog images to validation_dogs_dirfnames = ['dog.{}.jpg'.format(i) for i in range(200,250)]for fname in fnames: src = os.path.join(original_dataset_dir, fname) dst = os.path.join(validation_dogs_dir, fname) shutil.copyfile(src, dst)# Copy next 500 dog images to test_dogs_dirfnames = ['dog.{}.jpg'.format(i) for i in range(250,300)]for fname in fnames: src = os.path.join(original_dataset_dir, fname) dst = os.path.join(test_dogs_dir, fname) shutil.copyfile(src, dst) # Sanity checksprint('total training cat images:', len(os.listdir(train_cats_dir)))print('total training dog images:', len(os.listdir(train_dogs_dir)))print('total validation cat images:', len(os.listdir(validation_cats_dir)))print('total validation dog images:', len(os.listdir(validation_dogs_dir)))print('total test cat images:', len(os.listdir(test_cats_dir)))print('total test dog images:', len(os.listdir(test_dogs_dir)))

2. 从卷积+池化层中提取特征

同样的,我们将使用 Chollet(2017)提供的代码,并对其中一些部分进行修改,实现如下所示:

# Extract featuresimport os, shutilfrom keras.preprocessing.image import ImageDataGeneratordatagen = ImageDataGenerator(rescale=1./255)batch_size = 32def extract_features(directory, sample_count):features = np.zeros(shape=(sample_count, 7, 7, 512)) # Must be equal to the output of the convolutional baselabels = np.zeros(shape=(sample_count)) # Preprocess datagenerator = datagen.flow_from_directory(directory,target_size=(img_width,img_height),batch_size = batch_size,class_mode='binary') # Pass data through convolutional basei = 0for inputs_batch, labels_batch in generator:features_batch = conv_base.predict(inputs_batch)features[i * batch_size: (i + 1) * batch_size] = features_batchlabels[i * batch_size: (i + 1) * batch_size] = labels_batchi += 1if i * batch_size >= sample_count:breakreturn features, labels train_features, train_labels = extract_features(train_dir, train_size) # Agree with our small dataset sizevalidation_features, validation_labels = extract_features(validation_dir, validation_size)test_features, test_labels = extract_features(test_dir, test_size)

3. 分类器

3.1 DNN 全连接

使用 Keras 实现一个 DNN 相当简单,另外,根据斯坦佛的研究,我们将使用 Adam 作为优化器。代码如下所示:

# Define modelfrom keras import modelsfrom keras import layersfrom keras import optimizersepochs = 100model = models.Sequential()model.add(layers.Flatten(input_shape=(7,7,512)))model.add(layers.Dense(256, activation='relu', input_dim=(7*7*512)))model.add(layers.Dropout(0.5))model.add(layers.Dense(1, activation='sigmoid'))model.summary()# Compile modelmodel.compile(optimizer=optimizers.Adam(), loss='binary_crossentropy', metrics=['acc']) # Train modelhistory = model.fit(train_features, train_labels, epochs=epochs, batch_size=batch_size,  validation_data=(validation_features, validation_labels))

在前两步生成的数据集上运行该代码,可以看到该分类器的学习曲线如下:

图 5. 全连接层分类器在训练集和验证集上的准确率

图 6. 全连接层分类器的损失函数变化情况

从上述各图中可以看到:

- 验证准确度约为0.85;

- 模型过拟合了,可以看到:训练集和验证集的损失曲线之间存在很大差距;

- 由于我们已经使用了 dropout 策略防止过拟合,因此目前我们应该增加数据集的大小以降低过拟合的程度;

3.2 全局平均池化

该方法与前一种方法的区别在于,我们不是添加一堆全连接层,而是添加全局平均池层,并将其输出作为输入提供给 sigmoid 激活层。

值得注意的是,我们使用的是一个sigmoid激活层而不是softmax激活层,这是根据 Lin 于 2013年发表的论文作出的决定。当然你也可以 softmax 激活层,试试看不同激活层对结果的影响。代码如下所示:

# Define modelfrom keras import modelsfrom keras import layersfrom keras import optimizersepochs = 100model = models.Sequential()model.add(layers.GlobalAveragePooling2D(input_shape=(7,7,512)))model.add(layers.Dense(1, activation='sigmoid'))model.summary()# Compile modelmodel.compile(optimizer=optimizers.Adam(),loss='binary_crossentropy',metrics=['acc']) # Train modelhistory = model.fit(train_features, train_labels,epochs=epochs,batch_size=batch_size, validation_data=(validation_features, validation_labels))

运行该代码,模型的学习曲线如下所示:

图 7. 全局平均池化层的准确率

图 8. 全局平均池化层的损失函数曲线

从上面可以看出:

- 准确度与 DNN 方案的准确度差不多;

- 该模型在泛化能力上比 DNN 好,过拟合程度较低;

- 损失函数持续在下降,所以极有可能可以通过增加训练次数来提升模型的准确率;

3.3 SVM 线性支持向量机

SVM 是一种传统的机器学习方法,并且在很多领域都有不错的效果。我们将使用 K 重交叉验证来估计分类器的误差。同时,由于将使用 K 重交叉验证,我们可以将训练集和验证集放到一起,扩大我们的训练数据用以训练 SVM。

以下是将训练集和验证集合并的代码:

# Concatenate training and validation setssvm_features = np.concatenate((train_features, validation_features))svm_labels = np.concatenate((train_labels, validation_labels))

为了优化 SVM,我们将对 SVM 的参数进行超参训练。我们使用 scikit-learn 中的网格搜索进行超参训练,代码如下:

# Build modelimport sklearnfrom sklearn.cross_validation import train_test_splitfrom sklearn.grid_search import GridSearchCVfrom sklearn.svm import LinearSVCX_train, y_train = svm_features.reshape(300,7*7*512), svm_labelsparam = [{"C": [0.01, 0.1, 1, 10, 100] }] svm = LinearSVC(penalty='l2', loss='squared_hinge') # As in Tang (2013)clf = GridSearchCV(svm, param, cv=10)clf.fit(X_train, y_train)

实验效果如下:

图 9. SVM分类器的准确率

从上图可以看出:

- 模型的准确度约为0.86,与之前解决方案的准确性相差不大;

- 过拟合明显:训练准确率始终为 1.0。

- 模型的准确率应该应随着训练样本的数量而增加。但是,上图看起来并不会这样。你可以改一下代码试试,增加数据后,模型的准确率是否会增加。

七、总结

在本文中:

- 介绍了迁移学习、卷积神经网络和预训练模型等一些基本概念。

- 定义了基本的微调策略,以如何利用预训练模型等流程。

- 描述了一种结构化方法,根据数据集的大小和相似性来决定应该使用哪种微调策略。

- 引入了三种不同的分类器。

- 对比三种不同的分类器,并给出了端到端的代码示例。


八、参考文献

1. Bengio, Y., 2009. Learning deep architectures for AI. Foundations and trends in Machine Learning, 2(1), pp.1–127.

2. Canziani, A., Paszke, A. and Culurciello, E., 2016. An analysis of deep neural network models for practical applications. arXiv preprint arXiv:1605.07678.

3. Chollet, F., 2015. Keras.

4. Chollet, F., 2017. Deep learning with python. Manning Publications Co..

5. Deng, J., Dong, W., Socher, R., Li, L.J., Li, K. and Fei-Fei, L., 2009, June. Imagenet: A large-scale hierarchical image database. In Computer Vision and Pattern Recognition, 2009. CVPR 2009. IEEE Conference on (pp. 248–255). Ieee.

6. He, K., Zhang, X., Ren, S. and Sun, J., 2016. Deep residual learning for image recognition. In Proceedings of the IEEE conference on computer vision and pattern recognition (pp. 770–778).

7. Krizhevsky, A., Sutskever, I. and Hinton, G.E., 2012. Imagenet classification with deep convolutional neural networks. In Advances in neural information processing systems (pp. 1097–1105).

8. LeCun, Y., Bengio, Y. and Hinton, G., 2015. Deep learning. nature, 521(7553), p.436.

9. Lin, M., Chen, Q. and Yan, S., 2013. Network in network. arXiv preprint arXiv:1312.4400.

10. Pan, S.J. and Yang, Q., 2010. A survey on transfer learning. IEEE Transactions on knowledge and data engineering, 22(10), pp.1345–1359.

11. Rawat, W. and Wang, Z., 2017. Deep convolutional neural networks for image classification: A comprehensive review. Neural computation, 29(9), pp.2352–2449.

12. Simonyan, K. and Zisserman, A., 2014. Very deep convolutional networks for large-scale image recognition. arXiv preprint arXiv:1409.1556.

13. Szegedy, C., Vanhoucke, V., Ioffe, S., Shlens, J. and Wojna, Z., 2016. Rethinking the inception architecture for computer vision. In Proceedings of the IEEE conference on computer vision and pattern recognition (pp. 2818–2826).

14. Tang, Y., 2013. Deep learning using linear support vector machines. arXiv preprint arXiv:1306.0239.

15. Voulodimos, A., Doulamis, N., Doulamis, A. and Protopapadakis, E., 2018. Deep learning for computer vision: A brief review. Computational intelligence and neuroscience, 2018.

16. Yosinski, J., Clune, J., Bengio, Y. and Lipson, H., 2014. How transferable are features in deep neural networks?. In Advances in neural information processing systems (pp. 3320–3328).

17. Zeiler, M.D. and Fergus, R., 2014, September. Visualizing and understanding convolutional networks. In European conference on computer vision (pp. 818–833). Springer, Cham.

本文由本头条号独家编译,未经许可,不得转载。

weka分类器怎么设置类别_AI 迁移学习怎么玩?手把手教你实战迁移学习相关推荐

  1. 手把手教你用深度学习做物体检测(四):模型使用

    上一篇<手把手教你用深度学习做物体检测(三):模型训练>中介绍了如何使用yolov3训练我们自己的物体检测模型,本篇文章将重点介绍如何使用我们训练好的模型来检测图片或视频中的物体.   如 ...

  2. 手把手教你用深度学习做物体检测(二):数据标注

      "本篇文章将开始我们训练自己的物体检测模型之旅的第一步-- 数据标注."   上篇文章介绍了如何基于训练好的模型检测图片和视频中的物体,若你也想先感受一下物体检测,可以看看上篇 ...

  3. python硬件驱动_从零开始:手把手教你安装深度学习操作系统、驱动和各种python库!...

    原标题:从零开始:手把手教你安装深度学习操作系统.驱动和各种python库! 为了研究强化学习,最近购置了一台基于 Ubuntu 和英伟达 GPU 的深度学习机器.尽管目前在网络中能找到一些环境部署指 ...

  4. Python学习教程:手把手教你搭建自己的量化分析数据库

    Python学习教程:手把手教你搭建自己的量化分析数据库 引言: 数据是金融量化分析的重要基础,包括股票历史交易数据.上市公司基本面数据.宏观和行业数据等.随着信息流量的日益膨胀,学会获取.查询和加工 ...

  5. 手把手教你用深度学习做物体检测(三):模型训练

    本篇文章旨在快速试验使用yolov3算法训练出自己的物体检测模型,所以会重过程而轻原理,当然,原理是非常重要的,只是原理会安排在后续文章中专门进行介绍.所以如果本文中有些地方你有原理方面的疑惑,也没关 ...

  6. Android学习攻略:手把手教你循序渐进地学习Android知识

    前言 如果你也学习Android,那么你大概率会看过我的文章.经常有读者给我留言:"该怎么学习Android?"."日常学习Android的方法是什么". 所以 ...

  7. 【嵌入式Android开发学习攻略】手把手教你循序渐进的学习

    [嵌入式Android开发学习攻略]手把手教你循序渐进的学习 学习嵌入式主要有以下几个方面: C语言:C是必须学的,它是最基础的 操作系统:Linux.Android   目前Linux是主流,这个一 ...

  8. 视频教程-深度学习之图像识别 核心技术与案例实战-深度学习

    深度学习之图像识别 核心技术与案例实战 先后就读于华中科技大学,中国科学院,先后就职于奇虎360AI研究院,陌陌深度学习实验室,6年计算机视觉从业经验,拥有丰富的传统图像算法和深度学习图像项目经验,拥 ...

  9. weka分类器怎么设置样本类别_NeurIPS 2019 少样本学习研究亮点全解析

    少样本学习(Few-Shot Learning)是近两年来非常有研究潜力的一个子方向,由于深度学习在各学科交叉研究与商业场景都有比较普遍的应用,然而训练出高精度模型的情况大部分来源于充足的训练数据,这 ...

最新文章

  1. 机器学习入门(17)— 输入 4 维数据、基于 im2col 展开来实现卷积层
  2. iOS11、iPhone X、Xcode9 适配指南
  3. 赵本山:我的时代还没有结束 | Python告诉你
  4. 扇贝python课程打卡_Python爬虫:获取扇贝打卡信息
  5. haproxy参数优化
  6. Win10环境下,在Matlab R2016a上的SPM12安装教程
  7. 项目pom.xml第一行报错解决方案
  8. 简单工厂模式、工厂方法模式、抽象工厂模式 之间的对比
  9. Python使用openpyxl读写excel文件
  10. python multiprocessing — 基于进程的并行
  11. 【分享】C# 字节帮助类 ByteHelper
  12. java list 从0开始_Java从零开始学二十一(集合List接口)
  13. python和pytorch是什么关系_【PyTorch】Tensor和tensor的区别
  14. leetcode 130 python
  15. 11 款最好 CSS 框架 让你的网站独领风骚
  16. Alex 的 Hadoop 菜鸟教程: 第1课 hadoop体系介绍
  17. badboy使用手册
  18. 《东周列国志》第六十五回 弑齐光崔庆专权 纳卫衎宁喜擅政
  19. 大数据——DBT:dbt集成数据质量监控插件elementary(生产环境篇)
  20. 游戏服务端(MMORPG)的基础算法一、AOI

热门文章

  1. 人造电子皮肤、软体机器人、单孔腔镜手术机器人......青年科学家们都在研究哪些“黑科技”?...
  2. (动态规划 最长有序子序列)Monkey and Banana --HDU --1069
  3. 【iOS开发】如何用 Swift 语言进行LBS应用的开发?
  4. 桌面虚拟化的架构模块化
  5. 10、32位 x86处理器编程架构
  6. 【FFMPEG系列】FFMPEG linux下集成x264
  7. 学python必须得英语精通吗_“学习python必须精通的几个模块“
  8. centos java创建文件_CentOS java生成文件并赋予权限的问题
  9. python filter map区别_Python:map()、reduce()、filter()的区别
  10. 定时器的实现原理 不消耗cpu_brpc定时器实现原理