首发地址:https://yq.aliyun.com/articles/98593

更多深度文章,请关注:https://yq.aliyun.com/cloud

本文是作者最近发布的Google QuickDraw数据集一系列笔记中的第三部分,使用的最近发布的SketchRNN模型。下面介绍QuickDraw数据集及SketchRNN模型。
        QuickDraw数据集是由世界各地1500多万人参与的“快速绘画” AI实验后收集数百万幅图画建成,要求参与者在20秒内绘制出属于某个类(例如“猫”)的图像。如下图所示,选择一个类别,可以看见数据库中该类所有的形状。

当然,如果数据集中没有和你想象一样的形状,你也可以帮助向该数据库中增添一些涂鸦,进行个人绘画表演。

 

SketchRNN是基于上述数据集训练的生成模型,被训练成能够生成矢量图,它巧妙地集合了机器学习中最近开发的许多最新的工具和技术,例如Variational AutoencodersHyperLSTMs(一个用于LSTM的HyperNetwork)、自回归模型 ,Layer NormalizationRecurrent DropoutAdam optimizer等。

SketchRNN系统是由谷歌探究AI能否创作艺术的新项目的一部分,类似于教AI去绘画,另外不仅仅是让人工智能学习如何画画,还要能够“用类似于人类的方式概括归纳抽象的概念”,比如去画“猪”的广义概念,而不是画特定的动物,这显然不是一件简单的事情(下图 SketchRNN系统画的“猪”)。

该模型能够将你的灵魂画作对应成实际的物品,当然,如果画作太过于抽象,暂时是无法识别或者是识别错误,毕竟它还没有学会读心术(自己画的自行车,系统无法识别)。

本文代码舍弃了那些旨在解释或演示的代码块,只保留了运行实验所需的代码。“潜在空间中的主成分分析 ”部分的所有内容是直接从以前的博客中摘取。随意跳过该部分,因为接下来是真正有趣的分析。这里是第一第二博客的链接,之前所讲述的一切都是一些实用功能,以便于现在的可视化分析。

本文是笔记代码的结合,作者已经做出了风格以及其它一些细微的改变,以确保Python3能向前兼容。

  • 1. 本文有点令人误解,这是因为本文主要是探索Aaron Koblin羊市场 (aaron-sheep)数据集,这是一个较小的轻量级数据集,以及一个手册,演示了在这个数据集上已经预先训练好的各种模型。由于该数据集模式与QuickDraw数据集相同,因此在此数据集上执行的实验也不失一般性。
  • 2. Magenta目前只支持Python 2版本。

接下来都是实验的所需的python代码,其中[num]是表示该github工程文件的代码块:
在代码块[2]中:

%matplotlib inline
%config InlineBackend.figure_format = 'svg'
%load_ext autoreload
%autoreload 2

在代码块[3]中

import matplotlib.pyplot as plt
import matplotlib.patches as patchesimport numpy as np
import tensorflow as tffrom matplotlib.animation import FuncAnimation
from matplotlib.path import Path
from matplotlib import rcfrom sklearn.decomposition import PCA
from sklearn.manifold import TSNEfrom itertools import product
from six.moves import map, zip

在代码块[5]中:

from magenta.models.sketch_rnn.sketch_rnn_train import \(load_env,load_checkpoint,reset_graph,download_pretrained_models,PRETRAINED_MODELS_URL)
from magenta.models.sketch_rnn.model import Model, sample
from magenta.models.sketch_rnn.utils import (lerp,slerp,get_bounds, to_big_strokes,to_normal_strokes)

在代码块[6]中:

# For inine display of animation
# equivalent to rcParams['animation.html'] = 'html5'
rc('animation', html='html5')

在代码块[5]中:

# set numpy output to something sensible
np.set_printoptions(precision=8, edgeitems=6, linewidth=200, suppress=True)

在代码块[6]中:

tf.logging.info("TensorFlow Version: {}".format(tf.__version__))
INFO:tensorflow:TensorFlow版本:1.1.0

获得预训练的模型和数据

在代码块[7]中:

DATA_DIR = ('http://github.com/hardmaru/sketch-rnn-datasets/''raw/master/aaron_sheep/')
MODELS_ROOT_DIR = '/tmp/sketch_rnn/models'

在代码块[8]中:

DATA_DIR

输出[8]:

'http://github.com/hardmaru/sketch-rnn-datasets/raw/master/aaron_sheep/'

在代码块[9]中:

PRETRAINED_MODELS_URL

输出[9]:

'http://download.magenta.tensorflow.org/models/sketch_rnn.zip'

在代码块[10]中:

download_pretrained_models(models_root_dir=MODELS_ROOT_DIR,pretrained_models_url=PRETRAINED_MODELS_URL)

接下来让我们看看aaron_sheep现在数据集训练的规范化层模型。

在代码块[11]中:

MODEL_DIR = MODELS_ROOT_DIR + '/aaron_sheep/layer_norm'

在代码块[12]中:

(train_set, valid_set, test_set, hps_model, eval_hps_model, sample_hps_model) = load_env(DATA_DIR, MODEL_DIR)
INFO:tensorflow:Downloading http://github.com/hardmaru/sketch-rnn-datasets/raw/master/aaron_sheep/aaron_sheep.npz
INFO:tensorflow:Loaded 7400/300/300 from aaron_sheep.npz
INFO:tensorflow:Dataset combined: 8000 (7400/300/300), avg len 125
INFO:tensorflow:model_params.max_seq_len 250.
total images <= max_seq_len is 7400
total images <= max_seq_len is 300
total images <= max_seq_len is 300
INFO:tensorflow:normalizing_scale_factor 18.5198.

在代码块[13]中:

class SketchPath(Path):def __init__(self, data, factor=.2, *args, **kwargs):vertices = np.cumsum(data[::, :-1], axis=0) / factorcodes = np.roll(self.to_code(data[::,-1].astype(int)), shift=1)codes[0] = Path.MOVETOsuper(SketchPath, self).__init__(vertices, codes, *args, **kwargs)@staticmethoddef to_code(cmd):# if cmd == 0, the code is LINETO# if cmd == 1, the code is MOVETO (which is LINETO - 1)return Path.LINETO - cmd

在代码块[14]中:

def draw(sketch_data, factor=.2, pad=(10, 10), ax=None):if ax is None:ax = plt.gca()x_pad, y_pad = padx_pad //= 2y_pad //= 2x_min, x_max, y_min, y_max = get_bounds(data=sketch_data,factor=factor)ax.set_xlim(x_min-x_pad, x_max+x_pad)ax.set_ylim(y_max+y_pad, y_min-y_pad)sketch = SketchPath(sketch_data)patch = patches.PathPatch(sketch, facecolor='none')ax.add_patch(patch)

加载预先训练好的模型

在代码块[15]中:

# construct the sketch-rnn model here:
reset_graph()
model = Model(hps_model)
eval_model = Model(eval_hps_model, reuse=True)
sample_model = Model(sample_hps_model, reuse=True)
INFO:tensorflow:Model using gpu.
INFO:tensorflow:Input dropout mode = False.
INFO:tensorflow:Output dropout mode = False.
INFO:tensorflow:Recurrent dropout mode = True.
INFO:tensorflow:Model using gpu.
INFO:tensorflow:Input dropout mode = False.
INFO:tensorflow:Output dropout mode = False.
INFO:tensorflow:Recurrent dropout mode = False.
INFO:tensorflow:Model using gpu.
INFO:tensorflow:Input dropout mode = False.
INFO:tensorflow:Output dropout mode = False.
INFO:tensorflow:Recurrent dropout mode = False.

在代码块[16]中:

sess = tf.InteractiveSession()
sess.run(tf.global_variables_initializer())

在代码块[17]中:

# loads the weights from checkpoint into our model
load_checkpoint(sess=sess, checkpoint_path=MODEL_DIR)
INFO:tensorflow:Loading model /tmp/sketch_rnn/models/aaron_sheep/layer_norm/vector.
INFO:tensorflow:Restoring parameters from /tmp/sketch_rnn/models/aaron_sheep/layer_norm/vector

在代码块[18]中:

def encode(input_strokes):strokes = to_big_strokes(input_strokes).tolist()strokes.insert(0, [0, 0, 1, 0, 0])seq_len = [len(input_strokes)]z = sess.run(eval_model.batch_z,feed_dict={eval_model.input_data: [strokes], eval_model.sequence_lengths: seq_len})[0]return z

在代码块[19]中:

def decode(z_input=None, temperature=.1, factor=.2):z = Noneif z_input is not None:z = [z_input]sample_strokes, m = sample(sess, sample_model, seq_len=eval_model.hps.max_seq_len, temperature=temperature, z=z)return to_normal_strokes(sample_strokes)

用主成分分析探索潜在空间

PCA是一种降维方法,是通过线性变换将原始数据变换为一组各维度线性无关的表示,可用于提取数据的主要特征分量,常用于高维数据的降维。

下面,我们将测试集中的所有草图编码为学习到的128维潜在空间中的表示。

在代码块[20]中:

Z = np.vstack(map(encode, test_set.strokes))
Z.shape

输出[20]:

(300,128) 注:128代表128维

然后,找到潜在空间中编码数据中代表最大方差方向的两个主轴。

在代码块[22]中:

pca = PCA(n_components=2)

在代码块[23]中:

pca.fit(Z)

输出[23]:

PCA(copy = True,iterated_power ='auto',n_components = 2,random_state = None,svd_solver ='auto',tol = 0.0,whiten = False)

这两个组成部分分别约占方差的2%

在代码块[24]中:

pca.explained_variance_ratio_

输出[24]:

([0.02140247,0.02067117])

接下来将数据从128维的潜在空间映射为由前两个主要分量构建的二维空间

在代码块[25]中:

Z_pca = pca.transform(Z)
Z_pca.shape

输出[25]:

(300,2) 注:2代表2维

在代码块[26]中:

fig, ax = plt.subplots()ax.scatter(*Z_pca.T)ax.set_xlabel('$pc_1$')
ax.set_ylabel('$pc_2$')plt.show()

我们想在上图中通过图中的对应点可视化出原始草图,其中每个点对应于草图降维到2维的隐藏代码。然而,由于图像太密集,在没有重叠的情况下无法适应足够大的草图。因此,我们将注意力限制在包含80%数据点的较小区域,蓝色阴影矩形突出显示感兴趣的区域。

在代码块[106]中:

((pc1_min, pc2_min), (pc1_max, pc2_max)) = np.percentile(Z_pca, q=[5, 95], axis=0)

在代码块[107]中:

roi_rect = patches.Rectangle(xy=(pc1_min, pc2_min),width=pc1_max-pc1_min,height=pc2_max-pc2_min, alpha=.4)

在代码块[108]中:

fig, ax = plt.subplots()ax.scatter(*Z_pca.T)
ax.add_patch(roi_rect)ax.set_xlabel('$pc_1$')
ax.set_ylabel('$pc_2$')plt.show()

在代码块[109]中:

fig, ax = plt.subplots(figsize=(10, 10))ax.set_xlim(pc1_min, pc1_max)
ax.set_ylim(pc2_min, pc2_max)for i, sketch in enumerate(test_set.strokes):sketch_path = SketchPath(sketch, factor=7e+1)sketch_path.vertices[::,1] *= -1sketch_path.vertices += Z_pca[i]patch = patches.PathPatch(sketch_path, facecolor='none')ax.add_patch(patch)ax.set_xlabel('$pc_1$')
ax.set_ylabel('$pc_2$')plt.savefig("../../files/sketchrnn/aaron_sheep_pca.svg", format="svg", dpi=1200)

可以在这里找到SVG图像。

备注:一个更加聪明的方法涉及到使用matplotlib变换Collections API,具体是通过实例化带有关键参数oofets的PathCollection。

PCA投影中的线性插值
在先前定义的矩形区域生成100个均匀间隔的网格,下图网格中的点以橙色表示,覆盖在测试数据点的顶部。

在代码块[35]中:

pc1 = lerp(pc1_min, pc1_max, np.linspace(0, 1, 10))

在代码块[36]中:

pc2 = lerp(pc2_min, pc2_max, np.linspace(0, 1, 10))

在代码块[37]中:

pc1_mesh, pc2_mesh = np.meshgrid(pc1, pc2)

在代码块[38]中:

fig, ax = plt.subplots()ax.set_xlim(pc1_min-.5, pc1_max+.5)
ax.set_ylim(pc2_min-.5, pc2_max+.5)ax.scatter(*Z_pca.T)
ax.scatter(pc1_mesh, pc2_mesh)ax.set_xlabel('$pc_1$')
ax.set_ylabel('$pc_2$')plt.show()

接下来,通过应用PCA的逆变换来将网格上的100个点投影到原始的128维潜在空间。

在代码块[39]中:

np.dstack((pc1_mesh, pc2_mesh)).shape

输出[39]:

(10,10,2)

在代码块[40]中:

z_grid = np.apply_along_axis(pca.inverse_transform,  arr=np.dstack((pc1_mesh, pc2_mesh)),axis=2)
z_grid.shape

输出[40]:

(10,10,128)

然后,使用这些隐藏代码及译码器重建相应的草图,并观察草图如何在所关注的矩形区域范围之间转换,特别是草图从左到右、从上到下的转换,因为这些是潜在表示中最大差异的方向。首先,以较低的温度设置τ=0.1运行译码器以最小化样本的随机性。

在代码块[94]中:

fig, ax_arr = plt.subplots(nrows=10, ncols=10, figsize=(8, 8),subplot_kw=dict(xticks=[],yticks=[],frame_on=False))
fig.tight_layout()for i, ax_row in enumerate(ax_arr):    for j, ax in enumerate(ax_row):draw(decode(z_grid[-i-1,j], temperature=.1), ax=ax)ax.axis('off')plt.show()

可以从上图中观察到一些有趣的变换和模式。在右下角,可以看到一群类似的修剪过或者无修饰的羊;当向左上方移动时,会开始看到一些羊毛、粗糙的圆形涂鸦的羊;沿着中间部分,可以看到最逼真的羊;在左上角,可以看到一些抽象的草图。

在代码块[95]中:

fig, ax_arr = plt.subplots(nrows=10, ncols=10, figsize=(8, 8),subplot_kw=dict(xticks=[],yticks=[],frame_on=False))
fig.tight_layout()for i, ax_row in enumerate(ax_arr):    for j, ax in enumerate(ax_row):draw(decode(z_grid[-i-1,j], temperature=.6), ax=ax)ax.axis('off')plt.show()

特征羊分解
本文将草图学习的潜在代表主要组成部分称为特征羊。

使用译码器,可以可视化前2个特征羊的草图表示,这些特征羊是将潜在表示转换为二维子空间中的正交权重矢量。通过将它们视为隐藏代码,并将其重构建为草图,我们能够提炼出一些有意义的解释。

在代码块[48]中:

z_pc1, z_pc2 = pca.components_

在代码块[49]中:

pca.explained_variance_ratio_

输出[49]:

([0.02140247,0.02067117])

在代码块[50]中:

pca.explained_variance_

输出[50]:

([2.51394317,2.42804484])

我们从τ=.01...1.0增加温度并绘制特征羊的重建草图,每个温度设置采集5个样品。

在代码块[58]中:

fig, ax_arr = plt.subplots(nrows=5, ncols=10, figsize=(8, 4),subplot_kw=dict(xticks=[],yticks=[],frame_on=False))
fig.tight_layout()for row_num, ax_row in enumerate(ax_arr):    for col_num, ax in enumerate(ax_row):t = (col_num + 1) / 10.draw(decode(z_pc1, temperature=t), ax=ax)if row_num + 1 == len(ax_arr):ax.set_xlabel(r'$\tau={}$'.format(t))plt.show()

在代码块[63]中:

fig, ax_arr = plt.subplots(nrows=5, ncols=10, figsize=(8, 4),subplot_kw=dict(xticks=[],yticks=[],frame_on=False))
fig.tight_layout()for row_num, ax_row in enumerate(ax_arr):    for col_num, ax in enumerate(ax_row):t = (col_num + 1) / 10.draw(decode(z_pc2, temperature=t), ax=ax)if row_num + 1 == len(ax_arr):ax.set_xlabel(r'$\tau={}$'.format(t))plt.show()

对于第一特征羊,在τ=0.1,主要看到一个圆形的黑色头发、有两条长的圆形腿和一些尾巴。沿着轴线方向可以看到,羊的头部和腿部结构会发生变化,这占据了草图的大部分差异。

现在看看第二特征羊,集中τ值较低时生成的样品,可以看到一些粗略潦草的圆圈代表羊的身体、3-4个松散连接的腿和一个小圆头。从上到下的观察,可以发现,与第一特征不同的是这似乎是沿着羊身体结构变化的方向。

t-SNE可视化
     t分布随机相邻嵌入(t-SNE)是一种常用的非线性降维技术,通常用于可视化高维数据。它是几种嵌入方法之一,其目的是将数据点嵌入到较低维空间中,以保持原始高维空间中的点对点的距离,一般适用于二维空间的可视化。

特别地,t-SNE通常用于深度学习,以检查和可视化深层神经网络学到的内容。例如,在图像分类问题中,可以将卷积神经网络视为一系列变换,逐渐将图像转换为表示,使得类可以更容易地被线性分类器区分。因此,可以将分类器前的最后一层输出作为“代码”,然后使用t-SNE在二维中嵌入和可视化图像的代码表示。

  • 1.关于PCA与t-SNE的详细比较可以查看笔者先前的博客
  • 2.Wattenberg, et al.“How to Use t-SNE Effectively”, Distill, 2016.
  • 3.斯坦福大学CS231n:卷积神经网络视觉识别课程中关于网络的可视化课程笔记

在这里,将每个矢量绘图的128维隐藏代码嵌入到二维中,并将其在该二维子空间中进行可视化。

在代码块[97]中:

tsne = TSNE(n_components=2, n_iter=5000)

在代码块[98]中:

Z_tsne = tsne.fit_transform(Z)

在代码块[99]中:

fig, ax = plt.subplots()ax.scatter(*Z_tsne.T)ax.set_xlabel('$c_1$')
ax.set_ylabel('$c_2$')plt.show()

在代码块[100]中:

tsne.kl_divergence_

输出[100]:

2.2818214893341064

像以前一样,定义一个矩形区域

在代码块[101]中:

((c1_min, c2_min), (c1_max, c2_max)) = np.percentile(Z_tsne, q=[5, 95], axis=0)

在代码块[102]中:

roi_rect = patches.Rectangle(xy=(c1_min, c2_min),width=c1_max-c1_min,height=c2_max-c2_min, alpha=.4)

在代码块[103]中:

fig, ax = plt.subplots()ax.scatter(*Z_tsne.T)
ax.add_patch(roi_rect)ax.set_xlabel('$c_1$')
ax.set_ylabel('$c_2$')plt.show()

在代码块[104]中:

fig, ax = plt.subplots(figsize=(10, 10))ax.set_xlim(c1_min, c1_max)
ax.set_ylim(c2_min, c2_max)for i, sketch in enumerate(test_set.strokes):sketch_path = SketchPath(sketch, factor=2.)sketch_path.vertices[::,1] *= -1sketch_path.vertices += Z_tsne[i]patch = patches.PathPatch(sketch_path, facecolor='none')ax.add_patch(patch)ax.axis('off')plt.savefig("../../files/sketchrnn/aaron_sheep_tsne.svg", format="svg")

完整的SVG图片可以在此获得。虽然这产生了很好的结果,但作者认为如果可视化在一个具有多类的更大数据集上会更有说服力。通过这种方式,可以看到t-SNE有效地形成了对不同类的聚类图,并突出显示了每个集群中的变化。

如果是RGB光栅图,使用像t-SNE这样的技术会获得很奇特的结果(比如多个头的小狗等)。然而,相似之处往往是表面的,主要是基于色度、亮度等方面。当嵌入代码表示时,相似性变得更加抽象,并且往往是基于类和语义。此外,这也允许我们能够检测出神经网络形成了什么样的相似性概念。

在另一方面,矢量图是由连续的笔画组成,能被表示成笛卡尔平面上的偏移量序列。一般来说,任意长度序列之间的相似性度量并没有得到很好地定义。因此,不能直接在矢量图上应用降维技术。

使用变形的递归神经网络,比如SketchRNN,可以将原始图转换为潜在空间中丰富的表示,这样做可以使得我们运用强大的技术如t-SNE去组织并可视化这些表示。另外利用相似性能够提炼更多的语义和抽象的概念。

本文的工作是受到其它一些优秀的文章和工作的启发,如下所示:

  • 1. Visualizing MNIST
  • 2. Visualizing Representations
  • 3. Latent space visualization
  • 4. Analyzing 50k fonts using deep neural networks
  • 5. Organizing the World of Fonts with AI

作者信息

Louis Tiao:计算机科学和数学的研究者,研究领域集中于算法设计与分析、计算机科学理论、人工智能和机器学习。

Linkedin:http://www.linkedin.com/in/ltiao/?ppe=1

Github:https://github.com/scikit-learn/scikit-learn

本文由北邮@爱可可-爱生活老师推荐,阿里云云栖社区组织翻译。

文章原标题《Visualizing the Latent Space of Vector Drawings from the Google QuickDraw Dataset with SketchRNN, PCA and t-SNE》,作者:Louis Tiao,译者:海棠,审阅:阿福

文章为简译,更为详细的内容,请查看原文

翻译者: 海棠

Wechat:269970760
weibo:Uncle_LLD

Email:duanzhch@tju.edu.cn

微信公众号:AI科技时讯

通过SketchRNN、PCA和t-SNE从Google QuickDraw数据集中显示矢量图的潜在空间|附源码相关推荐

  1. 直接从Google Play下载apk(附源码)

    由于特殊需求需要,需要直接从google play下载各种app 的apk包,有很多网站提供现成的下载入口,但我的需求是要程序自动下载,于是分析了几个网站的下载接口 比如:https://apps.e ...

  2. Google技术应用---在线地图的应用开开使用(提供源码)

    我们都知道,以前要在自己网站上嵌入Google Maps的卫星地图数据,需要学习复杂的Google Maps API的知识,还要申请API Key,现在,Google发布了一个新的功能,只需要简单的粘 ...

  3. 通过Google内置计步器和加速度传感器制作android计步程序(附源码)

    最近需要做一个计步程序,在网站上研究了一些别人写的程序代码,比较普遍实用的是根据API大小,使用Google内置计步器或加速度传感器进行计步.但是网上源代码的注释很少,经过了一番波折,自己终于有了头绪 ...

  4. PCA算法的原理C++ Eigen库实现(附源码下载)

    PCA的目的:  pca算法,也叫主成分分析法,能够对一个多样本的多维特征向量构成的矩阵进行分析,分析主成分并去除维度之间的相关性,使线性相关的向量组变成线性无关的向量组.  并且可以对样本进行降维, ...

  5. 学习笔记(6):Google开发专家带你学 AI:入门到实战(Keras/Tensorflow)(附源码)-实战:电影评论分类模型

    立即学习:https://edu.csdn.net/course/play/26109/323206?utm_source=blogtoedu 同样的数据集,为什么训练结果会不同? 在google C ...

  6. 【Python机器学习】PCA降维算法讲解及二维、高维数据可视化降维实战(附源码 超详细)

    需要全部代码请点赞关注收藏后评论区留言私信~~~ 维数灾难 维数灾难是指在涉及到向量计算的问题中,当维数增加时,空间的体积增长得很快,使得可用的数据在空间中的分布变得稀疏,向量的计算量呈指数倍增长的一 ...

  7. google插件查看源码

    1. 访问 chrome://version/ 找到个人资料路径 2. 进入目录 ls "/Users/yanlp/Library/Application Support/Google/Ch ...

  8. Pca,Kpca,TSNE降维非线性数据的效果展示与理论解释

    Pca,Kpca,TSNE降维非线性数据的效果展示与理论解释 前言 一:几类降维技术的介绍 二:主要介绍Kpca的实现步骤 三:实验结果 四:总结 前言 本文主要介绍运用机器学习中常见的降维技术对数据 ...

  9. Google Mock(Gmock)简单使用和源码分析——源码分析

    源码分析 通过<Google Mock(Gmock)简单使用和源码分析--简单使用>中的例子,我们发现被mock的相关方法在mock类中已经被重新实现了,否则它们也不会按照我们的期待的行为 ...

最新文章

  1. 石头剪刀布python代码_我的第一个python程序,石头剪刀布猜拳游戏
  2. 从硬件到软件 统一沟通将引领通讯市场
  3. incident用法_“incident、accident、event”,都是“事件”,怎么区分?
  4. mac搜索服务器文件,ProFind——文件搜索神器
  5. tinyxml在linux和windows下的编译及使用详解
  6. Unity中使用RequireComponent,没有添加上组件
  7. android rn 和webview,RN Webview与Web的通信与调试
  8. 信息学奥赛一本通C++语言——1096:数字统计
  9. 为什么你学HTML5前端这么久,水平还是烂成渣?
  10. PACPerformance
  11. atitit.报表最佳实践oae 与报表引擎选型
  12. java 64位时间戳转换32位时间戳
  13. 【读书笔记】大话数据结构
  14. 2021年国内好用的可视化工具
  15. springboot基于微信小程序的宿舍管理系统毕业设计源码
  16. 软件测试思想者(Software Testing - Thinker) - Logo一览
  17. 史上最全的数据库面试题,面试前刷一刷
  18. 神经网络机器翻译(一)
  19. 电脑微信聊天记录删除后如何找回?三个简单方法
  20. SCI、EI和IEEE有什么区别

热门文章

  1. ios7 兼容之前版本
  2. 数据仓库搭建ADS层
  3. javascript-匀速动画
  4. html怎么添加圆圈按钮,如何使用HTML5和CSS 3在圆圈周围放置按钮?
  5. 练习打字第一天!努力学习
  6. 【科研工具】审稿人第一眼只看配图?
  7. Java swing二手书销售管理系统数据库课程设计
  8. arxiv文章下载慢解决方法(更新:arxiv搜索具体领域最新文章指南)
  9. 量子物理和中国传统文化中道佛两教及气功_東波_新浪博客
  10. 计算机画图软件技巧,Win7系统自带画图工具快速浏览图片的技巧