1. 低阶API神经网络模型

1.1. 关于tf.Module

关于Tensorflow 2.x,最令我觉得有意思的功能就是tf.function和AutoGraph了.他们可以把Python风格的代码转为效率更好的Tensorflow计算图。

TensorFlow 2.0主要使用的是动态计算图和Autograph。Autograph机制可以将动态图转换成静态计算图,兼收执行效率和编码效率之利。

我们使用tf.Module来更好地构建Autograph。在介绍Autograph的编码规范时提到构建Autograph时应该避免在@tf.function修饰的函数内部定义tf.Variable。

但是如果在函数外部定义tf.Variable的话,又会显得这个函数有外部变量依赖,封装不够完美。

一种简单的思路是定义一个类,并将相关的tf.Variable创建放在类的初始化方法中。而将函数的逻辑放在其他方法中。

因此,TensorFlow提供了一个基类tf.Module,通过继承它构建子类,我们不仅可以获得以上的自然而然,而且可以非常方便地管理变量,还可以非常方便地管理它引用的其它Module,最重要的是,我们能够利用tf.saved_model保存模型并实现跨平台部署使用。

1.2. 全连接神经网络

全连接神经网络是人工神经网络中最简单的一种,中间每一个全连接层都会对输入节点乘上权重,然后加上一个bias,经过几层计算后通过一个激活函数输出节点,完成分类。

如图定义神经网络,其中G(f(x))G(f(x))G(f(x))中GGG函数采用relu函数,g(f(x))g(f(x))g(f(x))中ggg函数采用多分类softmax函数。

1.3. 神经网络模型类的定义

layer=[2,32,32,2] #神经网络模型定义为4层全连接。
input_size = layer[0] #输入数据为2个class DNNModel(tf.Module):def __init__(self, layer=[input_size,128,2], keep_prob = 0.2, name=None):super(DNNModel, self).__init__(name=name)self.layer=layerself.keep_prob = keep_probself.num = len(self.layer)-1   # 网络层数self.w = []self.b = []h_in = self.layer[0]h_out = self.layer[1]for i in range(self.num):# tf.truncated_normal由tf.random.truncated_normal替换w0 = tf.Variable(tf.random.truncated_normal([h_in,h_out],stddev=0.1),name='weights' +str(i+1) )b0 = tf.Variable(tf.zeros([h_out],name='biases' + str(i+1)))  self.w.append(w0)self.b.append(b0)if i < self.num -1:h_in = self.layer[i + 1]h_out = self.layer[i + 2]self.var = []for i in range(len(self.w)):self.var.append(self.w[i])self.var.append(self.b[i])#@tf.function(input_signature=[tf.TensorSpec(shape = [None,2], dtype = tf.float32)])@tf.function(input_signature=[tf.TensorSpec(shape = [None,input_size], dtype = tf.float32, name='dnn')]) def __call__(self, x):x = tf.cast(x, tf.float32)  # 转换输入数据类型h1 = xfor i in range(self.num):#定义前向传播过程if i < self.num -1:#h0 = tf.nn.relu(x@self.w[i] + self.b[i], name='layer' +str(i+1))             h0 = tf.nn.relu(tf.matmul(h1, self.w[i]) + self.b[i], name='layer' +str(i+1))  # 或x@w,以及tf.add#使用dropoutif i == 0:h1 = h0else:h1 = tf.nn.dropout(h0, rate = 1 - self.keep_prob) else:h1 = tf.matmul(h1, self.w[i]) + self.b[i]# 定义输出层self.y_conv = tf.nn.softmax(h1,name='y_conv') return self.y_convmodel = DNNModel(layer = layer, keep_prob = 0.2)  # 实例化模型类

tf.function本质上就是一个函数修饰器,它能够帮助将用户定义的python风格的函数代码转化成高效的tensorflow计算图(可以理解为:之前tf1.x中graph需要自己定义,现在tf.function能够帮助一起定义)。转换的这个过程称为AutoGraph。

我们使用input_signature对tf.function修饰的函数进行数字签名,其中,tf.TensorSpec() 函数签名由函数原型组成。它告诉你的是关于函数的一般信息,它的名称,参数,它的范围以及其他杂项信息。

tf.TensorSpec ( shape, dtype=tf.dtypes.float32, name=None )

注:对于被tf.function修饰过的函数都有get_concrete_function的属性,可以通过该操作对函数添加函数签名,从而获取特定追踪。通过增加函数签名之后才能够将模型保存。

将python标量或列表作为参数传递tf.function将始终建立一个新图形。为了避免这种情况,请尽可能将数字参数作为张量传递

2. 神经网络训练

2.1. 训练结构定义

2.2.1. 损失函数

交叉熵是分类问题中使用比较广的一种损失函数,刻画了两个概率分布之间的距离。给定两个概率分布ppp和qqq,通过qqq来表示ppp的交叉熵为:

H(p,q)=−∑xp(x)logq(x)H(p,q)=-\sum_xp(x)logq(x)H(p,q)=−∑x​p(x)logq(x)

交叉熵损失函数定义为:

loss=−∑i=1nyilogy^iloss=-\sum_{i=1}^ny_ilog\hat{y}_iloss=−∑i=1n​yi​logy^​i​

其中,y^i\hat{y}_iy^​i​是预测值,yiy_iyi​是实际值。

2.2.2. 训练类代码示例

class Train_Model(object):def __init__(self, model):self.model = model# 将预测值限制在 1e-10 以上, 1 - 1e-10 以下,避免log(0)错误def loss_func(self, y_true, y_pred, eps = 1e-10):y_pred = tf.clip_by_value(y_pred, eps, 1.0-eps)y_pred = tf.cast(y_pred, tf.float32)y_true = tf.cast(y_true, tf.float32)y_pred = - y_true*tf.math.log(y_pred) # 自定义交叉熵损失loss = tf.reduce_sum(tf.cast(y_pred, tf.float32))        return loss# 计算准确度def metric_func(self, y_true, y_pred):preds = tf.argmax(y_pred, axis=1)  # 取值最大的索引,正好对应字符标签labels = tf.argmax(y_true, axis=1)accuracy = tf.reduce_mean(tf.cast(tf.equal(preds, labels), tf.float32))         return accuracy# 训练输入,训练集X,训练集Y,测试集X,测试集Y,过程数,学习率,批次尺寸,最小值def train(self, X_train, Y_train, X_test, Y_test, EPOCHS, LEARNING_RATE=0.01, batch_size=32, eps = 1e-10):for epoch in tf.range(1,EPOCHS+1):i = 0for features, labels in data_iter(X_train, Y_train, batch_size):with tf.GradientTape() as tape:  # 追踪梯度preds = self.model(features)loss = self.loss_func(labels, preds, eps=eps)trainable_variables = self.model.var # 需优化参数列表grads = tape.gradient(loss, trainable_variables)  # 计算梯度,求导#梯度优化裁剪    grads = [tf.clip_by_value(g, -0.5, 0.5) for g in grads if g is not None]optimizer = tf.optimizers.Adam(learning_rate=LEARNING_RATE)  # Adam 优化器optimizer.apply_gradients(zip(grads, trainable_variables))  # 更新梯度     # 计算准确度accuracy = self.metric_func(labels, preds)# 输出各项指标if (i + 1) % 10 == 0:print(f'Train: {i}, Train loss: {loss}, Test accuracy: {accuracy}')i = i + 1# 计算准确度y_pred = model(X_test)     accuracy = self.metric_func(Y_test, y_pred)print('-----------------------------------')tf.print(f'Epoch [{epoch}/{EPOCHS}], Train loss: {loss}, Test accuracy: {accuracy}')return self.model# 定义实例化训练类,输入神经网络模型
train = Train_Model(model)

2.2. 训练集及多分类one-hot编码

2.2.1. 训练集

按比例拆分(7/3)数据为训练集和测试,采用打乱Tensor索引方式,按索引抽取数据构建数据集。

使用tf.gather()按索引筛选数据集。

2.2.2. tf.one_hot()进行独热编码

独热编码(one-hot encoding)一般是在有监督学习中对数据集进行标注时候使用的,指的是在分类问题中,将分类数据转化为one-hot类型数据输出,相当于将多个数值联合放在一起作为多个相同类型的向量,可用于表示各自的概率分布,通常用于分类任务中作为最后的FC层的输出。

2.2.3. 代码及图示

这部分代码是本次实践过程的第一部分代码,在此引入所依赖的包。

import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
import tensorflow as tf
%matplotlib inline
%config InlineBackend.figure_format = 'svg'#正负样本数量
n_positive,n_negative = 2000,2000#生成正样本, 小圆环分布
r_p = 5.0 + tf.random.truncated_normal([n_positive,1],0.0,1.0)
theta_p = tf.random.uniform([n_positive,1],0.0,2*np.pi)
Xp = tf.concat([r_p*tf.cos(theta_p),r_p*tf.sin(theta_p)],axis = 1)
Yp = tf.ones_like(r_p)#生成负样本, 大圆环分布
r_n = 8.0 + tf.random.truncated_normal([n_negative,1],0.0,1.0)
theta_n = tf.random.uniform([n_negative,1],0.0,2*np.pi)
Xn = tf.concat([r_n*tf.cos(theta_n),r_n*tf.sin(theta_n)],axis = 1)
Yn = tf.zeros_like(r_n)#汇总样本
X = tf.concat([Xp,Xn],axis = 0)
Y = tf.concat([Yp,Yn],axis = 0)
#多分类编码
lables = Y.numpy().reshape(-1)
Y = tf.one_hot(indices=lables, depth=2, on_value=1.0, off_value=0.0, axis=-1)# 切分数据集,测试集占 30%
num_examples = len(X)
indices = list(range(num_examples))
np.random.shuffle(indices)  #样本的读取顺序是随机的
split_size = int(num_examples*0.7)
indexs = indices[0: split_size]
X_train, Y_train = tf.gather(X,indexs), tf.gather(Y,indexs)
indexs = indices[split_size: num_examples]
X_test, Y_test = tf.gather(X,indexs), tf.gather(Y,indexs)#可视化
plt.figure(figsize = (6,6))
plt.scatter(Xp[:,0].numpy(),Xp[:,1].numpy(),c = "r")
plt.scatter(Xn[:,0].numpy(),Xn[:,1].numpy(),c = "g")
plt.legend(["positive","negative"]);

2.3. 训练过程

  • 训练样本训练过程为3次,EPOCHS=3;
  • 学习率设置为,LEARNING_RATE=0.01;
  • 训练输入每批次数据为,batch_size=16。
EPOCHS=3
LEARNING_RATE=0.01
batch_size=16
eps = 1e-10train_model = train.train( X_train, Y_train, X_test, Y_test, EPOCHS, LEARNING_RATE=0.01, batch_size=32, eps = 1e-10)

训练过程如下:

 Train: 9, Train loss: 20.195472717285156, Test accuracy: 0.5625Train: 19, Train loss: 20.5467472076416, Test accuracy: 0.59375Train: 29, Train loss: 20.818161010742188, Test accuracy: 0.53125Train: 39, Train loss: 19.24681282043457, Test accuracy: 0.53125Train: 49, Train loss: 22.78866958618164, Test accuracy: 0.4375Train: 59, Train loss: 16.575977325439453, Test accuracy: 0.8125Train: 69, Train loss: 19.563919067382812, Test accuracy: 0.625Train: 79, Train loss: 18.80452537536621, Test accuracy: 0.6875Epoch [1/3], Train loss: 6.982160568237305, Test accuracy: 0.8075000047683716......Epoch [2/3], Train loss: 3.473015546798706, Test accuracy: 0.8600000143051147......Epoch [3/3], Train loss: 1.8749024868011475, Test accuracy: 0.9158333539962769

3. 梯度消失与梯度爆炸问题

梯度消失和梯度爆炸的根源主要是因为深度神经网络结构以及反向传播算法,在反向传播的过程中,需要对激活函数进行求导,如果导数大于1,那么会随着网络层数的增加梯度更新将会朝着指数爆炸的方式增加,这就是梯度爆炸。同样,如果导数小于1,那么随着网络层数的增加梯度更新信息会朝着指数衰减的方式减少,这就是梯度消失。

目前优化神经网络的方法都是基于反向传播的思想,即根据损失函数计算的误差通过反向传播的方式,指导深度网络权值的更新。

3.1. 简明通俗原理

通俗来讲,神经网络最基本节点的神经单元,数学表示为yi=f(wixi+bi)y_{i}=f(w_{i}x_{i}+b_{i})yi​=f(wi​xi​+bi​),一般通过反向求导模式追踪每一个节点如何影响一个输出。这是以误差为主导的反向传播(Back Propagation),旨在得到最优的全局参数矩阵(权重参数)。

当神经网络的层数较多、神经元节点较多时,模型的数值稳定性容易变差。假设一个层数为LLL的网络,第lll层HlH_{l}Hl​的权重参数为WlW_{l}Wl​,为了便于讨论,略去偏置参数bbb。对于给定XXX输入的网络,第lll层的输出Hl=XW1W2...WlH_{l}=XW_{1}W_{2}...W_{l}Hl​=XW1​W2​...Wl​。此时,HlH_{l}Hl​很容易出现衰减或爆炸。

例如第30层的输出将出现0.230≈1×10−210.2^{30} \approx 1 \times 10^{-21}0.230≈1×10−21,衰减掉了。

当我们训练神经网络时,把“损失“ 看作 ”权重参数“ 的函数。因此,出现nan现象就是∑wixi+bi\sum w_{i}x_{i}+b_{i}∑wi​xi​+bi​越界造成的。

那么,我们要做的就是如何防止出现∑wixi+bi\sum w_{i}x_{i}+b_{i}∑wi​xi​+bi​越界。

反向传播是以目标的负梯度方向对参数进行调整,参数的更新为:wi←wi+Δww_{i} \leftarrow w_{i} + \Delta wwi​←wi​+Δw。

给定学习率α\alphaα,得出:Δw=−α∂Loss∂w\Delta w = -\alpha \frac{\partial Loss}{\partial w}Δw=−α∂w∂Loss​

由于深度网络是多层非线性函数的堆砌,整个深度网络可以视为是一个复合的非线性多元函数(这些非线性多元函数其实就是每层的激活函数),那么对loss函数求不同层的权值偏导,相当于应用梯度下降的链式法则,链式法则是一个连乘的形式,所以当层数越深的时候,梯度将以指数传播。

如果接近输出层的激活函数求导后梯度值大于1,那么层数增多的时候,最终求出的梯度很容易指数级增长,也就是梯度爆炸;相反,如果小于1,那么经过链式法则的连乘形式,也会很容易衰减至0,也就是梯度消失。

3.2. 原因

神经网络训练过程中所有nan的原因:一般是正向计算时节点数值越界,或反向传播时gradient数值越界。无论正反向,数值越界基本只有三种操作会导致:

  1. 节点权重参数或梯度的数值逐渐变大直至越界;
  2. 有除零操作,包括0除0,这种比较常见于趋势回归预测;或者,交叉熵对0或负数取log;
  3. 输入数据存在异常,过大/过小的输入,导致瞬间NAN。

3.3. 解决方案

3.3.1. 减小学习率

减少学习率(learning_rate)是最直接的、简易的方法。

学习率较高的情况下,直接影响到每次更新值的程度比较大,走的步伐因此也会大起来。通常过大的学习率会导致无法顺利地到达最低点,稍有不慎就会跳出可控制区域,此时我们将要面对的就是损失成倍增大(跨量级)。

训练过程中,我们可以尝试把学习率减少10倍、100倍,乃至更多,该类问题绝大部分可以得到解决(注意学习率也别太小,否则收敛过慢浪费时间,要在速度和稳定性之间平衡)

3.3.2. 权重参数初始化

神经网络的训练过程,实质就是自动调整网络中参数的过程。在训练的起初,网络的参数总要从某一状态开始,而这个初始状态的设定,就是神经网络的初始化。

合适网络初始值,不仅有助于梯度下降法在一个好的“起点”上去寻找最优值。

通常,我们常见的初始化是随机正态分布初始化,权重和偏置的初始化采用的是符合均值为0、标准差为1的标准正态分布(Standard Noraml Distribution)随机化方法。但它是最佳的初始化策略吗?

常用初始化方法:

  1. tf.constant_initializer() 常数初始化
  2. tf.ones_initializer() 全1初始化
  3. tf.zeros_initializer() 全0初始化
  4. tf.random_uniform_initializer() 均匀分布初始化
  5. tf.random_normal_initializer() 正态分布初始化

3.3.3. 预测结果裁剪

交叉熵Loss计算中出现Nan值,交叉熵计算如下:
loss=−∑i=1nyilog(y^i)loss=-\sum_{i=1}^{n} y_{i} log(\hat{y}_{i})loss=−∑i=1n​yi​log(y^​i​)

虽然y^\hat{y}y^​是经过tf.nn.sigmoid函数得到的,但在输出的参数非常大,或者非常小的情况下,会给出边界值1或者0,因此给出个极小值eps,是预测结果最小值大于0为eps,最大值小于1为1-eps。

    y_pred = tf.clip_by_value(y_pred, eps, 1.0-eps)       y_pred = - y_true*tf.math.log(y_pred) # 自定义交叉熵损失

3.3.4. 梯度修剪

对于在tensorflow中解决梯度爆炸问题,原理很简单就是梯度修剪,把大于1的导数修剪为1。

Tensorflow梯度修剪函数为tf.clip_by_value(A, min, max):
输入一个张量A,把A中的每一个元素的值都压缩在min和max之间。小于min的让它等于min,大于max的元素的值等于max。

     trainable_variables = self.model.var # 需优化参数列表grads = tape.gradient(loss, trainable_variables)  # 计算梯度,求导#梯度优化裁剪    grads = [tf.clip_by_value(g, -0.5, 0.5) for g in grads if g is not None]optimizer = tf.optimizers.Adam(learning_rate=LEARNING_RATE)  # Adam 优化器optimizer.apply_gradients(zip(grads, trainable_variables))  # 更新梯度

另外,网络推荐clip_by_global_norm:
tf.clip_by_global_norm 重新缩放张量列表,以便所有范数的向量的总范数不超过阈值。但是它可以一次作用于所有梯度,而不是分别作用于每个梯度(即,如果有必要,将全部按相同的因子进行缩放,或者都不进行缩放)。这样会更好,因为可以保持不同梯度之间的平衡。

3.3.5. 其他

  1. 缩小Bathsize
    通过缩小batch_size,相当于缩小输入,达到缩减权重集合目标,有一定效果,但是训练速度可能减慢。

  2. 输入归一化
    对于输入数据,图片类比较好解决,把0—255归一化到0~1,而其他数据,需要根据数据分布分析处理,采用合适的归一化方案。

  3. 调整激活函数
    神经网络,现在经常使用relu做为激活函数,但是容易出现Nan的问题,可以根据实际情况换激活函数。

  4. batch normal
    BN具有提高网络泛化能力的特性。采用BN算法后,就可以移除针对过拟合问题而设置的dropout和L2正则化项,或者采用更小的L2正则化参数。

4. 模型应该效果

4.1. 输出结果

print(train_model(X_test))
print(tf.argmax(train_model(X_test), axis=1))
tf.Tensor(
[[1.0000000e+00 1.5792454e-08][5.5160701e-02 9.4483924e-01][1.2523024e-01 8.7476975e-01]...[2.0679454e-03 9.9793208e-01][6.4314783e-01 3.5685217e-01][1.1797199e-01 8.8202792e-01]], shape=(1200, 2), dtype=float32)
tf.Tensor([0 1 1 ... 1 0 1], shape=(1200,), dtype=int64)

4.2. 结果可视化

# 结果可视化
Xp = tf.boolean_mask(X_test,tf.squeeze(tf.argmax(Y_test, axis=1)==1),axis = 0)
Xn = tf.boolean_mask(X_test,tf.squeeze(tf.argmax(Y_test, axis=1)==0),axis = 0)fig, (ax1,ax2) = plt.subplots(nrows=1,ncols=2,figsize = (12,5))
ax1.scatter(Xp[:,0],Xp[:,1],c = "r")
ax1.scatter(Xn[:,0],Xn[:,1],c = "g")
ax1.legend(["positive","negative"]);
ax1.set_title("y_true");Xp_pred = tf.boolean_mask(X_test,tf.squeeze(tf.argmax(train_model(X_test), axis=1)==1),axis = 0)
Xn_pred = tf.boolean_mask(X_test,tf.squeeze(tf.argmax(train_model(X_test), axis=1)==0),axis = 0)ax2.scatter(Xp_pred[:,0],Xp_pred[:,1],c = "r")
ax2.scatter(Xn_pred[:,0],Xn_pred[:,1],c = "g")
ax2.legend(["positive","negative"]);
ax2.set_title("y_pred");

其中,tf.boolean_mask 的作用是 通过布尔值 过滤元素。

5. 总结

Tensorflow1.x升级到2.x低阶API,除了动态图带来容易上手开发外,其中基类tf.Module,通过继承它构建子类,更易封装完美的神经网络模型,而且可以非常方便地管理变量,还可以非常方便地管理它引用的其它Module,最重要的是,我们能够利用tf.saved_model保存模型并实现跨平台部署使用。

如果需要在TensorFlow2.0中使用静态图,可以使用@tf.function装饰器将普通Python函数转换成对应的TensorFlow计算图构建代码。运行该函数就相当于在TensorFlow1.0中用Session执行代码。使用tf.function构建静态图的方式叫做 Autograph。

参考:

[1]. Hollyprince. 梯度消失和梯度爆炸及解决方法. CSDN博客. 2022.07
[2]. 我是小白呀. TensorFlow2 手把手教你避开梯度消失和梯度爆炸. CSDN博客. 2021.06
[3]. Fain. 梯度消失梯度爆炸-Gradient Clip. 知乎 2021.11
[4]. 里列昂遗失的记事本. 深度神经网络的训练-梯度消失和梯度爆炸问题. 博客园. 2021.09
[5]. 肖永威. Tensorflow神经网络训练梯度爆炸与消失(Nan)问题实践分析. 知乎. 2021.07
[6]. lyhue1991. 《30天吃掉那只 TensorFlow2.0》 3-1,低阶API示范. https://github.com/lyhue1991. 2020.12
[7]. 倚剑天客. TensorFlow2——函数签名. CSDN博客. 2020.03
[8]. 肖永威. Python Tensorflow1.x升级到2.x低阶API实践. CSDN博客. 2022.09

基于Tensorflow2.x低阶API搭建神经网络模型并训练及解决梯度爆炸与消失方法实践相关推荐

  1. 【物体检测快速入门系列 | 01 】基于Tensorflow2.x Object Detection API构建自定义物体检测器

    这是机器未来的第1篇文章 原文首发地址:https://blog.csdn.net/RobotFutures/article/details/124745966 CSDN话题挑战赛第1期 活动详情地址 ...

  2. spark的kafka的低阶API createDirectStream的一些总结。

    大家都知道在spark1.3版本后,kafkautil里面提供了两个创建dstream的方法,一个是老版本中有的createStream方法,还有一个是后面新加的createDirectStream方 ...

  3. 这几天折腾spark的kafka的低阶API createDirectStream的一些总结。

    大家都知道在spark1.3版本后,kafkautil里面提供了两个创建dstream的方法,一个是老版本中有的createStream方法,还有一个是后面新加的createDirectStream方 ...

  4. Keras之ML~P:基于Keras中建立的回归预测的神经网络模型(根据200个数据样本预测新的5+1个样本)——回归预测

    Keras之ML~P:基于Keras中建立的回归预测的神经网络模型(根据200个数据样本预测新的5+1个样本)--回归预测 目录 输出结果 核心代码 输出结果 核心代码 # -*- coding: u ...

  5. 如何搭建神经网络模型,构建神经网络模型方法

    英伟达开发板功能 英伟达开发板功能:Jetson Nano 是一款功能强大的人工智能(AI)开发板,可助你快速入门学习 AI 技术,并将其应用到各种智能设备. 它搭载四核Cortex-A57处理器,1 ...

  6. DL-3利用MNIST搭建神经网络模型(三种方法):1.用CNN 2.用CNN+RNN 3.用自编码网络autoencoder

    Author:吾爱北方的母老虎 原创链接:https://blog.csdn.net/weixin_41010198/article/details/80286216 import tensorflo ...

  7. python实现简单的神经网络,python搭建神经网络模型

    1.如何用9行Python代码编写一个简易神经网络 学习人工智能时,我给自己定了一个目标--用Python写一个简单的神经网络.为了确保真得理解它,我要求自己不使用任何神经网络库,从头写起.多亏了An ...

  8. python搭建神经网络模型,python实现神经网络算法

    如何用 Python 构建神经网络择时模型 . importmathimportrandom(0)defrand(a,b):#随机函数return(b-a)*random.random()+adefm ...

  9. 如何搭建神经网络模型,神经网络建立数学模型

    如何用visio画卷积神经网络图.图形类似下图所示 . 大概试了一下用visio绘制这个图,除了最左面的变形图片外其余基本可以实现(那个图可以考虑用其它图像处理软件比如Photoshop生成后插入vi ...

最新文章

  1. C语言------运算符和表达式
  2. 机器学习算法一览,应用建议与解决思路
  3. java 找不到工程文件_类加载器找不到文件求帮助
  4. python 求连线相似度_Python分析《都挺好》中的人物关系,苏大强与蔡根花是真爱?...
  5. BZOJ3057 圣主的考验
  6. python分析pcap文件_利用Python库Scapy解析pcap文件的方法
  7. spring mvc和swagger整合
  8. python关于numpy常用函数思维导图
  9. 基于MBOM的工艺数据管理及集成技术研究
  10. python计算球体体积_漫谈超球体的体积公式
  11. login.defs文件基础
  12. 小米手机 VS 阿里手机,阿里输在512MB内存,华为Honor只有电池容量跟小米差不多...
  13. 穷爸爸富爸爸的作者破产了,这本书是垃圾还是宝藏?
  14. 机器学习数据集划分留出法,留一法,交叉法,自助法
  15. 解线性方程组的各种情况
  16. 华域php公司,华域智能DNS系统
  17. laya2.0 微信好友对战
  18. 小米手机 root权限 获取
  19. 【汇正财经】股本具体解析
  20. linux下双击执行.sh脚本文件

热门文章

  1. 【电气专业知识问答】问:调节器用TV熔丝熔断后的现象是什么?如何分析与处理?
  2. Oracle 10g数据库创建
  3. JWT 实现登录认证 + Token 自动续期方案
  4. 数据结构与算法基础(青岛大学-王卓)(1)
  5. MySql首次登录修改密码
  6. 快递100接口查询限制分析
  7. Hexo-matery主题美化以及zeit部署
  8. 如何加密文件或文件夹?
  9. python中stacked_Python 绘图 - Bokeh 柱状图小试(Stacked Bar)
  10. 哈工大:ChatGPT调研报告