python 神经网络入门

Python 徒手实现 卷积神经网络 CNN - 知乎 (zhihu.com)

MNIST 数据集的获取和使用

在python代码中,通过下面的代码就可以获取和使用mnist数据集

import mnist
test_images = mnist.test_images()[:1000]

但是这是通过mnist模块的自动下载获取数据集的.如果网络不好,往往获取不到完成的数据集,导致后续报数据集解压缩出现异常 报错信息如下During handling of the above exception, another exception occurred
通过单步调试,可以知道mnist.test_images()内部是通过

    fname = download_file(fname, target_dir=target_dir, force=force)
    fopen = gzip.open if os.path.splitext(fname)[1] == '.gz' else open

实现下载和解压的, 单步进去后查看fname的值,可以知道mnist默认的数据集下载地址. 我的环境下是C:\Users\Administrator\AppData\Local\Temp\t10k-images-idx3-ubyte.gz. 所以对应的解决办法就是从mnist的官网MNIST handwritten digit database, Yann LeCun, Corinna Cortes and Chris Burges
手动下载所有数据集之后,覆盖到这个目录下.

交叉熵损失函数的理解

(4条消息) 一文读懂交叉熵损失函数霜溪的博客-CSDN博客交叉熵损失函数的理解

keras 学习总结

(https://keras.io/getting_started/)

1 神经网络的数据表示

张量是矩阵向任意维度的推广,张量的维度通常叫轴

零维张量

一个数字就是一个标量也叫0维张量
在numpy中的表示如下, 通过ndim可以查询张量的维度.

import numpy as np
x = np.array(12)
x.ndim
0
x
array(12)

一维张量

x = np.array([10,11,12,13])
x.ndim
1
x
array([10, 11, 12, 13])

三维张量示例

x=np.array([[[11,12,13],[14,15,16]],[[1,1,1],[2,2,2]]])
x.ndim
3
x
array([[[11, 12, 13],
        [14, 15, 16]],
       [[ 1,  1,  1],
        [ 2,  2,  2]]])

解读张量的关键属性

x.shape
(2, 2, 3)

说明张量x是2个2行3列组成的形状. 他的维度有3个.

x.dtype
dtype('int32')

说明张量x的数据类型是int32类型

import mnist
train_images = mnist.train_images()
train_images.ndim
3
train_images.shape
(60000, 28, 28)
train_images.dtype
dtype('uint8')

上面的运行结果说明, train_images张量有3个维度, 形状是60000个28行x28行的结构. 数据类型是uint8, 也就是最大数值是255

二维张量的显示示范

借助matplotlib库进行二维张量的绘制

img = train_images[1]
import matplotlib.pyplot as plt
plt.imshow(img, cmap=plt.cm.binary)
plt.show()

张量的裁切操作

张量的操作主要目的是裁剪. 比如如下几种场景:

  1. 从60000个28x28的张量中选出前10个28x28的元素形成一个新的三维张量,对应的代码如下
first10Imgs = train_images[0:10]
first10Imgs.shape
(10, 28, 28)
  1. 从60000个28x28的张量中选出前10个28x28的元素,同时选出的10个28x28元素中,只要右下角的2x2数据
first10Imgs = train_images[0:10, 26:28,26:28]
first10Imgs.shape
(10, 2, 2)

2 张量的运算

张量的四则运算

  1. 形状相同两个张量的+-*/表示的的是两个张量逐个对应位置元素的运算
import numpy as np
a = np.array([1,2,3])
b = np.array([4,5,6])
c = a+b
c
array([5, 7, 9])
c = a*b
c
array([ 4, 10, 18])
  1. 形状不同时, 两个张量的+-*/运算时, 较小的张量会被广播,以匹配较大张量的形状.
a = np.array([1,2,3])
b = np.array(4)
a+b
array([5, 6, 7])

张量的点积

要理解张量的点积要区分三种情况. 而且要区分向量和张量的区别. 当我们说向量的时候,指的是一维张量,指的的一组数字.
下面的代码中a成为向量,没有行或者列的概念,是属于一维张量
b是一行3列的二维张量
c是3行1列的二维张量

a = np.array([1,2,3])
b = np.array([[1,2,3],])
c = np.array([[1],[2],[3]])
a.shape
(3,)
b.shape
(1, 3)
c.shape
(3, 1)
  1. 如果两个向量点积,结果是一个标量, 两个向量的各个对应元素进行乘积并累加, 如果两个向量的个数不一致,无法进行点积,程序会报错
a = np.array([1,3])
b = np.array([2,4,6])
c=np.dot(a, b)
Traceback (most recent call last):
  File "D:\Python\Python310\lib\code.py", line 90, in runcode
    exec(code, self.locals)
  File "<input>", line 1, in <module>
  1. 如果是一个矩阵A和向量B的点积, 他的结果是一个向量C. 结果C中每个向量C的值是矩阵中一行(作为一个待操作向量和向量B的点击,生成的标量作为向量C的一个元素.
    所以如果矩阵A中的一行的个数和向量B不一致,也是无法运算的
a = np.array([[1,1,1],[2,2,2]])
b = np.array([1,2,3])
c = np.dot(a,b)
c
array([ 6, 12])
a = np.array([[1,1,1,1],[2,2,2,2]])
b = np.array([1,2,3])
c = np.dot(a,b)
Traceback (most recent call last):
  File "D:\Python\Python310\lib\code.py", line 90, in runcode
    exec(code, self.locals)
  File "<input>", line 1, in <module>

同时无法进行向量跟矩阵的点积

a = np.array([[1,1,1],[2,2,2]])
b = np.array([1,2,3])
c = np.dot(a,b)
c
array([ 6, 12])
#无法执行np.dot(b,a)
  1. 矩阵和矩阵的点积
    这个点积规则跟大学学的矩阵点积规则一致.
  2. 更高维的点积,暂时不理解.

张量变形运算

  1. reshape
    张量变形指改变张量的行和列,以得到想要的形状,变形后的张量元素总个数没有变化.
    比如把2行2列的张量变成4行一列
a = np.array([[1,2],[3,4]])
b = a.reshape((4,1))
b
array([[1],
       [2],
       [3],
       [4]])
  1. transposition
    矩阵的转置也是一种常见的变形
a = np.array([[1,2],[3,4],[5,6]])
b = a.transpose()
b
array([[1, 3, 5],
       [2, 4, 6]])

深度学习的数学解释

神经网络就是由一系列的张量运算组成的, 输入张量和输出张量通过一定的数学训练后,找到一种映射关系,这种映射关系通过一层层的张量运算变换一步步实现.
对于实际的问题, 只要你针对实际问题提取的输入张量可靠, 训练的数据足够多写有代表性,那么你所针对的实际问题就有了数学上的识别模型, 这个模型可以通过你提供的数据训练出来.

3神经网络的训练引擎

神经网络的训练要利用损失的梯度方向传播的各个层的权重.

什么是导数

导数的数学含义, 对于一个函数 y = f(x). 在某个点的导数a记作d_y_d_x. 如果d_y_d_x是正数,说明微小的dx会引起dy = ad_x的正向变化(变大). 如果a是负数,说明dx的增加会导致y的减小. a的绝对值表示变化的速率.
如果我们反向求出d_L_d_w = a. 表示在当前权重w状态下,如果a是正值, 权重增加一些, 损失L就会增加,所以调节网络的时候我们要反向调节让权重减去(|a|
step). step表示一个很小的比例因子.
SGD 表示随机梯度下降法

4 keras CNN 示例

神经网络的keras实现

层+损失函数定义+反向传播方式指定

  1. 下面的代码创建了一个全连接层, 输入是二维张量, 可以认为有784个向量, 向量的元素个数不限. 新建层的输出是一个一维张量,包含32个值.
from keras import layers
layer = layers.Dense(32, input_shape=(784,))
  1. keras创建网络结构的时候,先指明结构框架,然后逐个添加层. 比如下面的代码
model = models.Sequential();
model.add(layers.Dense(32,input_shape=(784,)))
model.add(layers.Dense(32))

这段代码中添加了两个全连接层,第一个dense层输入时(784,),输出是32D向量. 第二个dense创建的时候,并没有指定输入,keras会自动推导我的输入应该是上一层的输出.所以是一个32D向量输入,32D向量输出的层.
神经网络的模型是由层构成的有向无环图. 最常见的例子是一个输入分支从头到尾经过多层移动到输出. 但也存在双分支网络,多头网络,inception网络(不知道是啥网络??)

  1. 根据任务类型选择合适的损失函数和反向传播函数(优化器)
    模型确定之后,需要编译,在编译的时候配置学习过程,如下面的代码, RMSprop是一种参数反向调整的方法.(SGD也是一种方法). loss='mse' 指定均方误差作为损失函数.
from keras import optimizers
model.compile(optimizer=optimizers.RMSprop(learning_rate=0.001),loss='mse',metrics =['accuracy'])
  1. 最后学习过程通过fit()方法启用
    类似代码如下
model.fit(input_tensor, target_tensor, batch_size=128, epochs=10)

keras创建层的两种方式

  1. 利用sequential类, 如下面的代码
model = models.Sequential();
model.add(layers.Dense(32,input_shape=(784,)))
model.add(layers.Dense(32))
  1. 基于过程方式,利用函数式api
input_tensor = layers.Input(shape=(784,))
x = layers.Dense(32,activation ='relu')(input_tensor)
output_tensor = layers.Dense(10,activation = 'softmax')(x)
model = models.Model(inputs=input_tensor,outputs=output_tensor)

5 建模示例

5.1 二分类问题

5.1.1 数据集获取和分析

让网络学习后,可以根据电影的评论内容,判断出该评论是正面评论还是负面评论. 用到的数据集IMDB,包含了50000条评论,这些评论都有评论是正面或者负面的标签.
一样的imdb.nbz数据集在实践的过程中也发生无法下载的问题, 解决方法就是先手动下载,让后放到指定目录中
手动下载的地址(https://www.cnblogs.com/wt7018/p/13092512.html)
覆盖的路径要通过单步调试下面的代码,确认imdb的数据集加载路径才行. 我这里是C:\Users\Administrator\.keras\datasets

import keras.datasets.imdb as myimdb
(train_data, train_labels),(test_data, test_labels) = myimdb.load_data(num_words=10000)
train_data.shape
(25000,)
train_labels.shape
(25000,)

imdb在加载数据的时候. num_words=10000的意思是仅保留训练数据中前10000个最常出现的单词,低于这个频率的单词被舍弃.label中0表示负面,1表示正面.
**观察数据集train_data.shape 返回的结果是(25000,)说明train_data是25000个向量(一维张量),通过下面的代码可以查看训练集中第0条评论的样子

oneComment = train_data[0]
oneComment
[1, 14, 22, 16, 43, 530, 973, 1622, 1385, 65, 458, 4468, 66, 3941, 4, 17....]

为什么不是单词,而是数字,这是因为imdb对单词做了索引.每个不同的索引代表一个单词.

5.1.2 训练数据处理

  1. 对于25000个训练样本,有的样本有10个单词,有的样本有2000个单词. 个数都不一样,这种情况下创建神经网络层的时候就会出现困难. 书上的解决办法是"归一化"成10000的向量. 因为不一样的单词总个数就10000个,每个评论都抽象成一个10000D向量,某个单词出现过就在其索引位置上赋值1,否者为0. 转换代码如下
import numpy as np
def vectorize_sequences(sequences,dimension=10000):
    results = np.zeros((len(sequences), dimension))
    for i, sequence in enumerate(sequences):
        results[i,sequence] = 1.
    return results
x_train = vectorize_sequences(train_data)
x_test = vectorize_sequences(test_data)
  1. 把输出数据转化为25000个浮点类型数据,表明概率
y_test = np.asarray(test_labels).astype('float32')
y_train=np.asarray(train_labels).astype('float32')
train_labels
array([1, 0, 0, ..., 0, 1, 0], dtype=int64)
y_test
array([0., 1., 1., ..., 0., 0., 0.], dtype=float32)
y_test.shape
(25000,)

处理后, 输入就是25000个10000D的向量, 输出是1个25000D的向量, 每次训练的时候选一个10000D的向量,目标是一个标量. 也就是输入数据是向量,而标签是标量,这是会遇到的最简单的情况.这种问题使用带有relu激活的全连接层处理.

5.1.3 构建网络

带relu激活的Dense层表示为Dense(16,activation="relu") 这里16表示该层隐藏单元的个数. 以这个例子的背景为例,我们每次训练的输入是一个评论也就是一个10000D的向量. 我们要用16个特征维度去解析这个输入.每个维度用隐藏层中16个神经元的一个去处理. 当输入10000D的一个向量连接这个神经元的时候,他要做的最基本的数学运算是 dot(W, input)+b, 可以认为这个卷积过程就是一个维度的分解过程, 每个维度代表的神经元有不同的权重W. 每个神经元的权重W要能够和input做卷积,所以一个神经元的W在这里必然也是一个10000D的向量. 那16个隐藏单元对应的权重矩阵W的形状就是((10000,),16). 隐藏单元的个数(分析的维度)并不是越多越好, 越多计算量越大,且最终学习到的识别方法有可能普适性更差(针对训练集更好了,但没有训练过的数据识别起来效果变差).
维度的解析基本运算是dot(W, input)+b, 为了模拟一些跳跃的非线性的映射关系,加上了激活函数relu(dot(W,input)+b
针对这个case, 书上根据经验选择了如下的网络结构

from keras import models
from keras import layers
model = models.Sequential()
model.add(layers.Dense(16,activation='relu',input_shape=(10000,)))
model.add(layers.Dense(16, activation='relu'))
model.add(layers.Dense(1,activation='sigmoid'))

最后要在编译过程中选择损失函数和优化器,代码如下

model.compile(optimizer='rmsprop', loss='binary_crossentropy',metrics=['accuracy'])

5.1.4 验证模型

拆分训练集,验证集,测试集
原始的训练集合中x_train 和 y_train中有25000个评论和标签

x_train.shape
(25000, 10000)
y_train.shape
(25000,)

通过下面的代码,拆分10000个评论出来作为验证集,并开始训练.训练时训练20轮,每一轮使用512个样本.

x_varification = x_train[:10000]
x_real_train = x_train[10000:]
y_varification = y_train[:10000]
y_real_train = y_train[10000:]
history = model.fit(x_real_train, y_real_train, epochs=20,batch_size=512,validation_data= (x_varification, y_varification))

训练结果

Epoch 1/20
30/30 [==============================] - 5s 110ms/step - loss: 0.5491 - accuracy: 0.7777 - val_loss: 0.4185 - val_accuracy: 0.8619

......
[==============================] - 1s 45ms/step - loss: 0.0237 - accuracy: 0.9958 - val_loss: 0.4962 - val_accuracy: 0.8714
Epoch 19/20
30/30 [==============================] - 1s 46ms/step - loss: 0.0192 - accuracy: 0.9969 - val_loss: 0.5181 - val_accuracy: 0.8699
Epoch 20/20
30/30 [==============================] - 1s 46ms/step - loss: 0.0177 - accuracy: 0.9973 - val_loss: 0.5396 - val_accuracy: 0.8685

模型可以优化的方向

可以尝试更少或更多的隐藏层
可以尝试不同的解析维度
可以使用不同的激活函数,损失函数
通过验证集上测试的loss和精度数据,确认是否发生过拟合的情况,并优化

5.2 多分类问题

训练集是一些新闻评论,每条新闻的标签是新闻的类别,和单分类问题不一样的是,标签的类别有46个.
这个示例问题的解决方法基本和单分类的示例类似. 需要注意的地方有两个

  1. 多分类标签也需要进行编码转换. 转换方法就是统一用46D的向量表示标签.
  2. 网络的输出有46种可能,如果在设计解析维度的时候依然沿用上例的16,就有些不够了.既然有46种可能输出,那至少有46个特征维度.所以Dense层的网络节点最好比46大,书中根据经验使用了64.
  3. 当然输出是1D向量, 只不过0-45的不同数据.这种标签编码是整数,如果直接用1D的向量作为输出,那么要选用sparse_categorical_crossentropy作为损失函数.
    如果把输出的1D向量转化为46D向量,每个向量上的标量取值不是0就是1,这种情况下使用categorical_crossentropy作为损失函数.

5.3 回归问题

5.3.1 数据集获取和分析

boston_housing数据集有506份样本. 每个样本有13个属性描述,如人均犯罪率,住宅平均房间数,公交遍历性等. 目标是房屋价格的平均值.

from keras.datasets import boston_housing
(train_data,train_targets),(test_data,test_targets) = boston_housing.load_data()
train_data.shape
(404, 13)
test_data.shape
(102, 13)

上述的代码如果下载数据集异常, 需要手动下载(https://gitee.com/polarisslk/boston_housing/blob/master/boston_housing.npz)

5.3.2 数据准备

每一个样本的13个属性, 即使是同一个属性相对于其他样本可能差异很大. 这样的数据对网络训练是不利的(为啥不利?).普遍的做法是针对每个属性(特征)做标准化.即输入的数据的每个特征减去特征的平均值,再除以标准差,这样得到的特征列都具有平均值为0,标准差为1的特点了.
数据处理代码如下. 需要注意的是训练集的数据特征标准化后,对测试集的数据特征也需要标准化. 但测试集的数据标准化,不能用测试集的统计数据!, 因为实际的测试数据集代表的是你没有提前拿到的数据,发生时,也只有发生时刻的那一份

mean =train_data.mean(axis=0)
mean.shape
(13,)
mean
array([3.74511057e+00, 1.14801980e+01, 1.11044307e+01, 6.18811881e-02,
       5.57355941e-01, 6.26708168e+00, 6.90106436e+01, 3.74027079e+00,
       9.44059406e+00, 4.05898515e+02, 1.84759901e+01, 3.54783168e+02,
       1.27408168e+01])
train_data -= mean
std = train_data.std(axis=0)
std
array([9.22929073e+00, 2.37382770e+01, 6.80287253e+00, 2.40939633e-01,
       1.17147847e-01, 7.08908627e-01, 2.79060634e+01, 2.02770050e+00,
       8.68758849e+00, 1.66168506e+02, 2.19765689e+00, 9.39946015e+01,
       7.24556085e+00])
train_data /=std
test_data -= mean
test_data /=std

5.3.3 构建网络

这个数据集的特点是数据较少. 书上交代如果一般训练集越少,过拟合会越严重,而较小的网络可以降低过拟合.
针对这个的网络构建和之前稍微有所不同的是,最后一层的Dense, 最后一层Dense不能使用激活函数.如果使用激活函数,最后一层的输出会是不连续的输出,而我们期望的预测价值是个连续值.
网络构建代码如下

from keras import models
from keras import layers
def build_model():
    model = models.Sequential()
    model.add(layers.Dense(64,activation='relu',input_shape=(train_data.shape[1],)))
    model.add(layers.Dense(64,activation='relu'))
    model.add(layers.Dense(1))
    model.compile(optimizer='rmsprop',loss='mse', metrics=['mae'])
    return model 

5.3.4 K折验证

这个数据集很小,我们只划分了100个样本作为验证集. 这样验证分数会有很大的波动.这种背景下,最佳做法是:
将可用的训练数据划分为K个分区,

train_data.shape
(404, 13)

把train_data中的404张拆分成4份
书上的3.6.4小节. 后续补充.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,451评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,172评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,782评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,709评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,733评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,578评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,320评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,241评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,686评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,878评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,992评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,715评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,336评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,912评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,040评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,173评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,947评论 2 355

推荐阅读更多精彩内容