前言

本文教程均来自b站【小白也能听懂的人工智能原理】,感兴趣的可自行到b站观看。

值得注意的是,该课程的keras框架当时是独立的,现在keras已经集成在tf2里了。

代码及工具箱

本专栏的代码和工具函数已经上传到GitHub:1571859588/xiaobai_AI: 零基础入门人工智能 (github.com),可以找到对应课程的代码

正文

我们已经学完了七节课,这些课程都是关于神经网络的基础理论知识。从第八节课开始,我们会更多地关注如何实际应用这些知识,而不是继续深入数学原理。

如果你想要更深入地研究机器学习和神经网络,建议你继续探索背后的数学原理和最新的研究进展。

在开始新阶段之前,我们先来聊聊一个对我们接下来学习很重要的工具。虽然它可能有点复杂,但别担心,我们会一起慢慢掌握它。简而言之,我们即将进入实践阶段,同时,深入学习的话,数学和最新研究是关键。

介绍向量和矩阵

在上一节课中,我们讨论了随着输入数据特征的增多,手动编写函数表达式会变得复杂和繁琐。为了简化这个过程,我们需要一个数学工具,那就是矩阵和向量。

向量就像一个箭头,表示一个方向和大小;矩阵则是由许多这样的向量组成的更大的结构。它们看起来可能有点吓人,但其实它们比看起来要简单得多。

不同领域的人对向量和矩阵有不同的理解:

  • 物理学家可能认为它们与空间变换有关。
  • 数据工程师可能认为它们代表数据的特征。
  • 程序员可能把它们看作是特殊的数组。
  • 办公人士可能觉得它们就像Excel表格中的行和列。

这些理解都对,但为了避免混淆,我们需要从数学的角度来理解它们。这样,无论我们处理的是物理问题、数据分析还是编程任务,都能更清晰地使用这些工具。简而言之,向量和矩阵是数学中用于简化多特征数据处理的强大工具。

矩阵和向量是数学中的实用工具,就像数学本身服务于所有科学一样。我们学习数学时,最开始是学习简单的一元一次函数,然后是处理两个变量的函数,比如初中数学中的鸡兔同笼问题。

随着学习的深入,我们开始处理三个、四个甚至更多变量的问题。数学家们觉得这样一直增加变量太麻烦,也不优雅。于是,他们发明了向量这个工具,来简化处理多个变量的过程。这样,我们就可以用一种更整洁、高效的方式来解决复杂问题了。简单来说,向量和矩阵帮助我们更方便地处理多变量的数学问题。


我们来用一个简单的例子说明向量和矩阵如何简化多变量数学问题的处理。想象一下,我们有一个包含三个变量的线性函数。我们可以这样做:

  1. 创建向量:把这三个变量放入一个向量中,把对应的权重(系数)放入另一个向量,再单独把偏置项(b)作为一个向量的元素。

  2. 定义运算规则:我们规定一种特殊的乘法,叫做点乘,用于向量间的运算。点乘的规则是将两个向量的对应元素相乘,然后把乘积相加。

  3. 转置操作:有时我们需要改变向量的方向,这可以通过转置操作实现。转置就是把向量从行变成列,或者从列变成行。

  4. 计算结果:通过点乘和转置,我们可以把多个变量的线性组合计算出来,并且加上偏置项b。

用这种方式,原本需要写为 ( $z = w_1 \times x_1 + w_2 \times x_2 + w_3 \times x_3 + b$ ) 的表达式,现在可以更简洁地表示为 ( $z = (x \cdot w^T) + b$ ),其中 ( x ) 是输入向量,( w ) 是权重向量,( b ) 是偏置向量,( z ) 是结果。

这样,向量和矩阵的运算不仅让表达式更简洁,而且计算过程也更加高效。通过向量运算,我们可以很容易地处理涉及多个变量的复杂问题。


当我们处理的变量越来越多时,传统的数学表达方式会变得非常繁琐。例如,一个有1000个变量的线性函数,如果不用向量来表示,我们将需要写下1000个乘法和加法的长串表达式,这不仅费时而且容易出错。

但是,使用向量和矩阵,即使是1000个变量的函数,也可以简洁地表示为 ( z = x \cdot w^T + b ),其中 ( x ) 是输入向量,( w ) 是权重系数矩阵,( b ) 是偏置向量,而 ( z ) 是结果向量。这里的关键是,无论向量的维度有多大,这种表示方法都保持不变。

向量和矩阵的强大之处在于它们的普适性和简洁性。无论我们处理的是单变量还是多变量问题,都可以用相同的数学形式来表达。这不仅让数学家感到满意,也让程序员的工作变得更加简单。

在编程实践中,许多编程语言,如Python,都有像NumPy这样的数学库,它们可以简洁方便地进行矩阵和向量的计算。这样,我们就可以在编写代码时,享受到和数学符号一样简洁的操作。

总之,向量和矩阵提供了一种统一的方法来处理无论多么复杂的线性函数,它们让数学表达更加简洁,也让编程变得更加高效。

利用向量和矩阵进行编程

好的,同学们,我们来做本节课的第一个编程实验,那看看我们如何用向量和矩阵运算的方式呢去构建我们的神经元。

两个输入一个神经元

import numpy as np
import utils.dataset as dataset
import utils.plot_utils as plot_utils
# %matplotlib
%matplotlib ipympl

## Create a dataset
n = 100
X, Y = dataset.get_beans5(n)

# 输入xs是二维向量
# print(X) 
# print(Y)
plot_utils.show_scatter(X,Y)


W = np.array([0.1,0.1])
B = np.array([0.1])


def forward_propgation(X):
    Z= X.dot(W.T)+B
    A = 1/(1+np.exp(-Z))
    return A


plot_utils.show_scatter_surface(X,Y, forward_propgation)

for _ in range(500):
    for i in range(n):
        Xi = X[i]
        Yi = Y[i]

        # 前向传播
        A = forward_propgation(Xi)
        E = (Yi-A)**2
        # 反向传播
        dEdA = -2*(Yi-A)
        dAdZ = A*(1-A)
        dZdW = Xi
        dZdB = 1

        dEdW = dEdA*dAdZ*dZdW
        dEdB = dEdA*dAdZ*dZdB

        # 梯度下降
        alpha = 0.01
        W = W - alpha*dEdW
        B = B - alpha*dEdB
plot_utils.show_scatter_surface(X, Y, forward_propgation)

两个输入一层隐藏层两个神经元

import dataset
import plot_utils

import numpy as np
#从数据中获取随机豆豆
m=100
X,Y = dataset.get_beans4(m)
print(X)
print(Y)

plot_utils.show_scatter(X, Y)

W1 = np.random.rand(2,2)
B1 = np.random.rand(1,2)
W2 = np.random.rand(1,2)
B2 = np.random.rand(1,1)


def forward_propgation(X):

    #Z1:(m,2)
    Z1 = X.dot(W1.T) + B1

    #A1:(m,2)
    A1 = 1/(1+np.exp(-Z1))
    #Z2:(m,1)
    Z2 = A1.dot(W2.T) + B2
    #A2:(m,1)
    A2 = 1/(1+np.exp(-Z2))
    return A2,Z2,A1,Z1

plot_utils.show_scatter_surface(X, Y,forward_propgation)


for _ in range(5000):
    for i in range(m):
        Xi = X[i]

        Yi = Y[i]
        A2,Z2,A1,Z1 = forward_propgation(Xi)


        E = (Yi - A2)**2


        #(1,1)
        dEdA2 = -2*(Yi-A2)
        #(1,1)
        dEdZ2 = dEdA2*A2*(1-A2)
        #(1,2)
        dEdW2 = dEdZ2*A1
        #(1,1)
        dEdB2 = dEdZ2*1

        #(1,2)
        dEdA1 = dEdZ2*W2

        #(1,2)
        dEdZ1 = dEdA1*A1*(1-A1)
        dEdW1 = (dEdZ1.T).dot(np.array([Xi]))
        dEdB1 = dEdZ1*1
        alpha = 0.05
        W2 = W2 - alpha*dEdW2
        B2 = B2 - alpha*dEdB2

        W1 = W1 - alpha*dEdW1

        B1 = B1 - alpha*dEdB1
    #计算准确率

    A2,Z2,A1,Z1 = forward_propgation(X)
    A2 = np.around(A2)#四舍五入取出0.5分割线左右的分类结果
    A2 = A2.reshape(1,m)[0]
    accuracy = np.mean(np.equal(A2,Y))
    print("准确率:"+str(accuracy))

plot_utils.show_scatter_surface(X, Y,forward_propgation)

keras框架介绍

欢迎回来,从这节课开始,我们将使用一个叫keras的机器学习框架来简化我们的工作,这样我们就不用自己从头开始编写复杂的神经网络代码了。

想象一下,如果你要写一个程序,理论上你可以用汇编语言来写,这是非常基础的编程语言,它非常接近计算机硬件。但是这样做非常麻烦,因为你需要非常了解计算机的内部工作原理,比如它有多少个寄存器,它们是如何工作的等等。

keras框架就像是给程序员提供的高级编程语言,它让我们不需要了解底层的所有细节,就可以快速构建和训练神经网络。这样,我们就可以专注于设计模型和解决问题,而不必深陷底层实现的复杂性中。

简而言之,keras是一个强大的工具,它帮助我们更容易地使用神经网络,节省我们的时间,让我们的机器学习工作变得更加高效和简单。


从现在起,我们将使用keras这个机器学习框架,它让我们构建神经网络变得更加简单,就像使用高级编程语言一样,我们不需要深入了解背后的复杂运算。

keras框架把创建和训练神经网络的复杂过程都封装好了,提供了很多易于使用的接口。这样,我们就可以快速搭建模型,而不必从零开始编写所有代码。

除了keras,还有其他一些框架,比如tensorflow。tensorflow功能非常强大,提供了很多底层的控制,但使用起来可能不如keras那样直观和简单。可以这么说,tensorflow就像C语言,而keras则更像Python,简单易用。


使用keras框架,我们可以轻松地构建神经网络。首先,我们需要导入keras库,然后创建一个模型。接下来,我们可以添加神经元,并选择使用sigmoid激活函数。设置好模型后,我们指定使用均方误差作为损失函数,并采用随机梯度下降法作为优化算法,然后就可以开始训练模型了。

如果想要增加神经元的数量,比如从1个变为2个,只需简单地更改一下设置中的units参数值即可。这个过程非常简单直观。


对于豆豆分类这样的任务,我们的目标是得到一个表示类别的输出,比如0或1。如果模型中有多个神经元,它们会产生多个输出,我们需要一个额外的神经元来整合这些信息。

假设想让模型更复杂一些,比如第一层有4个神经元而不是1个或2个,我们只需要在设置中将units参数改为4。这样的操作比起手动构建模型来说,使用keras框架要简单快捷得多。

keras是一个让用户轻松入门机器学习和神经网络的框架,它的设计非常简单直观,非常适合初学者。

不过,keras本身并不是一个完全独立的框架,而是依赖于其他底层框架如tensorflow、CNTK或Theano来执行计算。这有点像Python语言实际上是由更底层的C或C++代码来支持的。

因为keras非常易于使用,它在某些情况下可能没有像tensorflow这样的底层框架那么灵活。虽然keras也是高度模块化和可扩展的,但过度的封装有时意味着失去了一些对细节的控制能力。尽管如此,随着时间的发展,keras在灵活性方面也在不断改进。

目前,作为谷歌开发的两个框架,keras已经被集成到tensorflow中,成为其高级API。这可能会让人疑惑,既然如此,为什么还要用keras呢?实际上,就像选择编程语言一样,没有所谓的“最好”,只有“最适合”特定任务的工具。

选择使用keras还是tensorflow,或者其他任何工具,都取决于你的具体需求和偏好。每个工具都有其优势和局限,理解这些可以帮助你做出更明智的选择。

使用keras框架进行编程​

好的,接下来我们来做第二个编程实验,用keras来搭建我们的神经网络模型。在这个实验中,我们会做得更深入一些,用keras快速实现我们之前讨论过的所有类型的模型。

我们会从第五节课的内容开始,那时我们用单个神经元来预测只有一个特征的简单分类问题。然后,我们会进展到第六节课,那时我们加入了隐藏层,用更复杂的神经网络来预测同样只有一个特征的分类问题。

接着,我们会回顾上节课的内容,那时我们用单个神经元来预测有两个特征的简单分类问题。最后,我们会实现之前提到但尚未做过的模型,也就是用带有隐藏层的神经网络来预测有两个特征的复杂问题。

这听起来可能有点复杂,但实际上,由于keras的设计非常简洁直观,我们只需要稍微调整一下参数,就可以轻松地实现这些模型。

  • 准备上手keras框架

    • 首先安装keras和tensorflow:keras依赖于tensorflow,只需要安装tensorflow,就会自动安装keras。

      pip install -U tensorflow
      
    • 查看版本:tensorflow 2.9.1 | keras 2.9.0

      $ pip show tensorflow
      Name: tensorflow
      Version: 2.9.1
      Summary: TensorFlow is an open source machine learning framework for everyone.
      Home-page: https://www.tensorflow.org/
      Author: Google Inc.
      Author-email: packages@tensorflow.org
      License: Apache 2.0
      Location: d:\environment\miniconda\envs\ai\lib\site-packages
      Requires: flatbuffers, h5py, libclang, keras-preprocessing, gast, grpcio, protobuf, absl-py, tensorflow-estimator, google-pasta, packaging, tensorboard, tensorflow-io-gcs-filesystem, wrapt, numpy, astunparse, opt-einsum, termcolor, typing-extensions, keras, setuptools, six
      Required-by:
      $ pip show keras
      Name: keras
      Version: 2.9.0
      Summary: Deep learning for humans.
      Home-page: https://keras.io/
      Author: Keras team
      Author-email: keras-users@googlegroups.com
      License: Apache 2.0
      Location: d:\environment\miniconda\envs\ai\lib\site-packages
      Requires:
      Required-by: tensorflow
      
    • keras与tensorflow:keras=python,tensorflow=x

      • keras框架与tensorflow:keras框架就向机器学习里的高级语言实现了对机器学习神经网络底层复杂的数据运算的封装,我们可以轻松的通过它提供了各种上层接口搭建模型。而tensorflow更像是c语言对底层的封装并不是那么的完全,但是更加的强大和灵活,而keras更像是python,就两个字简单。
      • 一个同样的神经元模型,用keras和tensorflow架实现的代码量,你会很直观的看见它们的区别
  • keras实现含有一层隐藏层的神经网络代码

    • 代码

      from keras.models import Sequential
      from keras.layers import Dense
      from keras.optimizers import SGD
      
      # 创建模型
      model = Sequential() 
      # 创建一层网络,即隐藏层,含有两个神经元,激活函数使用sigmoid函数,输入特征为2维
      model.add(Dense(units=2, activation='sigmoid',input_dim=2 ))
      # 再创建一层网络,即输出层,含有一个神经元,激活函数使用sigmoid函数
      model.add(Dense(units=1, activation='sigmoid' ))
      # 告诉keras使用均方误差代价函数和随机梯度下降算法SGD,学习率为0.05,评估指标选择accuracy
      model.compile(loss='mse', optimizer=SGD(learning_rate=0.05),metrics=['accuracy'])
      # 开始训练
      model.fit(X, Y, epochs=5000, batch_size=10,verbose=0)
      # 训练完毕,进行预测
      pres= model.predict(X)
      
    • 概念解释:Sequential、Dense、activation、optimizer

      • Sequential:简单来说就是一个用来堆叠神经网络的序列,深度神经网络其实就是一层一层堆叠出来的,所以你可以认为这个Sequential呢就是用来堆叠神经网络的,那这些神经元堆叠在一起形成一个神经网络预测模型。
      • Dence:一个全连接层,简单来说就是一层的神经网络。全连接这三个字的意思呢,就是字面的意思,这一层的每个神经元的输入和输出和上一层和下一层的每一个都全连接着,一个都不少,那我们目前为止搭建的都是这种全连接神经网络。
      • optimizer表示优化器,也就是说用来优化或者说调整参数的算法,目前我们就指定使用随机梯度下降算法
  • 用keras框架快速实现课程前面的几个神经网络

    • 用单神经元预测输入数据特征为1的简单分类问题

      model = Sequential()
      model.add(Dense(units=1, activation='sigmoid',input_dim=1 ))
      model.compile(loss='mse', optimizer='sgd',metrics=['accuracy'])
      model.fit(X, Y, epochs=5000, batch_size=10,verbose=0)
      
    • 加入隐藏层的神经网络预测输入数据特征为1的复杂分类问题

```python
model = Sequential() # 创建模型
model.add(Dense(units=2, activation='sigmoid',input_dim=1 ))
model.add(Dense(units=1, activation='sigmoid' ))
model.compile(loss='mse', optimizer=SGD(learning_rate=0.05),metrics=['accuracy'])
model.fit(X, Y, epochs=5000, batch_size=10,verbose=0)
```
  • 用单神经元预测数据特征为2的简单分类问题

 

```python
model = Sequential()
model.add(Dense(units=1, activation=’sigmoid’,input_dim=2))
model.compile(loss=’mse’, optimizer=SGD(learning_rate=0.05),metrics=[‘accuracy’])
model.fit(X, Y, epochs=5000, batch_size=10,verbose=0)
```
  • 加入隐藏层的神经网络去预测数据特征为2的复杂问题

```python
model = Sequential()
model.add(Dense(units=2, activation='sigmoid',input_dim=2)) # 隐藏层
model.add(Dense(units=1, activation='sigmoid')) # 输出层
model.compile(loss='mse', optimizer=SGD(learning_rate=0.05),metrics=['accuracy'])
model.fit(X, Y, epochs=5000, batch_size=10,verbose=0) # 训练
```