第四次周报

手写数字识别

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(x1
x2) = 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作为激活函数,为什么用交叉熵作为损失函数,以及推导了交叉熵的公式
但对综述类英文文献做笔记的能力不足,下周会以此为中心改进

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值