CV 01 CNN MNIST识别
前言本文将通过CNN 让计算机识别MNIST数据集中的手写数字 以此来介绍Pytorch的基本使用方法:
Pytorch中的数据类型——tensor
Pytorch中的数据集、数据加载器——Dataset、DataLoader
Pytorch中的基础类模型——torch.nn.Module
以及程序设计上的一些小技巧。
1. tensor1.1 概念tensor一词译为张量,一般我们所接触的矩阵是二维的,称为二阶张量、向量称为一阶张量、标量称为零阶张量。接下来我们通过Numpy库了解一下张量。(这里并非数学上严格的定义,感性理解一下即可)
1.1.1 二阶张量 矩阵123456789101112131415161718192021222324# 首先我们举一个三行八列的矩阵import numpy as npa = np.arange(1,25).reshape(3,8)'''array([[ 1, 2, 3, 4, 5, 6, 7, 8], [ 9, 10, 11, 12, 13, 14, 15, 16], [17, 18, 19, 20, 21, 22, 23, 24]])'''b = np.ones_like(a).T''' array([[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1]])''' # 我们创建以上两个矩阵,接下来我们把他们做点乘a@b''' array([[ 36, 36, 36], [100, 100, 100], [164, 164, 164]]) '''
上面我们创建了两个矩阵a为三行八列,b为八行三列,两者做点积得到一个三行三列的矩阵。
我们拉到列表的角度解释这个矩阵,我们将所有数据都包含在一个大列表之内,大列表里有三个小列表,每个列表内有八个元素,
即三个小列表代表三行,每个列表的八个元素代表八个维度。
这里举个小栗子帮助理解一下维度:
我们在三年级二班给各位同学做信息登记,每个人所需要填写【姓名、年龄、身高、体重】四项内容,我们把每个人的信息记为一条数据,那么我们就可以说这条数据有四个特征,它的维度为四。
通常来讲我们把特征记为feature,称这条数据有四个特征。
现在整个班级的信息都填写好了应该是如何的形状,我们假设有32个人:
【【张三、7、130、 70】
【李四、7、131、 71】
。。。
【李小明、7、129、70】】 如何我们得到一个32行4列的矩阵,记为(32,4)
接下来我们把视角拉倒整个三年级,我们假设有7个班级:
那么我们得到的数据维度应该是(7,32,4),表示我们有7个班级的数据,每个班级的数据维度(32,4)。
此后我们都称这个“班级为batch“ ,至此我们从二维的矩阵上升到三维的张量。
1.1.2 张量下面我们将介绍常用的张量形式
1234567891011121314151617181920212223242526272829303132333435363738394041424344# 首先介绍下一张图片通常的数据格式,我们使用Numpy来伪造一下数据np.random.randn(1,3,28,28)'''array([[[[-1.03301822, -0.26956785, -1.81246034, ..., -0.2025034 , -0.24770628, 0.45183312], [ 1.09102807, 0.92955389, 0.07537901, ..., 0.69203358, -0.17726632, -0.74610015], [ 0.40508712, -1.2507095 , 0.68702445, ..., -0.12526432, 0.0390443 , 1.00993313], ..., [ 1.96843042, -2.43286678, 0.08543089, ..., -1.57232148, 0.92844287, -0.25532137], [ 0.46919141, -0.13700029, 1.78645959, ..., 0.01334257, 1.31030895, -0.22523819], [ 0.63897933, 0.54846445, -0.64030391, ..., 0.92298892, -0.50840421, 1.34232325]], [[-0.01892086, 0.1456131 , -0.08903806, ..., 1.68250139, 1.2097305 , -0.2680935 ], [ 0.92759263, 0.22665021, 1.28734004, ..., 0.09925943, 1.30039407, 3.34710594], [ 0.53486942, -0.56230181, -1.92117215, ..., 1.33047469, -1.19211895, -0.03081918], ..., [ 0.2539067 , -2.13160564, 0.27519544, ..., -0.62223126, 0.5818296 , 0.07102949], [-0.7524386 , -0.71244818, 0.88997093, ..., 0.16566338, 0.80577231, -3.35350436], [ 0.99558393, -2.32335969, -2.87512549, ..., 1.16290939, 2.24089232, 0.22083378]], [[ 1.35970859, 0.7961136 , 0.09896652, ..., 1.82609401, -0.49607535, 0.23424012], [-0.22283053, -1.35535905, -0.55896315, ..., 1.68093489, 0.80969216, 0.63538616], [-0.88285682, 0.59389887, -1.05559301, ..., -0.00719476, -0.25654492, -1.40716977], ..., [ 0.44508688, -0.05650302, -2.97674436, ..., 1.25730001, -1.66409024, 0.96057644], [-1.3237267 , -0.27798159, -1.8947621 , ..., 1.96216661, -0.10569547, -0.8446272 ], [ 0.22525617, 0.75040916, 0.72823974, ..., -1.93525763, -0.74464397, 0.55771249]]]]) '''
上面就是一张图片**W(width)为28,H(heigh)**为28,有RGB三个通道,batch为1的图片(这个batch里面只有一张图片)的数据表示形式。
当然上面的数据太过复杂,我们以下面W和H都为4的数据继续讲解一下各个数据的意义。
123456789101112131415np.random.randn(1,3,4,4)'''array([[[[ 0.43748082, -0.65000689, 0.13972451, -0.40213376], [ 0.09342289, -0.83655856, 0.51844492, 0.96505144], [ 0.68421876, 1.05527391, -0.30821748, -1.89826909], [-0.36654524, 0.22642376, 0.16545107, 0.00401234]], [[-0.13032482, 0.68182741, -0.52511016, 0.75875314], [-1.39072336, -0.22848391, -1.64733525, 0.3339502 ], [-1.06568103, -0.58455172, -0.02874822, -0.64499225], [-0.23380602, -0.74809941, -0.71214339, -0.44950305]], [[-1.51112191, 0.49145194, -0.01839728, -1.52788219], [ 0.93370593, 0.96444176, -0.67434299, -1.8492484 ], [ 0.51140855, -0.58682968, -1.16261225, -0.65782238], [ 0.8643421 , 0.79983446, -0.92330871, -2.45649675]]]]) '''
一号位batch=1表示只有一张图片
若batch=3,我们下面降到的模型依次取本批次内【0】号(3,4,4)、【1】(3,4,4)、【2】(3,4,4)进行处理
二号位Channel=3 表示有三个通道分别是RGB
如上面 0.43748082….0.00401234,就表示在R通道内,这张图片的颜色数据
G和B通道同理,让三者叠加就可以表示颜色的明暗,从而勾勒画面,渲染颜色
最后的两位就表示长宽,每个元素表示像素点的明暗程度。如R通道的第一个元素0.43748082就表示这个像素点有多红
(红也有程度对吧)
1.2 Pytorch中tensor的APIPytorch中tensor号称是跟Numpy及其相似的操作方式,有Numpy的学习基础的话几乎不用付出学习成本来适应tensor。但是实际情况就经常出现各种警告。无论如何,tensor可以享受到GPU的加速运算,总的来说也够友好,下面我们将介绍其常用的API
首先是随机函数,基本跟Numpy是一样的。
12345678910111213141516rand_tensor = torch.rand(shape)ones_tensor = torch.ones(shape)zeros_tensor = torch.zeros(shape)'''Random Tensor: tensor([[0.7453, 0.7993, 0.8484], [0.3592, 0.3243, 0.7226]])Ones Tensor: tensor([[1., 1., 1.], [1., 1., 1.]])Zeros Tensor: tensor([[0., 0., 0.], [0., 0., 0.]]) '''
接下来介绍tensor对象的一些属性,其中device默认就是使用cpu,表示我们数据在cpu上。
1234567891011tensor = torch.rand(3,4)'''tensor([[0.8165, 0.1909, 0.6631, 0.3062], [0.0178, 0.5158, 0.0267, 0.9819], [0.6103, 0.7354, 0.7933, 0.2770]]) '''tensor.shape # 将返回 torch.Size([3, 4])tensor.dtype # 将返回 torch.float32tensor.device # 将返回 cpudevice = 'cuda' if torch.cuda.is_available() else 'cpu' # 这句话经常来指定数据处理的设备
torch.cat也是一个常用的函数,用来链接数据。
下面以第一行为例,cat函数将[0.8165, 0.1909, 0.6631, 0.3062]与[0.8165, 0.1909, 0.6631, 0.3062]、[0.8165, 0.1909, 0.6631, 0.3062,]连接,这是因为dim=1表示在第一维度,其视角内的可操作单位为0.8165, 0.1909, 0.6631, 0.3062这些元素,dim=0则可操作的基本单位为tensor(这里的tensor表示上面的三行四列的实例张量)
123456789torch.cat([tensor, tensor, tensor], dim=1)'''tensor([[0.8165, 0.1909, 0.6631, 0.3062, 0.8165, 0.1909, 0.6631, 0.3062, 0.8165, 0.1909, 0.6631, 0.3062], [0.0178, 0.5158, 0.0267, 0.9819, 0.0178, 0.5158, 0.0267, 0.9819, 0.0178, 0.5158, 0.0267, 0.9819], [0.6103, 0.7354, 0.7933, 0.2770, 0.6103, 0.7354, 0.7933, 0.2770, 0.6103, 0.7354, 0.7933, 0.2770]]) '''
另外介绍一下常用的类型转换
123456t = torch.rand(3,4)n = np.random.randn(3,4)t.to_list() # 将tensor类型转换为listt.numpy() # 转换为Numpy类型torch.from_numpy(n) # 从Numpy转换为tensor
最后最常用的就是下面两句
1234data = list(range(1,10))torch.tensor(data) # 返回tensor([1, 2, 3, 4, 5, 6, 7, 8, 9])可使用dtype=torch.float32换成浮点数torch.Tensor(data) # 返回 tensor([1., 2., 3., 4., 5., 6., 7., 8., 9.])
2. Dataset DataLoader 都说数据科学家一般的时间都花在数据处理上,一点不假。前面花了这么大篇幅讲价tensor,接下来我们将介绍Pytorch中存储数据的
Dataset和数据加载器DataLoader
2.1 你的数据类虽然我们使用的MNIST数据集已经可以直接通过Pytorch的API调用,如下
from torchvision import datasets
datasets.MNIST(root='../dataset/mnist/', train=True, download=True, transform=transform)
root表示存储或者加载数据的路径
train表示是否只加载训练部分的数据集,不设定默认加载全部数据集
download字面意思
transform指代这批数据使用什么转换形式,一般来说是一种数据增强方式,以后会专门介绍
我们还是来具体解释下通常要自定义使用的dataset。
定义符合你要求的数据集有三步必须操作:
定义你自己的数据集类并继承自torch.utils.data.Dataset
需要包含__len__方法返回长度
需要包含__getitem__方法,按照下标取得数据
以上配置也都是为了配合DataLoader的使用,下面我们定义一个dataset类
1234567891011121314151617181920from torch.utils.data import Datasetclass TitanicDataSets(Dataset): def __init__(self,flag): xy = preprocess(pd.read_csv("Titanic.csv"), flag="train") if flag == "train": self.x_data = xy.iloc[:, :-1][:800] self.y_data = xy.iloc[:, -1][:800] self.len = self.x_data.shape[0] if flag == "valid": self.x_data = xy[0][800:892] self.y_data = xy[1][800:892] self.len = self.x_data.shape[0] def __getitem__(self, index): return self.x_data[index], self.y_data[index] def __len__(self): return self.len
上面我们引入kaggle著名的泰坦尼克号幸存者预测比赛使用的数据集,其中x_data获得的是前八百行乘客的信息,y_data记录的就是是否存活。
一般来说我们也将数据集分为train和valid两部分,因为最后我们需要预测的数据集并不会有是否存活的标签,所以通过训练模型参数以拟合train部分的数据,以valid为本次训练的结果导向以修正模型参数,最终预测,就是我们的目的。
如上面所示,Python中的语句就是这么简洁明了,我们在初始部分读取数据集,然后根据传入的flag决定是处理train还是valid的部分数据,最后我们赋予这个类像列表那样的获取下标和切片能力(__getitem__方法)、以及返回长度的能力(__len__方法)
2.2 数据加载器Pytorch的数据加载器DataLoader简单易用,下面介绍它部分常用参数。
12train_dataset = TitanicDataSets(flag="train")train_loader = DataLoader(dataset=train_dataset, batch_size=32, shuffle=True, num_workers=2)
dataset 表示它所处理的数据,一般是你定义的dataset类,或者具有下标取值,和返回长度的数据类型也可以
batch_size 表示一词传给模型多少条数据
shuffle 表示是否打乱
num_workers 表示使用你cpu的几个核进行读取
可以使用下面的语句查看dataloader返回给你的数据形状
12samples = next(iter(train_loader))samples[:2] # 查看本批次(batch)的前两个样本([0]号,[1]号)
3. 模型对于模型,以我的理解,数据虽然是死的,但是理解它的方式是活的;模型是活的,但是组合它的方式并没有那么灵活。这里之所以说是组合,说点题外话,是因为如今预训练模型大行其道,大模型在各个任务上不断刷新纪录(SOTA),小型机构很难有力量去训练这种大模型,于是在大模型上修修改改以适应下游任务的方式,只能使用这种像是Transformer的方式不断变形组合,总感觉缺了点活力。(奠定Pre-train的Bert就是在Transformer基础上提出来的)。
3.1 模型定义下面开始定义我们的CNN模型
123456789101112131415161718class Net(torch.nn.Module): def __init__(self): super().__init__() self.conv1 = torch.nn.Conv2d(1, 10, kernel_size=3, padding=1) self.conv2 = torch.nn.Conv2d(10, 20, kernel_size=3, padding=1) self.pooling = torch.nn.MaxPool2d(2) self.fc = torch.nn.Linear(320, 10) def forward(self, x): # flatten dat ...
Python基础01 数据类型
前言本文介绍Python中基本的数据类型:
字符串、数字
列表
字典
集合
元组
以及一些常用的处理小技巧。
1. 字符串、数字Python中字符串(str)的处理对于没有任何变成经验的同学可能有些苦恼,如下
1234s1 = '1222'i = 1222add_up = s1 + i # 这段函数就会报错,因为无法将 int类型 与 str类型相加
int :整数类型,将小数抹除
float :浮点数类型,因为二进制进位的关系数据并不准确的
以上就是提醒各位,对数据处理的时候,一定要留心数据的类型
1.1 字符串中的序号字符串的序号可以让你快速取得一串字符中任意位置的任意字符,有如下三种基本方式:
123456789101112131415s = 'Attention Is A Talent' # 首先创建一个字符串# 第一种 取单个字符s[0] # 将获得 'A's[20] # 将获得 't' # 第二种 取多个字符s[0:2] # 将获得 'At' s[:20] # 将获得 'Attention Is A Talen's[::2] # 将获得 'AtninI aet's[::1] # 将获得 'Attention Is A Talent'# 第三种 倒序s[-1] # 将获得 't' s[-21:] # 将获得 'Attention Is A Talent'
有如下需要注意的几个地方:
在python中我们使用引号包裹需要的字符串内容
字符串的序号是从 0 开始定义的,所以上面的s有21个字符,而方括号内的取值范围是[0,20], 细心你的肯定发现了,空格数也算进去了。没错空格也算一种特殊的字符。
第二种取值方式我们称之为切片,python中的切片方式等价数学上的**左闭右开~[x, y)**,上面字符s[:20],是取得s[20]号位左边的全部值,但是不会包含s[20]。
当然是用s[0:20] 也是等价的。
步长 即第二种方法的第三个式子,是用步长就是字面意思,每走n步取值。
s[::2]就是 s[0]第一步, s[1]第二步(存储),s[2]第三步,s[3]第四步(存储)….
倒序是字符串的另一套序号,它有很多应用场景,比如定义一个很长的字符串你可能需要用s[1222222]才能取得这个值,但是是用s[-1]就很方便。
当然,需要注意倒序是从[-1]开始的。
1.2 特殊字符'\n'(换行) '\b'(回退) '\r'(光标回到本行行首) '\t'(相当于八个空格,两个table)
以上就是几个常见的特殊字符,其中特别需要注意的是路径中的斜杠如遇到 \nigger 计算机可能就无法明白你输入的是 ‘igger’,还是含有n的字符串。有以下两种处理方式:
'\\nigger'
r'\nigger'
1.3 字符串的运算以及常用函数运算
123s1 + s2 # 将两个字符串连接s1*n # 将s1复制n次s1 in s2 # 如果s1是s2的字串 则返回 True 否 False
函数
1234567891011121314151617181920212223242526s = ' Attention,Is,A,Talent '# split函数s.split(',') # 输出为 [' Attention', 'Is', 'A', 'Talent '] # split函数以逗号为标志,返回一个分隔后的列表# count函数 s.count('A') # 统计A在s中的次数,本例中将返回int类型的2# upper,lower函数s.upper() / s1.lower() # 将字符串转化为对应的大小写# replace函数s.replace('tion', '') # 将字符串中的'tion'替换为'',即没有东西,相当于删除# center函数s.center(30, '=') # 将s放在中间,左右两侧平均填充等于号至总字符数为30# strip函数s.strip(' ') # s两侧删除空格,以及其他不可读符号如'\n'# join函数','.join(s) # s中每个字符间填充逗号 # 返回 'A,t,t,e,n,t,i,o,n,,,I,s,,,A,,,T,a,l,e,n,t'# len函数len(s) # 返回s的长度
上面我们以逗号为分隔符使用split函数,但是注意中英文的逗号是有区别的,其他有些符号也一样,需要注意。
上述中的replace几乎可以代替center函数,但是注意strip只能处理字符串的两端
如join函数返回的结果,再次提醒空格也算是字符
最后,上述操作产生都是一个新的对象,即调用s后返回的是原本的字符串,并不是函数作用后的结果。需要s = s.replace('tion', '') 赋值才‘生效’。
1.4 数字函数12345678910pow(x, n) #为x的n次方divmod(10, 3) # 输出为(3, 1)abs() #返回值为绝对值int(12.34) #输出 12float(12), float('12.23') #输出为 12.0 和 12.23round(1.2345, 2) #保留两位小数max(1, 2, 3) #返回值为3min(1, 2, 3) #返回值为1
1.5 格式化字符串在我们得到一个数据之后,经常需要对其做保留多少位小数、居中打印、靠右输出、等操作,我们一般叫使其格式化。在python中有三种格式化填充字符串的方式:
12345# format方式a, b, c = 'Is', 'Talent', 0.12222'Attention {a} A {b} version {:.2f}'.format(a, b, c)# 我们将得到如下输出 'Attention Is A Talent version0.12'
format格式化将按照顺序填入上面字符串{}的空位,{:.2f}表示此处保留两位小数
12345# f方式a, b, c = 'Is', 'Talent', 0.12222f'Attention {a} A {b} version {c:.2f}'# 我们将得到如下输出 'Attention Is A Talent version0.12'
f格式化就是对format方式的简化版
12345# %方式a, b, c = 'Is', 'Talent''Attention %s A %s '% ('Is', 'Talent')# 我们将得到如下输出 'Attention Is A Talent '
这种方式很老了,推荐使用f方式,非常简洁。
以下不是必看内容:format填充方式
12chr(Unicode) #返回Unicode对应的字符ord('字') #返回对应的编码,如chr(ord('a')+i ) 即可遍历26字母
填充物若为chr(12222),等特殊字符
12345678f'{chr(12222):^10}'# 输出为 ' ⾾ 'f''{chr(12222):=^10}''====⾾====='f''{chr(12222):=>10}''=========⾾'
如上{chr(12222):^10} 将chr(12222)对应的字符输出在中间,左右两侧填充空格。
也可用等号等其他符号填充,或者使用>大于号使结果置右。
2. 列表列表跟上文中提到的字符串很像,或者说字符串是一种特殊的列表,其所有元素都是字符串。
python中的列表(list),在我的印象里几乎可以装任何的东西: 字符串、数字、甚至你定义的函数…
列表是一种非常好用的数据类型,也是我们最常使用数据类型,以下概要对其简要介绍并补充几个判断符。
2.1 概要列表同字符串也有正反序号下标,切片操作,不同的是列表可以含有各种类型的数据
12345ls = ['Attention Is A Talent', 100, '% ']ls[0] == ls[-3] # 返回True'A' in ls # 返回Truestr(ls[1]) + ls[2] + ls[0] # '100% Attention Is A Talent'
上面我们使用双等号作为判断符,等价询问python 是否 ls[0] = ls[-3]
还有 != 、>=、<=、等
上面我们使用in作为判断词,等价询问python 是否 ‘A’ 在 ls
还有 not in,or,and等
第二点,我们使用了str(),它是一个函数,将对传给它的值做字符串化的处理
int类型的 100 ——> ‘100’ 即数字100变成字符串了
2.2 列表的运算以及常用函数运算
12345ls = ['Attention Is A Talent', 100, '% ']ls * 2 # 将返回 ['Attention Is A Talent', 100, '%', 'Attention Is A Talent', 100, '%']ls + ls[:1] # 将返回 ['Attention Is A Talent', 100, '%', 'Attention Is A Talent']
注意,列表的加法操作只能在列表跟列表之间。
如上图使用 ls + ls[0] 将会报错
函数
12345678# 下面x表示单个元素、ls表示列表0号、ls1表示列表1号ls.append(x) # 给ls尾添加x元素ls.remove(x) # 将ls中出现的第一个x删除,如要删除所有x可以用(while+flag)或者set集合类型除重ls.extend(ls1) # 将ls后面连接ls1ls.reverse() # 将列表的元素逆置ls.insert(i,x) # 在i位置 插入xls.pop(i) # i位置元素出栈,删除
(这里加些列表复杂一点的方法,如果没有了解python中的字典、元组数据类型,可以在下文中了解后再看)
12345678910111213141516ls = [('tom', 95), ('jerry', 80), ('mike', 99), ('john', 70)]ls.sort()ls.sort(reverse = ture) # 逆排序ls.sort(key= lambda x:x[1]) # 按值排序 -------------------------------sorted(ls) # 会自动把序列从小到大排序sorted(ls, reverse = true)sorted(ls, lambda x:x[0]) # 以lambda函数作为值排序-------------------------------seasons = ['Spring', 'Summer', 'Fall', 'Winter'] # enumerate()的对象必须是可以迭代的类型(iterable)list(enumerate(seasons))[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]list(enumerate(seasons, start=1))[(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]
第一部分中为ls的sort方法配置参数
reverse 表示逆置,如果对象没有‘大小’,则按照原来的顺序直接逆置
key 参数表示排序根据此值的大小,这里我们就是以每个元组的第二个值作为value排序
第二部分是使用python中的sorted和sort函数
第一个参数表示传入的可迭代数据类型(就是列表这种含有很多元素,可以一个一个出来的数据类型)
第二个参数 同上面的key
sort 与 sorted 区别:
sort 是应用在 list 上的方法,sorted 可以对所有可迭代的对象进行排序操作。
list 的 sort 方法返回的是对已经存在的列表进行操作,无返回值,而内建函数 sorted 方法返回的是一个新的 list,而不是在原来的基础上进行的操作。
第三部分使用了enumerate函数,这个函数主要是为元素添加下标,方便一些特殊场景处理
enumerate函数返回一个含有位置下标的元组类型,为(index,element)形式
2.3 列表应用的例子123456789ls = ['Alice', 'Bob']list(lt) = ls[0] # [0]号为字符,导入list中会分割成['a', 'l', 'i', 'c', 'e']------------------------------------ls = ['Ali:ce', 'Bo:b']lt = []for i in ls: elem = i.split(':')[-1] # ls[i]为字符串使用split分割->['Ali','ce']取[-1] lt.append(elem) # 直接+=会变成['c','e'],所以使用list的append,则直接将str的ce加入 # lt = [ce, b] 干净的字符串列表
3. 字典Python字典(dict)是另一种可变容器模型,可存储任意类型对象。如字符串、数字、元组等其他容器模型因为字典是无序的所以不支持索引和切片
注意:
key不可以重复,否则只会保留第一个;
value值可以重复;
key可以是任意的数据类型,但不能出现可变的数据类型,保证key唯一;
key一般形式为字符串。
3.1 基本属性123dic.keys() # 返回字典中所有的keydic.values() # 返回包含value的列表dic.items() # 返回包含(键值,实值)元组的列表
3.2 基本函数123456789101112dic.setdefault(k,value)#如果key值存在,那么返回对应字典的value,不会用到自己设置的value;#如果key值不存在.返回None,并且把新设置的key和value保存在字典中;#如果key值不存在,但设置了value,则返回设置的value;dic.get(k,value)#如果key值存在,那么返回对应字典的value,不会用到自己设置的value;#如果key值不存在.返回None,但是不会把新设置的key和value保存在字典中;#如果key值不存在,但设置了value,则返回设置的value;dic.items()#打印字典中的所有元组
以下不是必看内容:dic.get(key, init_value)
123456789ls = [('tom', 95), ('tom', 95), ('tom', 95), ('jerry', 80), ('mike', 99), ('john', 70)]ls1,dic = [], {}for i, j in ls: ls1.append(i)for i in ls1: dic[i] = dic.get(i, 0) + 1 dic
get第一个参数为对应的键,第二个参数为键对应的初始值。
get方法将键对应的值初始化为0,以后每见一次加一次,在文本统计时经常使用。
相对于dic[i],dic.get(i)不会报错,而它每见一次加一次而不是覆盖,明显速度会比前者慢。(但是不是大量数据都差不多。)
4.集合在Python中集合(set)元素之间无序,每个元素唯一,不存在相同元素集合元素不可更改,不能是可变数据类型
4.1 创建和运算1234567使用两种方式建立A = {"python", 123, ("python",123)} # 使用{}建立集合 输出为{123, 'python', ('python', 123)}B = set("pypy123") # 使用set()建立集合 输出为{'1', 'p', '2', '3', 'y'}C = {"python", 123, "python",123} # 去重 输出为{'python', 123}
123456S | T 并,返回一个新集合,包括在集合S和T中的所有元素 S - T 差,返回一个新集合,包括在集合S但不在T中的元素 S & T 交,返回一个新集合,包括同时在集合S和T中的元素 S ^ T 补,返回一个新集合,包括集合S和T中的非相同元素 S <= T 或 S < T 返回True/False,判断S和T的子集关系 S >= T 或 S > T 返回True/False,判断S和T的包含关系
4.2 函数12345len(s) #返回序列s的长度,即元素个数min(s) #返回序列s的最小元素,s中元素需要可比较max(s) #返回序列s的最大元素,s中元素需要可比较s.index(x) / s.index(x, i, j) #返回序列s从i开始到j位置中第一次出现元素x的位置s.count(x) #返回序列s中出现x的总次数
其中len()、min()、max()函数是内置的通用函数
5. 元组python中的元组(tuple)是一种序列类型,一旦创建就不能被修改 ,使用小括号 () 或 tuple() 创建,元素间用逗号 , 分隔 。
5.1 创建和取值123creature = "cat","dog","tiger","human"creature = ('cat', 'dog', 'tiger', 'human') # 可以使用括号和不带括号的两两种color = (0x001100, "blue", creature) # 输出会得到(,,())
元组的取值操作跟列表一样
1234creature = "cat","dog","tiger","human" ...
Transformer & Self-Attention
待完成
失效图片处理
阿三博客地址
李沐老师 48分钟讲解 encoder-decoder中(KV–Q)的运算:
KQ相乘就是单个q对所有k的相似度作为attention score(给这个K值多少注意力),与单个v做加权和(权值来自KQ)
再通过注意力分数与V向量相乘,得到每个V应该多大的缩放, 进行相加后就得到了最终V应该是什么样子了
李沐老师 56分 对multi-head输出和linear层相较于RNN的讲解:
词向量经过Attention层抓取全局信息,汇聚之后,在每个点上都有了所需要的信息
(权重不同,每个输出的向量的重点在不同的position编码位置上),因此只需要做linear transformation。
bert中transformer参数计算:
embedding: vocab_size=30522, max_position_embeddings=512, token_type_embeddings=2(就进行两句分别标记,多了截断)
(30522+512+2)*768 = 23835648 (23M)
self-attention: 768/12 = 64 (多头每头分64维度的向量) ,64*768(每个64映射回768),QKV三个矩阵,
最后一层 786(64 *12的拼接)->768的线性变换
(768/12 * 768 3 ) * 12 + (768768) = 2359296
经过12个transformer
2359296*12 = 28311552 (28M)
feedfoward: 自注意力层之后 分别在 encoder 和 decoder 中有个一个全连接层
维度从 768->4*768_768->768
(768*4 * 768 )*2 = 4718592
(768*4 * 768 )*2 * 12 = 56623104 (56M)
layernorm: 有伽马和贝塔两个参数,embedding层(768 * 2),12层的self-attention,
768 * 2 + 768 * 2 * 2 * 12 = 38400
总计: 23835648+28311552+56623104+38400 = 108808704 (108M)
每一层的参数为: 多头注意力的参数 + 拼接线性变换的参数 + feed-forward的参数 + layer-norm的参数
768 * 768 / 12 * 3 * 12 + 768 * 768 + 768 * 3072 * 2 + 768 * 2 * 2 = 7080960 (7M)
Encoder 编码阶段Multi-head Attention多头注意力机制将一个词向量留过八个 self-attention 头生成八个词向量 vector,
将八个词向量拼接,通过 fc 层进行 softmax 输出。
例如:
词向量为 (1,4) –>
经过 QKV 矩阵(系数) 得到 (1,3) 八个 (1,3)*8 –>
将输出拼接成 (8,3) 矩阵与全连接层的系数矩阵进行相乘再 softmax 确定最后输出的 词向量 –> (1,4)
注意 QKV矩阵怎么来的(attention分数),最后为什么要拼接,以及FC层的系数
qk相乘得到,词向量与其他词的attention分数( q1*(k1,k2,k3) )
多头注意力机制让一份词向量产生了多份答案,将每一份注意力机制的产物拼接,
获得了词向量在不同注意力矩阵运算后的分数,进行拼接后,softmax输出最注意的词,即是注意力机制。
多头注意力机制,将向量复制n份(n为多头头数),投影到如512/8 = 64的64维的低维空间,最后将每一层的输出结果
此处为八层,8*64=512 拼回512维的输出数据
由于Scale Dot Product 只是做乘法点积(向量变成qvk之后的attention运算),没什么参数,因此重点学习的参数在Multi-Head的线性变换中,
即将 64*8的八份数据线性变换的下文中的W0,给模型八次机会希望能够学到什么,最后在拼接回来。==
注意力机制流程:
q –> 查询向量
set( k,v) k –>关键字 v—-> 值
如果 q对k的相似度很高,则输出v的概率也变高
’多头’注意力机制
请注意并推演其词向量维度与系数矩阵带的行数
Scale Dot Product
step1
QK做点积,则输出每一行,是q与所有k的相乘相加结果,
α1 = (q11k11+q12k21+q13k31 , q11k12+q12k22+q13k32 )
α2同理。
step2
所以得到了query1对所有key的相似度,最后每一行做个softmax进行概率分布。
除以根号dk是为了平滑梯度,具体来说:当概率趋近于1的时候softmax函数的梯度很小,除以dk让数值接近函数中部,梯度会比较陡峭
step3
将第二步的结果与V相乘得到最后的输出
Position Embedding位置编码是 将embedding好的词向量加上 position embedding vector 将信息融合,在注意力机制中进行计算。
(原文是使用sin cos将词向量份两部分进行编码, 本文中将交替使用sin cos,即单数sin 双数cos)
位置嵌入编码,主要是为了编辑定位词向量的位置以及词向量间的相对距离
pos为 词的种类数,为行标号
i 为特征维度
len(pos) * len(i) 表示为一position embedding 矩阵, 每一行为词的位置信息,每一列表示在特征上偏置,
将位置信息 融入 词向量信息 使词获得 时间上的相对信息
Residual 细节
Decoder 解码阶段Mask Multi-head与encoder不同的是,解码器在工作时会引入 Mask Multi-head 机制,将右侧的词盖住(设为负无穷或者别的)。
具体来说:
encoder 将生成的K和V矩阵传入 decoder 的 self-attention 模块中,而 decoder 将 mask 后的Q矩阵与其做attention。
mask做的事情
解码还是得一个个来的
时间维度
在时间序列的情况下,词向量表示为,t1时刻的vector,t2时刻的vector….
mask做的事情就是将后面(右边)的 tn个时刻都屏蔽掉,
而Qmatrix的形成 将vector含有了其之后词的信息(共享了系数矩阵),所以将其右边屏蔽。
则剔除了后面词的信息,从而不进行考虑。
Mask 细节mask就是为了阻止词知道后面的信息,具体来说就是QKV矩阵还相乘,但是引入-inf来阻止右边(后面的信息汇聚)
第一次点积:将Q和K矩阵相乘得到attention分数,
将右上角置零就会得到只含有本身信息和相对位置之前(左边)的信息,
且第二次点积: Mask(QK)与V相乘由下三角矩阵的性质,
注: mask去负无穷是因为 SoftMax中 e的指数形式只有在负无穷才为零,
这样相乘数据不会有一点影响,取其他值,都会影响softmax
总结
特别注意理解 attention机制将词向量之间的联系, attention分数
embedding方式为 词向量+位置编码向量
引入了 Residual
encoder-decoder层的传入为KV矩阵,decoder生成Q矩阵
Mask方式
Attention机制
待完成
失效图片处理
博客地址
传统Seq2Seq
动画连接
左侧为 input 将句子一个一个投入到 encoder 中,
encoder整个处理其相关性得到 context,吐给 decoder,
decoder 进行一个一个解码输出,得到整个翻译后的句子。
AttentionAn attention model differs from a classic sequence-to-sequence model in two main ways:
First, the encoder passes a lot more data to the decoder. Instead of passing the last hidden state of the encoding stage, the encoder passes all the hidden states to the decoder:
注意力机制将产生的隐藏层信息(时间步骤信息),全部保留,一次性传给 Decoder。
Second, an attention decoder does an extra step before producing its output. In order to focus on the parts of the input that are relevant to this decoding time step, the decoder does the following:
Look at the set of encoder hidden states it received – each encoder hidden state is most associated with a certain word in the input sentence
Give each hidden state a score (let’s ignore how the scoring is done for now)
Multiply each hidden state by its softmaxed score, thus amplifying hidden states with high scores, and drowning out hidden states with low scores
decoder 将 encoder 输入的隐藏层的 vector 进行打分得到一个分数vector,
将分数 vector 做 softmax,得到一个权重 vector,
将权重 vector 与隐藏层 vector 相乘得到 注意力 vector,
最后把注意力 vector 进行相加就完成了。
注意: 将 encoder 的隐藏层信息传入 decoder之后,decoder 每一步都将使用其传入的隐藏层信息做 attention。
由上图可以看到,输出时 Attention 机制就是将注意力放在分数最高的向量上,所以,称之为’注意力机制’
22-12-03 trick总结
巧取下标NER任务中对 标签下标的偏移量
123456789101112131415161718192021a = torch.randn(2,4).softmax(dim=-1)b = torch.randn(2,4).softmax(dim=-1)a,b'''(tensor([[0.1310, 0.3604, 0.1726, 0.3360], [0.1955, 0.3245, 0.0700, 0.4101]]), tensor([[0.2199, 0.1130, 0.3976, 0.2695], [0.0455, 0.0804, 0.0514, 0.8227]]))''' scores = a.unsqueeze(1).transpose(-1,-2) @ b.unsqueeze(1)scores'''tensor([[[0.0288, 0.0148, 0.0521, 0.0353], [0.0792, 0.0407, 0.1433, 0.0971], [0.0380, 0.0195, 0.0686, 0.0465], [0.0739, 0.0380, 0.1336, 0.0905]], [[0.0089, 0.0157, 0.0100, 0.1608], [0.0148, 0.0261, 0.0167, 0.2670], [0.0032, 0.0056, 0.0036, 0.0576], [0.0187, 0.0330, 0.0211, 0.3374]]])'''
以上我们需要在三维张量中得到二维矩阵中最大值的坐标(x, y)
12345678910111213141516fin = torch.triu(scores)fin'''tensor([[[0.0288, 0.0148, 0.0521, 0.0353], [0.0000, 0.0407, 0.1433, 0.0971], [0.0000, 0.0000, 0.0686, 0.0465], [0.0000, 0.0000, 0.0000, 0.0905]], [[0.0089, 0.0157, 0.0100, 0.1608], [0.0000, 0.0261, 0.0167, 0.2670], [0.0000, 0.0000, 0.0036, 0.0576], [0.0000, 0.0000, 0.0000, 0.3374]]])''' fin[0].argmax(), fin[0].argmax() % 4, fin[0].argmax() // 4# (tensor(6), tensor(2), tensor(1)) ——> 0.1433
以上我们通过对argmax()返回的绝对坐标,对绝对坐标%得到列坐标(填满多少行后,余量在本行的列位),//得到行坐标(整除的行号)
接下来对fin的每个矩阵做循环即可。
JNotebook 魔法命令%%time 可以查看运行时间
!zip !unzip
!pip
kaggle 数据加载1234!mkdir -p ~/.kaggle/ # 创建一个专门的文件夹!mv /content/kaggle.json ~/.kaggle/ # 将json转到你的kaggle文件!kaggle competitions download -c ml2021-spring-hw7 # kaggle api 要去比赛处同意条款 获得!unzip /content/ml2021-spring-hw7.zip #解压文件
图片查看123456789101112# 将数据集 使用dataloader装载后,使其可迭代,并用next方法调用# samples, labels = next(iter(train_dl)) 取出第一个batch的data和targetimport matplotlib.pyplot as pltplt.figure(figsize=(10,5))for i, imgs in enumerate(samples[:20], 0): print(imgs.size()) npimg = imgs.numpy().transpose((1, 2, 0)) plt.subplot(2, 10, i+1) plt.imshow(npimg, cmap=plt.cm.binary) plt.axis('off')print(labels[:10])
tensor转换尽量使用torch.as_tensor(data: Any, dtype: _dtype=None, device: Optional[_device]=None)
HF Course 03 微调范式
dataIn this section we will use as an example the MRPC (Microsoft Research Paraphrase Corpus) dataset, introduced in a paper by William B. Dolan and Chris Brockett. The dataset consists of 5,801 pairs of sentences, with a label indicating if they are paraphrases or not (i.e., if both sentences mean the same thing). We’ve selected it for this chapter because it’s a small dataset, so it’s easy to experiment with training on it.
let’s focus on the MRPC dataset! This is one of the 10 datasets composing the GLUE benchmark
使用的是MRPC,很小很好实验
它属于 GLUE
接下来查看下数据
12345678910111213141516171819from datasets import load_datasetraw_datasets = load_dataset("glue", "mrpc")raw_datasets'''DatasetDict({ train: Dataset({ features: ['sentence1', 'sentence2', 'label', 'idx'], num_rows: 3668 }) validation: Dataset({ features: ['sentence1', 'sentence2', 'label', 'idx'], num_rows: 408 }) test: Dataset({ features: ['sentence1', 'sentence2', 'label', 'idx'], num_rows: 1725 })})'''
1234567raw_train_dataset = raw_datasets["train"]raw_train_dataset[0]'''{'idx': 0, 'label': 1, 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'}'''
123456raw_train_dataset.features'''{'sentence1': Value(dtype='string', id=None), 'sentence2': Value(dtype='string', id=None), 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None), 'idx': Value(dtype='int32', id=None)}'''
预处理tokenizer方法
123456tokenized_dataset = tokenizer( raw_datasets["train"]["sentence1"], raw_datasets["train"]["sentence2"], padding=True, truncation=True,)
This works well, but it has the disadvantage of returning a dictionary (with our keys, input_ids, attention_mask, and token_type_ids, and values that are lists of lists). It will also only work if you have enough RAM to store your whole dataset during the tokenization (whereas the datasets from the 🤗 Datasets library are Apache Arrow files stored on the disk, so you only keep the samples you ask for loaded in memory).
占内存
dataset.map 方法
1234567891011121314151617181920def tokenize_function(example): return tokenizer(example["sentence1"], example["sentence2"], truncation=True) tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)tokenized_datasets'''DatasetDict({ train: Dataset({ features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], num_rows: 3668 }) validation: Dataset({ features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], num_rows: 408 }) test: Dataset({ features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], num_rows: 1725 })})'''
since the tokenizer works on lists of pairs of sentences, as seen before. This will allow us to use the option batched=True in our call to map(), which will greatly speed up the tokenization. The tokenizer is backed by a tokenizer written in Rust from the 🤗 Tokenizers library. This tokenizer can be very fast, but only if we give it lots of inputs at once.
batch
This is because padding all the samples to the maximum length is not efficient: it’s better to pad the samples when we’re building a batch, as then we only need to pad to the maximum length in that batch, and not the maximum length in the entire dataset. This can save a lot of time and processing power when the inputs have very variable lengths!
batch内最长padding,将在下一小节介绍DataCollatorWithPadding
You can even use multiprocessing when applying your preprocessing function with map() by passing along a num_proc argument. We didn’t do this here because the 🤗 Tokenizers library already uses multiple threads to tokenize our samples faster, but if you are not using a fast tokenizer backed by this library, this could speed up your preprocessing.
多线程
but note that if you’re training on a TPU it can cause problems — TPUs prefer fixed shapes, even when that requires extra padding.
tpu更喜欢恒定形状,所以你很少数据with large pad 也没事
Dynamic paddingThe function that is responsible for putting together samples inside a batch is called a collate function. It’s an argument you can pass when you build a DataLoader, the default being a function that will just convert your samples to PyTorch tensors and concatenate them (recursively if your elements are lists, tuples, or dictionaries). This won’t be possible in our case since the inputs we have won’t all be of the same size.
DataCollator可以看成是一个函数,可以传入pytorch的dataloader的collate_fn参数
123from transformers import DataCollatorWithPaddingdata_collator = DataCollatorWithPadding(tokenizer=tokenizer) # 有model参数,可以把model也让collator知道
军火展示
12345678910111213samples = tokenized_datasets["train"][:8]samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]}[len(x) for x in samples["input_ids"]]'''[50, 59, 47, 67, 59, 50, 62, 32]'''batch = data_collator(samples){k: v.shape for k, v in batch.items()}'''{'attention_mask': torch.Size([8, 67]), 'input_ids': torch.Size([8, 67]), 'token_type_ids': torch.Size([8, 67]), 'labels': torch.Size([8])}'''
Fine-tuneTrainer首先是TrainingArguments的定义
The first step before we can define our Trainer is to define a TrainingArguments class that will contain all the hyperparameters the Trainer will use for training and evaluation. The only argument you have to provide is a directory where the trained model will be saved, as well as the checkpoints along the way. For all the rest, you can leave the defaults, which should work pretty well for a basic fine-tuning.
123456from transformers import TrainingArgumentsfrom transformers import AutoModelForSequenceClassificationtraining_args = TrainingArguments("test-trainer")model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
You will notice that unlike in Chapter 2, you get a warning after instantiating this pretrained model. This is because BERT has not been pretrained on classifying pairs of sentences, so the head of the pretrained model has been discarded and a new head suitable for sequence classification has been added instead. The warnings indicate that some weights were not used (the ones corresponding to the dropped pretraining head) and that some others were randomly initialized (the ones for the new head). It concludes by encouraging you to train the model, which is exactly what we are going to do now.
使用细分模型 (automode后带任务名称的)不会得到警告,是因为他会加载 最后面那个多分类的权重给你,这样预训练就又快了些,上面写的head 应该是指classifier的那几层吧。
接下来可以train了
123456789101112from transformers import Trainertrainer = Trainer( model, training_args, train_dataset=tokenized_datasets["train"], eval_dataset=tokenized_datasets["validation"], data_collator=data_collator, tokenizer=tokenizer,)trainer.train() # 调用训练
Note that when you pass the tokenizer as we did here, the default data_collator used by the Trainer will be a DataCollatorWithPadding as defined previously, so you can skip the line data_collator=data_collator in this call.
This will start the fine-tuning (which should take a couple of minutes on a GPU) and report the training loss every 500 steps.
不写collate的话默认就是DataCollatorWithPadding,不过声明一下,比较好,为了可读性
每500步给你一个loss返回,看看need loss有多大
Evaluation1234predictions = trainer.predict(tokenized_datasets["validation"])print(predictions.predictions.shape, predictions.label_ids.shape)# (408, 2) (408,)
predict的结果就是二分类的logits 接下来做个argmax 取位置信息即可
1234567891011import numpy as npimport evaluatepreds = np.argmax(predictions.predictions, axis=-1)metric = evaluate.load("glue", "mrpc")metric.compute(predictions=preds, references=predictions.label_ids)'''{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542}'''
The table in the BERT paper reported an F1 score of 88.9 for the base model. That was the uncased model while we are currently using the cased model, which explains the better result.
上面说 微调的power!
12345def compute_metrics(eval_preds): metric = evaluate.load("glue", "mrpc") logits, labels = eval_preds predictions = np.argmax(logits, axis=-1) return metric.compute(predicti ...