人工智能学习与实训笔记(一):零基础理解神经网络

前言

本专栏为《人工智能技术学习笔记》。希望以学习笔记的形式和大家一起一步步由浅入深地探索人工智能技术。 

本篇为第一篇,带你浅入深处了解什么是神经网络。

目录

一、什么是神经网络模型

二、神经网络的模型结构

2.1 单层网络

2.2 多层网络

2.3 非线性多层网络

三、机器学习的类型

3.1 监督学习

3.2 无监督学习

3.3 半监督学习

3.4 强化学习

 四、 神经网络问题实操:基于线性回归实现波士顿房价预测任务

4.1 波士顿房价预测任务简介

4.2 网络模型选择

4.3 模型训练实操(基于百度飞桨)

4.4 总结一下:

五、​完整Python源码 - 波士顿房价模型训练及测试:


一、什么是神经网络模型

简而言之:神经网络模型是拟合现实问题的函数方程,即可以通过输入得到输出。

只不过这个函数是用神经网络的参数来拟合的,神经网络的参数是通过大量数据的训练获得,训练效果越好,则函数越逼近现实情况,就可以用来解决各种实际任务。

一个简单的网络模型函数如下:

y=w1*x1 + w2*x2 + w3*x3... 

其中,y是函数值(模型输出的预测值),x1,x2, x3...是输入值(又叫特征值),w1, w2, w3...是网络参数。

机器学习最根本的目的在于训练出在某个问题上泛化能力强的模型。泛化能力强是指在某问题的所有数据上都能很好地反应输入和输出之间的关系,无论是训练数据,还是测试数据,还是任何属于该问题的未知数据

神经网络经常处理的问题包括:回归问题,图像分类问题,目标检测问题,自然语言处理,喜好推荐等等。

二、神经网络的模型结构

2.1 单层网络

只包含一个输入层和输出层:

(输入层) --w--> (输出层)

2.2 多层网络

在输入层和输出层之间有多个隐含层:

(输入层) --w--> (隐含层) --w--> (隐含层) ... --> (输出层)

2.3 非线性多层网络

单层网络和多层网络默认只能表达线性变换,加入非线性激活函数后,可以表达非线性函数:

(输入层) --w--> (隐含层) --> (激活函数) --w--> (隐含层) --> (激活函数) ... --> (输出层)

​加入非线性激励函数后,神经网络就有可能学习到平滑的曲线来分割平面,而不是用复杂的线性组合逼近平滑曲线来分割平面,使神经网络的表示能力更强了,能够更好的拟合目标函数。 这就是为什么我们要有非线性的激活函数的原因。

关于激活函数,可以参考:卷积神经网络中的激活函数sigmoid、tanh、relu_卷积神经网络激活函数

三、机器学习的类型

根据训练期间接受的监督数量和监督类型,可以将机器学习分为以下四种类型:

监督学习、非监督学习、半监督学习和强化学习。

3.1 监督学习

在监督学习中,提供给算法的包含所需解决方案的训练数据,成为标签或标记。


简单地说,就是监督学习是包含自变量和因变量(有Y),同时可以用于分类和回归。下来常见的算法都是监督学习算法。

  • K近邻算法
  • 线性回归
  • logistic回归
  • 支持向量机(SVM)
  • 决策树和随机森林
  • 神经网络


3.2 无监督学习

无监督学习的训练数据都是未经标记的,算法会在没有指导的情况下自动学习。

简单地说,就是训练数据只有自变量没有因变量(就是没有Y)。

无监督学习的常见算法如下:

  • 聚类算法
    • K均值算法(K-means)
    • 基于密度的聚类方法(DBSCAN)
    • 最大期望算法
  • 可视化和降维
    • 主成分分析
    • 核主成分分析
  • 关联规则学习
    • Apriori
    • Eclat

比如说,有很多猫和狗的图像,我们不需要对猫和狗的图像做标记预处理,算法也能够根据猫和狗各自的特征,将猫和狗分为两类。

另一种任务是降维,降维的目的在于不丢失太多的信息的情况下简化数据。方法之一就是将多个特征合并为一个特征,特别是特征之间存在很大的相关性的变量。如汽车的里程和使用年限是存在很大的相关性的,所以降维算法可以将它们合并为一个表示汽车磨损的特征。这个过程就叫做特征提取。

另一个典型的无监督学习的是异常检测,如可以从检测信用卡交易中发现异常,并且这些异常我们实现没有标记的,算法可以自动发现异常。


3.3 半监督学习


有些算法可以处理部分标记的训练数据,通常是大量未标记的数据和少量标记的数据,这种成为半监督学习。

如照片识别就是很好的例子。在线相册可以指定识别同一个人的照片(无监督学习),当你把这些同一个人增加一个标签的后,新的有同一个人的照片就自动帮你加上标签了。


大多数半监督学习算法都是无监督和监督算法的结合。例如深度信念网络(DBN)基于一种相互堆叠的无监督式组件。

3.4 强化学习

强化学习是一个非常与众不同的算法,它的学习系统能够观测环境,做出选择,执行操作并获得回报,或者是以负面回报的形式获得惩罚。它必须自行学习什么是最好的策略,从而随着时间推移获得最大的回报。


例如,许多机器人通过强化学习算法来学习如何行走。AlphaGo项目也是一个强化学习的好例子。

 四、 神经网络问题实操:基于线性回归实现波士顿房价预测任务

用神经网络模型预测数据是比较常见的回归类问题,即根据输入的数值得到预测的输出数值。我们就用一个基本的线性回归问题来实际感受一下神经网络的训练和预测方法:

4.1 波士顿房价预测任务简介

波士顿房价预测是一个经典的机器学习任务,类似于程序员世界的“Hello World”。和大家对房价的普遍认知相同,波士顿地区的房价受诸多因素影响。该数据集统计了13种可能影响房价的因素和该类型房屋的均价,期望构建一个基于13个因素进行房价预测的模型,如图所示。

对于预测问题,可以根据预测输出的类型是连续的实数值,还是离散的标签,区分为回归任务和分类任务。因为房价是一个连续值,所以房价预测显然是一个回归任务。下面我们尝试用最简单的线性回归模型解决这个问题,并用神经网络来实现这个模型。

4.2 网络模型选择

神经网络的标准结构中每个神经元由加权和与非线性变换构成,然后将多个神经元分层的摆放并连接形成神经网络。线性回归模型可以认为是神经网络模型的一种极简特例,是一个只有加权和、没有非线性变换的神经元(无需形成网络),因此可以选择2.1节中说的单层网络模型进行训练。

4.3 模型训练实操(基于百度飞桨)

深度学习不仅实现了模型的端到端学习,还推动了人工智能进入工业大生产阶段,产生了标准化、自动化和模块化的通用框架。不同场景的深度学习模型具备一定的通用性,五个步骤即可完成模型的构建和训练,如图所示。



正是由于深度学习的建模和训练的过程存在通用性,即在构建不同的模型时,只有模型三要素不同,其它步骤基本一致,才产生了深度学习框架来加速建模。

4.3.1 数据处理
数据处理包含五个部分:数据导入、数据形状变换、数据集划分、数据归一化处理和封装load data函数。数据预处理后,才能被模型调用。

(1)数据读取
将波士顿房价的数据集导入到numpy中。

# 导入需要用到的package
import numpy as np
import json
# 读入训练数据
datafile = './work/housing.data'
data = np.fromfile(datafile, sep=' ')

(2)数据形状变换
由于读入的原始数据是1维的,所有数据都连在一起。因此需要我们将数据的形状进行变换,形成一个2维的矩阵,每行为一个数据样本(14个值),每个数据样本包含13个x(影响房价的特征)和1个y(该类型房屋的均价)。

# 读入之后的数据被转化成1维array,其中array的第0-13项是第一条数据,第14-27项是第二条数据,以此类推.... 
# 这里对原始数据做reshape,变成N x 14的形式
feature_names = [ 'CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE','DIS', 
                 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV' ]
feature_num = len(feature_names)
data = data.reshape([data.shape[0] // feature_num, feature_num])

# 查看数据
x = data[0]
print(x.shape)
print(x)

(14,)
[6.320e-03 1.800e+01 2.310e+00 0.000e+00 5.380e-01 6.575e+00 6.520e+01
 4.090e+00 1.000e+00 2.960e+02 1.530e+01 3.969e+02 4.980e+00 2.400e+01]

(3)数据集划分
将数据集划分成训练集和测试集,其中训练集用于确定模型的参数,测试集用于评判模型的效果。为什么要对数据集进行拆分,而不能直接应用于模型训练呢?这与学生时代的授课和考试关系比较类似,如图所示。


上学时总有一些自作聪明的同学,平时不认真学习,考试前临阵抱佛脚,将习题死记硬背下来,但是成绩往往并不好。因为学校期望学生掌握的是知识,而不仅仅是习题本身。另出新的考题,才能鼓励学生努力去掌握习题背后的原理。同样我们期望模型学习的是任务的本质规律,而不是训练数据本身,模型训练未使用的数据,才能更真实的评估模型的效果。


在本案例中,我们将80%的数据用作训练集,20%用作测试集,实现代码如下。通过打印训练集的形状,可以发现共有404个样本,每个样本含有13个特征和1个预测值。

ratio = 0.8
offset = int(data.shape[0] * ratio)
training_data = data[:offset]
training_data.shape
(404, 14)

(4)数据归一化处理
对每个特征进行归一化处理,使得每个特征的取值缩放到0~1之间。这样做有两个好处:一是模型训练更高效;二是特征前的权重大小可以代表该变量对预测结果的贡献度(因为每个特征值本身的范围相同)。

# 计算train数据集的最大值,最小值
maximums, minimums = \
                     training_data.max(axis=0), \
                     training_data.min(axis=0), 
# 对数据进行归一化处理
for i in range(feature_num):
    data[:, i] = (data[:, i] - minimums[i]) / (maximums[i] - minimums[i])

4.3.2 模型设计
模型设计是深度学习模型关键要素之一,也称为网络结构设计,相当于模型的假设空间,即实现模型“前向计算”(从输入到输出)的过程。
如果将输入特征和输出预测值均以向量表示,输入特征x有13个向量,y有1个向量,那么参数权重的形状是13×1。假设我们以如下任意数字赋值参数做初始化:
w=[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,−0.1,−0.2,−0.3,−0.4,0.0]

w = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, -0.1, -0.2, -0.3, -0.4, 0.0]
w = np.array(w).reshape([13, 1])

取出第1条样本数据,观察样本的特征向量与参数向量相乘的结果。

x1=x[0]
t = np.dot(x1, w)
print(t)
[0.69474855]

完整的线性回归公式,还需要初始化偏移量b,同样随意赋初值-0.2。那么,线性回归模型的完整输出是z=t+b,这个从特征和参数计算输出值的过程称为“前向计算”。

b = -0.2
z = t + b
print(z)
[0.49474855] 

将上述计算预测输出的过程以“类和对象”的方式来描述,类成员变量有参数�w和�b。通过写一个forward函数(代表“前向计算”)完成上述从特征和参数到输出预测值的计算过程,代码实现如下。

class Network(object):
    def __init__(self, num_of_weights):
        # 随机产生w的初始值
        # 为了保持程序每次运行结果的一致性,
        # 此处设置固定的随机数种子
        np.random.seed(0)
        self.w = np.random.randn(num_of_weights, 1)
        self.b = 0.
        
    def forward(self, x):
        z = np.dot(x, self.w) + self.b
        return z

基于Network类的定义,模型的计算过程如下所示。

net = Network(13)
x1 = x[0]
y1 = y[0]
z = net.forward(x1)
print(z)
[2.39362982]

4.3.3 模型训练配置

模型设计完成后,需要通过训练配置寻找模型的最优值,即通过损失函数来衡量模型的好坏。训练配置也是深度学习模型关键要素之一。通过模型计算1x1​表示的影响因素所对应的房价应该是z, 但实际数据告诉我们房价是y。这时我们需要有某种指标来衡量预测值z跟真实值y之间的差距。

这个指标就是损失函数,它是衡量模型好坏的指标。在回归问题中常用均方误差作为损失函数,而在分类问题中常用采用交叉熵(Cross-Entropy)作为损失函数。对其中任意一个样本计算损失函数值的代码实现如下:

Loss = (y1 - z)*(y1 - z)
print(Loss)
[3.88644793]

4.3.4 训练过程

上述计算过程描述了如何构建神经网络,通过神经网络完成预测值和损失函数的计算。接下来介绍如何求解参数wb的数值,这个过程也称为模型训练过程。训练过程是深度学习模型的关键要素之一,其目标是让定义的损失函数尽可能的小,也就是说找到一个参数解wb,使得损失函数取得极小值。下面介绍两种求解损失函数的方法。

(1)梯度下降法

在现实中存在大量的函数正向求解容易,但反向求解较难,被称为单向函数,这种函数在密码学中有大量的应用。密码锁的特点是可以迅速判断一个密钥是否是正确的(已知x,求y很容易),但是即使获取到密码锁系统,无法破解出正确的密钥是什么(已知y,求x很难)。神经网络模型的损失函数就是这样的单向函数,反向求解并不容易。

这种情况特别类似于一位想从山峰走到坡谷的盲人,他看不见坡谷在哪(无法逆向求解出Loss导数为0时的参数值),但可以伸脚探索身边的坡度(当前点的导数值,也称为梯度)。那么,求解Loss函数最小值可以这样实现:从当前的参数取值,一步步的按照下坡的方向下降,直到走到最低点。这种方法笔者称它为“盲人下坡法”。哦不,有个更正式的说法“梯度下降法(Gradient Descent,GD)”。

梯度是一个向量(矢量),表示某一函数在该点处的方向导数沿着该方向取得最大值,即函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为该梯度的模)。 

训练的关键是找到一组(w,b),使得损失函数L取极小值。微积分的基础知识告诉我们,沿着梯度的反方向,是函数值下降最快的方向,如图所示。简单理解,函数在某一个点的梯度方向是曲线斜率最大的方向,但梯度方向是向上的,所以下降最快的是梯度的反方向。

实现逻辑:“前向计算输出、根据输出和真实值计算Loss、基于Loss和输入计算梯度、根据梯度更新参数值”四个部分反复执行,直到到损失函数最小。具体代码如下所示。

class Network(object):
    def __init__(self, num_of_weights):
        # 随机产生w的初始值
        # 为了保持程序每次运行结果的一致性,此处设置固定的随机数种子
        np.random.seed(0)
        self.w = np.random.randn(num_of_weights, 1)
        self.b = 0.
        
    def forward(self, x):
        z = np.dot(x, self.w) + self.b
        return z
    
    def loss(self, z, y):
        error = z - y
        num_samples = error.shape[0]
        cost = error * error
        cost = np.sum(cost) / num_samples
        return cost
    
    def gradient(self, x, y):
        z = self.forward(x)
        gradient_w = (z-y)*x
        gradient_w = np.mean(gradient_w, axis=0)
        gradient_w = gradient_w[:, np.newaxis]
        gradient_b = (z - y)
        gradient_b = np.mean(gradient_b)        
        return gradient_w, gradient_b
    
    def update(self, gradient_w, gradient_b, eta = 0.01):
        self.w = self.w - eta * gradient_w
        self.b = self.b - eta * gradient_b
        
    def train(self, x, y, iterations=100, eta=0.01):
        losses = []
        for i in range(iterations):
            z = self.forward(x)
            L = self.loss(z, y)
            gradient_w, gradient_b = self.gradient(x, y)
            self.update(gradient_w, gradient_b, eta)
            losses.append(L)
            if (i+1) % 10 == 0:
                print('iter {}, loss {}'.format(i, L))
        return losses

# 获取数据
train_data, test_data = load_data()
x = train_data[:, :-1]
y = train_data[:, -1:]
# 创建网络
net = Network(13)
num_iterations=1000
# 启动训练
losses = net.train(x,y, iterations=num_iterations, eta=0.01)

# 画出损失函数的变化趋势
plot_x = np.arange(num_iterations)
plot_y = np.array(losses)
plt.plot(plot_x, plot_y)
plt.show()
iter 9, loss 5.143394325795511
iter 19, loss 3.097924194225988
iter 29, loss 2.082241020617026
iter 39, loss 1.5673801618157397
iter 49, loss 1.296620473507743
iter 59, loss 1.1453399043319765
iter 69, loss 1.0530155717435201
iter 79, loss 0.9902292156463153
iter 89, loss 0.9426576903842502

下图可以看到损失函数的损失值越来越小,表示训练有效:

(2)随机梯度下降法( Stochastic Gradient Descent)

在上述程序中,每次损失函数和梯度计算都是基于数据集中的全量数据。对于波士顿房价预测任务数据集而言,样本数比较少,只有404个。但在实际问题中,数据集往往非常大,如果每次都使用全量数据进行计算,效率非常低,通俗地说就是“杀鸡焉用牛刀”。由于参数每次只沿着梯度反方向更新一点点,因此方向并不需要那么精确。一个合理的解决方案是每次从总的数据集中随机抽取出小部分数据来代表整体,基于这部分数据计算梯度和损失来更新参数,这种方法被称作随机梯度下降法(Stochastic Gradient Descent,SGD),核心概念如下:

  • minibatch:每次迭代时抽取出来的一批数据被称为一个minibatch。
  • batch size:每个minibatch所包含的样本数目称为batch size。
  • Epoch:当程序迭代的时候,按minibatch逐渐抽取出样本,当把整个数据集都遍历到了的时候,则完成了一轮训练,也叫一个Epoch(轮次)。启动训练时,可以将训练的轮数num_epochsbatch_size作为参数传入。

观察上述损失函数的变化,随机梯度下降加快了训练过程,但由于每次仅基于少量样本更新参数和计算损失ii,所以损失下降曲线会出现震荡。

4.4 总结一下:

我们详细介绍了如何使用Numpy实现梯度下降算法,构建并训练了一个简单的线性模型实现波士顿房价预测,可以总结出,使用神经网络建模房价预测有三个要点:

  • 构建网络,初始化参数wb,定义预测和损失函数的计算方法。
  • 随机选择初始点,建立梯度的计算方法和参数更新方式。
  • 将数据集的数据按batch size的大小分成多个minibatch,分别灌入模型计算梯度并更新参数,不断迭代直到损失函数几乎不再下降。

神经网络模型训练主要有以下一些要点需注意:

1. 样本数据需要归一化,使得后续的神经网络模型参数可表征有效的权重。样本数据归一化是以列(特征值)为单位的。注意,在用测试集测试模型时,模型输出的函数预测值需要进行反归一化。


2. 数据集划分:80%用于训练,20%用于测试,训练和测试数据集必须分开,才能验证网络的有效性。


3. 影响波士顿房价的样本数据有13个特征值,每个特征值会有不同的权重,因此神经网络模型的可调参数为13个,分别代表不同特征值对最终房价影响的权重:y=w1*x1 + w2*x2 + ... +w13*x13


4. 损失函数是模型输出的值与样本数据中实际值偏差的一种表达函数,损失函数的选择既要考虑准确衡量问题的“合理性”,也还要考虑“易于优化求解”。


5. 训练过程就是通过不断调整网络模型参数,将损失函数的值降到最小(收敛)的过程, 损失函数的收敛需要通过梯度下降法来不断训练。以房价预测任务为例,影响房价的特征值有13个,因此我们需要调教的模型参数也是13个,这13个特征值和损失函数的值共同构成一个14维的空间,在这个空间中存在一个方向(13个参数构成向量决定这个方向)使得损失函数的值(预测值和实际值之偏差)下降最快。我们步进地将13个参数构成的向量朝此方向做出微调,再重新计算损失函数的值,如此往复,直到损失函数的值收敛趋于最小,则参数训练完成。

6. 数据集采用分批训练的方式,batch的取值会影响模型训练效果,batch过大,会增大内存消耗和计算时间,且训练效果并不会明显提升(每次参数只向梯度反方向移动一小步,因此方向没必要特别精确);batch过小,每个batch的样本数据没有统计意义,计算的梯度方向可能偏差较大。由于房价预测模型的训练数据集较小,因此将batch设置为10

五、​完整Python源码 - 波士顿房价模型训练及测试:

# 导入需要用到的package
import numpy as np
import json
import matplotlib.pyplot as plt

def load_data():
    # 从文件导入数据
    datafile = './work/housing.data'
    data = np.fromfile(datafile, sep=' ')

    # 每条数据包括14项,其中前面13项是影响因素,第14项是相应的房屋价格中位数
    feature_names = [ 'CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', \
                      'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV' ]
    feature_num = len(feature_names)

    # 将原始数据进行Reshape,变成[N, 14]这样的形状
    data = data.reshape([data.shape[0] // feature_num, feature_num])

    # 将原数据集拆分成训练集和测试集
    # 这里使用80%的数据做训练,20%的数据做测试
    # 测试集和训练集必须是没有交集的
    ratio = 0.8
    offset = int(data.shape[0] * ratio)
    training_data = data[:offset]

    # 计算训练集的最大值,最小值(找的是每一列的极值)
    global maximums, minimums
    #maximums, minimums = data.max(axis=0), data.min(axis=0)
    maximums, minimums = training_data.max(axis=0), training_data.min(axis=0)
    #print("max:", maximums, "min:", minimums)

    # 对数据进行归一化处理,按列归一化处理
    for i in range(feature_num):
        data[:, i] = (data[:, i] - minimums[i]) / (maximums[i] - minimums[i])
    #print("归一化后的数据:\n", data)

    # 训练集和测试集的划分比例
    training_data = data[:offset]
    test_data = data[offset:]
    return training_data, test_data

# 获取数据
training_data, test_data = load_data()
x = training_data[:, :-1] #所有行+所有列(除了最后一列)
y = training_data[:, -1:] #所有行+最后一列

#w = [1, 2, 3] #shape = (3,)
#w = [[1], [2], [3]] #shape = (3,1)
#w = [[1,1], [2,2], [3,3]] #shape = (3,2)
#x = np.array(w)
# 查看数据
#print(x.shape)
#print(y.shape)

class Network(object):
    def __init__(self, num_of_weights):
        # 随机产生w的初始值
        # 为了保持程序每次运行结果的一致性,
        # 此处设置固定的随机数种子
        np.random.seed(0)
        self.w = np.random.randn(num_of_weights, 1)
        #print("init self.w", self.w)
        self.b = 0.
        
    def forward(self, x):
        z = np.dot(x, self.w) + self.b #x是[404,13]的矩阵(404行,13列), w是[13, 1]的矩阵(13行,1列),做点乘
        return z

    def loss(self, z, y):
        error = z - y
        #print(error.shape)
        cost = error * error
        cost = np.mean(cost)
        return cost

    def gradient(self, x, y):
        z = self.forward(x)
        gradient_w = (z-y)*x #梯度公式
        gradient_w = np.mean(gradient_w, axis=0) #对各列求均值
        gradient_w = gradient_w[:, np.newaxis]
        gradient_b = (z - y)
        gradient_b = np.mean(gradient_b)   
        return gradient_w, gradient_b

    def update(self, gradient_w, gradient_b, eta = 0.01):
        self.w = self.w - eta * gradient_w
        self.b = self.b - eta * gradient_b
        
    def train(self, x, y, iterations=100, eta=0.01):
        losses = []
        for i in range(iterations):
            z = self.forward(x)
            L = self.loss(z, y)
            gradient_w, gradient_b = self.gradient(x, y)
            self.update(gradient_w, gradient_b, eta)
            losses.append(L)
            if (i+1) % 10000 == 0:
               print('iter {}, loss {}'.format(i, L))
        return losses

# 运行模式一:每次用所有数据进行训练
train_data, test_data = load_data()
x = train_data[:, :-1]
#print("x.shape:", x.shape)
y = train_data[:, -1:]
# 创建网络
net = Network(13)
num_iterations=100000
# 启动训练
losses = net.train(x,y, iterations=num_iterations, eta=0.01)

# 画出损失函数的变化趋势
"""
plot_x = np.arange(num_iterations)
plot_y = np.array(losses)
plt.plot(plot_x, plot_y)
plt.show()
"""

#对数据做反归一化处理
def restore_data(d):
     d = d* (maximums[-1] - minimums[-1]) + minimums[-1]
     return round(d,2)

#用测试集做测试
print("测试集测试结果:")
x = test_data[:, :-1]
y = test_data[:, -1:]
z = net.forward(x)
print("样本数据", "\t", "预测数据")
print("-------------------------")
for i in range(x.shape[0]):
    print(restore_data(y[i][0]), "\t\t", restore_data(z[i][0]))



——————————————————————————————————————

关注微信公众号【数字众生】即刻获取干货满满的 “AI学习大礼包” 和 “AI副业变现指南”

  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

穿越光年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值