快速上手深度学习项目(一)

Yizumi Konata Lv3

原博客连接:https://zhouyifan.net/2022/04/23/DLS-note-1/

github代码链接:https://github.com/SingleZombie/DL-Demos

原本的内容是这位大佬记录的吴恩达《深度学习专项》笔记以及代码。原内容是按照课程内容来的,内容很扎实,但是对于有了一点点深度学习的基础知识,要开始做项目的高年级本科或者研究生同学来说有点长了,所以我这里简单记录一下我学习之后觉得实战中比较有用的知识,对前面提到的人群来说可以起到一个快速上手的作用。同时原博客作者的代码写的很好,可以帮助我们理解pytorch等框架为什么这么设计,每一步都发生了什么。

基础知识

深度学习的基本概念这里不会介绍,基本概念不理解的建议先去看看科普视频,包括训练集,验证集,测试集,损失函数,常见损失函数表达式,神经网络,激活函数,常见激活函数表达式,梯度下降法。

数学知识这里也不会做介绍,包括基本的代数知识如矩阵、向量的运算,文章中不同函数梯度的求法等等。

这里会介绍以下python基础知识,但基本只做一个复习用,不会太细。

首先介绍深度学习项目的架构,一般深度学习项目由以下几个部分组成

  • 数据预处理
  • 定义网络结构
  • 定义损失函数
  • 定义优化策略
  • 编写训练流程(一般就是前向传播,计算梯度,梯度回传三步)
  • 测试模型精度

其中,我个人认为最复杂的地方在于数据预处理,因为其他步骤其实我们用pytorch等框架的话很好实现。而数据处理,需要对高维数据的变换以及python中数据处理的api比较熟练。这里先介绍常用的api

python元组、列表、集合、字典基本操作

元组、列表、集合和字典是python常用的内置容器,元组和列表是有序容器,集合和字典是无序容器。这些python容器都是有自己的数据结构和算法的,不是类C语言中底层的数组,这一点要注意,不要用数组这种原始的数据结构去理解这些高度封装的容器。这些容器由于其用法灵活但是速度慢,一般用于数据预处理早期组织样本数据,真正参与神经网络运算时都是使用numpy或者深度学习框架提供的矩阵(或者叫张量)。

列表

列表是一种有序可变的集合,可对其进行增删改查。注意列表中的元素不一定是同类型的,还是那句话,不要用类C语言中提供的底层数组去理解这种高度封装的容器。列表的符号是[],用[]框起来的数据就是一个列表

列表的初始化有以下几种方法

1
2
3
4
5
6
list = []  # 空列表初始化
list = ['a', 'b', 'c'] # 有值初始化
list = [1, 2, 3, 'a', 'b', 'c'] # 包含不同类型元素的元组
list = [x for x in range(10)] # 使用列表推导式
list = list() # 使用类型的构造器
list = list(iterable) # iterable 是可迭代对象,例如:tuple、set、str

列表的常用方法如下,以下方法基本囊括了常用的增删改操作,注意调用下列方法会改变列表自身的值,如果要得到一个新值而不改变原列表的值应该使用切片操作,或者调用别的函数而不是调用容器自身方法。

1
2
3
4
5
6
7
8
9
list.append(obj) #末尾添加元素
list.count(obj) #统计元素个数
list.extend(seq) #将序列中元素添加到末尾
list.index(obj) #查询元素第一次出现的下标
list.insert(index, obj) #在下标位置插入元素
list.pop([index=-1]) #移除下标元素(默认尾元素)
list.remove(obj) #移除列表中某个值的第一个匹配项
list.reverse() #反向,如果要得到一个新值而不改变原列表的值可以使用list[::-1]
list.sort(key=None, reverse=False) #排序,默认升序,key可以指定排序方法,如果要得到一个新值而不改变原列表的值可以使用sorted方法

列表中的元素可以使用[]加下标访问,并且[]也支持切片操作,切片操作的基本格式为

1
list[start:end:step]

表示切片的范围为,左闭右开,步长为step。其中startendstep都可以是负数,前两者是负数时表示倒数第几个元素,-1是倒数第一个元素,step是负数时表示反向切片,即切片得到的新列表的排列顺序是从endstart,步长依然是step绝对值

最后要介绍列表常用的脚本操作符

操作符表达式结果描述
+[1, 2, 3] + [4, 5, 6][1, 2, 3, 4, 5, 6]组合
*['Hi!'] * 4['Hi!', 'Hi!', 'Hi!', 'Hi!']重复
in3 in [1, 2, 3]True元素是否存在于列表中
for...in...for x in [1, 2, 3]: print x1 2 3迭代

元组

元组是一种有序不可变的集合,用()表示,其初始化方法有

1
2
3
4
tup = () # 空元组初始化
tup = (1,) # 仅有一个元素的初始化,应当在元素后加一个逗号,以防被当成运算符(括号)处理
tup = tuple() # 利用构造器进行初始化
tup = tuple(iterable) # 支持传入迭代器对象,例如:列表,字符串、集合等

元组其他的操作除了增删改以外,都和列表一致,不做过多赘述

字典

字典是可变容器,可存储任意类型对象; 包括键和值。可类比其他语言中的map,键不能重复,向字典中添加键相同的键值对, 后者的值会覆盖前者。键必须不可变, 一般用数字或者字符串,使用其他类做键需要这个类有__hash__方法,这一点和java是一致的。字典用{}表示,每个元素是一个键值对

字典的常见初始化方法有

1
2
3
dict = {}   # 空元组初始化
dict = dict() # 利用构造器进行初始化
dict = {'abc':123, 'aaa':345, 'string':6666} # 带有初始值的初始化

字典常见的方法有

1
2
3
4
5
6
7
8
9
10
11
12
dict.clear() #删除字典内所有元素
dict.copy() #返回一个字典的浅复制
dict.fromkeys(seq[, val]) #创建一个新字典,以序列 seq 中元素做字典的键,val 为字典所有键对应的初始值
dict.get(key, default=None) #返回指定键的值,如果值不在字典中返回default值
key in dict #如果key在字典中返回True, 否则返回False
dict.items() #以列表返回可遍历的(键,值)元组数组
dict.keys() #返回一个包含所有key的迭代器,可用list转换为列表。
dict.setdefault(key, default=None) #与get类似, 如果不存在, 则添加该键, 值为default对应值
dict.update(dict2) #把字典dict2的键/值对更新到dict中
dict.values() #返回一个包含所有values的迭代器, 可用list转换为列表。
pop(key[,default]) #删除字典给定键所对应的键值对,返回被删除的值
popitem() #随机返回并删除字典中的最后一对键和值。

更新或者添加键值对可以直接用[]进行,例如

1
2
3
4
tinydict = {'Name': 'Zara', 'Age': 7, 'Class': 'First'}

tinydict['Age'] = 8 # 更新
tinydict['School'] = "RUNOOB" # 添加

集合

集合中的元素是无序的,不重复的,一般就用来去重,用{}表示,初始化方法有以下几种。注意不能直接用{}初始化空集合,这是初始化空字典用的。

1
2
3
set = set()  # 创建空集合必须用这种方式
set = {1,2,3,4} # 可以用这种方式创建有初始值的集合
set = set(iterable) # 可以使用可迭代对象创建, 例如列表和元组、字符串

集合支持数学上的集合操作,包括交并补以及对称差,运算符分别为& | - ^

1
2
3
4
5
6
7
8
9
10
11
12
>>> a = set('abracadabra')
>>> b = set('alacazam')
>>> a
{'a', 'r', 'b', 'c', 'd'}
>>> a - b # 集合a中包含而集合b中不包含的元素
{'r', 'd', 'b'}
>>> a | b # 集合a或b中包含的所有元素
{'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'}
>>> a & b # 集合a和b中都包含了的元素
{'a', 'c'}
>>> a ^ b # 不同时包含于a和b的元素
{'r', 'd', 'b', 'm', 'z', 'l'}

in判断一个元素是否在元组中。由于集合一般用的比较少,其他操作不过多赘述。

numpy基本操作

下面的副标题都是我自己瞎编的,好分类整理内容,不是什么官方名称。pytorch的Tensor用法与ndarray大差不差,有部分函数的名称不同而已

创建

NumPy的主要对象是同构多维数组,即ndarray。在计算任务中需要使用矩阵或者向量运算时一般都使用numpy。创建np.array的常用方法有以下几种,其中的order参数指行优先和列优先,一般不用管

1
2
3
4
5
6
numpy.array(a, dtype = None, order = None) #从已有数组创建(a可以是列表),当a是ndarray时该方法会克隆一个新的数组
numpy.asarray(a, dtype = None, order = None) #从已有数组创建(a可以是列表),当a是ndarray时该方法不会克隆一个新的数组
numpy.empty(shape, dtype = float, order = 'C') #未初始化数组
numpy.zeros(shape, dtype = float, order = 'C') #全0数组:numpy
numpy.ones(shape, dtype = None, order = 'C') #全1数组
numpy.arange(start, stop, step, dtype) #数值范围数组

上面是比较常用的创建方法,除此之外还有以下会见到的生成数组的方法,

1
2
3
np.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None) #返回在[start,stop]区间内均匀分布的num个数字,endpoint表示返回数字中包含stop,retstep控制是否返回数字之间的间隔(公差)
np.logspace(start, stop, num=50, endpoint=True, base=10.0, dtype=None) #等比序列数组,start和end是指数位的数字,基数由base指定,即生成[base**start, base**stop]区间内的50个在对数尺度上返回间隔均匀的数字。
np.eye(N, M=None, k=0, dtype=, order='C') #对角数组,M不指定的话就是创建一个N*N类型的数组,k默认为0时将主对角线设为1,为正数时设置高对角线,为负数时设置低对角线

ndarray常用的属性包括ndimshapendim表示数组的秩,返回一个数,一维数组(向量)返回1,二维数组(矩阵)返回2,三维数组返回3,以此类推。shape返回数组的维度,是一个元组,元组的每一个元素代表一个轴(axis)。对于矩阵来说,就是几行几列,行是第0个轴,列是第1个轴。

切片与索引

numpy的在每个维度上的切片操作与列表的切片操作一致,即arr[start:end:step],不同维度之间用逗号隔开,某个维度上用省略号表示该维度上不操作。以二维数组为例

1
2
3
4
5
6
import numpy as np

a = np.array([[1,2,3],[3,4,5],[4,5,6]])
print (a[...,1]) # 第2列元素
print (a[1,...]) # 第2行元素
print (a[...,1:]) # 第2列及剩下的所有元素

numpy还提供一些语法糖,支持更加高级的索引,比如整数索引、布尔索引,可以取出特定的元素或者特定的行、列。名字起的花里胡哨,但是只要记住索引的基本用法(中括号表示索引,逗号分割维度)就能看懂,这里不过多介绍。

数组操作

numpy常见的数组操作有修改形状、修改维度、翻转、连接和分割,并且有很多函数可以实现这些功能,这里介绍一些常用的方法,下面的方法没有特殊说明的话都是不改变原始数组的,但是生成的新数组可能和原数组公用一块内存空间,即当执行下面的操作时

1
2
3
a = ap.array([1,2,3,4,5,6])
b = a.reshape((2,3))
b[0,0] = 3

a[0]的值也会改变,所以尽量不要去修改数值,如果要修改应该先查一下文档,或者试验一下。

方法是ndarray自带的方法,可以用arr.f()的方式调用,函数则是numpy某个模块函数,使用numpy.f(arr,...)的形式调用。一般而言,数组的方法都会对应一个模块函数,后面介绍的方法或函数也是,具体我也没有考证,我这里只介绍我用着顺手的形式。

修改形状可以使用数组的reshapeflatten方法

1
2
3
4
5
6
7
8
9
# arr.reshape(newshape, order='C') 
# 将形状调整为newshape新的形状
# 可以理解为先按照order将数组展平,然后按order调整为newshape。
# 新的形状应该和原有形状兼容,否则报错。
# 下面两种写法都是可以的,默认order='C'
arr.reshape(2,3)
arr.reshape((2,3))
#reshape可以接受-1作为参数,代表该轴的值由元素总数和其他轴的值计算出来
arr.flatten(order='C') #将数组展平为一维,默认按行展开,可以修改order按列展开

修改维度可以使用expand_dimssqueeze函数,注意这两个函数都不修改数据,只是单纯增加一个维度用来对齐数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
#expand_dims原型numpy.expand_dims(arr, axis)
#在指定位置插入新的轴来扩展数组形状
#只是插入一个轴,没有插入值
x = np.array(([1,2],[3,4]))
y = np.expand_dims(x, axis = 0)
#x的shape为(2,2),y的shape为 (1,2,2)

# squeeze原型numpy.squeeze(arr, axis=None),只能删除单维度
#删除非单维度会报错
# 默认删除所有单维度数据,可以通过指定axis删除指定单维度
x = np.arange(9).reshape(1,3,3)
y = np.squeeze(x)
#y的shape为(3,3)

翻转一般就用转置,可以使用数组的T方法或者np.transpose函数

1
2
3
#效果就是转置,注意T不用括号
np.transpose(arr)
arr.T

连接数组可以使用concatenatestack函数,用这两个函数不建议去看结果,容易绕进去,应该关注维度的变化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# concatenate原型numpy.concatenate((a1, a2, ...), axis=0)
# 沿指定轴连接相同形状的两个或多个数组
# 轴必须是所有数组都有的,即concatenate无法对数据进行升维
a = np.array([[1,2],[3,4]])
b = np.array([[5,6],[7,8]])
print (np.concatenate((a,b)))#按0轴合并,最终shape为(4,2),结果如下
#[[1 2]
#[3 4]
#[5 6]
#[7 8]]
#同理,如果指定按1轴合并,最终shape为(2,4)

#stack函数原型numpy.stack(arrays, axis=0)
#沿着新轴连接数组的序列,axis指定新轴
print (np.stack((a,b)))#新的轴的下标为0,最终shape为(2,2,2)
#第一个2是因为两个数组stack,后面两个2是a和b的shape,结果如下
#[[[1 2]
# [3 4]]
#
#[[5 6]
# [7 8]]]

分割数组可以使用切片或者split函数

1
2
3
4
5
6
7
8
9
10
11
# split原型为numpy.split(ary, indices_or_sections, axis=0)
# ary:被分割的数组
# indices_or_sections:如果是一个整数,就用该数平均切分,如果是一个数组,为沿轴切分的位置(左开右闭)
# axis:设置沿着哪个方向进行切分,默认为 0,横向切分,即水平方向。
# 一维的例子如下
a = np.arange(9)
b = np.split(a,3)
#[array([0, 1, 2]), array([3, 4, 5]), array([6, 7, 8])]
b = np.split(a,[4,7])
#[array([0, 1, 2]), array([3, 4, 5]), array([6, 7, 8])]
#多维情况吧数组当成元素看就行,无非是分方向

当然numpy数组也可以做一般的对单个元素增删查改,但是深度学习里面很少对单个元素操作,一般都是以矩阵的形式操作数据

统计操作

numpy求最值、求和、求平均、求中位数分别为np.amin np.amax np.sum np.average np.mean np.median,这些函数的用法可以归类为

1
np.f(a, axis=None, keepdims=<no value>)

a是待操作的数组

axis指定在哪一个轴上进行统计操作,为None是对整个数组a进行操作

keepdims,如果为True,将保持结果数组的维度数目与输入数组相同。如果为False(默认值),则会去除计算后维度为1的轴。

对于 np.average ,他还有个weights参数用以计算加权平均数

注意上面是我总结的用法,并不是说这几个函数的原型如此。他们都还有一些其他参数

numpy排序操作如下

1
2
3
4
5
6
7
8
9
10
# 原型sort(axis, kind='quicksort', order=None)
# axis是排序的轴,毕竟排序只能在一维上进行。kind指定排序类型,默认快排
# order指定排序的字段,需要数组具有字段,涉及到自定义dtype,自定义的dtype可以为每个数组的元素定义字段名,一般用不到
a = np.array([[3,7],[9,1]])
print (np.sort(a))
#[[3 7]
# [1 9]]
print (np.sort(a, axis = 0))
#[[3 1]
# [9 7]]

大多数时候我们不需要对数组进行排序,而是只需要知道最大值和最小值,可以使用np.argmax(a,axis=0)np.argmin(a,axis=0)函数,返回指定轴上最大(小)值的下标(数组)。

np.where函数可以统计满足特定条件的元素的下标。除此之外,np.where还可以用来打mask用法如下

1
2
3
4
5
6
7
8
9
10
11
12
#原型为np.where(condition, x, y),condition中需要包含一个待处理的ndarray(这里记为A),那么对于A中的每个元素,如果满足条件,则将这个元素替换为x,否则,将这个元素替换为y
#简单用法如下,统计x中大于3的元素下标
x = np.arange(9.).reshape(3, 3)
y = np.where(x > 3)
print(y)
# (array([1, 1, 2, 2, 2]), array([1, 2, 0, 1, 2]))
# 注意多维数组下标的返回方式,还是像前面说的,逗号分开不同维度
# np.where还可以使用后面两个参数替换不同元素,该操作常用于给图像打mask,此时返回的不是下标,而是和传入数组同shape的数组
A = np.array([1, 2, 3, 4, 5])
res = np.where(A > 3, 1, 0)
print(res)
#[0 0 0 1 1]

计算操作

numpy支持相同shape的数组进行+ - * / **运算,都是逐元素进行运算

numpy支持数组与标量进行+ - * / **运算,结果是标量与数组的每个元素进行运算

numpy中包含一些列数学函数,如三角函数、取整函数等,这些函数都是np.f(a) 形式作用于a的每一个元素

numpy还支持以下线性代数计算

函数描述
dot两个数组的点积,即元素对应相乘。
vdot两个向量的点积
inner两个数组的内积
matmul两个数组的矩阵积
linalg.det数组的行列式
linalg.solve求解线性矩阵方程
linalg.inv计算矩阵的乘法逆矩阵
  • Title: 快速上手深度学习项目(一)
  • Author: Yizumi Konata
  • Created at : 2024-07-13 12:05:26
  • Updated at : 2024-07-15 23:47:56
  • Link: https://zz12138zz.github.io/2024/07/13/dl1/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments