一、文件
1、文件编码的概念
(1)UTF-8是目前全球通用的编码格式
2、文件的操作
(1)open ()打开函数
在Python,使用open函数,可以打开一个已经存在的文件,或者创建一个新文件,语法如下:
open(name, mode, encoding)
name:是要打开的目标文件名的字符串(可以包含文件所在的具体路径)。
mode:设置打开文件的模式(访问模式):只读、写入、追加等。
encoding:编码格式(推荐使用UTF-8)
示例代码:
f = open('python.txt', 'r', encoding="UTF-8)
encoding的顺序不是第三位,所以不能用位置参数,用关键字参数直接指定
注意事项
注意:此时的f是open函数的文件对象,对象是Python中一种特殊的数据类型,拥有属性和方法,可以使用对象.属性或对象.方法对其进行访问,后续面向对象课程会给大家进行详细的介绍。
注:常用的三种基础访问模式
r:以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。
w:打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,原有内容会被删除。如果该文件不存在,创建新文件。
a:打开一个文件用于追加。如果该文件已存在,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
(2)读取文件
read()方法:
文件对象.read(num)
num表示要从文件中读取的数据的长度(单位是字节),如果没有传入num,那么就表示读取文件中所有的数据。
readlines()方法:
readlines可以按照行的方式把整个文件中的内容进行一次性读取,并且返回的是一个列表,其中每一行的数据为一个元素。
f = open('python.txt')
content = f.readlines()
# ['hello world\n', 'abcdefg\n', 'aaa\n', 'bbb\n', 'ccc']
print(content)
# 关闭文件
f.close()
readline()方法: 一次读取一行内容
f = open('python.txt')
content = f.readline()
print(f'第一行: {content}')
content = f.readline()
print(f'第二行: {content}')
# 关闭文件
f.close()
for循环读取文件行
for line in open("python.txt", "r"):
print(line)
# 每一个line临时变量,就记录了文件的一行数据
(3)关闭文件对象
close() 关闭文件对象
f = open("python.txt", "r")
f.close()
# 最后通过close,关闭文件对象,也就是关闭对文件的占用
# 如果不调用close,同时程序没有停止运行,那么这个文件将一直被Python程序占用。
(4)with open 语法
通过在with open的语句块中对文件进行操作
可以在操作完成后自动关闭close文件,避免遗忘掉close方法
with open("python.txt", "r") as f:
f.readlines()
3、文件的写出
# 1. 打开文件
f = open('python.txt', 'w')
# 2. 文件写入
f.write('hello world')
# 3. 内容刷新
f.flush()
注意:
• 直接调用write,内容并未真正写入文件,而是会积攒在程序的内存中,称之为缓冲区
• 当调用flush的时候,内容会真正写入文件
• 这样做是避免频繁的操作硬盘,导致效率下降(攒一堆,一次性写磁盘)
import time
f = open("D:/test.txt", "w", encoding="UTF-8")
# write写入
f.write("Hello World!!!") # 内容写入到内存中
# flush刷新
# f.flush() # 将内存中积攒的内容,写入到硬盘的文件中
# close关闭
f.close() # close方法,内置了flush的功能
f = open("D:/test.txt", "w", encoding="UTF-8")
# write写入、flush刷新
f.write("黑马程序员")
# close关闭
f.close()
4、文件的追加
# 1. 打开文件,通过a模式打开即可
f = open('python.txt', 'a')
# 2. 文件写入
f.write('hello world')
# 3. 内容刷新
f.flush()
(1)a模式,文件不存在会创建文件(2)a模式,文件存在会在最后,追加写入文件
二、异常、模块、包
1、异常的捕获方法
(1)基本语法:
try:
可能发生错误的代码
except:
如果出现异常执行的代码
需求:尝试以r模式打开文件,如果文件不存在,则以w方式打开。
try:
f = open('linux.txt', 'r')
except:
f = open('linux.txt', 'w')
(2)捕获指定异常
基本语法:
try:
print(name)
except NameError as e:
print('name变量名称未定义错误')
注意事项
① 如果尝试执行的代码的异常类型和要捕获的异常类型不一致,则无法捕获异常。
② 一般try下方只放一行尝试执行的代码。
(3)捕获多个异常
当捕获多个异常时,可以把要捕获的异常类型的名字,放到except 后,并使用元组的方式进行书写。
try:
print(1/0)
except (NameError, ZeroDivisionError):
print('ZeroDivision错误...')
(4)异常else
else表示的是如果没有异常要执行的代码。
try:
print(1)
except Exception as e:
print(e)
else:
print('我是else,是没有异常的时候执行的代码')
(5)异常的finally
finally表示的是无论是否异常都要执行的代码,例如关闭文件。
try:
f = open('test.txt', 'r')
except Exception as e:
f = open('test.txt', 'w')
else:
print('没有异常,真开心')
finally:
f.close()
2、异常的传递
异常是具有传递性的
当函数func01中发生异常,并且没有捕获处理这个异常的时候,异常会传递到函数func02,当func02也没有捕获处理这个异常的时候,main函数会捕获这个异常, 这就是异常的传递性.
提示:
当所有函数都没有捕获异常的时候,程序就会报错
def func01(): # 异常在func01中没有被捕获
print("这是func01开始")
num = 1 / 0
print("这是func01结束")
def func02(): # 异常在func02中没有被捕获
print("这是func02开始")
func01()
print("这是func02结束")
def main(): # 异常在main中被捕获
try:
func02()
except Exception as e:
print(e)
3、模块的导入
(1)Python 模块(Module),是一个 Python 文件,以 .py 结尾。 模块能定义函数,类和变量,模块里也能包含可执行的代码。
模块的作用:python中有很多各种不同的模块,每一个模块都可以帮助我们快速的实现一些功能,比如实现和时间相关的功能就可以使用time模块。我们可以认为一个模块就是一个工具包,每一个工具包中都有各种不同的工具供我们使用进而实现各种不同的功能。
大白话:模块就是一个Python文件,里面有类、函数、变量等,我们可以拿过来用(导入模块去使用)
(2)模块的导入方式
模块在使用前需要先导入,导入的语法如下:
[from 模块名] import [模块 | 类 | 变量 | 函数 | *] [as 别名]
常用的组合形式如:
• import 模块名
• from 模块名 import 类、变量、方法等
• from 模块名 import *
• import 模块名 as 别名
• from 模块名 import 功能名 as 别名
# 案例:导入time模块
import 模块名
import 模块名1, 模块名2
模块名.功能名()
# 导入时间模块
import time
print("开始")
# 让程序睡眠1秒(阻塞)
time.sleep(1)
print("结束")
基本语法:
from 模块名 import 功能名
案例:导入time模块中的sleep方法
from 模块名 import 功能名
功能名()
# 导入时间模块中的sleep方法
from time import sleep
print("开始")
# 让程序睡眠1秒(阻塞)
sleep(1)
print("结束")
基本语法:
from 模块名 import *
案例:导入time模块中所有的方法
from 模块名 import *
功能名()
# 导入时间模块中所有的方法
from time import *
print("开始")
# 让程序睡眠1秒(阻塞)
sleep(1)
print("结束")
as定义别名
基本语法:
# 模块定义别名
import 模块名 as 别名
# 功能定义别名
from 模块名 import 功能 as 别名
案例:
# 模块别名
import time as tt
tt.sleep(2)
print('hello')
# 功能别名
from time import sleep as sl
sl(2)
print('hello')
4、自定义模块
模块的命名一定要符合标识符命名规则
#import my_module1
#my_module1.test(1,5)
from my_module1 import test
test(5,6)
注意:若调用相同的模块,第二个会覆盖第一个。即使用第二个模块
5、python包(文件夹)包含多个python模块
(1)python包:
多种导用方式
三、数据可视化
1.数据可视化
1.json
2.地图
3、生成基本树状图
(1)
(2)bar.reversal_axis() 反转x轴和y轴
(3)label_opts=LabelOpts(position="right") 将数据放在树状图的右边
(4)柱状图总结
4.基础时间线柱状图绘制
from pyecharts.charts import Bar,Timeline
bar1=Bar()
bar1.add_xaxis(["中国","美国","英国"])
bar1.add_yaxis("GDP",[100,50,30])
bar1.reversal_axis()
bar2=Bar()
bar2.add_xaxis(["中国","美国","英国"])
bar2.add_yaxis("GDP",[10000,5000,800])
bar2.reversal_axis()
timeline=Timeline()
timeline.add(bar1,"2021年GDP")
timeline.add(bar2,"2025年GDP")
timeline.render("时间线树状图.htel")
from pyecharts.charts import Bar,Timeline
bar1=Bar()
bar1.add_xaxis(["中国","美国","英国"])
bar1.add_yaxis("GDP",[100,50,30])
bar1.reversal_axis()
bar2=Bar()
bar2.add_xaxis(["中国","美国","英国"])
bar2.add_yaxis("GDP",[10000,5000,800])
bar2.reversal_axis()
timeline=Timeline()
timeline.add(bar1,"2021年GDP")
timeline.add(bar2,"2025年GDP")
timeline.render("时间线树状图.htel")
5、设置自动播放
timeline.add_schema(
play_interval=1000, #自动播放的时间间隔,单位毫秒
is_timeline_show=True, #是否在自动播放的时候显示时间线
is_auto_play=True, #是否自动播放
is_loop_play=True #是否循环自动播放
)
6、时间线设置主题
from pyecharts.globals import ThemeType #先导入包
#创建时间对象
timeline =Timeline({"theme":ThemeType.LIGHT}) 这其实是一个字典
7、GDP动态树状图的绘制
列表的sort方法
列表.sort(key=选择排序依据的函数,reverse=True|False)
参数key,是要求传入一个函数,表示将列表的每一个元素都传入函数中,返回排序的依据
参数reverse,是否反转排序结果,True表降序,False表示升序
my_list=[["a",95],["b",921],["c",11]]
def choose (element):
return element[1] #下标1
my_list.sort(key=choose,reverse=True) #降序排序
print(my_list)
[['b', 921], ['a', 95], ['c', 11]]
也可以简化为lambda函数
四、类
Python 中使用 class 关键字来定义类,类要遵循下述格式
class 类名:
def __init__(self, 参数 , …): # 构造函数
. . .
def 方法名 1(self, 参数 , …): # 方法 1
. . .
def 方法名 2(self, 参数 , …): # 方法 2
这里有一个特殊的 __init__ 方法,这是进行初始化的方法,也称为构造
函数(constructor), 只在生成类的实例时被调用一次。
class Man:
def __init__(self, name):
self.name = name
print("Initialized!")
def hello(self):
print("Hello " + self.name + "!")
def goodbye(self):
print("Good-bye " + self.name + "!")
m = Man("David")
m.hello()m.goodbye()
这里我们定义了一个新类Man。上面的例子中,类Man 生成了实例(对象)m。
类 Man 的构造函数(初始化方法)会接收参数 name,然后用这个参数初始
化实例变量 self.name。实例变量是存储在各个实例中的变量。Python 中可
以像 self.name 这样,通过在 self 后面添加属性名来生成或访问实例变量。
五、Numpy
1、导入 NumPy 库
import numpy as np
2、 生成 NumPy 数组
要生成 NumPy 数组,需要使用 np.array() 方法。np.array() 接收 Python
列表作为参数,生成 NumPy 数组(numpy.ndarray)。
x = np.array([1.0, 2.0, 3.0])
>>> print(x)
[ 1. 2. 3.]
>>> type(x)<class 'numpy.ndarray'>
3、 NumPy 的算术运算
下面是 NumPy 数组的算术运算的例子。
>>> x = np.array([1.0, 2.0, 3.0])
>>> y = np.array([2.0, 4.0, 6.0])
>>> x + y # 对应元素的加法
array([ 3., 6., 9.])
>>> x - y
array([ -1., -2., -3.])
>>> x * y # element-wise product
array([ 2., 8., 18.])
>>> x / y
array([ 0.5, 0.5, 0.5])
这里需要注意的是,数组 x 和数组 y 的元素个数是相同的(两者均是元素
个数为 3 的一维数组)。当 x 和 y 的元素个数相同时,可以对各个元素进行算
术运算。如果元素个数不同,程序就会报错,所以元素个数保持一致非常重要。
4、NumPy 的 N 维数组
NumPy 不仅可以生成一维数组(排成一列的数组),也可以生成多维数组。
比如,可以生成如下的二维数组(矩阵)。
>>> B = np.array([[3, 0],[0, 6]])
>>> A + B
array([[ 4, 2],
[ 3, 10]])
>>> A * B
array([[ 3, 0],[ 0, 24]])
六、Matplotlib
1、可以使用 matplotlib 的 pyplot 模块绘制图形。
绘制 sin 函数曲线的例子。
import numpy as np
import matplotlib.pyplot as plt
# 生成数据
x = np.arange(0, 6, 0.1) # 以 0.1 为单位,生成 0 到 6 的数据
y = np.sin(x)
#
# 绘制图形
plt.plot(x, y)
plt.show()
以上为python入门全部内容
六、感知机
1、感知机是什么
感知机接收多个输入信号,输出一个信号。
例:一个接收两个输入信号的感知机。x1、x2 是输入信号,y 是输出信号,w1、w2 是 权 重(w 是 weight 的首字母)。图中的○称为“神经元”或者“节点”。输入信号被送往神经元时,会被分别乘以固定的权重(w1x1、w2x2)。神经元会计算传送过来的信号的总和,只有当这个总和超过了某个界限值时,才会输出 1。这也称为“神经元被激活”。这里将这个界限值称为阈值,用符号 θ 表示。
2、感知机的实现
(1)现在,我们用 Python 来实现刚才的逻辑电路。这里,先定义一个接收
参数 x1 和 x2 的 AND 函数。
def AND(x1, x2):
w1, w2, theta = 0.5, 0.5, 0.7
tmp = x1*w1 + x2*w2
if tmp <= theta:
return 0
elif tmp > theta:
return 1
在函数内初始化参数 w1、w2、theta,当输入的加权总和超过阈值时返回 1,
否则返回 0。
AND(0, 0) # 输出 0
AND(1, 0) # 输出 0
AND(0, 1) # 输出 0
AND(1, 1) # 输出 1
(2)导入权重和偏置
刚才的与门的实现比较直接、容易理解,但是考虑到以后的事情,我们
将其修改为另外一种实现形式。在此之前,首先把式(2.1)的 θ 换成 −b,
>>> import numpy as np
>>> x = np.array([0, 1]) # 输入
>>> w = np.array([0.5, 0.5]) # 权重
>>> b = -0.7 # 偏置
>>> w*x
array([ 0. , 0.5])
>>> np.sum(w*x)
0.5
>>> np.sum(w*x) + b
-0.19999999999999996 # 大约为 -0.2(由浮点小数造成的运算误差)
3、感知机的局限性
感知机的局限性就在于它只能表示由一条直线分割的空间。
4、多层感知机
通过组合与门、与非门、或门实现异或门
def XOR(x1, x2):
s1 = NAND(x1, x2)
s2 = OR(x1, x2)
y = AND(s1, s2)
return y
这个 XOR 函数会输出预期的结果。
XOR(0, 0) # 输出 0
XOR(1, 0) # 输出 1
XOR(0, 1) # 输出 1
XOR(1, 1) # 输出 0
实际上,与门、或门是单层感知机,而异或门是 2 层感知机。叠加了多
层的感知机也称为多层感知机(multi-layered perceptron)。
七、神经网络
1、激活函数
(1)sigmoid 函数
神经网络中用 sigmoid 函数作为激活函数,进行信号的转换,转换后的信号被传送给下一个神经元。
(2)阶跃函数的实现
>>> import numpy as np
>>> x = np.array([-1.0, 1.0, 2.0])
>>> x
array([-1., 1., 2.])
>>> y = x > 0
>>> y
array([False, True, True], dtype=bool)
阶跃函数的图形
import numpy as np
import matplotlib.pylab as plt
def step_function(x):
return np.array(x > 0, dtype=np.int)
x = np.arange(-5.0, 5.0, 0.1)
y = step_function(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1) # 指定 y 轴的范围
plt.show()
阶跃函数以 0 为界,输出从 0 切换为 1(或者从 1 切换为 0)。它的值呈阶梯式变化,所以称为阶跃函数。
(3)sigmoid 函数的实现
def sigmoid(x):
return 1 / (1 + np.exp(-x))
x = np.arange(-5.0, 5.0, 0.1)
y = sigmoid(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1) # 指定 y 轴的范围
plt.show()
代码和刚才的阶跃函数的代码几乎是一样的,唯一不同的地方是把输出 y 的函数换成了 sigmoid 函数。
(4)sigmoid 函数和阶跃函数的比较
sigmoid 函数是一条平滑的曲线,输出随着输入发生连续性的变化。而阶跃函数以 0 为界,输出发生急剧性的变化。sigmoid 函数的平滑性对神经网络的学习具有重要意义。
相对于阶跃函数只能返回 0 或 1,sigmoid 函数可以返回 0.731 . . .、0.880 . . . 等实数(这一点和刚才的平滑性有关)。也就是说,感知机中神经元之间流动的是 0 或 1 的二元信号,而神经网络中流动的是连续的实数值信号。
阶跃函数和 sigmoid函数虽然在平滑性上有差异,但是如果从宏观视角,可以发现它们
具有相似的形状。实际上,两者的结构均是“输入小时,输出接近 0(为 0);随着输入增大,输出向 1 靠近(变成 1)”。也就是说,当输入信号为重要信息时,阶跃函数和 sigmoid 函数都会输出较大的值;当输入信号为不重要的信息时,两者都输出较小的值。不管输入信号有多小,或者有多大,输出信号的值都在 0 到 1 之间。
(5)非线性函数
神经网络的激活函数必须使用非线性函数。换句话说,激活函数不能使用线性函数。
(6)ReLU 函数
sigmoid 函数很早就开始被使用了,而最近则主要使用 ReLU(Rectified Linear Unit)函数。
ReLU 函 数 是 一 个 非 常 简 单 的 函 数。因 此,ReLU 函数的实现也很简单,可以写成如下形式。
def relu(x):
return np.maximum(0, x)
2、多维数组的运算
(1)一维数组
>>> import numpy as np
>>> A = np.array([1, 2, 3, 4])
>>> print(A)
[1 2 3 4]
>>> np.ndim(A)
1
>>> A.shape
(4,)
>>> A.shape[0]
4
如上所示,数组的维数可以通过 np.dim() 函数获得。此外,数组的形状可以通过实例变量 shape 获得。在上面的例子中,A 是一维数组,由 4 个元素构成。注意,这里的 A.shape 的结果是个元组(tuple)。这是因为一维数组的情况下也要返回和多维数组的情况下一致的结果。例如,二维数组时返回的是元组 (4,3),三维数组时返回的是元组 (4,3,2),因此一维数组时也同样以元组的形式返回结果。
(2)二维数组
>>> B = np.array([[1,2], [3,4], [5,6]])
>>> print(B)
[[1 2]
[3 4]
[5 6]]
>>> np.ndim(B)
2
>>> B.shape
(3, 2)
这里生成了一个 3 × 2 的数组 B。3 × 2 的数组表示第一个维度有 3 个元素,第二个维度有 2 个元素。另外,第一个维度对应第 0 维,第二个维度对应第1 维(Python 的索引从 0 开始)。二维数组也称为矩阵(matrix)。数组的横向排列称为行(row),纵向排列称为列(column)。
(3)矩阵乘法
矩阵的乘积是通过左边矩阵的行(横向)和右边矩阵的列(纵向)以对应元素的方式相乘后再求和而得到的。并且,运算的结果保存为新的多维数组的元素。
这个运算在 Python 中可以用如下代码实现。
>>> A = np.array([[1,2], [3,4]])
>>> A.shape
(2, 2)
>>> B = np.array([[5,6], [7,8]])
>>> B.shape
(2, 2)
>>> np.dot(A, B)
array([[19, 22],
[43, 50]])
注:矩阵 A 的第 1 维的元素个数(列数)必须和矩阵 B 的第 0 维的元素个数(行数)相等。
3、3 层神经网络的实现
(1)下面我们用 NumPy 多维数组来实现式,这里将输入信号、权重、偏置设置成任意值。
A1 = np.dot(X, W1) + B1
X = np.array([1.0, 0.5])
W1 = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
B1 = np.array([0.1, 0.2, 0.3])
print(W1.shape) # (2, 3)
print(X.shape) # (2,)
print(B1.shape) # (3,)
隐藏层的加权和(加权信号和偏置的总和)用 a 表示,被激活函数转换后的信号用 z 表示。此外,图中 h() 表示激活函数,这里我们使用的是 sigmoid 函数。用 Python 来实现,代码如下所示。
Z1 = sigmoid(A1)
print(A1) # [0.3, 0.7, 1.1]
print(Z1) # [0.57444252, 0.66818777, 0.75026011]
实现第 1 层到第 2 层的信号传递 A2 = np.dot(Z1, W2) + B2
Z2 = sigmoid(A2)
W2 = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
B2 = np.array([0.1, 0.2])
print(Z1.shape) # (3,)
print(W2.shape) # (3, 2)
print(B2.shape) # (2,)
最后是第 2 层到输出层的信号传递。输出层的实现也和之前的实现基本相同。不过,最后的激活函数和之前的隐藏层有所不同。
def identity_function(x):
return x
W3 = np.array([[0.1, 0.3], [0.2, 0.4]])
B3 = np.array([0.1, 0.2])
A3 = np.dot(Z2, W3) + B3
Y = identity_function(A3) # 或者 Y = A3
4、输出层的设计
(1)恒等函数
恒等函数会将输入按原样输出,对于输入的信息,不加以任何改动地直接输出。因此,在输出层使用恒等函数时,输入信号会原封不动地被输出。
(2)softmax 函数
实现softmax 函数
def softmax(a):
exp_a = np.exp(a)
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
实现 softmax 函数时的注意事项
溢出问题。softmax 函数的实现中要进行指数函数的运算,但是此时指数函数的值很容易变得非常大。改进后的代码为:
def softmax(a):
c = np.max(a)
exp_a = np.exp(a - c) # 溢出对策
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
softmax 函数的特征
>>> a = np.array([0.3, 2.9, 4.0])
>>> y = softmax(a)
>>> print(y)
[ 0.01821127 0.24519181 0.73659691]
>>> np.sum(y)
1.0
softmax 函数的输出是 0.0 到 1.0 之间的实数。并且,softmax函数的输出值的总和是 1。输出总和为 1 是 softmax 函数的一个重要性质。正因为有了这个性质,我们才可以把 softmax 函数的输出解释为“概率”。
5、手写数字识别
(1)MNIST 数据集
介绍:MNIST 是机器学习领域最有名的数据集之一,被应用于从简单的实验到发表的论文研究等各种场合。实际上,在阅读图像识别或机器学习的论文时,MNIST 数据集经常作为实验用的数据出现。
运用:MNIST 数据集是由 0 到 9 的数字图像构成的(图 3-24)。训练图像有 6 万张,
测试图像有 1 万张,这些图像可以用于学习和推理。MNIST 数据集的一般使用方法是,先用训练图像进行学习,再用学习到的模型度量能在多大程度上对测试图像进行正确的分类。
(2)神经网络的推理处理
MNIST 数据集实现神经网络的推理处理。神经网络的输入层有 784 个神经元,输出层有 10 个神经元。输入层的 784 这个数字来源于图像大小的 28 × 28 = 784,输出层的 10 这个数字来源于 10 类别分类(数字 0 到 9,共 10 类别)。此外,这个神经网络有 2 个隐藏层,第 1 个隐藏层有50 个神经元,第 2 个隐藏层有 100 个神经元。这个 50 和 100 可以设置为任何值。
先定义 get_data()、init_network()、predict() 这 3 个函数
def get_data():
(x_train, t_train), (x_test, t_test) = \
load_mnist(normalize=True, flatten=True, one_hot_label=False)
return x_test, t_test
def init_network():
with open("sample_weight.pkl", 'rb') as f:
network = pickle.load(f)
return network
def predict(network, x):
W1, W2, W3 = network['W1'], network['W2'], network['W3']
b1, b2, b3 = network['b1'], network['b2'], network['b3']
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
z2 = sigmoid(a2)
a3 = np.dot(z2, W3) + b3
y = softmax(a3)
return y
(3)批处理
>>> x, _ = get_data()
>>> network = init_network()
>>> W1, W2, W3 = network['W1'], network['W2'], network['W3']
>>>
>>> x.shape
(10000, 784)
>>> x[0].shape
(784,)
>>> W1.shape
(784, 50)
>>> W2.shape
(50, 100)
>>> W3.shape
(100, 10)
输 入 数 据 的 形 状 为 100 × 784,输 出 数 据 的 形 状 为100 × 10。这表示输入的 100 张图像的结果被一次性输出了。比如,x[0] 和y[0] 中保存了第 0 张图像及其推理结果,x[1] 和 y[1] 中保存了第 1 张图像及其推理结果,等等。
这种打包式的输入数据称为批(batch)。批有“捆”的意思,图像就如同纸币一样扎成一捆。
批处理的代码实现
x, t = get_data()
network = init_network()
batch_size = 100 # 批数量
accuracy_cnt = 0
for i in range(0, len(x), batch_size):
x_batch = x[i:i+batch_size]
y_batch = predict(network, x_batch)
p = np.argmax(y_batch, axis=1)
accuracy_cnt += np.sum(p == t[i:i+batch_size])
print("Accuracy:" + str(float(accuracy_cnt) / len(x)))
首先是 range() 函数。range() 函数若指定为 range(start, end),则会生成一个由 start 到 end-1 之间的整数构成的列表。若像 range(start, end, step) 这样指定 3 个整数,则生成的列表中的
下一个元素会增加 step 指定的值。在 range() 函数生成的列表的基础上,通过 x[i:i+batch_size] 从输入数据中抽出批数据。x[i:i+batch_n] 会取出从第 i 个到第 i+batch_n 个之间的数据。然后,通过 argmax() 获取值最大的元素的索引。不过这里需要注意的是,我们给定了参数 axis=1。这指定了在 100 × 10 的数组中,沿着第 1 维方向(以第 1 维为轴)找到值最大的元素的索引(第 0 维对应第 1 个维度)。最后,我们比较一下以批为单位进行分类的结果和实际的答案。为此,需要在 NumPy 数组之间使用比较运算符(==)生成由 True/False 构成的布尔型数组,并计算 True 的个数。
八、神经网络的学习
1、特征量
“特征量”是指可以从输入数据(输入图像)中准确地提取本质数据(重要的数据)的转换器。图
像的特征量通常表示为向量的形式。在计算机视觉领域,常用的特征量包括SIFT、SURF 和 HOG 等。使用这些特征量将图像数据转换为向量,然后对转换后的向量使用机器学习中的 SVM、KNN 等分类器进行学习。
将图像转换为向量时使用的特征量仍是由人设计的。对于不同的问题,必须使用合适的特征量(必须设计专门的特征量),才能得到好的结果。
2、深度学习
深 度 学 习 有 时 也 称 为 端 到 端 机 器 学 习(end-to-end machine learning)。这里所说的端到端是指从一端到另一端的意思,也就是从原始数据(输入)中获得目标结果(输出)的意思。
3、训练数据和测试数据
机器学习中,一般将数据分为训练数据和测试数据两部分来进行学习和实验等。首先,使用训练数据进行学习,寻找最优的参数;然后,使用测试数据评价训练得到的模型的实际能力。
为了正确评价模型的泛化能力,就必须划分训练数据和测试数据。另外,训练数据也可以称为监督数据。泛化能力是指处理未被观察过的数据(不包含在训练数据中的数据)的能力。获得泛化能力是机器学习的最终目标。
只对某个数据集过度拟合的状态称为过拟合(over fitting)。
4、损失函数
(1)神经网络的学习中所5用的指标称为损失函数(loss function)。这个损失函数可以使用任意函数,但一般用均方误差和交叉熵误差等。
损失函数是表示神经网络性能的“恶劣程度”的指标,即当前的神经网络对监督数据在多大程度上不拟合,在多大程度上不一致。
(2)均方误差
均方误差会计算神经网络的输出和正确解监督数据的各个元素之差的平方,再求总和。现在,我们用 Python 来实现这个均方误差
def mean_squared_error(y, t):
return 0.5 * np.sum((y-t)**2)
>>> # 设 “2” 为正确解
>>> t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
>>>
>>> # 例 1:“2” 的概率最高的情况(0.6)
>>> y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
>>> mean_squared_error(np.array(y), np.array(t))
0.097500000000000031
>>>
>>> # 例 2:“7” 的概率最高的情况(0.6)
>>> y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
>>> mean_squared_error(np.array(y), np.array(t))0.59750000000000003
这里举了两个例子。第一个例子中,正确解是“2”,神经网络的输出的最大值是“2”;第二个例子中,正确解是“2”,神经网络的输出的最大值是“7”。如实验结果所示,我们发现第一个例子的损失函数的值更小,和监督数据之间的误差较小。也就是说,均方误差显示第一个例子的输出结果与监督数据更加吻合。
(3)交叉熵误差
用代码实现交叉熵误差。
def cross_entropy_error(y, t):
delta = 1e-7
return -np.sum(t * np.log(y + delta))
这里,参数 y 和 t 是 NumPy 数组。函数内部在计算 np.log 时,加上了一个微小值 delta。这是因为,当出现 np.log(0) 时,np.log(0) 会变为负无限大的 -inf,这样一来就会导致后续计算无法进行。作为保护性对策,添加一个微小值可以防止负无限大的发生。
使用 cross_entropy_error(y, t)进行一些简单的计算。
>>> t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
>>> y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
>>> cross_entropy_error(np.array(y), np.array(t))
0.51082545709933802
>>>
>>> y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
>>> cross_entropy_error(np.array(y), np.array(t))
2.3025840929945458
第一个例子中,正确解标签对应的输出为 0.6,此时的交叉熵误差大约为 0.51。第二个例子中,正确解标签对应的输出为 0.1 的低值,此时的交叉熵误差大约为 2.3。
(4)mini-batch 学习
神经网络的学习也是从训练数据中选出一批数据(称为 mini-batch, 小批量),然后对每个 mini-batch 进行学习。比如,从 60000 个训练数据中随机选择 100 笔,再用这 100 笔数据进行学习。这种学习方式称为 mini-batch 学习。
编写从训练数据中随机选择指定个数的数据的代码,以进行mini-batch 学习。
在这之前,先来看一下用于读入 MNIST 数据集的代码。
import sys, os
sys.path.append(os.pardir)
import numpy as np
from dataset.mnist import load_mnist
(x_train, t_train), (x_test, t_test) = \
load_mnist(normalize=True, one_hot_label=True)
print(x_train.shape) # (60000, 784)
print(t_train.shape) # (60000, 10)
load_mnist 函数是用于读入 MNIST 数据集的函数。,通过设定参数 one_hot_label=True,可以得到 one-hot 表示(即仅正确解标签为 1,其余为 0 的数据结构)。
读入上面的 MNIST 数据后,训练数据有 60000 个,输入数据是 784 维(28 × 28)的图像数据,监督数据是 10 维的数据。因此,上面的 x_train、t_train 的形状分别是 (60000, 784) 和 (60000, 10)。
那么,如何从这个训练数据中随机抽取 10 笔数据呢?我们可以使用NumPy的np.random.choice() 写成如下形式。
train_size = x_train.shape[0]
batch_size = 10
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
使用 np.random.choice() 可以从指定的数字中随机选择想要的数字。比如np.random.choice(60000, 10) 会从 0 到 59999 之间随机选择 10 个数字。如下面的实际代码所示,我们可以得到一个包含被选数据的索引的数组。
>>> np.random.choice(60000, 10)
array([ 8013, 14666, 58210, 23832, 52091, 10153, 8107, 19410, 27260,21411])
之后,我们只需指定这些随机选出的索引,取出 mini-batch,然后使用这个 mini-batch 计算损失函数即可。
(5)mini-batch 版交叉熵误差的实现
实现一个可以同时处理单个数据和批量数据(数据作为 batch 集中输入)两种情况的函数。
def cross_entropy_error(y, t):
if y.ndim == 1:
t = t.reshape(1, t.size)
y = y.reshape(1, y.size)
batch_size = y.shape[0]
return -np.sum(t * np.log(y + 1e-7)) / batch_size
y 是神经网络的输出,t 是监督数据。y 的维度为 1 时,即求单个数据的交叉熵误差时,需要改变数据的形状。并且,当输入为 mini-batch 时,要用 batch 的个数进行正规化,计算单个数据的平均交叉熵误差。
当监督数据是标签形式(非 one-hot 表示,而是像“2”“7”这样的标签)时,交叉熵误差可通过如下代码实现。
def cross_entropy_error(y, t):
if y.ndim == 1:
t = t.reshape(1, t.size)
y = y.reshape(1, y.size)
batch_size = y.shape[0]
return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
t 为 one-hot 表示时通过t * np.log(y) 计 算 的 地 方,在 t 为 标 签 形 式 时,可 用 np.log( y[np.arange(batch_size), t] ) 实现相同的处理(为了便于观察,这里省略了微小值 1e-7)。
简单介绍一下np.log( y[np.arange(batch_size), t] )。np.arange(batch_size) 会生成一个从 0 到 batch_size-1 的数组。比如当 batch_size 为 5时,np.arange(batch_size) 会生成一个 NumPy
数组 [0, 1, 2, 3, 4]。t 中标签是以 [2, 7, 0, 9, 4] 的形式存储的,所以 y[np.arange(batch_size),
t] 能抽出各个数据的正确解标签对应的神经网络的输出(在这个例子中,y[np.arange(batch_size), t] 会 生 成 NumPy 数 组 [y[0,2], y[1,7], y[2,0],y[3,9], y[4,4]])。
九、损失函数《Loss Functions for Image Restoration with Neural Networks》图像恢复领域中损失函数设计与应用的研究
研究背景
图像恢复是计算机视觉中的一个重要任务,其目标是从退化的图像中恢复出高质量的图像。随着深度学习的发展,基于神经网络的方法在图像恢复任务中取得了显著的成果。然而,损失函数的选择对于神经网络的性能至关重要。传统的损失函数(如均方误差)虽然简单,但在某些情况下可能无法很好地衡量图像恢复的质量,尤其是在纹理、细节和视觉感知方面。因此,研究新的损失函数对于进一步提升图像恢复性能具有重要意义。
研究方法
论文首先回顾了传统的图像恢复损失函数,包括均方误差(MSE)、绝对误差(MAE)等。这些损失函数主要关注像素级别的误差,但往往忽略了图像的结构和纹理信息。
接着,论文介绍了几种新兴的损失函数设计思路:
-
感知损失函数(Perceptual Loss):通过在预训练的深度神经网络(如VGG网络)的特征层上计算损失,感知损失能够更好地捕捉图像的高级语义信息和视觉感知质量。它将输入图像和目标图像在特征空间中进行比较,而不是直接在像素空间中比较。
-
对抗损失函数(Adversarial Loss):利用生成对抗网络(GAN)的思想,通过训练一个判别器来区分恢复的图像和真实的图像,从而生成更接近真实图像的恢复结果。对抗损失能够增强图像的细节和纹理,使恢复的图像更具真实感。
-
复合损失函数(Composite Loss):将多种损失函数结合起来,以综合考虑像素级误差、感知误差和对抗误差等。例如,将MSE、感知损失和对抗损失加权求和,以达到更好的恢复效果。
-
其他损失函数:论文还探讨了一些其他类型的损失函数,如基于结构相似性(SSIM)的损失函数、基于梯度的损失函数等。这些损失函数从不同的角度衡量图像恢复的质量,例如SSIM损失更注重图像的结构相似性,而梯度损失则关注图像的边缘信息。
实验与结果
论文通过一系列实验验证了不同损失函数在图像恢复任务中的性能。实验使用了多种图像恢复任务,如图像去噪、超分辨率重建、去模糊等。实验结果表明:
1.感知损失函数:在视觉效果上优于传统的MSE损失,能够更好地保留图像的纹理和细节。例如,在超分辨率重建任务中,使用感知损失的模型生成的图像在纹理和细节上更接近真实图像。
2.对抗损失函数:能够显著提升图像的视觉质量,使恢复的图像更具真实感。然而,对抗损失可能导致训练过程不稳定,需要仔细调整训练参数。
3.复合损失函数:通过结合多种损失函数,能够在像素级误差和视觉感知质量之间取得平衡。例如,在图像去噪任务中,复合损失函数能够同时减少噪声并保留图像的细节。
4.其他损失函数:基于SSIM的损失函数在结构相似性方面表现较好,而基于梯度的损失函数则在边缘信息的保留上具有优势。
研究贡献
论文的主要贡献在于系统地总结和分析了图像恢复任务中常用的损失函数,并通过实验验证了它们的性能。论文不仅回顾了传统的损失函数,还介绍了新兴的感知损失、对抗损失和复合损失等,并探讨了它们在不同图像恢复任务中的适用性和优缺点。这些研究为后续的图像恢复工作提供了重要的参考,也为设计更有效的损失函数提供了思路。
研究局限与未来方向
尽管论文对多种损失函数进行了深入的探讨,但仍存在一些局限性。例如,论文主要关注了损失函数的选择和设计,但对于不同损失函数的组合方式和权重分配缺乏系统的分析。此外,论文的实验主要集中在一些常见的图像恢复任务上,对于一些新兴的图像恢复任务(如图像去雨、去雾等)的适用性还需要进一步验证。
[1603.08155] Perceptual Losses for Real-Time Style Transfer and Super-Resolution Abstract page for arXiv paper 1603.08155: Perceptual Losses for Real-Time Style Transfer and Super-Resolutionhttps://doi.org/10.48550/arXiv.1603.08155 Photo-Realistic Single Image Super-Resolution Using a Generative Adversarial Networkhttps://doi.org/10.48550/arXiv.1609.04802
https://doi.org/10.48550/arXiv.1609.04802[1809.07517] The 2018 PIRM Challenge on Perceptual Image Super-resolutionAbstract page for arXiv paper 1809.07517: The 2018 PIRM Challenge on Perceptual Image Super-resolution
https://doi.org/10.48550/arXiv.1809.07517