第四次周报
手写数字识别
K近邻算法实现数字识别
算法流程
对未知类别属性的数据集中的每个点依次执行以下操作:
(1) 计算已知类别数据集中的点与当前点之间的距离;
(2) 按照距离递增次序排序;
(3) 选取与当前点距离最小的 k 个点;
(4) 确定前 k 个点所在类别的出现频率;
(5) 返回前k个点出现频率最高的类别作为当前点的预测分类。
代码实现
```javascript
// An highlighted block
# 案例1 k近邻算法实现数字识别
# 用200张训练集 预测50张测试集的图像
# 基于k近邻算法的思想 k=1 找到一个距离最近的点
test = 50
acc = 0
for i in range(test):
test_data = test_images[i]
train_data = train_images[:,:]# 取前6000张图像
distant = np.argmin(np.sum(np.abs(train_data - test_data),axis=1)) #返回最近的train集点的 下标
# train_labels[distant] 独热编码
predict = np.argmax(train_labels[distant])
real = np.argmax(test_labels[i])
if predict == real:
acc+=1
print("预测值:",predict)
print("真实值:",real)
print("准确率为:",acc/test)
总结
虽然最高准确率达到百分之98,但K近邻算法存在以下 : 1.距离不能反映差别 2.每次训练都要比较很多次,速度很慢
全连接神经网络实现数字识别
代码实现
```javascript
// An highlighted block
# 案例2 构建两层神经网络 实现数字识别 (数据集载入 ---- 搭建模型 ------训练模型-----评估模型)
# 1.数据集载入
(train_images,train_labels),(test_images,test_labels) = tf.keras.datasets.mnist.load_data() # 载入数据
train_images = train_images.reshape(60000,28*28) # 三维变成二维
test_images = test_images.reshape(10000,28*28)
# get_dummies 是利用pandas实现one hot encode的方式
train_labels = pd.get_dummies(train_labels) # 变成独热编码
train_labels = np.array(train_labels) # 从 dataframe 变成 array
test_labels = pd.get_dummies(test_labels)
test_labels = np.array(test_labels)
# 归一化 从0-255 到 0-1 更好的梯度下降 数据之间差值过大可能导致梯度下降不工作
train_images = train_images / 255
test_images = test_images / 255
train_images.shape
# 2.搭建模型
## 两层神经网络 784 64 10 sigmoid softmax
model = tf.keras.models.Sequential() # 创建一个神经网络模型
model.add(tf.keras.layers.Flatten(input_shape=(28,28))) # 全连接层只能接收向量(一维数据),用flatten层展平,input_shape:输入数据尺寸
model.add(tf.keras.layers.Dense(64,activation='sigmoid'))
model.add(tf.keras.layers.Dense(10,activation='softmax'))
history = model.fit(train_images,train_labels,epochs=25,validation_data=(test_images,test_labels))
model.compile(
optimizer ='adam',# 设置优化器 ---梯度下降法+自动调整学习率(先把学习率设为较大的值再自动下降)
loss='categorical_crossentropy', # 设置损失函数----交叉熵 解决多分类问题
metrics=['acc'] # 记录准确率
)
总结
与k近邻算法相比 更方便 不需要跟全部的数据计算距离,能较快达到百分之98的准确率,#而且模型训练好了就能多次使用 很方便
带来的问题:
1.以一个像素点的方式输入到全连接网络层里,没有考虑像素点之间的关系
2.图片尺寸增大,一维输入很不方便,神经元增加带来的训练参数增多 训练时间增加
由于以上问题,下文使用卷积神经网络来实现数字识别
CNN实现数字识别
卷积神经网络默认输入是图像,可以让我们把特定的性质编码入网络结构,使是我们的前馈函数更加有效率,并减少了大量参数,可以有效的从大量样本中学习到相应地特征, 避免了复杂的特征提取过程。
一共四层,网络结构为:卷积池化卷积池化全连接1全连接2
除最后一层激活函数为softmax,其余都用relu
卷积核为5x5,步长为1
最大池化从2x2的区域提取(选取块中最大的值 值越大说明特征越重要,所以使用最大池化)
代码实现
```javascript
// An highlighted block
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt # 画图
import tensorflow.compat.v1 as tf
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('MNIST_data', one_hot=True) # 傻逼
tf.disable_v2_behavior()
# tf中所有变量都要进行初始化 造出一个变量要转成tf支持的张量格式 用 tf.Variable(变量名)
def weight_variable(shape): # 对卷积核进行初始化
# 从截断的正态分布中输出随机值 用tf.random_normal也可以
initial = tf.compat.v1.random.truncated_normal(shape,stddev=0.1) #生成随机值
return tf.Variable(initial)
def bias_variable(shape): # 对偏置项进行初始化
initial = tf.constant(0.1,shape=shape) # 随机值为常数
return tf.Variable(initial)
def conv2d(x,w):
return tf.nn.conv2d(x,w,strides=[1,1,1,1],padding="SAME") # 导入x,w,设置每个维度的步长
# SAME不丢弃像素点 保证卷积之后输出还是28x28
# VALID默认填充0.会丢弃像素点
def max_pool_2x2(x):
return tf.nn.max_pool(x,ksize=[1,2,2,1],strides=[1,2,2,1],padding="SAME")
#2.搭建网络模型(计算流图) :搭建结构 -- 初始化传参
# 构建输入数据
xs = tf.compat.v1.placeholder(tf.float32,shape=(None,784),name="x_input") # 对输入的行数(样本个数)占位 此处不传真实值 只构建计算流图
ys = tf.placeholder(tf.float32,shape=(None,10),name="y_input") # 对输入的行数(样本个数)占位 此处不传真实值 只构建计算流图
x_images = tf.reshape(xs,[-1,28,28,1]) #传入的第一层为卷积层 要求输入四维的数据 所以此处用reshape -1表示不确定行数
# 卷积层1代码
# input_size:6000x28x28x1
w_conv1 = weight_variable([5,5,1,32]) #定义卷积核 5x5代表卷积核大小 1表示图片通道数 32表示卷积核个数
# 1和32也可以理解成 当前卷积层前面连接了深度为1的输入 后面输出深度为32
b_conv1 = bias_variable([32]) #定义偏置项 数目跟卷积核个数一样
h_conv1 = tf.nn.relu(conv2d(x_images,w_conv1) + b_conv1) # 定义该层激活函数为relu
# 输出为60000x 28x28x32
h_pool1 = max_pool_2x2(h_conv1) # 定义第一层的池化层
# 输出为60000x 14x14x32 长宽都/池化层边长 (2) 池化不改变特征图深度
# 卷积层2代码
# input_size:6000x14x14x32
w_conv2 = weight_variable([5,5,32,64]) #定义卷积核 32表示特征图深度
b_conv2 = bias_variable([64]) #定义偏置项 数目跟卷积核个数一样
h_conv2 = tf.nn.relu(conv2d(h_pool1,w_conv2) + b_conv2) # 定义该层激活函数为relu
# 输出为 ? x 14x14x64 因为padding定义为“SAME”
h_pool2 = max_pool_2x2(h_conv2) # 定义第一层的池化层
# 输出为? x 7x7x64 长宽都/池化层边长 (2)
# 全连接1
# 摊平之后 一行代表一个样本
h_pool2_flat = tf.reshape(h_pool2,[-1,7*7*64]) # 把卷积层2的输出摊平 ,-1代表不确定输入的行数,先做一个占位(60000)
w_fc1 = weight_variable([7*7*64,1024]) # 第一个元素表示输入元素的大小 第二个元素代表神经元的数量 该层输出的大小
b_fc1 = bias_variable([1024])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat,w_fc1) + b_fc1) #函数:tf.matmul 表示:将矩阵 a 乘以矩阵 b,生成a * b
# 输出为?x 1024
# 防止过拟合
keep_prob = tf.placeholder('float') # 传入一个浮点型的占位符 并不是具体值 运行才能有具体值
tf.nn.dropout(h_fc1,keep_prob) # 应用在全连接层 让模型泛化 并不只有在训练集表现才好
keep_prob_rate = 0.5
# 如果传入的keep_prob是0.6 代表保存60%的神经元
# 全连接2
# 不需要摊平
w_fc2 = weight_variable([1024,10])
b_fc2 = bias_variable([10])
#得到预测值
prediction = tf.nn.softmax(tf.matmul(h_fc1,w_fc2) + b_fc2) #softmax:转为概率值(归一化)
# 定义损失函数-交叉熵
cross_entropy = -tf.reduce_sum(ys*tf.log(prediction)) # ys:?x10 prediction:?x10 *:对于元素相乘(不是矩阵乘法)
#tf.reduce_sum 是tf对张量求和的函数
#定义优化器--使用ADAM
learning_rate = 1e-4 # 0.0001
train_step = tf.train.AdamOptimizer(learning_rate).minimize(cross_entropy)
# 3 训练模型
# 评估(evaluation)
# 把测试集传入
def compute_accuracy(v_xs,v_ys):
global prediction # 把prediction设为全局变量
y_pre = sess.run(prediction,feed_dict={xs:v_xs,keep_prob:1}) # 把训练集的标签喂给模型
correct_predoction = tf.equal(tf.argmax(y_pre,1),tf.argmax(v_ys,1)) # 按行搜索最大值
#tf.euqal 返回布尔值 两个值相等返回true
accuracy = tf.reduce_mean(tf.cast(correct_predoction,'float'))# tf.cast将boo转为float32 求均值看看 正确值在总值的占比 作为准确率
result = sess.run(accuracy, feed_dict={xs: v_xs, ys: v_ys})
return result
# TensorFlow构建完毕后,每次都需要创建Session对象,才能run构建好的计算流图
epoch = 2000
#一个Session可能会拥有一些资源,例如Variable或者Queue。当我们不再需要该session的时候,需要将这些资源进行释放。有两种方式,
#1.调用session.close()方法;
#2.使用with tf.Session()创建上下文(Context)来执行,当上下文退出时自动释放。
with tf.Session() as sess:# 构建执行tf的区域,用完释放
init = tf.global_variables_initializer() #含有tf.Variable的环境下,因为tf中建立的变量是没有初始化的,所以使用该函数进行初始化
sess.run(init) #将初始化的值传入,执行计算流图
for i in range(epoch): #训练轮次
batch_image_xs,batch_label_ys = mnist.train.next_batch(100)# 每次按顺序返回100张图片作为输入数据
sess.run(train_step,feed_dict={xs:batch_image_xs,ys:batch_label_ys,keep_prob:keep_prob_rate})# 优化器 feed_dict参数的作用是替换图中的某个tensor的值或设置graph的输入值。
if(i+1) % 50 ==0: #每隔50次 把模型用在测试集上
print("step: %d , test accuracy %g" %(i+1,compute_accuracy(mnist.test.images,mnist.test.labels)))
结果:
关于为什么数字识别最后一层激活函数用softmax
使用relu函数产生的问题:relu函数的值可以无限大,但损失函数如果是交叉熵或极大似然估计都基于概率,最后的输出值到0-1之间
使用sigmoid函数虽然可以把输出值归到0-1之间,但sigmoid函数不能解决互斥的多分类问题,输出的值求和结果不为1(如果要解决的任务不是互斥多分类问题,可以用sigmoid,例如:给电影贴标签,构建用户画像等)
所以最后一层要使用softmax,来解决数字识别这样的互斥多分类问题,使得最后输出的值求和为1
关于为什么使用交叉熵作为损失函数
思考如下:
1.数字识别的输出是概率值,为了衡量真实概率模型和预测概率模型,要先把模型换成熵这个数值,再用这个数值来比较不同模型之间的区别
2.kl散度公式后半部分为基准模型p的熵(真实概率模型)已经固定了,衡量kl散度的大小只需要看前面的部分(交叉熵)
交叉熵已被吉布斯不等式证明一定大于基准的值,交叉熵越小,两个概率模型越接近,所以需要求损失函数的最小值,而不是最大值。
交叉熵是怎么被推导出来的
熵用来衡量一个系统的不确定性,要说清楚什么是熵,得先说明信息量
信息量:一件事情从不确定到确定的困难程度
以8支球队参赛为例,可以把f(阿根廷夺冠)得信息量定义为以下内容
有图可知,信息量满足 f(x1x2) = f(x1) + f(x2)
用数学公式表示为:
使用log函数: 满足 f(x1x2) = f(x1) + f(x2)
前面加-的原因:
8支球队参数,获胜的信息量可表示为 f(1/8)
2支球队参数,获胜的信息量可表示为 f(1/2)
前者信息量更大,因为不确定性更高,但log函数单调递增,不满足这个性质,所以前面要加负号,使得log函数满足信息量的性质
为什么以2为底?:计算机使用二进制存储,不过这里不限制底数
熵:一个系统从不确定性到确定的困难程度
如果只是把一个系统中的所有事件的信息量相加,则不满足熵的定义,如图所示:
把比赛看作一个系统,左边的系统对右边的系统(有中国队)明显不确定性更大,熵更大,如果仅仅是把信息量相加,则得到的结果不满足实际含义,这里得到熵的公式
熵 = 事件在系统的占比 * 事件的信息量
把信息量换成其他的,其实就是在求期望
期望:(一个具体的值x它在系统的占比)所有情况都累加如图所示
得到熵的定义之和,就可以引入kl散度的概念衡量两个概率模型的差异,为什么使用交叉熵作为损失函数,上文已经说明。
文献阅读
本周阅读了一篇综述类文献《A Survey of the Usages of Deep Learning for
Natural Language Processing》该文章发表于2021年,主要是在过去的几年里,介绍了该自然语言处理领域与深度学习模型领域,并快速概述了深度学习的架构和方法。然后,它筛选了最近的大量研究,并总结了大量的相关贡献。分析的研究领域除了包括计算语言学的许多应用外,还包括几个核心的语言处理问题。然后讨论了目前的技术状况,并为该领域的未来研究提出了建议。
但缺乏对综述类文章做记录的能力,本周没有记录阅读情况,下周会改进
总结
本周用三种方法手写数字识别,并对比了三者之间的优缺点,明白了为什么使用softmax作为激活函数,为什么用交叉熵作为损失函数,以及推导了交叉熵的公式
但对综述类英文文献做笔记的能力不足,下周会以此为中心改进