Task 01
线性回归
理论部分笔记
代码知识记录
torch.randn():产生一个标准正态分布的张量
np.random.normal():高斯分布,参数:(均值,标准差,shape)
torch.tensor():生成tensor的函数
display.set_matplotlib_formats('svg'):用来设置展示的时候使用的图像类型,这里是设置成“向量图”的类型
plt.rcParams['figure.figsize']:用来设置展示向量图的时候图像的大小
plt.scatter():画散点图的函数,参数:(x坐标,y坐标,点的大小)
list(range()):创建一个可用于循环的整数列表,其中range()返回的是一个可迭代对象
random.shuffle():将序列的所有元素随机排序
torch.LongTensor()是一种CPU张量,是64-bit integer(signed)的数据类型
使用了 yield 的函数被称为生成器,生成器是一个返回迭代器的函数。
-
pytorch中张量的函数index_select(dim, index):从张量的某个维度的指定位置选取数据。
参数dim:表示从第几维挑选数据,类型为int值
参数index:表示从第一个参数维度中的哪个位置挑选数据,类型为torch.Tensor类的实例
该函数的另一种使用方法:torch.index_select(张量,dim,index)
torch.zeros(shape,dtype):生成全0张量
所有的tensor都有.requires_grad属性,用于说明当前量是否需要在计算中保留对应的梯度信息,默认为False,要训练的参数以及它所在的梯度回传链上的其它参数需要保留梯度信息,即需要将该属性设为True。如果想改变该属性,就调用tensor.requires_grad_()方法。
torch.mm():矩阵相乘
torch.view(size):resize操作,改变张量的size
torch.item():得到一个元素张量里面的元素值
torch.manual_seed(种子):使用种子生成随机数,种子固定的话随机数就是固定的
torch.set_default_tensor_type(数据类型):用于改变tensor中数据的默认类型
torch.nn.Linear(每个输入样本的大小,每个输出样本的大小)
torch.nn.Sequential().一个时序容器。Modules会以他们传入的顺序被添加到容器中。当然,也可以传入一个OrderedDict。
init模块的初始化函数: init.normal_(参数,mean,std)
init.constant_(参数,值) 使用init模块初始化参数能够自动给参数附加梯度(不用手动添加了)nn.MSELoss():均方误差损失函数
随机梯度下降函数torch.optim.SGD(参数,lr,momentum=0,...)
Softmax与分类模型
理论部分笔记
代码知识记录
- torchvision包是服务于PyTorch深度学习框架的,主要用来构建计算机视觉模型。torchvision主要由以下几部分构成:
- torchvision.datasets: 一些加载数据的函数及常用的数据集接口;
- torchvision.models: 包含常用的模型结构(含预训练模型),例如AlexNet、VGG、ResNet等;
- torchvision.transforms: 常用的图片变换,例如裁剪、旋转等;
- torchvision.utils: 其他的一些有用的方法。
-
class torchvision.datasets.FashionMNIST(root, train=True, transform=None, target_transform=None, download=False)
root(string)– 数据集的根目录,其中存放processed/training.pt和processed/test.pt文件。
train(bool, 可选)– 如果设置为True,从training.pt创建数据集,否则从test.pt创建。
download(bool, 可选)– 如果设置为True,从互联网下载数据并放到root文件夹下。如果root目录下已经存在数据,不会再次下载。
transform(可被调用 , 可选)– 一种函数或变换,输入PIL图片,返回变换之后的数据。如:transforms.RandomCrop。
target_transform(可被调用 , 可选)– 一种函数或变换,输入目标,进行变换。
-
对多维Tensor按维度求和:
X = torch.tensor([[1, 2, 3], [4, 5, 6]]) print(X.sum(dim=0, keepdim=True)) # dim为0,按照相同的列求和,并在结果中保留列特征 print(X.sum(dim=1, keepdim=True)) # dim为1,按照相同的行求和,并在结果中保留行特征 print(X.sum(dim=0, keepdim=False)) # dim为0,按照相同的列求和,不在结果中保留列特征 print(X.sum(dim=1, keepdim=False)) # dim为1,按照相同的行求和,不在结果中保留行特征
运行结果:
tensor([[5, 7, 9]])
tensor([[ 6],
[15]])
tensor([5, 7, 9])
tensor([ 6, 15]) #虽然按行求和,但没保留行特征
-
math.exp()
:返回x的指数ex
-
numpy的广播机制:numpy中两个数组的加减乘除都是对应元素之间的操作,而当两个数组的形状并不相同时,比如其中一个shape是(1,),那么会自动将该数组的形状扩展至匹配与它做运算的数组
例:
A = numpy.array([1,2,3]) result = A + 100 print(result)
能得到结果[101 102 103],不需要自己去写A + [100, 100, 100]了。
-
torch.gather(input, dim, index, out=None)
: 沿给定轴dim,将输入索引张量index指定位置的值取出并进行进行聚合。或
input.gather(dim, index, ...)
例:
y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]]) y = torch.LongTensor([0, 2]) y_hat.gather(1, y.view(-1, 1)) #也就是y_hat.gather(1, torch.LongTensor([[0], [2]]))
dim=1,按行取。index为[[0], [2]],也就是说第0行取第0列的,第1行取第2列的
结果:
tensor([[0.1000],
[0.5000]]) torch.log()
:求自然对数torch.argmax(input, dim=None, keepdim=False)
:返回指定维度最大值的序号torch.nn.CrossEntropyLoss(weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean')
:求交叉熵torch.optim.SGD(params, lr=, momentum=0, dampening=0, weight_decay=0, nesterov=False)
:定义SGD优化器
多层感知机
代码知识记录
torch.arange(start=0, end, step=1,..., requires_grad=False)
:生成一个从start到end(不包含),步长为step的tensorx.relu():求tensor x的ReLU函数值:
x.sigmoid():求tensor x的Sigmoid函数值:
-
x.tanh():求tensor x的双曲正切函数值:
-
标量f对矩阵X的导数,定义为:
即f对X逐元素求导排成与X尺寸相同的矩阵。(矩阵和向量的求导法则:https://blog.csdn.net/jmh1996/article/details/85040660)
-
关于torch.backward()矩阵的说明:
设x是一个tensor,y是关于x的函数y(x)。
backward()默认是标量对某个量(标量、向量、矩阵)的求导。
如果y本身就是标量,那么若x的requires_grad属性设置为True,则y.backward()表示。这时候backward()函数中不用加参数。
如果y是向量或者矩阵,那么y.backward()表示的是将y先进行所有元素的“加权求和”转换成一个标量,再用这个标量对x求导。这时候就必须要在backward()函数中加入一个tensor类型的参数,而且这个参数的size必须和y的size一致,这个参数表示的是对y的各个元素进行“加权求和”时各个元素对应的权重。
另外要注意backward()会自动累加所求的梯度,所以有需要的时候要进行梯度清零操作x.grad.zero_()或者optimizer.zero_grad()
-
关于激活函数的选择:
ReLu函数是一个通用的激活函数,目前在大多数情况下使用。但是,ReLU函数只能在隐藏层中使用。
用于分类器时,sigmoid函数及其组合通常效果更好。由于梯度消失问题,有时要避免使用sigmoid和tanh函数。
在神经网络层数较多的时候,最好使用ReLu函数,ReLu函数比较简单计算量少,而sigmoid和tanh函数计算量大很多。
在选择激活函数的时候可以先选用ReLu函数如果效果不理想可以尝试其他激活函数。
- 多层感知机就是含有至少一个隐藏层的由全连接层组成的神经网络,且每个隐藏层的输出通过激活函数进行变换。多层感知机的层数和各隐藏层中隐藏单元个数都是超参数。
-
torch.max()
:torch.max(input):选出input中最大的元素
torch.max(input, dim, keepdim=False, out=None)
torch.max(input, other, out=None):将input和other做逐元素的比较,每个元素都返回较大的那个值。
Task 02
文本预处理
代码知识记录
-
文本数据的常见预处理步骤:
- 读入文本
- 分词
- 建立字典,将每个词映射到一个唯一的索引(index)
- 将文本从词的序列转换为索引的序列,方便输入模型
-
with语句打开文件:
with open(文件路径, 打开方式) as f:
-
每次读入文件一行的方法:
法1:
while True: line = f.readline() if line: pass # do something here else: break
法2:
for line in f: pass # do something here
- 读入整个文件:f.read()
-
re.sub(pattern, repl, string, count=0, flags=0)
:通过正则表达式实现替换字符串中的匹配项(简单的替换功能可以用普通字符串的replace()函数实现)pattern:表示正则表达式中的模式字符串;
repl:被替换的字符串(既可以是字符串,也可以是函数)
string:: 要被查找替换的原始字符串
count:模式匹配后替换的最大次数,默认 0 表示替换所有的匹配
flags:编译时用的匹配模式,数字形式
正则表达式模式:
- ^:匹配字符串的开头
- $:匹配字符串的末尾
- [...]:用来表示一组字符,单独列出:[amk]匹配'a','m'或'k'
- [^...]:不在[]中的字符:[ ^abc]:匹配除了a,b,c之外的字符
- re+:匹配1个或多个的表达式。
- [a-z]:匹配任何小写字母
- [0-9]:匹配任何数字
例:
line = "asq3w7452e1"
print(re.sub('[^a-z]+','*',line))
#输出为:asq*w*e*
(更多正则表达式模式详见https://www.runoob.com/python3/python3-reg-expressions.html)
-
str.split(str="", num=string.count(str))
: 通过指定分隔符对字符串进行切片,如果参数 num 有指定值,则分隔 num+1 个子字符串。分隔符默认为所有的空字符,包括空格、换行(\n)、制表符(\t)等。
- collections.Counter(列表或者字符串等):可返回对象的Counter字典,其中<key, value>: <列表中的元素,元素出现的频率>
-
enumerate(sequence, [start=0])
:用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。
-
isinstance(object, classinfo)
:判断一个对象是否是一个已知的类型,类似 type()。它与type()的区别:
- type() 不会认为子类是一种父类类型,不考虑继承关系。
- isinstance() 会认为子类是一种父类类型,考虑继承关系。
-
有一些现有的工具可以很好地进行分词,例如:spaCy和NLTK
用法:
text = "Mr. Chen doesn't agree with my suggestion." #spaCy: import spacy nlp = spacy.load('en_core_web_sm') doc = nlp(text) print([token.text for token in doc]) #NLTK: from nltk.tokenize import word_tokenize from nltk import data data.path.append('/home/kesci/input/nltk_data3784/nltk_data') print(word_tokenize(text)) #输出结果均为:['Mr.', 'Chen', 'does', "n't", 'agree', 'with', 'my', 'suggestion', '.']
语言模型
理论部分笔记
代码知识记录
- 集合(set)是一个无序的不重复元素序列。可以使用大括号 { } 或者 set() 函数创建集合,注意:创建一个空集合必须用 set()。
时序数据的采样
假设语言数据集为:
想要有直升机,想要和你飞到宇宙去。想要和你融化在一起,融化在宇宙里……
在训练中我们需要每次随机读取小批量样本和标签。与之前章节的实验数据不同的是,时序数据的一个样本通常包含连续的字符。假设时间步数为5,样本序列为5个字符,即“想”“要”“有”“直”“升”。该样本的标签序列为这些字符分别在训练集中的下一个字符,即“要”“有”“直”“升”“机”,即=“想要有直升”,=“要有直升机”。
现在我们考虑序列“想要有直升机,想要和你飞到宇宙去”,如果时间步数为5,有以下可能的样本和标签:
- :“想要有直升”,:“要有直升机”
- :“要有直升机”,:“有直升机,”
- :“有直升机,”,:“直升机,想”
- ...
- :“要和你飞到”,:“和你飞到宇”
- :“和你飞到宇”,:“你飞到宇宙”
- :“你飞到宇宙”,:“飞到宇宙去”
可以看到,如果序列的长度为,时间步数为,那么一共有个合法的样本,但是这些样本有大量的重合,我们通常采用更加高效的采样方式。我们有两种方式对时序数据进行采样,分别是随机采样和相邻采样。
随机采样
设批量大小batch_size
是每个小批量的样本数,num_steps
是每个样本所包含的时间步数。
过程:将语料库的字符数减1整除时间步数,得到的就是样本数量。将样本之间的顺序打乱,然后每个batch取batch_size个样本。
代码:
import torch
import random
def data_iter_random(corpus_indices, batch_size, num_steps, device=None):
#corpus_indices是语料库的字符数,device用来控制最后返回的批量要放到什么设备上
# 减1是因为对于长度为n的序列,X最多只有包含其中的前n - 1个字符
num_examples = (len(corpus_indices) - 1) // num_steps # 下取整,得到不重叠情况下的样本个数
example_indices = [i * num_steps for i in range(num_examples)] # 每个样本的第一个字符在corpus_indices中的下标
random.shuffle(example_indices)
def _data(i):
# 返回从i开始的长为num_steps的序列
return corpus_indices[i: i + num_steps]
if device is None:
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
for i in range(0, num_examples, batch_size):
# 每次选出batch_size个随机样本
batch_indices = example_indices[i: i + batch_size] # 当前batch的各个样本的首字符的下标
X = [_data(j) for j in batch_indices]
Y = [_data(j + 1) for j in batch_indices]
yield torch.tensor(X, device=device), torch.tensor(Y, device=device)
相邻采样
相邻的两个随机小批量在原始序列上的位置相毗邻。
代码:
def data_iter_consecutive(corpus_indices, batch_size, num_steps, device=None):
if device is None:
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
corpus_len = len(corpus_indices) // batch_size * batch_size # 保留下来的序列的长度
corpus_indices = corpus_indices[: corpus_len] # 仅保留前corpus_len个字符
indices = torch.tensor(corpus_indices, device=device)
indices = indices.view(batch_size, -1) # resize成(batch_size, )
batch_num = (indices.shape[1] - 1) // num_steps
for i in range(batch_num):
i = i * num_steps
X = indices[:, i: i + num_steps]
Y = indices[:, i + 1: i + num_steps + 1]
yield X, Y
循环神经网络基础
理论知识笔记
代码知识记录
-
scatter_(input, dim, index, src) → Tensor
将src中的所有值按照index确定的索引写入本tensor中。其中索引是根据给定的dimension,dim按照
gather()
描述的规则来确定。注意,index的值必须是在0到(self.size(dim)-1)之间,
参数:input (Tensor)-源tensor;dim (int)-索引的轴向;index (LongTensor)-散射元素的索引指数;src (Tensor or float)-散射的源元素
例:
>>> x = torch.rand(2, 5) >>> x 0.4319 0.6500 0.4080 0.8760 0.2355 0.2609 0.4711 0.8486 0.8573 0.1029 [torch.FloatTensor of size 2x5] >>> torch.zeros(3, 5).scatter_(0, torch.LongTensor([[0, 1, 2, 0, 0], [2, 0, 0, 1, 2]]), x) 0.4319 0.4711 0.8486 0.8760 0.2355 0.0000 0.6500 0.0000 0.8573 0.0000 0.2609 0.0000 0.4080 0.0000 0.1029 [torch.FloatTensor of size 3x5] >>> z = torch.zeros(2, 4).scatter_(1, torch.LongTensor([[2], [3]]), 1.23) >>> z 0.0000 0.0000 1.2300 0.0000 0.0000 0.0000 0.0000 1.2300 [torch.FloatTensor of size 2x4]
torch.detach()
:将一个变量从创建它的计算图中分离,并把它设置成叶子结点变量。-
如果模型采用随机采样,那么在每个小批量更新前初始化隐藏状态。
如果模型采用相邻采样,那么在每个epoch开始时初始化隐藏状态即可。但是在每个小批量更新前,需要使用detach函数从计算图分离隐藏状态。
原因:在训练过程中,同个epoch随着batch的增大,模型的损失函数关于隐层变量的梯度传播的更远,计算开销也更大。为减小计算开销,一般在每个batch开始的时候把隐层状态从计算图中分离出来。 torch.cat(inputs, dimension=0) → Tensor
:在给定维度上对输入的张量序列进行连接操作。-
class torch.nn.RNN
之中的几个构造函数参数:- input_size - 输入x的特征数量。
- hidden_size – 隐层的特征数量。
- num_layers – RNN的层数。
- nonlinearity – 指定非线性激活函数使用tanh还是relu。默认是tanh。
- batch_first – 如果True的话,那么输入Tensor的shape应该是[batch_size, time_step, feature],输出也是这样。默认是False。
使用默认的参数False对应的输入形状是 (num_steps, batch_size, input_size)。
torch.stack(sequence, dim=0)
:沿着一个新维度对输入张量序列进行连接。 序列中所有的张量都应该为相同形状。