野生程序员在线
Day 08-进阶(02)
本节重点:softmax实现欧式距离计算概率抽样寻优(局部极大值)1. 如何计算Softmax得分?描述:计算sepallength的softmax分数。给定:url = '<https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data>'
sepallength = np.genfromtxt(url, delimiter=',', dtype='float', usecols=[0])答案:import numpy as np
#Input
url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data'
iris = np.genfromtxt(url, delimiter=',', dtype='object')
sepallength = np.array([float(row[0]) for row in iris])
#Solution
def softmax(x):
"""Compute softmax values for each sets of scores in x.
https://stackoverflow.com/questions/34968722/how-to-implement-the-softmax-function-in-python"""
#np.exp = e^a
#第一步:归一化
#第二部:求softmax
e_x = np.exp(x - np.max(x))
return e_x / e_x.sum()
print(sepallength)
prob = softmax(sepallength)
print(prob)
print(np.sum(prob))2. 如何计算两个数组之间的欧氏距离?问题:计算两个数组a和数组b之间的欧氏距离。欧式距离:范数:答案:#函数说明:https://numpy.org/doc/stable/reference/generated/numpy.linalg.norm.html
#Input
a = np.array([1,2,3,4,5])
b = np.array([4,5,6,7,8])
#Solution1 范数
dist1 = np.linalg.norm(a-b)
print(dist1)
#> 6.7082039324993694
#Solution 2
dist2 = np.sqrt(np.sum(np.square(a - b)))
print(dist2)3. 如何在numpy中进行概率抽样?描述:随机抽鸢尾属植物的种类,使得刚毛的数量是云芝和维吉尼亚的两倍给定:# Import iris keeping the text column intact
url = '<https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data>'
iris = np.genfromtxt(url, delimiter=',', dtype='object')思路:1、
random.choice : <https://numpy.org/doc/stable/reference/random/generated/numpy.random.choice.html>
2、
np.linspace :<https://blog.csdn.net/You_are_my_dream/article/details/53493752>
np.searchsorted : <https://blog.csdn.net/qq_33757398/article/details/89876088>
答案:#Import iris keeping the text column intact
url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data'
iris = np.genfromtxt(url, delimiter=',', dtype='object')
#Solution
#Get the species column
species = iris[:, 4]
#Approach 1: Generate Probablistically
np.random.seed(100)
a = np.array(['Iris-setosa', 'Iris-versicolor', 'Iris-virginica'])
species_out = np.random.choice(a, 150, p=[0.5, 0.25, 0.25])
print(np.unique(species_out,return_counts=True))
#Approach 2: Probablistic Sampling (preferred)
np.random.seed(100)
probs = np.r_[np.linspace(0, 0.500, num=50), np.linspace(0.501, .750, num=50), np.linspace(.751, 1.0, num=50)]
print(probs)
index = np.searchsorted(probs, np.random.random(150))
print(index)
species_out = species[index]
print(np.unique(species_out, return_counts=True))
#> (array([b'Iris-setosa', b'Iris-versicolor', b'Iris-virginica'], dtype=object), array([77, 37, 36]))4. 如何在一维数组中找到所有的局部极大值(或峰值)?描述:找到一个一维数字数组a中的所有峰值。峰顶是两边被较小数值包围的点。给定:a = np.array([1, 3, 7, 1, 2, 6, 0, 1])期望的输出:> array([2, 5])
其中,2和5是峰值7和6的位置。答案:#np.diff :https://numpy.org/doc/stable/reference/generated/numpy.diff.html
#np.sigh :https://blog.csdn.net/lyq_12/article/details/86645425
a = np.array([1, 3, 7, 1, 2, 6, 0, 1])
print(np.diff(a))
diff_sign = np.sign(np.diff(a))
print(diff_sign)
doublediff = np.diff(diff_sign)
print(doublediff)
peak_locations = np.where(doublediff == -2)[0] + 1
peak_locations
#> array([2, 5])
野生程序员在线
Day 03-数组创建
本节内容¶1、python list创建2、现有数据创建3、特殊值创建(重要)4、范围创建(重要)5、创建特定矩阵6、随机创建(重要)一、简单创建import numpy as np
# 一维
x = np.array([[1, 2, 3], [4, 5, 6]], np.int32)
print(type(x))
# <class 'numpy.ndarray'>
print(x.shape)
# (2, 3)
print(x.dtype)
# dtype('int32')
print("===============")
#python list创建
x = np.array([2, 3, 1, 0])
print(x)二、现有数据创建数组有以下常用的方法,可以将现有的各类数据创建一个数组(按 np.xxx 格式使用)# python list
np.array([1, 2, 3])
#直接创建多维度
np.array([[1, 2], [3, 4]])
'''
array([[1, 2],
[3, 4]])
'''
#指定维度
np.array([1, 2, 3], ndmin=2)
# array([[1, 2, 3]])
#指定类型
np.array([1, 2, 3], dt='float16')
#从子类创建
np.array(np.mat('1 2; 3 4'))
'''
array([[1, 2],
[3, 4]])
'''2.1、转为数组 numpy.asarray1、numpy.asarray(a, dtype=None, order=None) 将输入的类似列表的序列转换为数组。返回一个 ndarray2、如果输入已经是具有匹配 dtype 和 order 的 ndarray,则不执行复制3、如果 a 是 ndarray 的子类,则返回基类 ndarray。#将列表转换为数组:
a = [1, 2]
np.asarray(a)
np.asarray(a) is a #数组未复制
#设置了 dtype,则仅当 dtype 不匹配时才复制数组:
a = np.array([1, 2], dtype=np.float32)
np.asarray(a, dtype=np.float32) is a
# True
np.asarray(a, dtype=np.float64) is a
# False2.2、深拷贝 numpy.copy返回给定对象的数组副本,相当于 np.array(a, copy=True)。x = np.array([1, 2, 3])
y = x # 赋值
z = np.copy(x) # 深拷贝
x[0] = 0 # 修改数据
x
# array([0, 2, 3])
y # 随 x 修改而修改
# array([0, 2, 3])
z # 深拷贝未随 x 修改而修改
# array([1, 2, 3])三、创建特殊值数组(重要)有以下常用的方法,可以创建一个矩阵内为特殊数字的数组(按 np.xxx 格式使用)。值全为 0
np.zeros(5)
# array([ 0., 0., 0., 0., 0.])
np.zeros((5,), dtype=int)
# array([0, 0, 0, 0, 0])
np.zeros((2, 1))
'''
array([[ 0.],
[ 0.]])
'''
# 值全为 1
np.ones(5)
# array([1., 1., 1., 1., 1.])
# 值全为指定填充值
np.full((2, 2), np.inf)
'''
array([[inf, inf],
[inf, inf]])
'''
np.full((2, 2), 10)
'''
array([[10, 10],
[10, 10]])
'''
np.full((2, 2), [1, 2])
'''
array([[1, 2],
[1, 2]])
'''
#np.empty() 返回给定形状和类型的新数组,内容随机
np.empty([2, 3], dtype=int)
'''
array([[0, 0, 1],
[1, 1, 1]])
'''
#np.ones_like(a) 等按传入的数据(array_like)形状生成指定值的新数组:
a = np.arange(6).reshape((2, 3))
a
'''
array([[0, 1, 2],
[3, 4, 5]])
'''
np.ones_like(a)
'''
array([[1, 1, 1],
[1, 1, 1]])
'''
#np.eye 返回对角线上值为 1,其他位置为 0 的数据:
np.eye(3, dtype=int)
'''
array([[1, 0, 0],
[0, 1, 0],
[0, 0, 1]])
'''四、范围创建数组有以下常用的方法,可以根据给定的数据范围创建一个数组:4.1、范围均匀值 np.arangenp.arange 在给定的间隔内返回均匀分布的值。值是在半开区间 [start,stop) 内生成的#默认0-3
np.arange(3)
# array([0, 1, 2])
np.arange(3,7)
# array([3, 4, 5, 6])
np.arange(3,7,2)
# array([3, 5])
np.arange(3,4,.2)
# array([3. , 3.2, 3.4, 3.6, 3.8])4.2、等距数字 np.linspacenp.linspace() 返回指定间隔内的等距数字。返回在 [start,stop] 间隔内计算的等距采样数#指定数量
np.linspace(2.0, 3.0, num=5)
# array([2. , 2.25, 2.5 , 2.75, 3. ])
# 右开区间(不包含右值)
np.linspace(2.0, 3.0, num=5, endpoint=False)
# array([2. , 2.2, 2.4, 2.6, 2.8])4.3、对数均匀分布 np.logspace同上np.logspace(2.0, 3.0, num=4)
# array([ 100. , 215.443469 , 464.15888336, 1000. ])
np.logspace(2.0, 3.0, num=4, endpoint=False)
# array([100. , 177.827941 , 316.22776602, 562.34132519])
np.logspace(2.0, 3.0, num=4, base=2.0)
# array([4. , 5.0396842 , 6.34960421, 8. ])五、numpy构建规则矩阵有以下常用的方法,可以构建一定规则的矩阵:5.1、提取构造对角线数组 np.diag1、如果传入的是一个二维数组,提取出对角线的值形成一个一维数组,还可以传入参数 k 对对角线做下移和下移2、如果传入一个一维数组,则生成一个对角线数组,对角线上的值为一维数组的值。x = np.arange(9).reshape((3,3))
x
'''
array([[0, 1, 2],
[3, 4, 5],
[6, 7, 8]])
'''
# 传入的是二维数组,提取出对角线上的值数组
np.diag(x)
# array([0, 4, 8])
np.diag(x, k=1) # 上移
# array([1, 5])
np.diag(x, k=-1) # 下移
# array([3, 7])
# 将一维数组转为方阵,对角线为数组值
np.diag(np.arange(4))
'''
array([[0, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 2, 0],
[0, 0, 0, 3]])
'''
5.2、展开为对角线 np.diagflat将原数组(array_like)展平输入作为对角线二维数组的对角线的值。np.diagflat([[1,2], [3,4]])
'''
array([[1, 0, 0, 0],
[0, 2, 0, 0],
[0, 0, 3, 0],
[0, 0, 0, 4]])
'''
# 设定 k 值,上移 1 位
np.diagflat([1,2], 1)
'''
array([[0, 1, 0],
[0, 0, 2],
[0, 0, 0]])
'''5.3、三角矩阵 np.tri用于创建一个数组,该数组在给定对角线处和下方(在这种情况下为k)包含1,在数组的所有其他位置包含 0。# 上移 2 位
np.tri(3, 5, 2, dtype=int)
'''
array([[1, 1, 1, 0, 0],
[1, 1, 1, 1, 0],
[1, 1, 1, 1, 1]])
'''
# 下移一位
np.tri(3, 5, -1)
'''
array([[0., 0., 0., 0., 0.],
[1., 0., 0., 0., 0.],
[1., 1., 0., 0., 0.]])
'''六、NumPy 创建随机样本数组6.1、numpy.random.rand()numpy.random.rand(d0,d1...dn)rand函数根据给定维度生成半开区间[0,1)之间的数据,包含0,不包含1dn表示每个维度返回值为指定纬度的numpy.ndarraynp.random.rand(3, 3) # shape: 3*3
'''
array([[0.94340617, 0.96183216, 0.88510322],
[0.44543261, 0.74930098, 0.73372814],
[0.29233667, 0.3940114 , 0.7167332 ]])
'''
6.2、np.random.randn()numpy.random.randn(d0,d1,…,dn)randn函数返回一个或一组样本,具有标准正态分布。dn表示个维度返回值为指定维度的numpy.ndarraynp.random.randn() # 当没有输入参数时,仅返回一个值
-0.7377941002942127
np.random.randn(3, 3)
array([[-0.20565666, 1.23580939, -0.27814622],
[ 0.53923344, -2.7092927 , 1.27514363],
[ 0.38570597, -1.90564739, -0.10438987]])6.3、numpy.random.randint()numpy.random.randint(low, high=None, size=None, dtype=’l’)从区间[low,high)返回随机整形参数:low为最小值,high为最大值,size为数组维度大小,dtype为数据类型,默认的数据类型是np.inthigh没有填写时,默认生成随机数的范围是[0,low)np.random.randint(1, size = 10) # 返回[0, 1)之间的整数,所以只有0
#array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
np.random.randint(1, 5) # 返回[1, 5)之间随机的一个数字
#26.4、numpy.random.choice()(重要)numpy.random.choice(a, size=None, replace=True, p=None)从给定的一位数组中生成一个随机样本a要求输入一维数组类似数据或者是一个int;size是生成的数组纬度,要求数字或元组;replace为布尔型,决定样本是否有替换;p为样本出现概率
野生程序员在线
Day 09-实战:线性回归
本节重点:• 基于numpy实现线性回归算法• 参考视频:https://www.bilibili.com/video/BV1oD4y1u7U8/?spm_id_from=333.999.0.0&vd_source=ea41ada76e180cf6c5af0f913147e4c0一、线性回归:表达式:损失函数:梯度更新:lr为学习率,w和b的梯度就是对loss function的求导【这里为手动求解导数后,放入公式,也用diff进行计算】二、numpy实现线性回归数据准备import numpy as np
import matplotlib.pyplot as plt
x = np.random.randint(2,200, size = 100)
y = np.linspace(0.4, 0.500, num=100)* x + np.random.randint(1,3,size=100)
plt.plot(x,y,'o')
plt.show()predictdef count_y_prediction(X, w, b):
y_pred = np.add(np.multiply(w,X) , b)
# y_pred = np.add(np.dot(w,x) ,b)
# print(y_pred)
return y_pred损失函数def compete_error_for_given_points(y, y_pred):
error = np.power(np.subtract(y, y_pred) , 2)
error = np.sum(error) / y.shape[0]
# print(error)
return error梯度更新def compete_gradient_and_update(x, w, b, lr):
w_gradient = 0
b_gradient = 0
N = x.shape[0]
w_gradient = 2*np.multiply(np.subtract(np.add(np.multiply(self.w,x),b),y),x)
w_gradient_sum = np.sum(w_gradient)
b_gradient = 2*np.subtract(np.add(np.multiply(self.w,x),b),y)
b_gradient_sum = np.sum(b_gradient)
w -= lr * w_gradient_sum / N
b -= lr * b_gradient_sum / N
return w,b画图def draw(X, y, y_pred,final=True):
# plt.ion()
plt.clf()
plt.scatter(X, y, c="red")
plt.plot(X, y_pred, c="blue")
if final:
plt.pause(0.2)
# plt.close()
else:
plt.show()三、代码封装#简单线性回归
class SimpleRegress(object):
def __init__(self):
self.b = np.linspace(1, 3, num=100)
self.w = np.ones((100,))
self.lr = 0.003
return
#梯度更新
def compete_gradient_and_update(self,x,y):
w_gradient = 0
b_gradient = 0
N = x.shape[0]
w_gradient = 2*np.multiply(np.subtract(np.add(np.multiply(self.w,x),self.b),y),x)
w_gradient_sum = np.sum(w_gradient)
b_gradient = 2*np.subtract(np.add(np.multiply(self.w,x),self.b),y)
b_gradient_sum = np.sum(b_gradient)
self.w -= self.lr * w_gradient_sum / N
self.b -= self.lr * b_gradient_sum / N
return self.w,self.b
#求损失函数
def compete_error_for_given_points(self,y, y_pred):
error = np.power(np.subtract(y , y_pred) , 2)
error = np.sum(error)/ error.shape[0]
# print(error)
return error
#计算多少个epoch
def gradient_desent_runner(self,times,x,y):
for i in range(times):
self.w,self.b = self.compete_gradient_and_update(x,y)
return [self.w,self.b]
#推理
def count_y_prediction(self,X):
y_pred = np.add(np.multiply(self.w,X) ,self.b)
# print(y_pred)
return y_pred
#画图
def draw(self,X, y, y_pred,final=True):
# plt.ion()
plt.clf()
plt.plot(X, y, 'o')
plt.plot(X, y_pred, c="blue")
if final:
plt.pause(0.2)
# plt.close()
else:
plt.show()#数据
x_data = np.random.randint(2,200, size = 100)
y_data = np.linspace(0.4, 0.450, num=100)* x_data
times = 50 # epoch次数
test_data = list([16]) #测试数据
sr = SimpleRegress()
# 预测
y_pred = sr.count_y_prediction(x_data)
# 记录原始值
print("Starting Giadient desent at w ={0},b ={1},error={2}"
.format(sr.w,sr.b,sr.compete_error_for_given_points(y_data,y_pred)))
print("Running:")
# 梯度更新求解
[w,b] = sr.gradient_desent_runner(times,x_data,y_data)
# 最新预测值
y_pred = sr.count_y_prediction(x_data)
print("After {0} times w = {1},b = {2},error = {3}"
.format(times,w,b,sr.compete_error_for_given_points(y_data,y_pred)))>
Starting Giadient desent at w =[1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.
1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.
1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.
1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.
1. 1. 1. 1.],b =[1. 1.02020202 1.04040404 1.06060606 1.08080808 1.1010101
1.12121212 1.14141414 1.16161616 1.18181818 1.2020202 1.22222222
1.24242424 1.26262626 1.28282828 1.3030303 1.32323232 1.34343434
1.36363636 1.38383838 1.4040404 1.42424242 1.44444444 1.46464646
1.48484848 1.50505051 1.52525253 1.54545455 1.56565657 1.58585859
1.60606061 1.62626263 1.64646465 1.66666667 1.68686869 1.70707071
1.72727273 1.74747475 1.76767677 1.78787879 1.80808081 1.82828283
1.84848485 1.86868687 1.88888889 1.90909091 1.92929293 1.94949495
1.96969697 1.98989899 2.01010101 2.03030303 2.05050505 2.07070707
2.09090909 2.11111111 2.13131313 2.15151515 2.17171717 2.19191919
2.21212121 2.23232323 2.25252525 2.27272727 2.29292929 2.31313131
2.33333333 2.35353535 2.37373737 2.39393939 2.41414141 2.43434343
2.45454545 2.47474747 2.49494949 2.51515152 2.53535354 2.55555556
2.57575758 2.5959596 2.61616162 2.63636364 2.65656566 2.67676768
2.6969697 2.71717172 2.73737374 2.75757576 2.77777778 2.7979798
2.81818182 2.83838384 2.85858586 2.87878788 2.8989899 2.91919192
2.93939394 2.95959596 2.97979798 3. ],error=4217.362019403632
Running:
After 50 times w = [3.37361812e+92 3.37361812e+92 3.37361812e+92 3.37361812e+92
3.37361812e+92 3.37361812e+92 3.37361812e+92 3.37361812e+92
3.37361812e+92 3.37361812e+92 3.37361812e+92 3.37361812e+92
...
2.6302579e+90 2.6302579e+90 2.6302579e+90 2.6302579e+90 2.6302579e+90
2.6302579e+90 2.6302579e+90 2.6302579e+90 2.6302579e+90 2.6302579e+90
2.6302579e+90 2.6302579e+90 2.6302579e+90 2.6302579e+90 2.6302579e+90
2.6302579e+90 2.6302579e+90 2.6302579e+90 2.6302579e+90 2.6302579e+90],error = 1.3779443376053316e+189
野生程序员在线
Day 02-ndarray
本节内容(重要)ndarray介绍基础信息类型转换更改阵列形状 reshape&resize转置 transpose & array.T展平 flatten展平为连续数组 ravel删除数组单维条目 squeeze数组堆叠(vstack&hstack)一、NdarrayNumPy 的 ndarray 是一个(通常是固定大小)由相同类型和大小的数据组成的多维容器。数组中的维数和项数由其形状定义,形状是由 N 个非负整数组成的元组,指定每个维数的大小。数组中的项类型由单独的数据类型对象(dtype)指定,与每个 ndarray 关联。NumPy 的数组类称为 ndarray,简称为 array。请注意,numpy.array 与标准 Python 库类 array.array 不同,后者仅处理一维数组且功能较少二、基础信息与 Python 中的其他容器对象一样,可以通过对数组进行索引或切片(例如,使用 N 个整数)并通过 ndarray 的方法和属性来访问和修改 ndarray 的内容。import numpy as np
# 创建15个元素的数组,形状修改成 3 行 5 列
a = np.arange(15).reshape(3, 5)
print(a)
'''
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]])
'''
print(a.shape) # 形状
# (3,5)
print(a.ndim) # 维数
# 2
print(a.dtype.name) # 类型名
# 'int32'
print(a.itemsize) # 一个数组元素的字节总长度(和类型也相关);数组中每个元素以字节为单位的大小, 例如,float64 类型的元素数组的项目大小为8(= 64/8),而complex32 类型的元素数组的项目大小为 4(= 32/8)。
# 4
print(a.size) # 大小,元素数
print(a.dtype) #数据类型
# dtype('int32')
# 15
print(type(a))
# <class 'numpy.ndarray'>三、类型转换ndarray.astype 是最为常用的类型转换方法,它作用于 ndarray ,可以将原数据转换为我们想要的类型,当然数据特征需要满足目标类型的要求语法如下:# astype 语法
ndarray.astype(dtype, order='K', casting='unsafe',
subok=True, copy=True)其中参数有:dtype: str or dtype(dtype 对象)将数组强制转换为的类型代码或数据类型order: {‘C’, ‘F’, ‘A’, ‘K’}, optional控制结果的内存布局顺序。“C”表示 C 顺序“F” 表示 Fortran 顺序“A”表示“F”顺序(如果所有数组都是Fortran连续的),否则是 “C” 顺序“K”表示尽可能接近数组元素在内存中出现的顺序。默认值为“K”。casting: {‘no’, ‘equiv’, ‘safe’, ‘same_kind’, ‘unsafe’}, optional控制可能发生的数据转换类型,,默认为 “unsafe” 以实现向后兼容性。‘no’ 数据类型根本不应该被强制转换‘equiv’ 只允许更改字节顺序‘safe’ 只允许保留值的强制转换。‘same_kind’ 只允许安全强制转换或类内强制转换,如 float64 到 float32‘unsafe’ 可以进行任何数据转换subok: bool, optional如果为True,则传递子类(默认),否则返回的数组将强制为基类数组copy: bool, optional默认情况下,astype 总是返回新分配的数组,如果设置为 false,并且满足了 dtype、order 和 subok 要求,则返回输入数组而不是副本x = np.array([1, 2, 3.4])
print(x)
# array([1. , 2. , 3.4])
print(x.astype(int))
b = x.astype(int)
# array([1, 2, 3])
print(x.dtype) # dtype('float64')
print(b.dtype) # dtype('int32')
print("---------------")
# 理解部分内容转换
a = np.array([[1,2,3],
[4,5,6]])
print(a[:,1])
# array([2, 5])
print(a[:,1].astype('str')) # 转换后生成此副本
# array(['2', '5'], dtype='<U21')
print(a.dtype) # 原来的数组没有改变
# dtype('int64')
print("---------------")
# 构造一个时间表达数据
arr = [2020, 12, 0.6552562894775783]
custom_type = np.dtype([
('YEAR',np.uint16),
('DOY', np.uint16),
('REF',np.float16)
])
d = np.array([tuple(arr)], custom_type)
print(d)
'''
array([(2020, 12, 0.6553)],
dtype=[('YEAR', '<u2'), ('DOY', '<u2'), ('REF', '<f2')])
'''
print(d['YEAR']) # array([2020], dtype=uint16)四、NumPy 更改阵列形状NumPy 常用的数据变形操作有:4.1、数组变形 np.reshapeNumPy 提供了 numpy.reshape 和 ndarray.reshape 两个层面的变形方法,它的实现的效果是一样的,返回包含具有新形状的相同数据的数组。新形状应与原形状兼容#ndarray.reshape
a = np.array([[1,2,3],
[4,5,6]])
print(a)
print('---------------')
#ndarray reshape
# 以下方法只管指定行列数,其他位置用-1,会自动计算
print(a.reshape([-1, 3])) # 要3列(常用)
print('---------------')
print(a.reshape([2, -1])) # 要两行,效果相同(常用)
print('---------------')
print(a.reshape(6)) # 一维,6列
print('---------------')
print(a.reshape(-1)) # 同上
# array([1, 2, 3, 4, 5, 6])
print('---------------')
print(a.reshape([3,2])) # 转为3行两列
print('---------------')
print(a.reshape([2, 4])) # 报错,形状无法兼容
# Traceback (most recent call last) ----> 1 a.reshape([2, 4])
# ValueError: cannot reshape array of size 6 into shape (2,4)# numpy reshape
a = np.arange(6).reshape((3, 2))
print(a)
print('---------------')
b = np.reshape(a, (2, 3)) # C-like 索引顺序
print(b)
print('---------------')
# np.ravel(a)-->np.reshape(-1)
c = np.reshape(np.ravel(a), (2, 3)) # 相当于 C ravel 然后 C 重塑
d = np.reshape(np.reshape(a,(-1)), (2, 3)) # 相当于 C ravel 然后 C 重塑
print(c)
print(d)4.2、更改形状 np.resize1、numpy.resize(a, new_shape) 和 ndarray.resize(new_shape, refcheck=True) 等同的作用,numpy.resize 如果新数组比原始数组大,则新数组中会填充 a 的重复副本。请注意,此行为与 ndarray.resize 不同,后者填充 0 而不是 a 的重复副本2、resize 将在必要时为数据区域重新分配空间,当数组的总大小不变时,应使用 reshapea=np.array([[0,1],
[2,3]])
print(np.resize(a,(2,3)))
print('---------------')
print(np.resize(a,(1,4)))
print('---------------')
# array([[0, 1, 2, 3]])
print(np.resize(a,(2,4)))缩小数组:数组被展平、调整大小和形状#nadaryy reize
a = np.array([[0, 1],
[2, 3]], order='C')
a.resize((2, 1))
print(a)
'''
array([[0],
[1]])
'''
a2 = np.array([[0, 1],
[2, 3]], order='F')
a2.resize((2, 1))
print(a2)
'''
array([[0],
[2]])
'''放大数组:如上所述,但缺少的条目用零填充:b = np.array([[0, 1],
[2, 3]])
print(b.resize(2, 3)) # 新的形状参数不必是元组
print(b)
'''
array([[0, 1, 2],
[3, 0, 0]])
'''resize 和 reshape 的区别:resize 如果新数组比原数组大,则将会copy原数组中的值对新数组进行填充reshape 在不改变原数组数据的情况下,将它 reshape 成一个新的维度,如果给定的数组数据和需要reshape的形状不符合时,将会报错五、转置 transpose array.T1、ndarray.transpose(*axes) 和 ndarray.T 效果一样。2、对于一维数组,这没有任何影响,因为转置向量只是同一个向量a = np.array([[0, 1],
[2, 3]])
print(a)
print("========1==========")
'''
[[0 1]
[2 3]]
'''
print(a.transpose())
print("=========2=========")
'''
[[0 2]
[1 3]]
'''
# (2,2)->2:0维,2:1维
print(a.transpose(0,1))
print("=========3=========")
'''
[[0 1]
[2 3]]
'''
print(a.transpose(1,0))
print("=========4=========")
'''
[[0 2]
[1 3]]
'''
# T 操作
x = np.array([[0, 1],
[2, 3]])
print(x)
print("=========5=========")
'''
[[0 1]
[2 3]]
'''
print(x.T)
print("=========6=========")
'''
[[0 2]
[1 3]]
'''
#一维向量
x = np.array([1.,2.,3.,4.])
print(x)
# array([ 1., 2., 3., 4.])
print("=========7=========")
print(x.T)
# array([ 1., 2., 3., 4.])transpose转置说明:x[0][0] == 0x[0][1] == 1x[1][0] == 2x[1][1] == 3我们不妨设第一个方括号“[]”为 0轴 ,第二个方括号为 1轴 ,则x可在 0-1坐标系 下表示如下:1、x.transpose((0,1)) 表示按照原坐标轴改变序列,也就是保持不变2、x.transpose((1,0)) 表示交换 ‘0轴’ 和 ‘1轴’,所以就得到如下图所示结果:六、展平 flattenndarray.flatten(order='C') 返回折叠为一维的数组的副本,order 参数可选 {‘C’, ‘F’, ‘A’, ‘K’}。a = np.array([[1,2], [3,4]])
a.flatten()
# array([1, 2, 3, 4])
a.flatten('F')
# array([1, 3, 2, 4])“C”表示按行为主(C样式)顺序展平。“F”表示按列为主(Fortran 样式)顺序展平。七、展平为连续数组 ravel1、numpy.ravel(a, order='C') 如同 ndarray.ravel([order]) 返回一个连续的扁平数组,包含输入元素的一维数组2、返回的数组将具有与输入数组相同的类型,例如,将为屏蔽数组输入返回屏蔽数组。x = np.array([[1, 2, 3], [4, 5, 6]])
# 等同于 reshape(-1, order=order)
np.ravel(x)
# array([1, 2, 3, 4, 5, 6])
x.reshape(-1)
# array([1, 2, 3, 4, 5, 6])
np.ravel(x, order='F')
# array([1, 4, 2, 5, 3, 6])ravel和flatten的区别:两者的区别在于返回拷贝(copy)还是返回视图(view),numpy.ravel() 返回的是视图,会影响原始矩阵;numpy. flatten() 返回的是拷贝,对拷贝所做的修改不会影响原始矩阵平时使用的时候flatten()更为合适.在使用过程中flatten()分配了新的内存a = np.arange(12).reshape(3,4)
print(a)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
# 创建一个和a相同内容的数组b
b = a.copy()
c = a.ravel()
d = b.flatten()
# 输出c和d数组
print(c)
# [ 0 1 2 3 4 5 6 7 8 9 10 11]
print(d)
# [ 0 1 2 3 4 5 6 7 8 9 10 11]
# 可以看到c和d数组都是扁平化后的数组,具有相同的内容
print(a is c)
# False
print(b is d)
# False
# 可以看到以上a,b,c,d是四个不同的对象
# 但因为c是a的一种展示方式,虽然他们是不同的对象,但在修改c的时候,a中相应的数也改变了
c[1] = 99
d[1] = 99
print(a) #ravel
# [[ 0 99 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(b) #flatten
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(c)
# [ 0 99 2 3 4 5 6 7 8 9 10 11]
print(d)
# [ 0 99 2 3 4 5 6 7 8 9 10 11]八、去掉为1的维度 squeeze1、ndarray.squeeze(axis=None) 和 numpy.squeeze(a, axis=None)[source]从数组的形状中删除单维度条目,即把 shape 中为1的维度去掉2、将输入的数组删除长度为1的所有维度或维度的子集c = np.arange(10).reshape(2,5)
print(c)
print(np.squeeze(c))
print("-----------------")
d = np.arange(10).reshape(1,2,5)
print(d)
print(d.shape)
print("-----------------")
print(np.squeeze(d))
print(np.squeeze(d).shape)
print("-----------------")
e = np.arange(10).reshape(2,1,5)
print(e)
print(e.shape)
print("-----------------")
print(np.squeeze(e))
print(np.squeeze(e).shape)九、数组堆叠(vstack&hstack)多个数组可以沿不同的轴堆叠在一起a = np.array([[0, 1],
[2, 3]])
print(a)
b = np.array([[5, 6],
[7, 8]])
print(b)
print("---------------")
print(np.vstack((a, b)))
print("---------------")
print(np.hstack((a, b)))
野生程序员在线
Day 01-基础入门
本节内容1、numpy简介2、ndarray介绍3、numpy安装4、numpy基础用法5、numpy数组广播一、什么是 NumPy?NumPy是一个功能强大的Python库,主要用于对多维数组执行计算。NumPy这个词来源于两个单词-- Numerical和Python。NumPy提供了大量的库函数和操作,可以帮助程序员轻松地进行数值计算。这类数值计算广泛用于以下任务:机器学习模型:在编写机器学习算法时,需要对矩阵进行各种数值计算。例如矩阵乘法、换位、加法等。NumPy提供了一个非常好的库,用于简单(在编写代码方面)和快速(在速度方面)计算。NumPy数组用于存储训练数据和机器学习模型的参数。图像处理和计算机图形学:计算机中的图像表示为多维数字数组。NumPy成为同样情况下最自然的选择。实际上,NumPy提供了一些优秀的库函数来快速处理图像。例如,镜像图像、按特定角度旋转图像等。数学任务:NumPy对于执行各种数学任务非常有用,如数值积分、微分、内插、外推等。因此,当涉及到数学任务时,它形成了一种基于Python的MATLAB的快速替代。二、Ndarray 对象从ndarray对象提取的任何元素(通过切片)由一个数组标量类型的 Python 对象表示。 下图显示了ndarray,数据类型对象(dtype)和数组标量类型之间的关系。它从任何暴露数组接口的对象,或从返回数组的任何方法创建一个ndarray。numpy.array(object, dtype = None, copy = True, order = None, subok = False, ndmin = 0)三、安装numpy安装numpy!pip install numpy -i https://pypi.tuna.tsinghua.edu.cn/simple安装jupyter!pip install jupyter notebook -i https://pypi.tuna.tsinghua.edu.cn/simple四、基础用法(重要)4.1、为什么要用numpy比如:我要针对一个数组的元素都乘以2,有2种做法# 方法一
num_list=[1,2,3,4,5,6]
ret_list = []
for num in num_list:
ret_list.append(num*2)
print(ret_list)
print("-----------------")
# 方案二
np_array = np.array(num_list)
print(np_array*2)4.2、各维度数据定义:# 创建ndarray数据
import numpy as np
# 一维数组
a = np.array([1,2,3])
a_ = np.array((1,2,3))
# 二维数组
b = np.array([[1, 2],
[3, 4]])
# 三维数组
c = np.array([[[1, 2],
[3, 4]],
[[1, 2],
[3, 4]]])
print(a)
print("-----------------")
print(a_)
print("-----------------")
print(b)
print("-----------------")
print(c)4.3、数组信息print(a.shape)
print("-----------------")
print(b.shape)
print("-----------------")
print(c.shape)五、数据类型5.1、numpy数据类型NumPy 支持比 Python 更多种类的数值类型。 下表显示了 NumPy 中定义的不同标量数据类型。NumPy 数字类型是dtype(数据类型)对象的实例,每个对象具有唯一的特征5.2、类型对象(dtype)dtype可由一下语法构造:numpy.dtype(object, align, copy)参数为:Object:被转换为数据类型的对象。Align:如果为true,则向字段添加间隔,使其类似 C 的结构体。Copy ? 生成dtype对象的新副本,如果为flase,结果是内建数据类型对象的引用。# 使用数组标量类型
# #int8,int16,int32,int64 可替换为等价的字符串 'i1','i2','i4',以及其他。
import numpy as np
dt = np.dtype(np.int32)
dt_ = np.dtype('i4')
print(dt)
print("----------------")
print(dt_)# 创建结构化数据类型
dt = np.dtype([('age',np.int8)])
a = np.array([(10,),(20,),(30,)], dtype = dt)
print(dt)
print(a['age'])5.4、常量(只需有印象即可)常用的常量如下:# 无穷大
# inf
print(np.inf)
# 它是一个浮点型
print(type(np.inf))
# float
a = np.array([np.inf, -np.inf, 1])
# 显示哪些元素是正无穷大或负无穷大
np.isinf(a) # array([ True, True, False])
#自然数 e
print(type(np.e))
# float
print(np.e)
# 2.718281828459045
#圆周率
print(type(np.pi))
# float
print(np.pi)
# 3.1415926535897935.5、广播(重要)广播(Array Broadcasting):描述的是 NumPy 如何计算不同形状的数组之间的运算。如果是较大的矩阵和较小的矩阵进行运算的话,较小的矩阵就会被广播,从而保证运算的正确进行。如果满足以下规则,可以进行广播:ndim较小的数组会在前面追加一个长度为 1 的维度。输出数组的每个维度的大小是输入数组该维度大小的最大值。如果输入在每个维度中的大小与输出大小匹配,或其值正好为 1,则在计算中可它。如果输入的某个维度大小为 1,则该维度中的第一个数据元素将用于该维度的所有计算。如果上述规则产生有效结果,并且满足以下条件之一,那么数组被称为可广播的。数组拥有相同形状。数组拥有相同的维数,每个维度拥有相同长度,或者长度为 1。数组拥有极少的维度,可以在其前面追加长度为 1 的维度,使上述条件成立。a = np.array([[0.0,0.0,0.0],[10.0,10.0,10.0],[20.0,20.0,20.0],[30.0,30.0,30.0]])
print ('a shape:')
print(a.shape)
b = np.array([1.0,2.0,3.0])
print ('b shape:')
print(b.shape)
print ('第一个数组:')
print(a)
print ('\n')
print ('第二个数组:')
print(b)
print ('\n')
print ('\n')
print ('第一个数组加第二个数组:')
print(a + b)
print ('\n')图片展示了数组b如何通过广播来与数组a兼容:
野生程序员在线
Day 04-数组索引
本节内容:1、基本切片和索引2、数组索引3、布尔数组索引4、缺省索引5、索引数组与切片结合1、基本切片和索引基本切片(Basic slicing)将 Python 的基本概念扩展到 N 个维。 当 obj 是一个切片对象(由方括号内的 start:stop:step 表示法构造),整数或切片对象和整数的元组时,会产生切片操作。1.1、单元素索引单元素索引的工作方式与其他标准 Python 序列的工作方式完全相同。它是基于 0 的,并接受从数组末尾开始索引的负索引x = np.arange(10)
print(x)
x[2]
# 2
x[-2]
# 8
x.shape = (2, 5) # 现在x是二维的
x
'''
array([[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9]])
'''
x[1, 3]
# 8
x[1, -1]
# 9
x[0]
# array([0, 1, 2, 3, 4])
x[0][2]
# 21.2、切片和跨步1)基本的切片语法是 i:j:k,其中 i 是起始索引,j 是停止索引,k是步骤:x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
x[1:7:2]
# array([1, 3, 5])2)负 i 和 j 被解释为 n+i 和 n+j,其中 n 是相应维度中的元素数。负k使步进指数变小。根据上述示例:x[-2:10]
# array([8, 9])
x[-3:3:-1]
# array([7, 6, 5, 4])3)假设n是要切片的维度中的元素数。然后,如果没有给i,k>0 时默认为0,k<0 时默认为 n-1。如果未给定j,则k>0时默认为n,k<0时默认为-n-1。如果未指定k,则默认为1。注意 ::与 : 相同,表示选择沿该轴的所有索引x[5:]
# array([5, 6, 7, 8, 9])4)如果选择元组中的对象数小于 N,则 : 假定后继的任意维。例如:x = np.array([[[1],[2],[3]], [[4],[5],[6]]])
x.shape
# (2, 3, 1)
x[1:2]
'''
array([[[4],
[5],
[6]]])
'''2、数组索引整数数组索引允许根据数组中的任意项的 N 维索引来选择它们。每个整数数组表示该维度中的多个索引。索引数组中允许出现负值,并且与处理单个索引或切片时一样x = np.arange(10, 1, -1)
x
# array([10, 9, 8, 7, 6, 5, 4, 3, 2])
x[np.array([3, 3, 1, 8])]
# array([7, 7, 9, 2])
x[np.array([3, 3, -3, 8])]
# array([7, 7, 4, 2])如果索引值超出界限,则引发 IndexError:x = np.array([[1, 2], [3, 4], [5, 6]])
x[np.array([1, -1])]
'''
array([[3, 4],
[5, 6]])
'''
x[np.array([3, 4])]
'''
Traceback (most recent call last):
...
IndexError: index 3 is out of bounds for axis 0 with size 3
'''3、布尔数组索引当obj是布尔类型的数组对象(可能从比较运算返回)时,会发生这种高级索引案例:可能希望从非NaN的数组中选择所有条目:x = np.array([[1., 2.], [np.nan, 3.], [np.nan, np.nan]])
x[~np.isnan(x)]
# array([1., 2., 3.])或者希望为所有负元素添加一个常量:x = np.array([1., -1., -2., 3])
x[x < 0] += 20
x
# array([ 1., 19., 18., 3.])
x = np.arange(35).reshape(5, 7)
b = x > 20
b[:, 5]
# array([False, False, False, True, True])
x[b[:, 5]]
'''
array([[21, 22, 23, 24, 25, 26, 27],
[28, 29, 30, 31, 32, 33, 34]])
'''4、缺省索引不完全索引是从多维数组的第一个维度获取索引或切片的一种方便方法a = np.arange(0, 100, 10)
b = a[:5]
c = a[a >= 50]
print(b) # >>>[ 0 10 20 30 40]
print(c) # >>>[50 60 70 80 90]5、索引数组与切片结合索引数组可以与切片组合。例如:>>> y[np.array([0, 2, 4]), 1:3]
array([[ 1, 2],
[15, 16],
[29, 30]])实际上,切片和索引数组操作是独立的。切片操作提取索引为 1 和 2 的列(即第 2 和第 3 列),然后是索引数组操作提取索引为 0、2 和 4 的行(即第一、第三和第五行)。
野生程序员在线
Day 10-实战:MLP
本节重点numpy实现MLP一、神经网络结构神经网络的一般结构是由输入层、隐藏层(神经元)、输出层构成的。隐藏层可以是1层或者多层叠加,层与层之间是相互连接的,如下图所示:本节简化一下,现在MLP只有三层:输入层、一层隐藏层、输出层:1、神经元神经元一般常用sigmoid函数,具有激活功能import numpy as np
def sigmoid(x):
return 1 / (1 + np.exp(-x))
# derivative of sigmoid
# sigmoid(y) * (1.0 - sigmoid(y))
# the way we use this y is already sigmoided
def dsigmoid(y):
return y * (1.0 - y)2、神经网络初始化参数隐藏层参数输出层参数输入层参数class MLP_NeuralNetwork(object):
def __init__(self, input, hidden, output):
"""
:param input: number of input neurons
:param hidden: number of hidden neurons
:param output: number of output neurons
"""
self.input = input + 1 # add 1 for bias node
self.hidden = hidden
self.output = output
# set up array of 1s for activations
self.ai = [1.0] * self.input
self.ah = [1.0] * self.hidden
self.ao = [1.0] * self.output
# create randomized weights
self.wi = np.random.randn(self.input, self.hidden)
self.wo = np.random.randn(self.hidden, self.output)
# create arrays of 0 for changes
self.ci = np.zeros((self.input, self.hidden))
self.co = np.zeros((self.hidden, self.output))一般用矩阵做所有这些计算,因为它们速度快,而且非常容易阅读,输入层的大小(特性)、隐藏层的大小(要调优的变量参数)和输出层的数量(可能的类的数量)注意:我们将所有的权重初始化为随机数。重要的是权值是随机的,否则我们将无法调整网络。如果所有的权重是一样的,那么所有隐藏的单位都是一样的,那你的神经网络算法就废了3、前馈网络第一层计算:第一层激活函数:第二层计算(output):第二层激活函数:def feedForward(self, inputs):
if len(inputs) != self.input-1:
raise ValueError('Wrong number of inputs you silly goose!')
# input activations
for i in range(self.input -1): # -1 is to avoid the bias
self.ai[i] = inputs[i]
# hidden activations
for j in range(self.hidden):
sum = 0.0
#输入层神经单元计算结果之和
for i in range(self.input):
sum += self.ai[i] * self.wi[i][j]
self.ah[j] = sigmoid(sum)
# output activations
for k in range(self.output):
sum = 0.0
#隐藏层神经单元计算结果之和
for j in range(self.hidden):
sum += self.ah[j] * self.wo[j][k]
self.ao[k] = sigmoid(sum)
return self.ao[:]4、反向传播(重要)参考:https://www.aiexplorer.blog/article/bp.html4.1、统计误差4.2、反向传播其实如下图所示,输出层BP:隐藏层BP:def backPropagate(self, targets, N):
"""
:param targets: y values
:param N: learning rate
:return: updated weights and current error
"""
if len(targets) != self.output:
raise ValueError('Wrong number of targets you silly goose!')
# calculate error terms for output
# the delta tell you which direction to change the weights
# 计算输出层的梯度
output_deltas = [0.0] * self.output
for k in range(self.output):
error = -(targets[k] - self.ao[k])
output_deltas[k] = dsigmoid(self.ao[k]) * error
# calculate error terms for hidden
# delta tells you which direction to change the weights
# 计算隐藏层的梯度
hidden_deltas = [0.0] * self.hidden
for j in range(self.hidden):
error = 0.0
for k in range(self.output):
error += output_deltas[k] * self.wo[j][k]
hidden_deltas[j] = dsigmoid(self.ah[j]) * error
# update the weights connecting hidden to output
# 更新输出层参数
for j in range(self.hidden):
for k in range(self.output):
change = output_deltas[k] * self.ah[j]
self.wo[j][k] -= self.learning_rate * change + self.co[j][k]
self.co[j][k] = change
# update the weights connecting input to hidden
# 更新隐藏层参数
for i in range(self.input):
for j in range(self.hidden):
change = hidden_deltas[j] * self.ai[i]
self.wi[i][j] -= self.learning_rate * change + self.ci[i][j]
self.ci[i][j] = change
# calculate error loss funciton
# 计算损失函数
error = 0.0
for k in range(len(targets)):
error += 0.5 * (targets[k] - self.ao[k]) ** 2
return error5、训练指定epoch(iter)传入数据集feedforwardbackpropagatedef train(self, patterns, iterations = 3000, N = 0.0002):
# N: learning rate
for i in range(iterations):
error = 0.0
for p in patterns:
inputs = p[0]
targets = p[1]
self.feedForward(inputs)
error = self.backPropagate(targets, N)
if i % 500 == 0:
print('error %-.5f' % error)6、推理(predict)调用feedforwarddef predict(self, X):
"""
return list of predictions after training algorithm
"""
predictions = []
for p in X:
predictions.append(self.feedForward(p))
return predictions二、MLP代码import time
import math
import random
import numpy as np
class MLP_NeuralNetwork(object):
def __init__(self, input, hidden, output,iterations, learning_rate, rate_decay):
"""
:param input: number of input neurons
:param hidden: number of hidden neurons
:param output: number of output neurons
"""
# initialize parameters
self.iterations = iterations
self.learning_rate = learning_rate
self.rate_decay = rate_decay
self.input = input + 1 # add 1 for bias node
self.hidden = hidden
self.output = output
# set up array of 1s for activations
self.ai = [1.0] * self.input
self.ah = [1.0] * self.hidden
self.ao = [1.0] * self.output
# create randomized weights
# use scheme from 'efficient backprop to initialize weights
input_range = 1.0 / self.input ** (1/2)
output_range = 1.0 / self.hidden ** (1/2)
self.wi = np.random.normal(loc = 0, scale = input_range, size = (self.input, self.hidden))
self.wo = np.random.normal(loc = 0, scale = output_range, size = (self.hidden, self.output))
# create arrays of 0 for changes
self.ci = np.zeros((self.input, self.hidden))
self.co = np.zeros((self.hidden, self.output))
#前馈网络
def feedForward(self, inputs):
if len(inputs) != self.input-1:
raise ValueError('Wrong number of inputs you silly goose!')
# input activations
for i in range(self.input -1): # -1 is to avoid the bias
self.ai[i] = inputs[i]
# hidden activations
for j in range(self.hidden):
sum = 0.0
for i in range(self.input):
sum += self.ai[i] * self.wi[i][j]
self.ah[j] = sigmoid(sum)
# output activations
for k in range(self.output):
sum = 0.0
for j in range(self.hidden):
sum += self.ah[j] * self.wo[j][k]
self.ao[k] = sigmoid(sum)
return self.ao[:]
#反向传播
def backPropagate(self, targets):
"""
:param targets: y values
:param N: learning rate
:return: updated weights and current error
"""
if len(targets) != self.output:
raise ValueError('Wrong number of targets you silly goose!')
# calculate error terms for output
# the delta tell you which direction to change the weights
output_deltas = [0.0] * self.output
for k in range(self.output):
error = -(targets[k] - self.ao[k])
output_deltas[k] = dsigmoid(self.ao[k]) * error
# calculate error terms for hidden
# delta tells you which direction to change the weights
hidden_deltas = [0.0] * self.hidden
for j in range(self.hidden):
error = 0.0
for k in range(self.output):
error += output_deltas[k] * self.wo[j][k]
hidden_deltas[j] = dsigmoid(self.ah[j]) * error
# update the weights connecting hidden to output
for j in range(self.hidden):
for k in range(self.output):
change = output_deltas[k] * self.ah[j]
self.wo[j][k] -= self.learning_rate * change + self.co[j][k]
self.co[j][k] = change
# update the weights connecting input to hidden
for i in range(self.input):
for j in range(self.hidden):
change = hidden_deltas[j] * self.ai[i]
self.wi[i][j] -= self.learning_rate * change + self.ci[i][j]
self.ci[i][j] = change
# calculate error
error = 0.0
for k in range(len(targets)):
error += 0.5 * (targets[k] - self.ao[k]) ** 2
return error
#测试
def test(self, patterns):
"""
Currently this will print out the targets next to the predictions.
Not useful for actual ML, just for visual inspection.
"""
for p in patterns:
print(p[1], '->', self.feedForward(p[0]))
#训练
def train(self, patterns):
# N: learning rate
for i in range(self.iterations):
error = 0.0
random.shuffle(patterns)
for p in patterns:
inputs = p[0]
targets = p[1]
self.feedForward(inputs)
error += self.backPropagate(targets)
if i % 10 == 0:
print("iterations:%d ,lr:%-.5f ,error:%-.5f " % (i,self.learning_rate,error))
# with open('error.txt', 'a') as errorfile:
# errorfile.write(str(error) + '\n')
# errorfile.close()
# if i % 10 == 0:
# print('error %-.5f' % error)
# learning rate decay
self.learning_rate = self.learning_rate * (self.learning_rate / (self.learning_rate + (self.learning_rate * self.rate_decay)))
#预测
def predict(self, X):
"""
return list of predictions after training algorithm
"""
predictions = []
for p in X:
predictions.append(self.feedForward(p))
return predictions
野生程序员在线
Huggingface Transformers库学习笔记(三)
前言本部分是Transformer库的基础部分的下半部分,主要包括训练和微调、模型共享和上传、分词器汇总、多语言模型。使用Transformers(Using Transformers)训练和微调(Training and fine-tuning) Transformers 中的模型类被设计成符合PyTorch和TensorFlow 2书写习惯的方式,可以与之无缝衔接使用。在这个快速入门中,我们将展示如何使用两种框架中可用的标准培训工具来微调(或从头开始训练)模型。我们还将展示如何使用内含的Trainer()类,它可以处理很多复杂的训练。本指南假设您已经熟悉加载和使用我们的模型进行推理;否则,请参见任务摘要。我们还假设您熟悉在PyTorch或TF2中训练深度神经网络,并特别关注在🤗 Transformers 中进行模型训练的细微差别和工具。在本地PyTorch中进行微调(Fine-tuning in native PyTorch)在Transformer库中的模型类不以TF或PyTorch模块开始,这意味着您可以使用它们就像任何在PyTorch中的模型那样进行推理和优化。让我们考虑在序列分类数据集上微调像BERT这样的掩码语言模型的常见任务。当我们用from_pretrained()实例化模型时,将使用指定模型的模型配置和预先训练过的权值来初始化模型。该库还包括许多特定于任务的最终层或“头”,它们的权值在指定的预训练模型中不存在时被随机实例化。例如,用 BertForSequenceClassification.from_pretrained('bert-base-uncased', num_labels=2) 实例化一个模型将创建一个BERT模型实例的编码器,它的权值是从 bert-base-uncased 得到,同时位于编码器顶部的随机初始化序列分类头,其输出大小为2。默认情况下,模型以eval模式初始化。我们可以调用model.train()将它置于训练模式。from transformers import BertForSequenceClassification
model = BertForSequenceClassification.from_pretrained('bert-base-uncased')
model.train()输出为:
BertForSequenceClassification(
(bert): BertModel(
(embeddings): BertEmbeddings(
(word_embeddings): Embedding(30522, 768, padding_idx=0)
(position_embeddings): Embedding(512, 768)
(token_type_embeddings): Embedding(2, 768)
(LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
(dropout): Dropout(p=0.1, inplace=False)
)
(encoder): BertEncoder(
(layer): ModuleList(
(0): BertLayer(
(attention): BertAttention(
(self): BertSelfAttention(
(query): Linear(in_features=768, out_features=768, bias=True)
(key): Linear(in_features=768, out_features=768, bias=True)
(value): Linear(in_features=768, out_features=768, bias=True)
(dropout): Dropout(p=0.1, inplace=False)
)
(output): BertSelfOutput(
(dense): Linear(in_features=768, out_features=768, bias=True)
(LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
(dropout): Dropout(p=0.1, inplace=False)
)
)
(intermediate): BertIntermediate(
(dense): Linear(in_features=768, out_features=3072, bias=True)
)
(output): BertOutput(
(dense): Linear(in_features=3072, out_features=768, bias=True)
(LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
(dropout): Dropout(p=0.1, inplace=False)
)
)
## 省略中间(1)-(10)都是一样的结构# (11): BertLayer(
(attention): BertAttention(
(self): BertSelfAttention(
(query): Linear(in_features=768, out_features=768, bias=True)
(key): Linear(in_features=768, out_features=768, bias=True)
(value): Linear(in_features=768, out_features=768, bias=True)
(dropout): Dropout(p=0.1, inplace=False)
)
(output): BertSelfOutput(
(dense): Linear(in_features=768, out_features=768, bias=True)
(LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
(dropout): Dropout(p=0.1, inplace=False)
)
)
(intermediate): BertIntermediate(
(dense): Linear(in_features=768, out_features=3072, bias=True)
)
(output): BertOutput(
(dense): Linear(in_features=3072, out_features=768, bias=True)
(LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
(dropout): Dropout(p=0.1, inplace=False)
)
)
)
)
(pooler): BertPooler(
(dense): Linear(in_features=768, out_features=768, bias=True)
(activation): Tanh()
)
)
(dropout): Dropout(p=0.1, inplace=False)
(classifier): Linear(in_features=768, out_features=2, bias=True)
)输出展示了BERTbaseBERT_{base}BERTbase的内部结构,一共有12层Transformer的encoder层在其中。微调模型是有用的,因为它允许我们使用预先训练的BERT编码器,并且很容易地在我们选择的任何序列分类数据集上训练它。我们可以使用任何PyTorch优化器,但是我们的库也提供了AdamW()优化器,它实现了梯度偏差校正和权重衰减。from transformers import AdamW
optimizer = AdamW(model.parameters(), lr=1e-5)优化器允许我们为特定的参数组应用不同的超参数。例如,我们可以对除偏差和层归一化项外的所有参数应用权重衰减:no_decay = ['bias', 'LayerNorm.weight']
optimizer_grouped_parameters = [
{'params': [p for n, p in model.named_parameters() if not any(nd in n for nd in no_decay)], 'weight_decay': 0.01},
{'params': [p for n, p in model.named_parameters() if any(nd in n for nd in no_decay)], 'weight_decay': 0.0}
]
optimizer = AdamW(optimizer_grouped_parameters, lr=1e-5)这里any() 函数用于判断给定的可迭代参数 iterable 是否全部为 False,则返回 False,如果有一个为 True,则返回 True。元素除了是 0、空、FALSE 外都算 TRUE。现在我们可以使用__call__()设置一个简单的虚拟训练批处理。这将返回一个BatchEncoding()实例,该实例准备我们可能需要传递给模型的所有东西。from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
text_batch = ["I love Pixar.", "I don't care for Pixar."]
encoding = tokenizer(text_batch, return_tensors='pt', padding=True, truncation=True)
input_ids = encoding['input_ids']
attention_mask = encoding['attention_mask']当我们使用labels参数调用分类模型时,第一个返回的元素是预测和传递的标签之间的交叉熵损失。在建立了优化器之后,我们可以向后传递并更新权重:labels = torch.tensor([1,0]).unsqueeze(0)
outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
loss = outputs.loss
loss.backward()
optimizer.step()或者,你也可以直接得到logit,自己计算损失。下面的示例与前面的示例相同:from torch.nn import functional as F
labels = torch.tensor([1,0])
outputs = model(input_ids, attention_mask=attention_mask)
loss = F.cross_entropy(outputs.logits, labels)
loss.backward()
optimizer.step()当然,你可以像往常一样通过调用.to('cuda')将模型和输入放到GPU上来训练。我们还提供了一些学习速率调度工具。通过以下步骤,我们可以设置一个调度器,该调度器先为num_warmup_steps热身,然后在训练结束时线性衰减为0。from transformers import get_linear_schedule_with_warmup
scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps, num_train_steps)然后,我们要做的就是在optimizer.step()之后调用scheduler.step()。loss.backward()
optimizer.step()
scheduler.step()我们强烈推荐使用以下即将讨论的Trainer(),它可以方便地处理训练模型时移动的部分,与诸如混合精度之类的功能和容易进行tensorboard日志记录。冻结编码器(Freezing the encoder)在某些情况下,我们可能对保持预训练编码器的权重不变和只优化头部层的权重感兴趣。要做到这一点,只需在编码器参数上将requires_grad属性设置为False,它可以通过库中任何特定于任务的模型的base_model子模块访问:for param in model.base_model.parameters():
param.requires_grad = False在本地TF-2中微调(Fine-tuning in native TensorFlow 2)暂时不太熟悉,略训练器(Trainer)我们还通过Trainer()和TFTrainer()提供了一个简单但功能完整的训练和评估接口。你可以训练、调整和评估任 Transformers 上的模型,使用广泛的训练选项和内置功能,如日志记录、梯度积累和混合精度。from transformers import BertForSequenceClassification, Trainer, TrainingArguments
model = BertForSequenceClassification.from_pretrained("bert-large-uncased")
training_args = TrainingArguments(
output_dir='./results', # output directory
num_train_epochs=3, # total # of training epochs
per_device_train_batch_size=16, # batch size per device during training
per_device_eval_batch_size=64, # batch size for evaluation
warmup_steps=500, # number of warmup steps for learning rate scheduler
weight_decay=0.01, # strength of weight decay
logging_dir='./logs', # directory for storing logs
)
trainer = Trainer(
model=model, # the instantiated Transformers model to be trained
args=training_args, # training arguments, defined above
train_dataset=train_dataset, # training dataset
eval_dataset=test_dataset # evaluation dataset
)现在只需调用trainer.train()来进行培训,调用trainer.evaluate()来进行评估。也可以使用自己的模块,但是forward返回的第一个参数必须是希望优化的loss。Trainer()使用一个内置的默认函数来整理batch并准备它们以供输入模型。如果需要,还可以使用data_collator参数来传递自己的collator函数,该函数以数据集提供的格式接收数据,并返回准备提供给模型的批处理。注意,TFTrainer()期望传递的数据集是来自tensorflow_datasets的数据集对象。为了计算损失之外的额外指标,您还可以定义自己的compute_metrics函数并将其传递给训练器。from sklearn.metrics import accuracy_score, precision_recall_fscore_support
def compute_metrics(pred):
labels = pred.label_ids
preds = pred.predictions.argmax(-1)
precision, recall, f1, _ = precision_recall_fscore_support(labels, preds, average='binary')
acc = accuracy_score(labels, preds)
return {
'accuracy': acc,
'f1': f1,
'precision': precision,
'recall': recall
}Trainer()类的初始化构造方法中是可以接受compute_metrics参数的,因而可以直接传入。最后,您可以通过在指定的logging_dir目录中启动tensorboard来查看结果,包括任何计算指标。模型共享和上传(Model sharing and uploading)在本页面中,我们将向您展示如何在model hub上与社区共享您培训过的模型或对新数据进行微调的模型。此处内容暂时省略,详细请参考Model sharing and uploading分词器汇总(Summary of the tokenizers)在本页中,我们将更仔细地研究分词器。正如我们在预处理教程中看到的,对文本进行标记就是将其分解为word或sub-word,然后通过查找表将它们转换为id。将word或sub word转换为ids非常简单,因此在本总结中,我们将重点关注将文本拆分为word或subword(即对文本进行tokenize)。更确切的说,我们将关注于Transformer中三种主要的分词器:Byte-Pair Encoding(BPE),WordPiece和SentencePiece,并展示这些类型的分词器被哪些模型所使用。注意:在每个模型页面上,您可以查看相关标记器的文档,以了解预先训练过的模型使用的是哪种标记器类型。例如,如果我们查看BertTokenizer,我们可以看到模型使用WordPiece。简介(Introduction)将文本分割成更小的块是一项比看起来更困难的任务,有多种方法可以做到这一点。例如,让我们来看看这句话“Don't you love🤗transformer ? We sure do.”标记这个文本的一个简单方法是用空格分隔它,这将给出:["Don't", "you", "love", "🤗", "Transformers?", "We", "sure", "do."]这是明智的第一步,但如果我们看看令牌“Transformer ?”和“do.”,我们会注意到标点被附加到单词“Transformer”和“do”上,这不是最优的。我们应该考虑到标点符号,这样模型就不必学习单词的不同表示形式以及它后面可能出现的每一个标点符号,否则模型必须学习的表示形式的数量就会激增。考虑到标点符号,我们将得到如下结果["Don", "'", "t", "you", "love", "🤗", "Transformers", "?", "We", "sure", "do", "."]更好了。然而,符号化如何处理“don't”这个词是不太恰当的。“Don't”代表“do not”,所以最好把它标记为[“do”,“n't”]。这就是事情开始变得复杂的地方,这也是每个模型都有自己的标记器类型的部分原因。根据我们申请对文本进行标记的规则,将为相同的文本生成不同的标记化输出。一个预先训练过的模型只有在输入一个标记化了的规则的情况下才能正常执行,这些标记化的规则与用于标记它的训练数据的规则相同。spaCy和Moses是两个流行的基于规则的分词器。将它们应用到我们的例子中,spaCy和Moses会输出如下内容:["Do", "n't", "you", "love", "🤗", "Transformers", "?", "We", "sure", "do", "."]可以看到,这里使用了空格和标点分词,以及基于规则的分词。空间、标点和规则化都是词的词素化,词的词素化被宽泛地定义为把句子分裂成词。虽然它是将文本分割成更小块的最直观的方法,但这种标记化方法可能会导致大量文本语料库出现问题。在这种情况下,空格和标点标记化通常会生成一个非常大的词汇表(使用的所有唯一单词和标记的集合)。例如,Transformer XL使用空格和标点符号化,导致词汇大小为267,735。如此大的词汇量迫使模型有一个巨大的嵌入矩阵作为输入和输出层,这导致了内存和时间复杂性的增加。一般来说,transformer模型的词汇量很少超过50,000个,特别是当它们仅被预先训练为一种语言时。因此,如果简单的空格和标点符号化不能令人满意,为什么不简单地对字符进行符号化呢? 虽然字符标记化非常简单,可以大大减少内存和时间复杂性,但它使模型更难学习有意义的输入表示。例如,学习字母“t”的有意义的语境无关的表达比学习单词“today”的语境无关的表达要困难得多。因此,字符标记化常常伴随着性能的损失。因此,为了两全其美,transformer模型使用了一种介于词级和字符级的标记化,称为sub word标记化。Sub-word 分词(Subword tokenization)subword 分词算法的原理是:频繁使用的词不应该被拆分为更小的子词,而罕见的词应该被拆分为有意义的子词。例如, "annoyingly" 可能被认为是一个罕见的词,它可以被分解为 "annoying" 和 "ly"。相比把"annoyingly" 单独作为一个subword来说,"annoying" 和 "ly"都是会更频繁出现的独立subword。这在黏着性语言中特别有用,比如土耳其语,你可以通过把subword串在一起来形成(几乎)任意长的复杂单词。subword分词允许模型拥有一个合理的词汇量,同时能够学习有意义的上下文无关的表示。此外,subword 分词使该模型能够通过将它们分解为已知子词来处理它以前从未见过的词。例如, BertTokenizer将把"I have a new GPU!"分词为如下形式from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
tokenizer.tokenize("I have a new GPU!")输出为:["i", "have", "a", "new", "gp", "##u", "!"]因为我们考虑的是无大小写模型,所以句子首先是小写的。我们可以看到这些单词 ["i", "have", "a", "new"] 出现在分词器的词汇表中,但“gpu”这个词却不是。因此,分词器将“gpu”拆分为已知的子词:[“gp”和“##u”]。“##”表示该标记的其余部分应该附加到前一个标记上,不包含空格(用于解码或反转标记)。+作为另一个例子,XLNetTokenizer对我们之前的示例文本分词如下:from transformers import XLNetTokenizer
tokenizer = XLNetTokenizer.from_pretrained("xlnet-base-cased")
tokenizer.tokenize("Don't you love 🤗 Transformers? We sure do.")输出为:["▁Don", "'", "t", "▁you", "▁love", "▁", "🤗", "▁", "Transform", "ers", "?", "▁We", "▁sure", "▁do", "."]我们将在介绍SentencePiece时来介绍"__"的意思,我们可以看到,“Transformer”这个罕见的词已经被分成了更常用的子词“Transform”和“ers”。现在让我们看看不同的subword 分词算法是如何工作的。请注意,所有这些分词算法都依赖于某种形式的训练,而这种训练通常是在训练相应模型的语料库上进行的。字节对编码(Byte-Pair Encoding (BPE))字节对编码(BPE)是由 _ Neural Machine Translation of Rare Words with Subword Units (Sennrich et al., 2015)._ 引入。BPE依赖于将训练数据分解成单词的预标记器。预标记化可以像空间标记化一样简单,例如GPT-2、Roberta。更高级的预标记化包括基于规则的标记化,例如XLM,FlauBERT对大多数语言使用Moses,或使用Spacy和ftfy的GPT,来计算训练语料库中每个单词的频率。在预标记化之后,创建了一组唯一的单词,并确定了每个单词在训练数据中出现的频率。接下来,BPE创建一个基础词汇表,该词汇表包含出现在唯一词汇集中的所有符号,并学习合并规则,从基础词汇表的两个符号形成一个新符号。它一直这样进行下去,直到词汇表达到所需的词汇量为止。注意,所需的词汇表大小是在训练标记器之前定义的超参数。举个例子,假设在预标记化之后,已经确定了以下一组单词,包括它们的频率:("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)因此,基本词汇是“b”、“g”、“h”,“n”,“p”,“s”、“u”。将所有单词分解为基础词汇的符号,我们得到:("h" "u" "g", 10), ("p" "u" "g", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "u" "g" "s", 5)然后BPE计算每个可能的符号对的频率,并选择出现频率最高的符号对。在这个例子中,前面是"h",后面跟着"u"的组合出现了10+5=15次(10次在"hug"中出现,5次在"hugs"中出现)。然而,最频繁的是"u"后面跟着"g",这个组合出现了10+5+5=20次。因此,分词器学习的第一个合并规则是将所有的“u”符号和一个“g”符号组合在一起。接下来,“ug”被添加到词汇表中。这组词就变成了:("h" "ug", 10), ("p" "ug", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "ug" "s", 5)然后BPE标识下一个最常见的符号对。就是"un"共出现16次,然后"u"和"n"合并加入到词表中。紧随其后的是“h”和“ug”,共出现了15次。再次将这两个词合并,"hug"也添加到词汇表中。在这个阶段,词汇表是["b", "g", "h", "n", "p", "s", "u", "ug", "un", "hug"]我们的一组唯一的单词被表示为("hug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("hug" "s", 5)假设字节对编码训练在此停止,那么学习到的合并规则将应用于新单词(只要这些新单词不包括基础词汇表中不包含的符号)。例如,当单词"bug"被分词为["b","ug"],但"mug"将被分词为["<unk>", "ug"],这是因为符号'm"并不在基础词汇表中。通常来说,单个字母并不会被替换为"<unk>"符号因为训练数据中每个字母都会至少出现一次,但对于一些非常特殊的字符比如表情来说就不是这样了。如前所述,词汇量大小,即基本词汇量大小+合并次数,是一个可以选择的超参数。例如,GPT的词汇表大小为40,478,因为它们有478个基本字符,并且选择在40,000个合并后停止训练。字节级别BPE(Byte-level BPE)一个包含所有可能的基本字符的基本词汇表可能非常大,例如,如果所有unicode字符都被认为是基本字符。为了获得更好的基本词汇表,GPT-2使用字节作为基本词汇表,这是一种聪明的技巧,它强制基本词汇表的大小为256,同时确保每个基本字符都包含在词汇表中。通过一些处理标点符号的额外规则,GPT2的标记化器可以对每个文本进行标记,而不需要<unk>符号。GPT-2的词汇表大小为50257,它对应256字节的基本token、一个特殊的文本结束token和通过50,000个合并学习到的符号。WordPieceWordPiece是用于BERT、蒸馏BERT和Electra的子词标记化算法。算法概述在 Japanese and Korean Voice Search (Schuster et al., 2012),和BPE非常相似。WordPiece首先初始化词汇表,包括训练数据中出现的每个字符,并逐步学习给定数量的合并规则。与BPE不同,WordPiece并不选择最频繁的符号对,而是选择在将训练数据添加到词汇表后最大限度地提高其可能性的符号对。那么这到底意味着什么呢?参照前面的例子,最大化训练数据的似然性等同于找到符号对,其概率除以其第一个符号之后第二个符号的概率在所有符号对中最大。如。只有当“ug”除以“u”,“g”的概率大于任何其他符号对时,“u”和“g”才会被合并。直观地说,WordPiece与BPE略有不同,因为它通过合并两个符号来评估它的损失,以确保它是有价值的。UnigramUnigram是一种subword 分词算法,详情在_Subword Regularization: Improving Neural Network Translation Models with Multiple Subword Candidates (Kudo, 2018)._。与BPE或WordPiece不同,Unigram将其基本词汇表初始化为大量的符号,并逐步削减每个符号以获得更小的词汇表。例如,基本词汇表可以对应所有预先标记的单词和最常见的子字符串。Unigram不是直接用于Transformer中的任何模型,但是它与SentencePiece连用。在每个训练步骤中,Unigram算法在给定当前词汇表和Unigram语言模型的情况下定义训练数据的损失(通常定义为对数似然)。然后,对于词汇表中的每个符号,算法计算如果将该符号从词汇表中移除,总损失将增加多少。Unigram然后移除p (p通常是10%或20%)损失增加最少的符号的百分比,即那些对训练数据的整体损失影响最小的符号。重复这个过程,直到词汇表达到所需的大小。Unigram算法总是保留基本字符,以便任何单词都可以被标记。由于Unigram不基于合并规则(与BPE和WordPiece不同),该算法在训练后有多种对新文本进行标记的方法。例如,如果一个训练好的Unigram标记器显示词汇表:
["b", "g", "h", "n", "p", "s", "u", "ug", "un", "hug"],"hugs"可以被分词为 ["hug", "s"], ["h", "ug", "s"]或者["h", "u", "g", "s"]。那么到底该选择哪一种呢?Unigram在保存词汇的基础上,保存了训练语料库中每个token的概率,这样在训练后就可以计算每个可能的token化概率。该算法只是在实践中选择最有可能的标记化,但也提供了根据其概率对可能的标记化进行抽样的可能性。这些概率是由标记器所受训练的损失定义的。假设训练数据由以下单词组成:x1x2⋯ xNx_1, x_2, \cdots, x_Nx1,x2,⋯,xN,并且单词xix_ixi的所有可能的标记化的集合定义为S(xi)S(x_i)S(xi),那么总的损失定义为SentencePiece到目前为止所描述的所有标记化算法都有相同的问题:假设输入文本使用空格来分隔单词。然而,并不是所有的语言都使用空格来分隔单词。一个可能的解决方案是使用特定语言的预分词(Pre-Tokenizer),例如,XLM使用特定的汉语、日语和泰国语预标记器)。为了更普遍地解决这个问题,_SentencePiece: A simple and language independent subword tokenizer and detokenizer for Neural Text Processing (Kudo et al., 2018) _ 将输入视为原始输入流,因此包括要使用的字符集中的空格。然后,它使用BPE或unigram算法构造适当的词汇表。例如,XLNetTokenizer使用了句子,这也是为什么在前面的例子中“__”字符被包含在词汇表中。使用SentencePiece进行解码非常简单,因为所有的符号都可以连接起来,而“__”被一个空格所取代。该库中所有使用句子块的transformer模型都将它与unigram结合使用。使用SentencePiece的例子有ALBERT、XLNet、Marian和T5。多语言模型(Multi-lingual models)这个库中的大多数模型都是单语言模型(英语、汉语和德语)。有一些多语言模型是可用的,它们具有与单语言模型不同的机制。此页面详细介绍了这些模型的使用。目前支持多种语言的两个模型是BERT和XLM。XLM不太了解,略BERTBERT有两个checkpoint,可以用于多语言任务:bert-base-multilingual- uncastion(掩码语言建模+下一句预测,102种语言)bert-base-multilingual(掩码语言建模+下一句预测,104种语言)这些checkpoint不需要在推理时进行语言嵌入。他们应该识别上下文中使用的语言,并据此进行推断。
野生程序员在线
Huggingface Transformers库学习笔记(一)
前言Huggingface的Transformers库是一个很棒的项目,该库提供了用于自然语言理解(NLU)任务(如分析文本的情感)和自然语言生成(NLG)任务(如用新文本完成提示或用另一种语言翻译)的预先训练的模型。其收录了在100多种语言上超过32种预训练模型。这些先进的模型通过这个库可以非常轻松的调取。同时,也可以通过Pytorch和TensorFlow 2.0进行编写修改等。本系列学习资料来自于该库的官方文档(v4.4.2),链接为Transformers入门(Get Started)快速开始(Quick tour)该库下载了用于自然语言理解(NLU)任务(如分析文本的情感)和自然语言生成(NLG)任务(如用新文本完成提示或用另一种语言翻译)的预先训练的模型。首先,我们利用管道API来快速使用那些预先训练好的模型。然后,我们将进一步挖掘,看看这个库是如何访问这些模型并预处理数据。使用Pipeline快速上手(Getting started on a task with a pipeline)在给定任务上使用预训练模型的最简单方法是使用pipeline()方法。Transformers对以下任务提供了开箱即用的方法:情感分析:文本是积极的还是消极的?文本生成(英文):提供一个提示,模型将生成以下内容。名称实体识别(NER):在一个输入句子中,用它所代表的实体(人、地点等)标记每个单词。填空文本:给定一个带有填空单词的文本(例如,用[MASK]代替),填空。摘要:生成一篇长文章的摘要。翻译:用另一种语言翻译一篇文章。特征提取:返回文本的张量表示。对于一个简单的任务,例如对一个句子进行文本情感分类,利用Pipeline可以在几行代码之间完成这项工作:from transformers import pipeline
# 导入pipeline
classifier = pipeline('sentiment-analysis')
# 使用文本情感分析任务的分类器
classifier('We are very happy to show you the 🤗 Transformers library.') # 进行文本情感分类输出如下:[{'label': 'POSITIVE', 'score': 0.9997795224189758}]可以看到,分类器很快给出了判别结果和得分。当然,也可以使用一个句子列表作为输入,来作为一个batch形式输入到模型中。results = classifier(["We are very happy to show you the 🤗 Transformers library.",
"We hope you don't hate it."])
for result in results:
print(f"label: {result['label']}, with score: {round(result['score'], 4)}")输出如下:label: POSITIVE, with score: 0.9998label: NEGATIVE, with score: 0.5309默认情况下,为该pipeline方法下载的模型称为“distilbert-base-uncased-finetuned-sst-2-english”。它使用蒸馏器架构,并在一个名为SST-2的数据集上进行了微调,用于情感分析任务。详细信息参考:huggingface.co/distilbert-…假设我们想使用另一个模型; 比如,一个接受过法语数据训练的文本情感分类模型。那么可以在模型中心搜索,链接为:model hub。然后限定左边的标签栏中选定fr和text classification标签,就可以看到符合任务需求的模型展示,这里可以看到第一个模型的下载量是143k,可以使用这个模型来做法语的文本情感分析。在使用时,可以直接在pipeline方法中指定model为“nlptown/bert-base-multilingual-uncased-sentiment”。classifier = pipeline('sentiment-analysis', model="nlptown/bert-base-multilingual-uncased-sentiment")这个分类器现在可以处理文本在英语,法语,也可以处理荷兰语,德语,意大利语和西班牙语! 我们还可以用一个保存了预训练模型的本地文件路径替换该名称。我们还可以传递一个模型对象及其相关的分词器(Tokenizer)。为此我们需要两个类。第一个是AutoTokenizer,我们将使用它来下载与我们选择的模型相关联的分词器并实例化它。第二个是AutoModelForSequenceClassification,我们将使用它来下载模型本身。首先我们导入这两个类from transformers import AutoTokenizer, AutoModelForSequenceClassification现在,要下载我们前面找到的模型和标记器,我们只需要使用from_pretrained()方法(可以随意用model hub中的任何其他模型替换model_nammodel_name = "nlptown/bert-base-multilingual-uncased-sentiment"model = AutoModelForSequenceClassification.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)
classifier = pipeline('sentiment-analysis', model=model, tokenizer=tokenizer)预训练模型的简要剖析(Under the hood: pretrained models)现在让我们看看在使用这些管道时,在引擎盖下面会发生什么。正如我们所看到的,模型和标记器是使用from_pretrained方法创建的。from transformers import AutoTokenizer, AutoModelForSequenceClassification
model_name = "distilbert-base-uncased-finetuned-sst-2-english"pt_model = AutoModelForSequenceClassification.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)使用分词器(Using the tokenizer)分词器负责文本的预处理。首先,它将以单词(或部分单词、标点符号等)分割给定的文本,通常称为token。有多种规则可以管理这个过程,因此需要使用模型的名称来实例化分词器,以确保使用与模型预先训练时相同的规则。第二步是将这些token转换为数字,以便能够利用它们构建一个张量并将它们提供给模型。因而,分词器有一个词汇表,在使用from_pretrained方法实例化它时,程序将自动下载。要确保使用与模型预训练时相同的词汇表。要在给定文本上应用这些步骤,只需将文本提供给分词器。inputs = tokenizer("We are very happy to show you the 🤗 Transformers library.")
print(inputs)得到输出{'input_ids': [101, 2057, 2024, 2200, 3407, 2000, 2265, 2017, 1996, 100, 19081, 3075, 1012, 102], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}可以直接将句子列表传递给标记器。 如果我们的目标是将它们作为批处理发送到模型中,那么首先将它们填充到相同的长度,之后将它们截断到模型可以接受的最大长度,然后返回张量。pt_batch = tokenizer(
["We are very happy to show you the 🤗 Transformers library.", "We hope you don't hate it."],
padding=True,
truncation=True,
max_length=512,
return_tensors="pt")填充将自动应用到模型长度不够的那些句子上(如例子中的第二句,"We hope you don't hate it."),并使用模型预训练的填充token。 attention mask也适用。for key, value in pt_batch.items():
print(f"{key}: {value.numpy().tolist()}")使用模型(Using the model)输入被分词器预处理后,就可以直接发送到模型。对于PyTorch模型,需要通过添加**解包字典方式实现参数传递。关于**方式解包参数,请参考Python函数参数中的*与**运算符pt_outputs = pt_model(**pt_batch)
print(pt_outputs)
print(pt_outputs[0])输出为SequenceClassifierOutput(loss=None, logits=tensor([[-4.0833, 4.3364], [ 0.0818, -0.0418]], grad_fn=<AddmmBackward>), hidden_states=None, attentions=None)
tensor([[-4.0833, 4.3364], [ 0.0818, -0.0418]], grad_fn=<AddmmBackward>)输出时是一个SequenceClassifierOutput对象,其中logoits属性即经过最后分类(如果config.num_labels==1,则回归)分数(在SoftMax之前)。之后,可以使用Softmax激活来获取最终的预测结果。import torch.nn.functional as F
pt_predictions = F.softmax(pt_outputs[0], dim=-1)
print(pt_predictions)得到输出为:tensor([[2.2043e-04, 9.9978e-01], [5.3086e-01, 4.6914e-01]], grad_fn=<SoftmaxBackward>)预训练模型本身都是torch.nn.Module或tensorflow.keras.Model类型的, 因此可以在PyTorch或TensorFlow的框架下进行训练。可以将标签提供给模型,它将返回一个包含loss和最终激活的元组。import torch
pt_outputs = pt_model(**pt_batch, labels = torch.tensor([1, 0]))
print(pt_outputs[0])得到输出为:SequenceClassifierOutput(loss=None, logits=tensor([[-4.0833, 4.3364],
[ 0.0818, -0.0418]], grad_fn=<AddmmBackward>), hidden_states=(tensor([[[ 0.3549, -0.1386, -0.2253, ..., 0.1536, 0.0748, 0.1310],
[-0.5773, 0.6791, -0.9738, ..., 0.8805, 1.1044, -0.7628],
[-0.3451, -0.2094, 0.5709, ..., 0.3208, 0.0853, 0.4575],
...,
[ 0.4431, 0.0931, -0.1034, ..., -0.7737, 0.0813, 0.0728],
[-0.5605, 0.1081, 0.1229, ..., 0.4519, 0.2104, 0.2970],
[-0.6116, 0.0156, -0.0555, ..., -0.1736, 0.1933, -0.0021]],
[[ 0.3549, -0.1386, -0.2253, ..., 0.1536, 0.0748, 0.1310],
[-0.5773, 0.6791, -0.9738, ..., 0.8805, 1.1044, -0.7628],
[-0.7195, -0.0363, -0.6576, ..., 0.4434, 0.3358, -0.9249],
...,
[ 0.0073, -0.5248, 0.0049, ..., 0.2801, -0.2253, 0.1293],
[-0.0790, -0.5581, 0.2347, ..., 0.2370, -0.5104, 0.0770],
[-0.0958, -0.5744, 0.2631, ..., 0.2453, -0.3293, 0.1269]]],
grad_fn=<NativeLayerNormBackward>), tensor([[[ 5.0274e-02, 1.2093e-02, -1.1208e-01, ..., 6.2100e-02, 1.9892e-02, 3.6863e-02],
……
# 中间结果太多,省略
……
[[3.4474e-02, 3.1367e-02, 2.3187e-01, ..., 0.0000e+00, 0.0000e+00, 0.0000e+00],
[3.5311e-02, 3.3985e-02, 5.4093e-02, ..., 0.0000e+00, 0.0000e+00, 0.0000e+00],
[1.0943e-02, 3.1733e-03, 2.9226e-01, ..., 0.0000e+00, 0.0000e+00, 0.0000e+00],
...,
[9.0263e-03, 3.5084e-03, 2.9081e-01, ..., 0.0000e+00, 0.0000e+00, 0.0000e+00],
[1.5904e-02, 8.0937e-03, 2.1290e-01, ..., 0.0000e+00, 0.0000e+00, 0.0000e+00],
[1.3486e-02, 6.0674e-03, 2.1980e-01, ..., 0.0000e+00, 0.0000e+00, 0.0000e+00]]]], grad_fn=<SoftmaxBackward>)))可以看到各种隐藏层的状态也都在这个对象中包含。Note: 这里给不给标签都有输出的各个类别的概率,我觉得给了标签就相当于是训练所以可以得到loss,而不给标签相当于测试,所以没有loss。一旦模型经过了微调,就可以按照以下方式与分词器一同保存tokenizer.save_pretrained(save_directory)model.save_pretrained(save_directory)然后,可以使用from_pretrained()方法通过传递目录名而不是模型名来加载这个模型。from transformers import AutoModel, AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(save_directory)
model = AutoModel.from_pretrained(save_directory)最后,如果需要,也可以要求模型返回所有隐藏状态和所有注意权重。pt_outputs = pt_model(**pt_batch, output_hidden_states=True, output_attentions=True)
all_hidden_states, all_attentions = pt_outputs[-2:]访问代码(Accessing the code)AutoModel和AutoTokenizer类只是快捷方式,可以自动地与任何预先训练过的模型一起工作。在幕后,每个体系结构加类的组合都有一个库模型类,所以如果需要,代码很容易访问和调整。在我们前面的例子中,“distilbert-base-uncased-finetuned-sst-2-english”模型使用DistilBERT架构。当使用 AutoModelForSequenceClassification时,自动创建的模型是 DistilBertForSequenceClassification。from transformers import DistilBertTokenizer, DistilBertForSequenceClassification
model_name = "distilbert-base-uncased-finetuned-sst-2-english"model = DistilBertForSequenceClassification.from_pretrained(model_name)
tokenizer = DistilBertTokenizer.from_pretrained(model_name)自定义模型(Customizing the model)如果想要改变模型本身的构建方式,可以定义自定义配置类。每个架构都有自己的相关配置(例如在DistilBERT模型中, 可以配置DistilBertConfig这个类),它允许设置隐藏层的维度、dropout概率等。如果做核心修改,比如改变隐藏的大小,将不能再使用一个预先训练的模型,需要从头开始训练。然后,可以直接从这个配置实例化模型。这里我们使用了DistilBERT的预定义词汇表(因此用from_pretrained()方法加载分词器))并从头初始化模型(因此从配置中实例化模型,而不是使用from_pretrained()方法)。from transformers import DistilBertConfig, DistilBertTokenizer, DistilBertForSequenceClassification
config = DistilBertConfig(n_heads=8, dim=512, hidden_dim=4*512)
tokenizer = DistilBertTokenizer.from_pretrained('distilbert-base-uncased')
model = DistilBertForSequenceClassification(config)对于只更改模型头部的东西(例如,标签的数量),仍然可以为主体使用一个预先训练过的模型。 例如,我们使用一个预先训练的body为10个不同的标签定义一个分类器。可以用所有默认值创建一个配置,只更改标签的数量,但更简单的是,直接将配置需要的任何参数传递给from_pretrained()方法,它将用它更新默认配置。from transformers import DistilBertConfig, DistilBertTokenizer, DistilBertForSequenceClassification
model_name = "distilbert-base-uncased"model = DistilBertForSequenceClassification.from_pretrained(model_name, num_labels=10)
tokenizer = DistilBertTokenizer.from_pretrained(model_name)安装(Installation)安装部分比较简单,首先需要配置pytorch或TensorFlow 2.0环境。可以根据TensorFlow installation page,PyTorch installation page ,上的提示进行下载安装。之后直接pip install transformers即可开发理念(Philosophy)Transformers是一个固执己见的(opinionated)库,它被开发为了以下人群:那些寻求使用/研究/扩展大型Transformer模型的NLP研究人员和教育者想要微调这些模型和/或在生产中为它们服务的实践者只想下载一个预先训练过的模型并使用它来解决给定的自然语言处理任务的工程师。该库的设计目标有两个:尽可能的简单和能够快速使用该库强烈地限制了要学习的面向用户的抽象的数量,事实上,几乎没有抽象,每个模型只需要三个标准类: configuration, models 和tokenizer.。所有这些类可以被用一种简单和统一的方式初始化:通过使用一个共同from_pretrained()实例化方法。该方法将负责从Hugging Face Hub提供或你自己保存的pretrained checkpoint进行下载(如果需要)、缓存和加载相关的类实例和相关数据(配置hyper-parameters、分词器词汇和模型权重)在这三个基类之上,库提供了两个api: pipeline()用于在给定任务上快速使用模型(及其相关的标记器和配置),Trainer()/TFTrainer()用于快速训练或调优给定模型。因此,这个库不是神经网络构建模块的模块化工具箱。如果你想扩展/构建这个库,只需要使用常规的Python/PyTorch/TensorFlow/Keras模块,并从库的基类继承来重用模型加载/保存等功能。提供最先进的模型,使其性能尽可能接近原始模型我们为每个体系结构提供至少一个例子,它重现了该体系结构的官方作者提供的结果。代码通常尽可能接近原始代码基础,这意味着一些PyTorch代码可能由于被转换为TensorFlow代码而不像它可能的那样pytorchic,反之亦然。其他几个目标:尽可能一致地公开模型内部:我们使用一个API来访问完整的隐藏状态和注意力权重。标记器和基本模型的API都经过了标准化,可以方便地在模型之间切换。结合对这些模型进行微调/研究的有希望的工具的主观选择:一种简单/一致的方式向词汇表和用于微调的嵌入添加新标记。简单的方法来屏蔽和修剪变压器头。在PyTorch和TensorFlow 2.0之间轻松切换,允许使用一个框架进行训练,使用另一个框架进行推理。主要概念(Main concepts)这个库围绕每个模型的三种类型的类构建:Model classes,比如BertModel,它是30多个PyTorch模型(torch.nn.Module)或Keras模型(tf.keras.Model),使用库中提供的预先训练的权重。Configuration classes,比如BertConfig,它存储了构建模型所需的所有参数。你并不总是需要自己实例化这些。特别地,如果您使用一个未经任何修改的预先训练的模型,创建模型将自动地处理配置的实例化(它是模型的一部分)。Tokenizer classes,比如BertTokenizer,它存储每个模型的词汇表,并提供对输入到模型的token嵌入索引列表中的字符串进行编码/解码的方法。所有这些类都可以从预先训练的实例中实例化,并使用两种方法在本地保存。from_pretrained() 让您从库本身提供的(受支持的模型在这里的列表中提供)或用户本地存储(或服务器上)的预训练版本中实例化模型/配置/标记器save_pretrained() 允许你在本地保存模型/配置/标记器,以便可以使用from_pretrained()重新加载它。术语表(Glossary)一般术语(General terms)autoencoding models: 自编码器模型。 参考 MLMautoregressive models: 自回归模型。 参考 CLMCLM: 因果语言模型。 因果语言模型是一种训练前的任务,模型按顺序阅读文本,并预测下一个单词。这通常是通过阅读整个句子来完成的,但是在模型中使用一个掩码来隐藏将来某个时间步长的标记。causal language modeling, a pretraining task where the model reads the texts in order and has to predict the next word. It’s usually done by reading the whole sentence but using a mask inside the model to hide the future tokens at a certain timestep.deep learning: 深度学习。 使用多层神经网络的机器学习算法。machine learning algorithms which uses neural networks with several layers.MLM: 遮罩语言模型。 这是一种预训练任务,让模型看到文本的损坏版本,通常通过随机屏蔽一些token来完成,并且必须预测原始文本。masked language modeling, a pretraining task where the model sees a corrupted version of the texts, usually done by masking some tokens randomly, and has to predict the original text.multimodal: 多模态。 将文本与另一种输入(例如图像)组合在一起的任务。a task that combines texts with another kind of inputs (for instance images).NLG: 自然语言生成。 所有与生成文本相关的任务(例如与Transformers对话、翻译)。natural language generation, all tasks related to generating text (for instance talk with transformers, translation).NLP: 自然语言处理。 一种处理文本的通用方式。natural language processing, a generic way to say “deal with texts”.NLU: 自然语言理解。 所有与理解文本内容相关的任务(例如对整个文本和单个单词进行分类)。natural language understanding, all tasks related to understanding what is in a text (for instance classifying the whole text, individual words).pretrained model: 预训练模型。 在某些数据(例如Wikipedia的所有数据)上预先训练过的模型。预训练方法包括一个自我监督的目标,它可以是阅读文本并尝试预测下一个单词(参见CLM),或者屏蔽一些单词并尝试预测它们(参见MLM)。a model that has been pretrained on some data (for instance all of Wikipedia). Pretraining methods involve a self-supervised objective, which can be reading the text and trying to predict the next word (see CLM) or masking some words and trying to predict them (see MLM).RNN: 循环神经网络。 一种在层上使用循环来处理文本的模型。recurrent neural network, a type of model that uses a loop over a layer to process texts.self-attention: 自注意力。 输入的每个元素都找出它们应该“注意”的其他输入元素。each element of the input finds out which other elements of the input they should attend to.seq2seq or sequence-to-sequence: 序列到序列(模型)。 从输入中生成新序列的模型,如翻译模型或总结模型(如Bart或T5)。models that generate a new sequence from an input, like translation models, or summarization models (such as Bart or T5).token : 一个句子的一部分,通常是一个词,但也可以是一个子词(非常用词常被拆分在子词中)或标点符号。a part of a sentence, usually a word, but can also be a subword (non-common words are often split in subwords) or a punctuation symbol.transformer : 基于自我注意的深度学习模型体系结构。self-attention based deep learning model architecture.translation models, or summarization models (such as Bart or T5).token : 一个句子的一部分,通常是一个词,但也可以是一个子词(非常用词常被拆分在子词中)或标点符号。a part of a sentence, usually a word, but can also be a subword (non-common words are often split in subwords) or a punctuation symbol.transformer : 基于自我注意的深度学习模型体系结构。self-attention based deep learning model architecture.模型输入(Model inputs)每一种模型都是不同的,但也有相似之处。因此,大多数模型使用相同的输入,这里将详细介绍使用示例。输入ID(Input IDs)输入id通常是作为输入传递给模型的唯一必需参数。它们是token的索引,是构建序列的token的数字表示,这些序列将被模型用作输入。分词器负责将序列拆分为分词器词汇表中可用的token。每个分词器的工作方式不同,但底层机制是相同的。下面是一个使用BERT分词器的例子,这是一个Word-piece 分词器。分词器负责将序列拆分为分词器词汇表中可用的标记。from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained("bert-base-cased")
sequence = "A Titan RTX has 24GB of VRAM"tokenized_sequence = tokenizer.tokenize(sequence)
print(tokenized_sequence)得到输出为['A', 'Titan', 'R', '##T', '##X', 'has', '24', '##GB', 'of', 'V', '##RA', '##M']可以看到,标记可以是单词,也可以是子单词。例如,在这里,VRAM不在模型词汇表中,所以它被分为V、RA和M。为了表明这些标记不是单独的单词,而是同一个单词的一部分,为RA和M添加了一个双哈希前缀(即#)然后可以将这些token转换为模型可以理解的id。这可以通过直接将句子提供给分词器来实现,该分词器利用huggingface/tokenizers的Rust实现峰值性能。标记器返回一个字典,其中包含其对应模型正常工作所需的所有参数。token的索引位于键input_ids之下。inputs = tokenizer(sequence)
encoded_sequence = inputs["input_ids"]
print(encoded_sequence)得到输出[101, 138, 18696, 155, 1942, 3190, 1144, 1572, 13745, 1104, 159, 9664, 2107, 102]注意,标记器会自动添加特殊的标记(如果关联的模型依赖它们的话),这些标记是模型有时使用的特殊id。如果我们对之前序列的id进行解码输出,可以看到decoded_sequence = tokenizer.decode(encoded_sequence)
print(decoded_sequence)输出为[CLS] A Titan RTX has 24GB of VRAM [SEP]这个就是BERT模型所希望的输入格式。注意力掩码(Attention mask)Attention mask是一个可选参数,当将序列批处理在一起时使用。这个参数指示模型应该关注哪些token,哪些不应该关注。例如,考虑这两个序列from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained("bert-base-cased")
sequence_a = "This is a short sequence."sequence_b = "This is a rather long sequence. It is at least longer than the sequence A."encoded_sequence_a = tokenizer(sequence_a)["input_ids"]
encoded_sequence_b = tokenizer(sequence_b)["input_ids"]两个序列编码后的长度并不一致。len(encoded_sequence_a), len(encoded_sequence_b)输出(8, 19)因此,我们不能把它们放在同一个张量中。除非第一个序列需要填充到第二个序列的长度,或者第二个序列需要截断到第一个序列的长度。在第一种情况下,id列表将由填充(Padding)索引扩展。我们可以传递一个列表给分词器,并要求它像这样填充。padded_sequences = tokenizer([sequence_a, sequence_b], padding=True)
padded_sequences["input_ids"]输出[[101, 1188, 1110, 170, 1603, 4954, 119, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [101, 1188, 1110, 170, 1897, 1263, 4954, 119, 1135, 1110, 1120, 1655, 2039, 1190, 1103, 4954, 138, 119, 102]]我们可以看到,在第一个句子的右边添加了0,使它与第二个句子的长度相同,然后可以将其转换为PyTorch或TensorFlow中的张量。注意attention mask是一个二值张量,表示填充指标的位置,这样模型就不会注意到它们。对于BertTokenizer, 1表示应该处理的值,而0表示填充的值。这个attention mask位于标记器返回的键attention_mask下的字典中。padded_sequences["attention_mask"]输出[[1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]Token类型ID(Token Type IDs)有些模型的目的是进行序列分类或回答问题。这需要将两个不同的序列连接到单个input_ids条目中,这通常需要特殊标记的帮助,例如分类器([CLS])和分隔符([SEP])。例如,BERT模型这样构建它的两个序列输入# [CLS] SEQUENCE_A [SEP] SEQUENCE_B [SEP]我们可以像这样使用分词器自动生成这样的句子,方法是将这两个序列作为两个参数(而不是像前面那样的列表)传递给分词器 from transformers import BertTokenizer tokenizer = BertTokenizer.from_pretrained("bert-base-cased") sequence_a = "HuggingFace is based in NYC"sequence_b = "Where is HuggingFace based?"encoded_dict = tokenizer(sequence_a, sequence_b) decoded = tokenizer.decode(encoded_dict["input_ids"])我们打印输出一下解码后的句子python复制代码
print(decoded)输出为[CLS] HuggingFace is based in NYC [SEP] Where is HuggingFace based? [SEP]对于一些模型来说,这足以理解一个序列在哪里结束,另一个序列在哪里开始。然而,其他模型,如BERT,也部署Token type id(也称为segment id)。它们被表示为一个二进制mask,标识模型中的两种序列类型。分词器将此mask作为token_type_ids条目返回。encoded_dict['token_type_ids']输出为[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1]第一个序列是用于问题的上下文,它的所有标记都用0表示,而第二个序列对应于问题,它的所有标记都用1表示。有些模型,如XLNetModel,使用了一个额外的token,由2表示。位置ID(Position IDs)与将每个token的位置嵌入其中的RNN相反,transformer不知道每个令牌的位置。因此,模型使用位置id (position_ids)来标识每个标记在标记列表中的位置。它们是一个可选参数。如果没有将position_ids传递给模型,则IDs将自动创建为绝对位置嵌入。绝对位置嵌入在范围[0,config.max_position_embeddings - 1)。一些模型使用其他类型的位置嵌入,如正弦位置嵌入或相对位置嵌入。标签(Labels)标签是一个可选参数,它可以被传递给模型来计算自己的损失。这些标签应该是模型的预期预测:它将使用标准损失来计算预测和期望值(标签)之间的损失。例如,这些标签根据模型头的类型而不同。对于序列分类模型(例如, BertForSequenceClassification),模型期望一个维度张量为(batch_size),其中每个batch的值都对应于整个序列的期望标签。对于token分类模型(例如,BertForTokenClassification),模型需要一个维度张量为(batch_size, seq_length),其中的每个值都对应于每个单个token的期望标签。对于遮罩语言模型(例如,BertForMaskedLM),模型需要一个维度张量(batch_size, seq_length),其中的每个值都对应于每个单个token的预期标签:标签是mask token的token ID,其余的值将被忽略(通常为-100)。对于seq2seq任务,(例如。 BartForConditionalGeneration, MBartForConditionalGeneration,模型期望一个维度张量(batch_size, tgt_seq_length),每个值对应于与每个输入序列相关联的目标序列。在训练期间,BART和T5都将在内部制作适当的decoder_input_ids和 decoder attention masks。它们通常不需要供应。这不适用于利用编码器-解码器框架的模型。基本模型(例如,BertModel)不接受标签,因为这些是基本transformer模型,只是输出特性。解码器输入ID(Decoder input IDs)这个输入是特定于编码器-解码器模型的,并且包含将被输入到解码器的输入id。这些输入应该用于序列到序列任务,例如翻译或摘要,并且通常以特定于每个模型的方式构建。大多数编码器-解码器模型(BART, T5)自己从标签中创建decoder_input_id。在这些模型中,传递标签是处理训练过程的首选方式。前馈模块(Feed Forward Chunking)在Transformer的每个残差块中,自我注意层通常后面跟着2个前馈层。前馈层的中间嵌入尺寸通常大于模型的隐藏尺寸(例如,bert-base-uncased)。对于input size为 [batch_size, sequence_length] 的输入,存储中间前馈嵌入所需的内存 [batch_size, sequence_length, config.intermediate_size] 可以占内存使用的很大一部分。Reformer: The Efficient Transformer的作者注意到,由于计算是独立于sequence_length维度的,它在数学上等价于计算两个前馈层的输出嵌入 _[batch_size, config.hidden_size]_0, ..., [batch_size, config.hidden_size]n 配置。然后将它们连接到 [batch_size, sequence_length, config.hidden_size] 中。其中n = sequence_length,这会增加计算时间,减少内存使用,但会产生数学上等价的结果。对于使用apply_chunking_to_forward()函数的模型,chunk_size定义了并行计算的输出嵌入的数量,从而定义了内存和时间复杂性之间的权衡。如果chunk_size设置为0,则不进行前馈分块。
野生程序员在线
Huggingface Transformers库学习笔记(二)
前言本部分是Transformer库的基础部分的上半部分,主要包括任务汇总、模型汇总和数据预处理三方面内容,由于许多模型我也不太了解,所以多为机器翻译得到,错误再所难免,内容仅供参考。使用Transformers(Using Transformers)任务汇总(Summary of the tasks)该部分介绍了使用该库时最常见的用例。可用的模型允许许多不同的配置,并且在各个实际用例中具有很大的通用性。可用的模型允许许多不同的配置,并且在用例中具有很大的通用性。这些例子利用了auto-models,这些类将根据给定的checkpoint实例化一个模型,自动地选择正确的模型架构。为了让模型在任务上良好地执行,它必须从与该任务对应的checkpoint加载。这些checkpoint通常针对大量数据进行预先训练,并针对特定任务进行微调。这意味着以下内容并不是所有的模型都对所有的任务进行了微调。如果想对特定任务的模型进行微调,可以利用示例目录中的run_$ task .py脚本之一。微调模型是在特定数据集上进行微调的。这个数据集可能与我们要做的用例和域重叠,也可能不重叠。如前所述,可以利用示例脚本来微调模型,或者可以创建自己的训练脚本。为了对任务进行推理,这个库提供了几种机制:Pipelines: 非常容易使用的抽象,只需要两行代码。直接使用模型:抽象较少,但通过直接访问分词器(PyTorch/TensorFlow)和充分的推理能力,更灵活和强大。下面的具体应用中展示了这两种方法。序列分类(Sequence Classification)序列分类是根据给定的类别数目对序列进行分类的任务。序列分类的一个例子是GLUE数据集,它完全基于该任务。如果想在GLUE序列分类任务上对模型进行微调,可以利用run_glue.py、run_tf_glue.py、run_tf_text_classification.py或run_xnlib .py脚本。下面是一个使用Pipeline进行情感分析的例子:识别一个序列是积极的还是消极的。它在sst2上利用了一个经过微调的模型,这是一个GLUE任务。这将在分数旁边返回一个标签(正的或负的),如下所示from transformers import pipeline
nlp = pipeline("sentiment-analysis")
result = nlp("I hate you")[0]
print(f"label: {result['label']}, with score: {round(result['score'], 4)}")
result = nlp("I love you")[0]
print(f"label: {result['label']}, with score: {round(result['score'], 4)}")输出为label: NEGATIVE, with score: 0.9991label: POSITIVE, with score: 0.9999下面是一个使用模型进行序列分类的例子,以确定两个序列是否相互转述(paraphrase)。过程如下:从checkpoint名称实例化分词器和模型。该模型被识别为BERT模型,并将存储在checkpoint中的权值加载到该模型中。从这两个句子构建一个序列,使用正确的特定于模型的分隔符、token类型id和注意掩码(encode()和__call__()负责这一点)。将这个序列传递到模型中,以便将其分类为两个可用类中的一个:0(不是释义)和1(是释义)。计算结果的softmax以得到所有类的概率。打印结果。相关代码如下:from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch
tokenizer = AutoTokenizer.from_pretrained("bert-base-cased-finetuned-mrpc")
model = AutoModelForSequenceClassification.from_pretrained("bert-base-cased-finetuned-mrpc")
classes = ["not paraphrase", "is paraphrase"]
sequence_0 = "The company HuggingFace is based in New York City"sequence_1 = "Apples are especially bad for your health"sequence_2 = "HuggingFace's headquarters are situated in Manhattan"paraphrase = tokenizer(sequence_0, sequence_2, return_tensors="pt")
not_paraphrase = tokenizer(sequence_0, sequence_1, return_tensors="pt")
paraphrase_classification_logits = model(**paraphrase).logits
not_paraphrase_classification_logits = model(**not_paraphrase).logits
paraphrase_results = torch.softmax(paraphrase_classification_logits, dim=1).tolist()[0]
not_paraphrase_results = torch.softmax(not_paraphrase_classification_logits, dim=1).tolist()[0]
# Should be paraphrasefor i in range(len(classes)):
print(f"{classes[i]}: {int(round(paraphrase_results[i] * 100))}%")
print("---"*24)
# Should not be paraphrasefor i in range(len(classes)):
print(f"{classes[i]}: {int(round(not_paraphrase_results[i] * 100))}%")得到输出如下not paraphrase: 10%paraphrase: 90%------------------------------------------------------------------------not paraphrase: 94%paraphrase: 6%提取式问答(Extractive Question Answering)提取式问答是指从给定的文本中提取一个答案的任务。关于QA数据集的一个例子是SQuAD数据集,它完全基于该任务。如果想对一个任务的模型在SQuAD数据集上进行微调,可以使用run_qa.py和run_tf_team .py脚本。下面是一个使用pipeline进行问题回答的示例:从给定问题的文本中提取答案。它利用了一个在SQuAD上微调过的模型。from transformers import pipeline
nlp = pipeline("question-answering")
context = r"""Extractive Question Answering is the task of extracting an answer from a text given a question. An example of a
question answering dataset is the SQuAD dataset, which is entirely based on that task. If you would like to fine-tune
a model on a SQuAD task, you may leverage the examples/question-answering/run_squad.py script.
"""
result = nlp(question="What is extractive question answering?", context=context)
print(f"Answer: '{result['answer']}', score: {round(result['score'], 4)}, start: {result['start']}, end: {result['end']}")
print("---"*24)
result = nlp(question="What is a good example of a question answering dataset?", context=context)
print(f"Answer: '{result['answer']}', score: {round(result['score'], 4)}, start: {result['start']}, end: {result['end']}")输出如下Answer: 'the task of extracting an answer from a text given a question', score: 0.6226, start: 34, end: 95------------------------------------------------------------------------Answer: 'SQuAD dataset', score: 0.5053, start: 147, end: 160下面是一个使用模型和分词器回答问题的示例。流程如下:从checkpoint名称实例化一个分词器和模型。该模型被识别为BERT模型,并将存储在checkpoint中的权值加载到该模型中。定义一篇文章和一些问题。迭代问题并从文本和当前问题构建一个序列,使用正确的特定于模型的分隔符、token类型id和attention mask。将此序列传递给模型。这将在整个序列token(问题和文本)中输出开始位置和结束位置的分数范围。计算结果的softmax以获得token上的概率。从标识的start和stop值中获取token,将这些token转换为字符串。打印结果。相关代码如下:from transformers import AutoTokenizer, AutoModelForQuestionAnswering
import torch
tokenizer = AutoTokenizer.from_pretrained("bert-large-uncased-whole-word-masking-finetuned-squad")
model = AutoModelForQuestionAnswering.from_pretrained("bert-large-uncased-whole-word-masking-finetuned-squad")
text = r"""🤗 Transformers (formerly known as pytorch-transformers and pytorch-pretrained-bert) provides general-purpose
architectures (BERT, GPT-2, RoBERTa, XLM, DistilBert, XLNet…) for Natural Language Understanding (NLU) and Natural
Language Generation (NLG) with over 32+ pretrained models in 100+ languages and deep interoperability between
TensorFlow 2.0 and PyTorch.
"""
questions = [
"How many pretrained models are available in 🤗 Transformers?",
"What does 🤗 Transformers provide?",
"🤗 Transformers provides interoperability between which frameworks?",
]
for question in questions:
inputs = tokenizer(question, text, add_special_tokens=True, return_tensors="pt")
input_ids = inputs["input_ids"].tolist()[0]
outputs = model(**inputs)
answer_start_scores = outputs.start_logits
answer_end_scores = outputs.end_logits
answer_start = torch.argmax(
answer_start_scores
) # Get the most likely beginning of answer with the argmax of the score answer_end = torch.argmax(answer_end_scores) + 1 # Get the most likely end of answer with the argmax of the score answer = tokenizer.convert_tokens_to_string(tokenizer.convert_ids_to_tokens(input_ids[answer_start:answer_end]))
print(f"Question: {question}")
print(f"Answer: {answer}")得到输出如下:Question: How many pretrained models are available in 🤗 Transformers?Answer: over 32 +Question: What does 🤗 Transformers provide?Answer: general - purpose architecturesQuestion: 🤗 Transformers provides interoperability between which frameworks?Answer: tensorflow 2. 0 and pytorch语言模型(Language Modeling)语言模型是将模型拟合到语料库中的任务,可以是特定领域的。所有流行的基于transformer的模型都是使用某种语言模型的变体进行训练的,例如BERT使用掩码语言建模,GPT-2使用因果语言建模。遮罩语言模型(Masked Language Modeling)屏蔽语言建模是用mask token屏蔽序列中的token,并提示模型用适当的token填充该屏蔽。这允许模型同时处理右上下文(掩码右边的token)和左上下文(掩码左边的token)。这样的训练为需要双向背景的下游任务创造了强大的基础,如SQuAD(问题回答,参见Lewis, Lui, Goyal等人,第4.2部分)。下面是一个使用pipeline从序列中替换掩码的示例。from transformers import pipeline
from pprint import pprint
nlp = pipeline("fill-mask")
pprint(nlp(f"HuggingFace is creating a {nlp.tokenizer.mask_token} that the community uses to solve NLP tasks."))得到输出如下:[{'score': 0.17927521467208862, 'sequence': 'HuggingFace is creating a tool that the community uses to solve ' 'NLP tasks.', 'token': 3944, 'token_str': ' tool'}, {'score': 0.1134946271777153, 'sequence': 'HuggingFace is creating a framework that the community uses to ' 'solve NLP tasks.', 'token': 7208, 'token_str': ' framework'}, {'score': 0.05243523046374321, 'sequence': 'HuggingFace is creating a library that the community uses to ' 'solve NLP tasks.', 'token': 5560, 'token_str': ' library'}, {'score': 0.034935325384140015, 'sequence': 'HuggingFace is creating a database that the community uses to ' 'solve NLP tasks.', 'token': 8503, 'token_str': ' database'}, {'score': 0.028602493926882744, 'sequence': 'HuggingFace is creating a prototype that the community uses to ' 'solve NLP tasks.', 'token': 17715, 'token_str': ' prototype'}]下面是一个使用模型和分词器进行掩码语言建模的示例。流程如下:从checkpoint名称实例化一个分词器和模型。该模型被识别为DistilBERT,并使用存储在checkpoint中的权重加载该模型。定义一个带有mask token的序列,放置 tokenizer.mask_token遮盖住一个单词。将该序列编码到一个id列表中,并找到mask token在该列表中的位置。在mask token的索引处检索预测:这个张量与词汇表的大小相同,值是赋给每个token的分数。模型会给它认为在该上下文中可能出现的token更高的分数。使用PyTorch topk或TensorFlow的top_k方法检索前5个token。用token替换mask token并打印结果相关代码如下:from transformers import AutoModelWithLMHead, AutoTokenizer
import torch
tokenizer = AutoTokenizer.from_pretrained("distilbert-base-cased")
model = AutoModelWithLMHead.from_pretrained("distilbert-base-cased")
sequence = f"Distilled models are smaller than the models they mimic. Using them instead of the large versions would help {tokenizer.mask_token} our carbon footprint."inputs = tokenizer.encode(sequence, return_tensors="pt")
mask_token_index = torch.where(inputs == tokenizer.mask_token_id)[1]
token_logits = model(inputs).logits
mask_token_logits = token_logits[0, mask_token_index, :]
top_5_tokens = torch.topk(mask_token_logits, 5, dim=1).indices[0].tolist()
for token in top_5_tokens:
print(sequence.replace(tokenizer.mask_token, tokenizer.decode([token])))输出结果为:Distilled models are smaller than the models they mimic. Using them instead of the large versions would help reduce our carbon footprint.
Distilled models are smaller than the models they mimic. Using them instead of the large versions would help increase our carbon footprint.
Distilled models are smaller than the models they mimic. Using them instead of the large versions would help decrease our carbon footprint.
Distilled models are smaller than the models they mimic. Using them instead of the large versions would help offset our carbon footprint.
Distilled models are smaller than the models they mimic. Using them instead of the large versions would help improve our carbon footprint.输出打印了5个序列,其中包含模型预测的前5个token。因果语言模型(Causal Language Modeling)因果语言模型是在一系列token之后预测token的任务。在这种情况下,模型只处理左侧上下文(掩码左侧的token)。这样的训练对于生成任务特别有趣。如果想在因果语言建模任务上对模型进行微调,可以利用run_clm.py脚本。通常,通过对模型从输入序列中产生的最后一个隐藏状态的logit进行采样来预测下一个令牌。下面是一个使用分词器和模型的示例,并利用top_k_top_p_filter()方法对token输入序列之后的下一个token进行取样。from transformers import AutoModelWithLMHead, AutoTokenizer, top_k_top_p_filtering
import torch
from torch.nn import functional as F
tokenizer = AutoTokenizer.from_pretrained("gpt2")
model = AutoModelWithLMHead.from_pretrained("gpt2")
sequence = f"Hugging Face is based in DUMBO, New York City, and "input_ids = tokenizer.encode(sequence, return_tensors="pt")
# get logits of last hidden statenext_token_logits = model(input_ids).logits[:, -1, :]
# filterfiltered_next_token_logits = top_k_top_p_filtering(next_token_logits, top_k=50, top_p=1.0)
# sampleprobs = F.softmax(filtered_next_token_logits, dim=-1)
next_token = torch.multinomial(probs, num_samples=1)
generated = torch.cat([input_ids, next_token], dim=-1)
resulting_string = tokenizer.decode(generated.tolist()[0])
print(resulting_string)输出结果为Hugging Face is based in DUMBO, New York City, and这将输出一个(希望)与原始序列一致的下一个标记,(这里我输出的结果还是原来的句子,不知道哪里出了问题)在下一节中,我们将展示如何在generate()中利用此功能生成多个用户定义长度的token。文本生成(Text Generation)在文本生成(也就是开放式文本生成)中,目标是创建文本的连贯部分,作为给定上下文的延续。下面的示例展示了如何在pipeline中使用GPT-2来生成文本。默认情况下,当在pipeline中使用时,所有的模型都应用Top-K采样,就像在它们各自的配置中配置的那样(参见gpt-2配置)。from transformers import pipeline
text_generator = pipeline("text-generation")
print(text_generator("As far as I am concerned, I will", max_length=50, do_sample=False))输出结果为:[{'generated_text': 'As far as I am concerned, I will be the first to admit that I am not a fan of the idea of a "free market." I think that the idea of a free market is a bit of a stretch. I think that the idea'}]在这里,模型从上下文“As far as I am concerned, I will”中生成一个最大长度为50个标记的随机文本。PreTrainedModel.generate()的默认参数可以在pipeline中直接覆盖,如上面所示的max_length参数。下面是一个使用XLNet及其分词器生成文本的示例。from transformers import AutoModelWithLMHead, AutoTokenizer
model = AutoModelWithLMHead.from_pretrained("xlnet-base-cased")
tokenizer = AutoTokenizer.from_pretrained("xlnet-base-cased")
# Padding text helps XLNet with short prompts - proposed by Aman Rusia in https://github.com/rusiaaman/XLNet-gen#methodologyPADDING_TEXT = """In 1991, the remains of Russian Tsar Nicholas II and his family(except for Alexei and Maria) are discovered.
The voice of Nicholas's young son, Tsarevich Alexei Nikolaevich, narrates the
remainder of the story. 1883 Western Siberia,
a young Grigori Rasputin is asked by his father and a group of men to perform magic.
Rasputin has a vision and denounces one of the men as a horse thief. Although his
father initially slaps him for making such an accusation, Rasputin watches as the
man is chased outside and beaten. Twenty years later, Rasputin sees a vision of
the Virgin Mary, prompting him to become a priest. Rasputin quickly becomes famous,
with people, even a bishop, begging for his blessing. <eod> </s> <eos>"""
prompt = "Today the weather is really nice and I am planning on "inputs = tokenizer.encode(PADDING_TEXT + prompt, add_special_tokens=False, return_tensors="pt")
prompt_length = len(tokenizer.decode(inputs[0], skip_special_tokens=True, clean_up_tokenization_spaces=True))
outputs = model.generate(inputs, max_length=250, do_sample=True, top_p=0.95, top_k=60)
generated = prompt + tokenizer.decode(outputs[0])[prompt_length:]
print(generated)输出为:Today the weather is really nice and I am planning on anning on going out to see a new band in the next few days. I will see some young guys there when I get back. This will likely be the last time that I have been to the Twilight Zone for a long time. I have been wanting to go to the Twilight Zone for a long time but have not been able to go there. Maybe I will have文本生成目前可以在PyTorch中使用GPT-2, OpenAi-GPT, CTRL, XLNet, Transfo-XL和Reformer,以及Tensorflow中的大多数模型。从上面的例子中可以看出,XLNet和Transfo-XL经常需要进行填充才能很好地工作。GPT-2通常是开放式文本生成的一个很好的选择,因为它是在数百万个带有因果语言建模目标的网页上进行训练的。命名实体识别(Named Entity Recognition)命名实体识别(NER)是根据类对token进行分类的任务,例如,将token标识为人、组织或位置。命名实体识别数据集的一个例子是CoNLL-2003数据集,它完全基于该任务。如果想在NER任务上对模型进行微调,可以利用run_ner.py脚本。下面是一个使用pipeline进行命名实体识别的示例,具体来说,尝试将token标识为属于9个类中的一个:O, 在命名实体之外,Outside of a named entityB-MIS, 杂项实体开始,Beginning of a miscellaneous entity right after another miscellaneous entityI-MIS, 杂项实体,Miscellaneous entityB-PER, 人名实体开始,Beginning of a person’s name right after another person’s nameI-PER, 人名实体,Person’s nameB-ORG, 组织实体开始,Beginning of an organisation right after another organisationI-ORG, 组织实体,OrganisationB-LOC, 位置实体开始,Beginning of a location right after another locationI-LOC, 位置实体,Location它利用了在control -2003上经过微调的模型,由dbmdz的@stefan-it进行微调。这将输出一个所有单词的列表,这些单词被标识为上面定义的9个类中的一个实体。from transformers import pipeline
nlp = pipeline("ner")
sequence = "Hugging Face Inc. is a company based in New York City. Its headquarters are in DUMBO, therefore very" "close to the Manhattan Bridge which is visible from the window."
pprint(nlp(sequence))输出为:{'word': 'Hu', 'score': 0.999578595161438, 'entity': 'I-ORG', 'index': 1, 'start': 0, 'end': 2}
{'word': '##gging', 'score': 0.9909763932228088, 'entity': 'I-ORG', 'index': 2, 'start': 2, 'end': 7}
{'word': 'Face', 'score': 0.9982224702835083, 'entity': 'I-ORG', 'index': 3, 'start': 8, 'end': 12}
{'word': 'Inc', 'score': 0.9994880557060242, 'entity': 'I-ORG', 'index': 4, 'start': 13, 'end': 16}
{'word': 'New', 'score': 0.9994345307350159, 'entity': 'I-LOC', 'index': 11, 'start': 40, 'end': 43}
{'word': 'York', 'score': 0.9993196129798889, 'entity': 'I-LOC', 'index': 12, 'start': 44, 'end': 48}
{'word': 'City', 'score': 0.9993793964385986, 'entity': 'I-LOC', 'index': 13, 'start': 49, 'end': 53}
{'word': 'D', 'score': 0.9862582683563232, 'entity': 'I-LOC', 'index': 19, 'start': 79, 'end': 80}
{'word': '##UM', 'score': 0.9514269828796387, 'entity': 'I-LOC', 'index': 20, 'start': 80, 'end': 82}
{'word': '##BO', 'score': 0.933659017086029, 'entity': 'I-LOC', 'index': 21, 'start': 82, 'end': 84}
{'word': 'Manhattan', 'score': 0.9761653542518616, 'entity': 'I-LOC', 'index': 28, 'start': 114, 'end': 123}
{'word': 'Bridge', 'score': 0.9914628863334656, 'entity': 'I-LOC', 'index': 29, 'start': 124, 'end': 130}请注意,Hugging Face Inc.被确定为一个组织,New York City、DUMBO和Manhattan Bridge被确定为地点。下面是一个使用模型和分词器进行命名实体识别的示例。过程如下:从checkpoint名称实例化分词器和模型。该模型被识别为BERT模型,并将存储在checkpoint中的权值加载到该模型中。定义用于训练模型的标签列表。定义一个具有已知实体的序列,例如将“Hugging Face”作为组织,将“New York City”作为地点。将单词拆分为token,以便它们可以映射到预测。我们使用了一个小hack,首先,完全编码和解码序列,这样我们就得到了一个包含特殊token的字符串。将该序列编码到id中(自动添加特殊标记)。通过将输入传递给模型并获得第一个输出来检索预测。这将导致每个token分布在9个可能的类上。我们使用argmax来检索每个token最可能出现的类。将每个token与其预测打包并打印。代码如下:from transformers import AutoModelForTokenClassification, AutoTokenizer
import torch
model = AutoModelForTokenClassification.from_pretrained("dbmdz/bert-large-cased-finetuned-conll03-english")
tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
label_list = [
"O", # Outside of a named entity "B-MISC", # Beginning of a miscellaneous entity right after another miscellaneous entity "I-MISC", # Miscellaneous entity "B-PER", # Beginning of a person's name right after another person's name "I-PER", # Person's name "B-ORG", # Beginning of an organisation right after another organisation "I-ORG", # Organisation "B-LOC", # Beginning of a location right after another location "I-LOC" # Location]
sequence = "Hugging Face Inc. is a company based in New York City. Its headquarters are in DUMBO, therefore very" \
"close to the Manhattan Bridge."# Bit of a hack to get the tokens with the special tokenstokens = tokenizer.tokenize(tokenizer.decode(tokenizer.encode(sequence)))
inputs = tokenizer.encode(sequence, return_tensors="pt")
outputs = model(inputs).logits
predictions = torch.argmax(outputs, dim=2)
输出为:[('[CLS]', 'O'), ('Hu', 'I-ORG'), ('##gging', 'I-ORG'), ('Face', 'I-ORG'), ('Inc', 'I-ORG'), ('.', 'O'), ('is', 'O'), ('a', 'O'), ('company', 'O'), ('based', 'O'), ('in', 'O'), ('New', 'I-LOC'), ('York', 'I-LOC'), ('City', 'I-LOC'), ('.', 'O'), ('Its', 'O'), ('headquarters', 'O'), ('are', 'O'), ('in', 'O'), ('D', 'I-LOC'), ('##UM', 'I-LOC'), ('##BO', 'I-LOC'), (',', 'O'), ('therefore', 'O'), ('very', 'O'), ('##c', 'O'), ('##lose', 'O'), ('to', 'O'), ('the', 'O'), ('Manhattan', 'I-LOC'), ('Bridge', 'I-LOC'), ('.', 'O'), ('[SEP]', 'O')]这将输出映射到相应预测的每个token的列表。与pipeline方法不同的是,这里每个token都有一个预测,因为我们没有删除第0个类,这意味着在该token上没有找到特定的实体。文本摘要(Summarization)摘要是将一份文件或一篇文章总结成较短的文本。如果想对汇总任务的模型进行微调,可以利用run_summary .py脚本。摘要数据集的一个例子是CNN /每日邮报数据集,它由长新闻文章组成,是为摘要任务而创建的。下面是一个使用pipeline进行汇总的示例。它利用了在CNN /每日邮报数据集上进行微调的Bart模型。因为文本摘要pipeline依赖于PreTrainedModel.generate()方法,所以我们可以在max_length和min_length的管道中直接覆盖PreTrainedModel.generate()的默认参数,如下所示。from transformers import pipeline
summarizer = pipeline("summarization")
ARTICLE = """ New York (CNN)When Liana Barrientos was 23 years old, she got married in Westchester County, New York.A year later, she got married again in Westchester County, but to a different man and without divorcing her first husband.
Only 18 days after that marriage, she got hitched yet again. Then, Barrientos declared "I do" five more times, sometimes only within two weeks of each other.
In 2010, she married once more, this time in the Bronx. In an application for a marriage license, she stated it was her "first and only" marriage.
Barrientos, now 39, is facing two criminal counts of "offering a false instrument for filing in the first degree," referring to her false statements on the
2010 marriage license application, according to court documents.
Prosecutors said the marriages were part of an immigration scam.
On Friday, she pleaded not guilty at State Supreme Court in the Bronx, according to her attorney, Christopher Wright, who declined to comment further.
After leaving court, Barrientos was arrested and charged with theft of service and criminal trespass for allegedly sneaking into the New York subway through an emergency exit, said Detective
Annette Markowski, a police spokeswoman. In total, Barrientos has been married 10 times, with nine of her marriages occurring between 1999 and 2002.
All occurred either in Westchester County, Long Island, New Jersey or the Bronx. She is believed to still be married to four men, and at one time, she was married to eight men at once, prosecutors say.
Prosecutors said the immigration scam involved some of her husbands, who filed for permanent residence status shortly after the marriages.
Any divorces happened only after such filings were approved. It was unclear whether any of the men will be prosecuted.
The case was referred to the Bronx District Attorney\'s Office by Immigration and Customs Enforcement and the Department of Homeland Security\'s
Investigation Division. Seven of the men are from so-called "red-flagged" countries, including Egypt, Turkey, Georgia, Pakistan and Mali.
Her eighth husband, Rashid Rajput, was deported in 2006 to his native Pakistan after an investigation by the Joint Terrorism Task Force.
If convicted, Barrientos faces up to four years in prison. Her next court appearance is scheduled for May 18.
"""
print(summarizer(ARTICLE, max_length=130, min_length=30, do_sample=False))输出结果为:[{'summary_text': ' Liana Barrientos, 39, is charged with two counts of "offering a false instrument for filing in the first degree" In total, she has been married 10 times, with nine of her marriages occurring between 1999 and 2002 . At one time, she was married to eight men at once, prosecutors say .'}]下面是一个使用模型和分词器进行汇总的示例。流程如下:从检查点名称实例化一个分词器和模型。摘要通常使用一个编码器-解码器模型来完成,例如Bart或T5。定义应该总结的文章。添加T5特有的前缀“summarize: “。使用PreTrainedModel.generate()方法生成摘要。在本例中,我们使用谷歌的T5模型。即使它只在多任务混合数据集(包括CNN /每日邮报)上进行了预先训练,它也会产生非常好的结果。from transformers import AutoModelWithLMHead, AutoTokenizer
model = AutoModelWithLMHead.from_pretrained("t5-base")
tokenizer = AutoTokenizer.from_pretrained("t5-base")
# T5 uses a max_length of 512 so we cut the article to 512 tokens.inputs = tokenizer.encode("summarize: " + ARTICLE, return_tensors="pt", max_length=512)
outputs = model.generate(inputs, max_length=150, min_length=40, length_penalty=2.0, num_beams=4, early_stopping=True)
print(tokenizer.decode(outputs.tolist()[0]))输出为:<pad> prosecutors say the marriages were part of an immigration scam. if convicted, barrientos faces two criminal counts of "offering a false instrument for filing in the first degree" she has been married 10 times, nine of them between 1999 and 2002.</s>文本翻译(Translation)文本翻译是把文本从一种语言翻译成另一种语言的任务。如果想对翻译任务中的模型进行微调,可以利用run_translation.py脚本。翻译数据集的一个例子是WMT英语到德语数据集,它以英语句子作为输入数据,以相应的德语句子作为目标数据。下面是一个使用pipeline进行翻译的示例。它利用了仅在多任务混合数据集(包括WMT)上预先训练的T5模型,然而,产生了令人印象深刻的翻译结果。from transformers import pipeline
translator = pipeline("translation_en_to_de")
print(translator("Hugging Face is a technology company based in New York and Paris", max_length=40))**输出为:[{'translation_text': 'Hugging Face ist ein Technologieunternehmen mit Sitz in New York und Paris.'}因为文本翻译pipeline依赖于PreTrainedModel.generate()方法,所以我们可以直接在管道中覆盖PreTrainedModel.generate()的默认参数,如上文max_length所示。下面是一个使用模型和分词器进行翻译的示例。过程如下:从checkpoint名称实例化分词器和模型。摘要通常使用一个编码器-解码器模型来完成,例如Bart或T5。定义应该总结的文章。添加特定于T5的前缀“translate English to German:”使用PreTrainedModel.generate()方法来执行翻译。代码如下:from transformers import AutoModelWithLMHead, AutoTokenizer
model = AutoModelWithLMHead.from_pretrained("t5-base")
tokenizer = AutoTokenizer.from_pretrained("t5-base")
inputs = tokenizer.encode("translate English to German: Hugging Face is a technology company based in New York and Paris", return_tensors="pt")
outputs = model.generate(inputs, max_length=40, num_beams=4, early_stopping=True)
print(tokenizer.decode(outputs.tolist()[0]))输出为:<pad> Hugging Face ist ein Technologieunternehmen mit Sitz in New York und Paris.</s>模型汇总(Summary of the models)下面总结了本库中的所有模型。这里假设读者熟悉原始的transformer模型。我们将重点放在模型之间的高层差异上。库中的每一个模型都可以分为以下几类:自回归模型自编码模型序列对序列模型多模态模型基于检索的模型自回归模型在经典的语言建模任务上进行了预先训练:在阅读了所有之前的token之后,猜测下一个token。它们对应于原Transformer模型的解码器,并且在整句话的顶部使用了掩码,以便注意头只能看到文本的前面,而不能看到后面。尽管这些模型可以进行微调,并在许多任务上取得良好的结果,但最自然的应用程序是文本生成。这种模型的典型例子是GPT。自编码模型的预训练是通过以某种方式破坏输入标记并试图重构原始句子。它们对应于原始Transformer模型的编码器,因为它们可以在没有任何掩码的情况下获得全部输入。这些模型通常是对整个句子的双向表示。它们可以进行微调,并在许多任务(如文本生成)上取得良好的结果,但它们最自然的应用是句子分类或token分类。这类模型的一个典型例子是BERT。请注意,自回归模型和自编码模型之间的唯一区别在于模型的预训练方式。因此,同样的体系结构可以用于自回归模型和自编码模型。当给定的模型被用于这两种类型的预训练时,我们将它放在与第一次介绍它的文章对应的类别中。序列到序列模型同时使用原始Transformer的编码器和解码器,用于转换任务或将其他任务转换为序列到序列问题。它们可以被微调到许多任务中,但它们最自然的应用是翻译、总结和回答问题。原始的transformer模型是这种模型的一个示例(仅用于翻译),T5是一个可以在其他任务上进行微调的示例。多模态模型将文本输入与其他类型(如图像)混合在一起,并且更特定于特定的任务。自回归模型(Autoregressive models)如前所述,这些模型依赖于原始Transformer的解码器部分,并使用注意掩码,以便在每个位置,模型只能看到注意头之前的标记。原始GPT(Original GPT)Improving Language Understanding by Generative Pre-Training, Alec Radford et al.第一个基于Transformer体系结构的自回归模型,在图书语料库数据集上进行预训练。该库提供了用于语言建模和多任务语言模型/多选择分类的模型版本。GPT-2Language Models are Unsupervised Multitask Learners, Alec Radford et al.GPT的一个更大更好的版本,在WebText(在Reddit网站上向外连接超过3karms的网页)。该库提供了用于语言建模和多任务语言建模/多项选择分类的模型版本。CTRLCTRL: A Conditional Transformer Language Model for Controllable Generation, Nitish Shirish Keskar et al.与GPT模型相同,但增加了控制代码的思想。文本由一个提示(可以是空的)和一个(或几个)控制代码生成,这些控制代码然后用于影响文本生成:以维基百科文章、一本书或电影评论的风格生成。这个库提供了一个仅用于语言建模的模型版本。Transformer-XLTransformer-XL: Attentive Language Models Beyond a Fixed-Length Context, Zihang Dai et al.与常规的GPT模型相同,但引入了两个连续段的递归机制(类似于具有两个连续输入的常规RNN)。在这个上下文中,一个段是一系列连续的token(例如512),它们可以跨多个文档,段是按照模型的顺序输入的。基本上,前一部分的隐藏状态与当前输入相连接,以计算注意力分数。这使得模型可以同时关注前一部分和当前部分中的信息。通过叠加多个注意层,感受野可以增加到多个先前的片段。这将位置嵌入改变为位置相对嵌入(因为常规的位置嵌入会在给定位置的当前输入和当前隐藏状态中给出相同的结果),并且需要在计算注意力分数的方式上做一些调整。这个库提供了一个仅用于语言建模的模型版本。ReformerReformer: The Efficient Transformer, Nikita Kitaev et al .一个具有许多技巧的自回归transformer模型,以减少内存占用和计算时间。这些技巧包括:使用轴向位置编码(参见下面了解更多细节)。它是一种通过分解成更小的矩阵来避免拥有一个巨大的位置编码矩阵(当序列长度非常大时)的机制。用LSH(本地敏感哈希)注意代替传统的注意(参见下面了解更多细节)。这是一种避免在注意层中计算完整产品查询键的技术。避免存储每一层的中间结果,方法是在反向过程中使用可逆变压器层获取中间结果(从下一层的输入中减去剩余将返回中间结果),或者为给定层中的结果重新计算中间结果(效率低于存储中间结果,但节省内存)。按块而不是整批计算前馈操作。利用这些技巧,该模型可以比传统的变压器自回归模型输入更大的句子。注意:这个模型可以很好地用于自动编码设置,但是对于这样的预训练还没有checkpoint。这个库提供了一个仅用于语言建模的模型版本。XLNetXLNet: Generalized Autoregressive Pretraining for Language Understanding, Zhilin Yang et al.XLNet不是传统的自回归模型,而是在此基础上使用了一种训练策略。它对句子中的记号进行排列,然后允许模型使用最后的n个记号来预测n+1个记号。由于这都是通过一个掩码完成的,所以句子实际上是按正确的顺序输入到模型中,但是XLNet没有屏蔽n+1的前n个标记,而是使用一个掩码,以序列长度为1的某些给定排列隐藏以前的标记。自编码模型(Autoencoding models)如前所述,这些模型依赖于原始Transformer的编码器部分,并且不使用掩码,因此模型可以查看注意头中的所有标记。对于预训练,目标是原始的句子,输入是它们的损坏版本。BERTBERT: Pre-training of Deep Bidirectional Transformers for Language Understanding, Jacob Devlin et al.通过使用随机mask破坏输入,更准确地说,在预训练期间,给定百分比的token(通常为15%)被mask80%的概率被mask token替换10%的概率被随机一个词替换10%的概率保持不动注意:这里的80%、10%是在15%的基础上划分的。即先有15%决定被mask,然后被mask的情况下有80%被mask token替换。模型必须预测原始句子,但有第二个目标:输入是两个句子A和B(中间有一个分隔符)。在语料库中,这些句子有50%的概率是连续的,剩下的50%的句子是不相关的。该模型必须预测句子是否连续。该库为语言建模(传统的或屏蔽的)、下一个句子预测、token分类、句子分类、多选择分类和问题回答都提供了一个版本的模型。ALBERTALBERT: A Lite BERT for Self-supervised Learning of Language Representations, Zhenzhong Lan et al.和BERT一样,只是做了些调整:嵌入大小E与隐藏大小H是不同的,因为嵌入是上下文独立的(一个嵌入向量代表一个标记),而隐藏状态是上下文相关的(一个隐藏状态代表一个标记序列),所以H>>E更符合逻辑。此外,嵌入矩阵很大,因为它是 V x E (V是词汇表大小)。如果E < H,它的参数更少层被分割成共享参数的组(以节省内存)。下一个句子预测被一个句子排序预测取代:在输入中,我们有两个句子A和B(连续的),我们要么输入A,然后输入B,要么输入B,然后输入A。模型必须预测它们是否被交换了。该库为遮罩语言模型、token分类、句子分类、多选择分类和问题回答都提供了一个版本的模型。RoBERTaRoBERTa: A Robustly Optimized BERT Pretraining Approach, Yinhan Liu et al.和BERT一样,但有更好的预训练技巧:动态mask:每个epoch中mask是不同的,而BERT则是一样的没有NSP(下一个句子预测)loss,不是将两个句子放在一起,而是将一组连续的文本放在一起以达到512个标记(这样句子的顺序就可以跨多个文档)更大的batch使用BPE字节作为一个单元,而不是字符(因为unicode)该库为遮罩语言模型、token分类、句子分类、多选择分类和问题回答都提供了一个版本的模型。DistilBERTDistilBERT, a distilled version of BERT: smaller, faster, cheaper and lighter, Victor Sanh et al.和BERT一样,但更小。通过对预先训练的BERT模型的蒸馏来训练,这意味着训练它预测的概率与较大模型相同。实际的目标是:找到与teacher model相同的概率正确预测masked token(没有下一句预测的目标)在student model和teacher model的隐层间的cosine similarity该库为遮罩语言模型、token分类、句子分类和问题回答都提供了一个版本的模型。ConvBERTConvBERT: Improving BERT with Span-based Dynamic Convolution, Zihang Jiang, Weihao Yu, Daquan Zhou, Yunpeng Chen, Jiashi Feng, Shuicheng Yan.像BERT这样的预先训练过的语言模型及其变体最近在各种自然语言理解任务中取得了令人印象深刻的成绩。然而,BERT严重依赖全局的自我注意块,因此内存占用和计算成本很大。虽然所有的注意头都是从全局角度查询整个输入序列来生成注意图,但我们观察到一些注意头只需要学习局部依赖关系,这意味着计算冗余的存在。因此,我们提出了一种新的基于区间的动态卷积来代替这些自我注意头,直接建模局部依赖关系。新的卷积头与其他的自我注意头一起形成新的混合注意块,在全局和局部语境学习中都更有效。我们给BERT配备了这种混合注意设计,并建立了ConvBERT模型。实验表明,ConvBERT在各种下游任务中显著优于BERT及其变体,其训练成本更低,模型参数更少。值得注意的是,ConvBERTbase模型的GLUE评分达到了86.4分,比ELECTRAbase高0.7分,而使用的培训成本不到1/4。该库为遮罩语言模型、token分类、句子分类和问题回答都提供了一个版本的模型。XLMCross-lingual Language Model Pretraining, Guillaume Lample and Alexis Conneau用几种语言训练的transformer模型。这个模型有三种不同的训练方式,库为所有这些类型提供了checkpoint。因果语言建模(CLM)是传统的自回归训练(所以这个模型也可以在前一节中介绍)。为每个训练样本选择一种语言,模型输入是一个包含256个标记的句子,它可以跨越使用其中一种语言的多个文档。遮罩语言模型(MLM)就像RoBERTa。为每个训练样本选择一种语言,模型输入是一个包含256个标记的句子,它可以跨越使用其中一种语言的多个文档,并对标记进行动态屏蔽。遮罩语言模型(MLM)与翻译语言建模(TLM)的结合。这包括用两种不同的语言连接一个句子,并使用随机屏蔽。为了预测其中一个被屏蔽的标记,模型可以同时使用语言1中的上下文和语言2给出的上下文。检查点指的是在名称中包含clm、mlm或mlm-tlm的方法用于预培训。在位置嵌入的基础上,该模型还具有语言嵌入。当使用MLM/CLM进行培训时,它会给模型一个使用语言的指示,而当使用MLM+TLM进行培训时,它会给每个部分使用的语言的指示。该库为语言建模、token分类、句子分类和问题回答都提供了一个版本的模型。XLM-RoBERTaUnsupervised Cross-lingual Representation Learning at Scale, Alexis Conneau et al.在XLM方法上使用RoBERTa的技巧,但不使用翻译语言建模目标。它只对来自一种语言的句子使用遮罩语言模型。然而,该模型训练了更多的语言(100种),并且没有使用语言嵌入,因此它能够自己检测输入语言。该库为掩码语言建模、token分类、句子分类、多项选择分类和问题回答都提供了一个版本的模型。FlauBERTFlauBERT: Unsupervised Language Model Pre-training for French, Hang Le et al.像RoBERTa,没有句子排序预测(所以只是训练在MLM目标)。该库提供了一个用于语言建模和句子分类的模型版本。该库提供了一个用于语言建模和句子分类的模型版本。ELECTRAELECTRA: Pre-training Text Encoders as Discriminators Rather Than Generators, Kevin Clark et al.ELECTRA是一个用另一个(小的)遮罩语言模型预训练的transformer模型。输入被该语言模型损坏,该模型接受一个随机mask的输入文本,并输出一个文本,ELECTRA必须在该文本中预测哪个token是原始的,哪个token被替换了。像GAN训练一样,小语言模型训练了几个步骤(但以原始文本为目标,而不是像传统GAN设置那样愚弄ELECTRA模型),然后ELECTRA模型训练了几个步骤。该库为遮罩语言模型、token分类和句子分类提供了一个版本的模型。Funnel TransformerFunnel-Transformer: Filtering out Sequential Redundancy for Efficient Language Processing, Zihang Dai et al.Funnel Transformer是一个使用池化的Transformer模型,有点像ResNet模型:层被分组在块中,在每个块的开始(除了第一个块),隐藏状态在序列维度中被池化。这样,它们的长度就除以2,这就加快了下一个隐藏状态的计算速度。所有预先训练的模型都有三个区块,这意味着最终隐藏状态的序列长度是原始序列长度的四分之一。对于分类这样的任务,这不是问题,但是对于掩藏语言建模或标记分类这样的任务,我们需要一个与原始输入具有相同序列长度的隐藏状态。在这些情况下,最终的隐藏状态被采样到输入序列长度,并经过两个额外的层。这就是为什么每个checkpoint有两个版本。带-base后缀的版本只包含三个块,而不带该后缀的版本包含三个块和带有附加层的上采样头。可用的预训练模型使用与ELECTRA相同的预训练目标。该库为遮罩语言模型、token分类、句子分类、多选择分类和问题回答提供了一个版本的模型。LongformerLongformer: The Long-Document Transformer, Iz Beltagy et al.为了加快速度而用稀疏矩阵代替注意力矩阵的Transformer模型。通常,本地上下文(例如,左边和右边的两个token是什么?)足以对给定的token采取行动。一些预先选定的输入token仍然得到全局关注,但注意矩阵的参数更少,导致加速。有关更多信息,请参阅本地注意部分。这和RoBERTa一样是预先训练好的。注意:这个模型可以很好地用于自回归模型设置,但是对于这样的预训练还没有checkpoint。该库为遮罩语言模型、token分类、句子分类、多选择分类和问题回答提供了一个版本的模型。序列到序列模型(Sequence-to-sequence models)如前所述,这些模型保留了原Transformer的编码器和解码器。BARTBART: Denoising Sequence-to-Sequence Pre-training for Natural Language Generation, Translation, and Comprehension, Mike Lewis et al.序列到序列模型,有一个编码器和一个解码器。编码器输入一个损坏版本的token,解码器输入原始token(但有一个掩码来隐藏未来的词,就像一个普通的transformer解码器)。以下转换的组合应用于编码器的预训练任务:随机mask掉一些token(如BERT)随机删除token将一段k个token的字段用一个mask token代替重新排列句子旋转文档,使其从特定token开始这个库为条件生成和序列分类提供了这个模型的一个版本。PegasusPEGASUS: Pre-training with Extracted Gap-sentences forAbstractive Summarization, Jingqing Zhang, Yao Zhao, Mohammad Saleh and Peter J. Liu on Dec 18, 2019.Pegasus在两个自我监督的目标函数上进行了联合训练:遮罩语言模型(MLM)和一种新的特定于摘要的预训练目标,称为间隙句生成(GSG)。MLM:编码器输入标记被一个mask token随机替换,并且必须由编码器预测(如BERT)GSG:整个编码器输入的句子被替换为第二个掩码令牌并输入到解码器,但它有一个因果掩码来隐藏未来的单词,就像一个常规的自回归Transformer解码器。与BART不同的是,Pegasus的预训练任务有意地类似于摘要:重要的句子被掩藏起来,并从剩余的句子中生成一个输出序列,类似于摘录摘要。这个库为条件生成提供了这个模型的一个版本,它应该用于摘要。MarianMTMarian: Fast Neural Machine Translation in C++, Marcin Junczys-Dowmunt et al.翻译模型的框架,使用与BART相同的模型。这个库为条件生成提供了这个模型的一个版本。T5Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer, Colin Raffel et al.使用传统的transformer模型(在每个层学习的位置嵌入中有微小的变化)。为了能够操作所有的自然语言处理任务,它通过使用特定的前缀:summarize:, question:, translate English To German:等等,将它们转换成文本到文本的问题。预训练包括有监督和自监督训练。有监督训练是通过由GLUE和SuperGLUE提供的下游任务(如上所述,将它们转换为文本到文本的任务)来进行推断。自监督训练则使用损坏的token方式,通过随机删除15%的token和替换他们为独立sentinel tokens(如果将多个连续token标记为要删除,则整个组将被替换为单个sentinel token)。编码器的输入是被破坏的句子,解码器的输入是原始的句子,然后目标是由它们的sentinel token分隔的被删除的标记。例如,如果我们有一个句子“My dog is very cute .”,我们决定删除标记:“dog”,“is”和“cute”,编码器输入变成了“My very .”目标输入变成了“ dog is cute .”这个库为条件生成提供了这个模型的一个版本。MT5mT5: A massively multilingual pre-trained text-to-text transformer, Linting Xue et al.模型架构与T5相同。mT5的培训前目标包括T5的自监督训练,但不包括T5的有监督训练。mT5接受101种语言的训练。这个库为条件生成提供了这个模型的一个版本。MBartMultilingual Denoising Pre-training for Neural Machine Translation by Yinhan Liu, Jiatao Gu, Naman Goyal, Xian Li, Sergey Edunov Marjan Ghazvininejad, Mike Lewis, Luke Zettlemoyer.模型体系结构和预训练目标与BART相同,但是BART是针对25种语言进行训练的,用于监督和非监督机器翻译。MBart是第一个通过对多语言全文去噪来预训练完整序列到序列模型的方法这个库为条件生成提供了这个模型的一个版本。ProphetNetProphetNet: Predicting Future N-gram for Sequence-to-Sequence Pre-training, by Yu Yan, Weizhen Qi, Yeyun Gong, Dayiheng Liu, Nan Duan, Jiusheng Chen, Ruofei Zhang, Ming Zhou.ProphetNet引入了一种新的序列到序列的预训练目标,称为未来n-gram预测。在未来的n-gram预测中,模型在每个时间步都根据之前的上下文标记同时预测下一个n个标记,而不是只预测单个的next token。未来的n-gram预测明确鼓励模型计划未来的token,并防止强局部相关性的过拟合。该模型架构是在原transformer的基础上,但以一种主要的自我注意机制取代了解码器中的“标准”自我注意机制。这个库为条件生成提供了这个模型的预训练版本,为摘要提供了一个经过微调的版本。XLM-ProphetNetProphetNet: Predicting Future N-gram for Sequence-to-Sequence Pre-training, by Yu Yan, Weizhen Qi, Yeyun Gong, Dayiheng Liu, Nan Duan, Jiusheng Chen, Ruofei Zhang, Ming Zhou.XLM-ProphetNet的模型体系结构和预训练目标与ProphetNet相同,但XLM-ProphetNet是在跨语言数据集XGLUE上进行预训练的。该库分别为多语言条件生成提供了该模型的预训练版本,为标题生成和问题生成提供了微调版本。多模态模型(Multimodal models)在这个库中有一个多模态模型,它没有像其他模型那样经过自我监督的预先训练。MMBTSupervised Multimodal Bitransformers for Classifying Images and Text, Douwe Kiela et al.一种用于多模态设置的Transformer模型,结合文本和图像进行预测。transformer模型将作为输入的映射进行标记化的文本,最后在一个预训练好的图像resnet上通过一个线性层(从特征的数量决定Transformer隐藏层的维度)。不同的输入被连接起来,并且在位置嵌入的基础上,添加一个分段嵌入,让模型知道输入向量的哪一部分对应于文本,哪一部分对应于图像。预先训练的模型只适用于分类。基于检索的模型(Retrieval-based models)有些模型在(前)训练和推理过程中使用文档检索来回答开放领域的问题。DPRDense Passage Retrieval for Open-Domain Question Answering, Vladimir Karpukhin et al.密集通道检索(DPR) -是最先进的开放领域问答研究的一套工具和模型。DPR包括三种模型:问题编码器:将问题编码为向量上下文编码器:将上下文编码为向量读者:在检索到的上下文中提取问题的答案,并给出一个相关性得分(如果推断的跨度确实回答了问题,那么得分就高)。DPR的pipeline(尚未实现)使用检索步骤查找给定某个问题的前k个上下文,然后用问题和检索到的文档调用阅读器以获得答案。RAGRetrieval-Augmented Generation for Knowledge-Intensive NLP Tasks, Patrick Lewis, Ethan Perez, Aleksandara Piktus, Fabio Petroni, Vladimir Karpukhin, Naman Goyal, Heinrich Küttler, Mike Lewis, Wen-tau Yih, Tim Rocktäschel, Sebastian Riedel, Douwe Kiela检索增强生成(RAG)模型结合了预先训练的密集检索(DPR)和Seq2Seq模型的功能。RAG模型检索文档,将它们传递给seq2seq模型,然后边缘化以生成输出。retriver和seq2seq模块由预先训练的模型初始化,并共同进行微调,允许检索和生成以适应下游任务。RAG-Token模型和RAG-Sequence模型可以进行生成。更多的技术内容(More technical aspects)Full vs sparse attention大多数Transformer模型在注意矩阵为方形的意义上使用了充分注意。当您有很长的文本时,它可能成为一个很大的计算瓶颈。Longformer和reformer是两种模型,它们试图提高效率,使用稀疏版本的注意力矩阵来加速训练。LSH attentionReformer使用LSH attention。在softmax(QKT)softmax(QK^T)softmax(QKT)中,只有最大的元素才会给出有用的贡献。所以对于q中的每一个查询q,我们可以只考虑k中接近q的键k。哈希函数用于确定q和k是否接近。注意掩码被修改为掩码当前标记(除了第一个位置),因为它将给出相同的查询和键(非常相似)。因为哈希可能有点随机,所以在实践中会使用几个哈希函数(由n_rounds参数确定),然后一起取平均值。Local attentionLongformer使用了局部注意:通常,局部上下文(例如,左边和右边的两个标记是什么?)足以对给定的标记采取行动。此外,通过叠加具有小窗口的注意层,最后一层将拥有一个接收域,而不仅仅是窗口中的标记,允许它们构建整个句子的表示。一些预先选择的输入token也会得到全局注意:对于那些少数的token,注意矩阵可以访问所有令牌,并且这个过程是对称的:所有其他token都可以访问这些特定的token(在其本地窗口的token之上)。如图本文的图2d所示,下面是注意罩的示例使用那些参数更少的注意矩阵可以让模型的输入具有更大的序列长度。Other tricksReformer使用轴向位置编码:在传统的Transformer模型中,位置编码E是一个大小为l × d的矩阵,l是序列长度,d是隐藏状态的维数。如果你有很长的文本,这个矩阵可能会很大,在GPU上占用太多的空间。为了减轻这种情况,轴向位置编码包括将大矩阵E分解为两个更小的矩阵E1和E2,维度为l1×d1l_1 \times d_1l1×d1和l2×d2l_2 \times d_2l2×d2,这样l1×l2=ll_1 \times l_2 = ll1×l2=l 和 d1+d2=dd_1 + d_2=dd1+d2=d(加上长度的乘积,这最终会小得多)。时间步长j在E中的嵌入是将时间步长j%l1j \% l_1j%l1在E1中的嵌入和j//l1j//l_1j//l1在E2中的嵌入连接起来得到的。数据预处理(Preprocessing data)在本教程中,我们将探讨如何使用 🤗 Transformers对数据进行预处理。这方面的主要工具是我们所说的分词器。我们可以使用与想要使用的模型相关联的tokenizer类,或者直接使用AutoTokenizer类来构建一个。正如我们在Quick tour中看到的,分词器首先将给定文本分割成通常称为token的单词(或部分单词、标点符号等)。然后,它将把这些token转换为数字,以便能够利用它们构建一个张量,并将它们提供给模型。它还将添加模型可能希望正常工作的任何额外输入。注意:如果你计划使用一个预训练模型,使用它相关联的预训练的分词器是很重要的。它将以与前训练语料库相同的方式分割输入文本成为token,并且它将使用与预训练期间相同的对应token索引(我们通常称之为词汇表)。要自动下载预训练期间使用的词汇表或对给定模型进行微调,可以使用from_pretraining()方法。from transformers import AutoTokenizer+
tokenizer = AutoTokenizer.from_pretrained('bert-base-cased')基本使用(Base use)一个 PreTrainedTokenizer 有很多方法,但是你需要记住的唯一一个预处理方法是它的__call__:你只需要把你的句子喂给你的分词器对象。Note:__call__是python的魔法方法,可以把类实例当做函数调用。调用时执行的函数就是类实例的__call__方法。encoded_input = tokenizer("Hello, I'm a single sentence!")
print(encoded_input)输出为:{'input_ids': [101, 8667, 117, 146, 112, 182, 170, 1423, 5650, 106, 102],
'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}这将返回一个字典,值就是字符串对应到整数的列表。input_id是与句子中每个标记对应的索引。下面我们将看到attention_mask的用途,下一节将看到token_type_ids的目标。分词器可以解码正确句子中的标记id列表。tokenizer.decode(encoded_input["input_ids"])输出为:"[CLS] Hello, I'm a single sentence! [SEP]"可以看到,分词器自动添加了模型所期望的一些特殊标记。并不是所有的模型都需要特殊的token。例如,如果我们使用gpt2-medium而不是bert-base-case来创建分词器,那么我们将看到与原来的句子相同的句子。你可以通过传递 add_special_tokens=False 来禁用这个行为(只有当你自己手动添加那些特殊的token时才会被建议)。如果有几个句子需要处理,可以通过将它们作为列表发送给分词器来有效地完成这一任务:batch_sentences = ["Hello I'm a single sentence",
"And another sentence",
"And the very very last one"]
encoded_inputs = tokenizer(batch_sentences)
print(encoded_inputs)输出为:{'input_ids': [[101, 8667, 146, 112, 182, 170, 1423, 5650, 102], [101, 1262, 1330, 5650, 102],
[101, 1262, 1103, 1304, 1304, 1314, 1141, 102]],
'token_type_ids': [[0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0]],
'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1]]}我们再次得到了一个字典,这次的值是整数列表的列表。如果一次向tokenizer发送几个句子的目的是构建batch处理为模型提供信息,那么您可能需要这样做:把每个句子填充到batch中最大的长度。将每个句子截断到模型所能接受的最大长度(如果适用的话)。返回张量。当将句子列表输入到分词器时,可以使用以下选项来完成所有这一切。batch = tokenizer(batch_sentences, padding=True, truncation=True, return_tensors="pt")
print(batch)输出为:{'input_ids': tensor([[ 101, 8667, 146, 112, 182, 170, 1423, 5650, 102], [ 101, 1262, 1330, 5650, 102, 0, 0, 0, 0],
[ 101, 1262, 1103, 1304, 1304, 1314, 1141, 102, 0]]),
'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0]]),
'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 0]])}它返回一个包含字符串键和张量值的字典。现在我们可以看到attention_mask是关于什么的:它指出模型应该注意哪些标记,哪些不应该注意(因为它们在本例中表示填充)。注意,如果您的模型没有与其关联的最大长度,那么上面的命令将抛出一个警告。你可以安全地忽略它。您还可以传递 verbose=False 来阻止分词器抛出此类警告。对句子对进行处理(Preprocessing pairs of sentences)有时你需要给你的模型提供一对句子。例如,如果您想对一对中的两个句子是否相似进行分类,或者对问句回答模型进行分类,它们接受一个上下文和一个问题。对于BERT模型,输入可以这样表示:[CLS] Sequence A [SEP] Sequence B [SEP]我们可以将两个句子作为两个参数提供(不是一个列表,因为两个句子的列表将被解释为两个单个句子的批处理,正如我们前面看到的那样),从而以模型所期望的格式编码一对句子。这将再次返回一个dict字符串到整数列表。encoded_input = tokenizer("How old are you?", "I'm 6 years old")
print(encoded_input)输出为:{'input_ids': [101, 1731, 1385, 1132, 1128, 136, 102, 146, 112, 182, 127, 1201, 1385, 102],
'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1],
'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}这向我们展示了token_type_ids的用途:它们向模型指示输入的哪一部分对应第一个句子,哪一部分对应第二个句子。注意,token_type_ids不是所有模型都需要或处理的。默认情况下,分词器将只返回其关联模型所期望的输入。可以使用return_input_ids或return_token_type_ids强制返回(或不返回)任何这些特殊参数。如果我们解码我们获得的token id,我们将看到特殊的token被适当地添加了。tokenizer.decode(encoded_input["input_ids"])输出为:"[CLS] How old are you? [SEP] I'm 6 years old [SEP]"如果有一个需要处理的序列对列表,那么应该将它们作为两个列表提供给分词器:第一个句子列表和第二个句子列表。batch_sentences = ["Hello I'm a single sentence",
"And another sentence",
"And the very very last one"]
batch_of_second_sentences = ["I'm a sentence that goes with the first sentence",
"And I should be encoded with the second sentence",
"And I go with the very last one"]
encoded_inputs = tokenizer(batch_sentences, batch_of_second_sentences)
print(encoded_inputs)输出为:{'input_ids': [[101, 8667, 146, 112, 182, 170, 1423, 5650, 102, 146, 112, 182, 170, 5650, 1115, 2947, 1114, 1103, 1148, 5650, 102],
[101, 1262, 1330, 5650, 102, 1262, 146, 1431, 1129, 12544, 1114, 1103, 1248, 5650, 102],
[101, 1262, 1103, 1304, 1304, 1314, 1141, 102, 1262, 146, 1301, 1114, 1103, 1304, 1314, 1141, 102]],
'token_type_ids': [[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1]],
'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]}正如我们所看到的,它返回一个字典,其中每个值都是一个整型数列表的列表。为了再次检查输入到模型中的内容,我们可以逐个解码input_ids中的每个列表。for ids in encoded_inputs["input_ids"]:
print(tokenizer.decode(ids))输出为:[CLS] Hello I'm a single sentence [SEP] I'm a sentence that goes with the first sentence [SEP][CLS] And another sentence [SEP] And I should be encoded with the second sentence [SEP][CLS] And the very very last one [SEP] And I go with the very last one [SEP]同样,可以自动将输入填充到批处理中的最大句子长度,截断到模型可以接受的最大长度,并使用以下方法直接返回张量batch = tokenizer(batch_sentences, batch_of_second_sentences, padding=True, truncation=True, return_tensors="pt")
print(batch)输出为:{'input_ids': tensor([[ 101, 8667, 146, 112, 182, 170, 1423, 5650, 102, 146,
112, 182, 170, 5650, 1115, 2947, 1114, 1103, 1148, 5650,
102],
[ 101, 1262, 1330, 5650, 102, 1262, 146, 1431, 1129, 12544,
1114, 1103, 1248, 5650, 102, 0, 0, 0, 0, 0,
0],
[ 101, 1262, 1103, 1304, 1304, 1314, 1141, 102, 1262, 146,
1301, 1114, 1103, 1304, 1314, 1141, 102, 0, 0, 0,
0]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0]])}关于填充和截断(Everything you always wanted to know about padding and truncation)我们已经看到了适用于大多数情况的命令(将批处理填充到最大句子的长度,并截断到模式可以接受的最大长度)。但是,如果需要的话,API支持更多的策略。为此需要知道的三个参数是padding、truncation和max_length。padding控制填充。它可以是一个布尔值或字符串,应该是:True或'longest'参数将使得每个句子都填充到batch中最长的序列(如果只提供一个序列,则不填充)。'max_length'参数将使得每个句子都填充到max_length参数指定的长度,或者如果没有提供max_length (max_length=None),则填充为模型可接受的最大长度(例如BERT为512个token)。如果你只提供一个序列,填充仍然会应用到它上面。False或'do_not_pad'不填充序列。正如我们之前看到的,这是默认行为。truncation控制截断。它可以是一个布尔值或字符串,应该是:True或'only_first'截断为max_length参数指定的最大长度,或者如果没有提供max_length,则为模型接受的最大长度(max_length=None)。如果提供了一对序列(或一批序列),这只会截断一对序列的第一个句子。'only_second'截断为max_length参数指定的最大长度,如果没有提供max_length,则截断为模型接受的最大长度(max_length=None)。如果提供了一对序列(或一批序列),这只会截断一对序列的第二个句子。'longest_first'截断为max_length参数指定的最大长度,或者如果没有提供max_length,则为模型接受的最大长度(max_length=None)。这将逐个截断token,从该对中的最长序列中删除一个标记,直到达到适当的长度。False或'do_not_truncate'不截断序列。正如我们之前看到的,这是默认行为。max_length来控制填充/截断的长度。它可以是一个整数或None,在这种情况下,它将默认为模型可以接受的最大长度。如果模型没有特定的最大输入长度,截断/填充到max_length将被禁用。下面是一个表,总结了设置填充和截断的建议方法。如果你用一双输入序列在下列例子中,你可以用一个在[‘only_first’,‘only_second’,' longest_first ']的STRATEGY替换truncation=True,即 truncation='only_second' or truncation= 'longest_first'来控制序列对中的两个序列如何截断。这里通过实验来验证一下这些指令,我使用的是一组pair的形式。首先定义这对pair的batch形式,注意,这里batch_sentences的第i个元素和batch_of_second_sentences的第i个元素才是一个pair。batch_sentences = ["Hello I'm a single sentence",
"And another sentence",
"And the very very last one"]
batch_of_second_sentences = ["I'm a sentence that goes with the first sentence",
"And I should be encoded with the second sentence",
"And I go with the very last one"]No truncation首先是no truncation,这里分为no padding、padding to max sequence in batch、padding to max model input length、padding to specific length四种情况。no paddingbatch = tokenizer(batch_sentences, batch_of_second_sentences)
for ids in batch['input_ids']: # 打印输出的代码全部一样,后续省略 print("===="*36)
print(len(tokenizer.convert_ids_to_tokens(ids)))
print(tokenizer.convert_ids_to_tokens(ids))
print()输出为:================================================================================================================================================
21['[CLS]', 'Hello', 'I', "'", 'm', 'a', 'single', 'sentence', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', 'goes', 'with', 'the', 'first', 'sentence', '[SEP]']================================================================================================================================================
15['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]']================================================================================================================================================
17['[CLS]', 'And', 'the', 'very', 'very', 'last', 'one', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', 'one', '[SEP]']这里输出了经过tokenizer后的sentence pair,再分词得到的token基础上共新增了三个特殊的token。可以看到,在no padding、no truncation的情况下,并未有任何填充和截断的情形(除非句子长到模型最大输入才会截断)发生。由于打印输出的代码都一样,后续打印输出代码省略。padding to max sequence in batchbatch = tokenizer(batch_sentences, batch_of_second_sentences, padding=True)
# 等价于# batch = tokenizer(batch_sentences, batch_of_second_sentences, padding='longest')输出为:================================================================================================================================================
21['[CLS]', 'Hello', 'I', "'", 'm', 'a', 'single', 'sentence', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', 'goes', 'with', 'the', 'first', 'sentence', '[SEP]']================================================================================================================================================
21['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']================================================================================================================================================
21['[CLS]', 'And', 'the', 'very', 'very', 'last', 'one', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', 'one', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']在这种情况下,文本已经填充对齐到了最长的那个sentence。*padding to max model input length **batch = tokenizer(batch_sentences, batch_of_second_sentences, padding='max_length')这种情况下,文本会自动填充到模型能接受的最长输入,BERT这里是512. 输出(由于填充到了512个长度,故以下输出只展示了几个PAD)为:================================================================================================================================================
512['[CLS]', 'Hello', 'I', "'", 'm', 'a', 'single', 'sentence', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', 'goes', 'with', 'the', 'first', 'sentence', '[SEP]', '[PAD]', ]================================================================================================================================================
512['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]', '[PAD]',]================================================================================================================================================
512['[CLS]', 'And', 'the', 'very', 'very', 'last', 'one', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', 'one', '[SEP]', '[PAD]',]padding to specific length同样,我们也可以自己设置max_length。batch = tokenizer(batch_sentences, batch_of_second_sentences, padding='max_length', max_length=24)================================================================================================================================================
24['[CLS]', 'Hello', 'I', "'", 'm', 'a', 'single', 'sentence', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', 'goes', 'with', 'the', 'first', 'sentence', '[SEP]', '[PAD]', '[PAD]', '[PAD]']================================================================================================================================================
24['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']================================================================================================================================================
24['[CLS]', 'And', 'the', 'very', 'very', 'last', 'one', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', 'one', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']这里设置max_length=24,可以看到文本都被填充到了24。truncation to max model input lengthno paddingbatch = tokenizer(batch_sentences, batch_of_second_sentences, truncation=True)输出为:================================================================================================================================================
21['[CLS]', 'Hello', 'I', "'", 'm', 'a', 'single', 'sentence', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', 'goes', 'with', 'the', 'first', 'sentence', '[SEP]']================================================================================================================================================
15['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]']================================================================================================================================================
17['[CLS]', 'And', 'the', 'very', 'very', 'last', 'one', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', 'one', '[SEP]']同时,在sentence pair的情况下,我们实验了以下三句代码输出都是一样的batch = tokenizer(batch_sentences, batch_of_second_sentences, truncation='only_first')
batch = tokenizer(batch_sentences, batch_of_second_sentences, truncation='only_second')
batch = tokenizer(batch_sentences, batch_of_second_sentences, truncation='longest_first')输出同上,不再展示。padding to max sequence in batchbatch = tokenizer(batch_sentences, batch_of_second_sentences, padding=True, truncation=True)输出为:================================================================================================================================================
21['[CLS]', 'Hello', 'I', "'", 'm', 'a', 'single', 'sentence', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', 'goes', 'with', 'the', 'first', 'sentence', '[SEP]']================================================================================================================================================
21['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']================================================================================================================================================
21['[CLS]', 'And', 'the', 'very', 'very', 'last', 'one', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', 'one', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']这里的文本已经填充对齐,同时由于远远没有达到模型最大输入的长度限制,没有发生截断。然后测试batch = tokenizer(batch_sentences, batch_of_second_sentences, padding=True, truncation='only_first')
batch = tokenizer(batch_sentences, batch_of_second_sentences, padding=True, truncation='only_second')
batch = tokenizer(batch_sentences, batch_of_second_sentences, padding=True, truncation='longest_first')输出与上面完全一样。padding to max model input lengthbatch = tokenizer(batch_sentences, batch_of_second_sentences, padding='max_length', truncation=True)输出全部填充到了512长度。================================================================================================================================================
512['[CLS]', 'Hello', 'I', "'", 'm', 'a', 'single', 'sentence', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', 'goes', 'with', 'the', 'first', 'sentence', '[SEP]', '[PAD]', ]================================================================================================================================================
512['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]', '[PAD]',]================================================================================================================================================
512['[CLS]', 'And', 'the', 'very', 'very', 'last', 'one', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', 'one', '[SEP]', '[PAD]',]同样测试以下三句代码结果也都相同batch = tokenizer(batch_sentences, batch_of_second_sentences, padding='max_length', truncation='only_first')
batch = tokenizer(batch_sentences, batch_of_second_sentences, padding='max_length', truncation='only_second')
batch = tokenizer(batch_sentences, batch_of_second_sentences, padding='max_length', truncation='longest_first')同时在truncation to max model input length的设置下无法实现padding to specific length。truncation to specific lengthno paddingbatch = tokenizer(batch_sentences, batch_of_second_sentences, truncation=True, max_length=20)这里设置最大长度为16,输出为:================================================================================================================================================
16['[CLS]', 'Hello', 'I', "'", 'm', 'a', 'single', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', 'goes', '[SEP]']================================================================================================================================================
15['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]']================================================================================================================================================
16['[CLS]', 'And', 'the', 'very', 'very', 'last', 'one', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', '[SEP]']可以看到,第一三段文本被截断了,其他第二段既没有被截断也没有被填充。接着,我们分别测试一下[‘only_first’,‘only_second’,’ longest_first ']的差异:首先是batch = tokenizer(batch_sentences, batch_of_second_sentences, truncation='only_first', max_length=16)其输出为================================================================================================================================================
16['[CLS]', 'Hello', 'I', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', 'goes', 'with', 'the', 'first', 'sentence', '[SEP]']================================================================================================================================================
15['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]']================================================================================================================================================
16['[CLS]', 'And', 'the', 'very', 'very', 'last', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', 'one', '[SEP]']可以看到,在only_first设置下,第一个句子被优先截断。batch = tokenizer(batch_sentences, batch_of_second_sentences, truncation='only_second', max_length=16)输出为:================================================================================================================================================
16['[CLS]', 'Hello', 'I', "'", 'm', 'a', 'single', 'sentence', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', '[SEP]']================================================================================================================================================
15['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]']================================================================================================================================================
16['[CLS]', 'And', 'the', 'very', 'very', 'last', 'one', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', '[SEP]']可以看到,在only_second设置下,第二个句子被优先截断。在batch = tokenizer(batch_sentences, batch_of_second_sentences, truncation='longest_first', max_length=16)
输出为:================================================================================================================================================
16['[CLS]', 'Hello', 'I', "'", 'm', 'a', 'single', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', 'goes', '[SEP]']================================================================================================================================================
15['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]']================================================================================================================================================
16['[CLS]', 'And', 'the', 'very', 'very', 'last', 'one', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', '[SEP]']此时,优先截断最长的那个(一个token一个token的截断,可能先截断第二句,删除几个token之后一二相同了,就继续截断第一句……)。需要注意的是,截断长度一定要保证两句话都有内容(最少要剩下1个token),否则会报错。batch = tokenizer(batch_sentences, batch_of_second_sentences, truncation='only_first', max_length=15)
batch = tokenizer(batch_sentences, batch_of_second_sentences, truncation='only_first', max_length=14)例如上面第一句不会报错,因为这样截断还可以保证第一句有一个token为'I',而第二句会报错。padding to max sequence in batch设置padding=True后,达不到batch中最长长度的句子会被填充。如下代码batch = tokenizer(batch_sentences, batch_of_second_sentences, padding=True, truncation='only_first', max_length=18)输出为:================================================================================================================================================
18['[CLS]', 'Hello', 'I', "'", 'm', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', 'goes', 'with', 'the', 'first', 'sentence', '[SEP]']================================================================================================================================================
18['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]', '[PAD]', '[PAD]', '[PAD]']================================================================================================================================================
18['[CLS]', 'And', 'the', 'very', 'very', 'last', 'one', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', 'one', '[SEP]', '[PAD]']同时截断也正常发挥作用。使用[‘only_first’,‘only_second’,’ longest_first ']设置时也是截断相应的部分。这里不再展示。padding to specific length这里将确保PAD到特定的长度(通过设置max_length来实现)。注意一下代码的对比:batch = tokenizer(batch_sentences, batch_of_second_sentences, padding='max_length', truncation=True, max_length=22)输出:================================================================================================================================================
22['[CLS]', 'Hello', 'I', "'", 'm', 'a', 'single', 'sentence', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', 'goes', 'with', 'the', 'first', 'sentence', '[SEP]', '[PAD]']================================================================================================================================================
22['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']================================================================================================================================================
22['[CLS]', 'And', 'the', 'very', 'very', 'last', 'one', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', 'one', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']而如下代码:batch = tokenizer(batch_sentences, batch_of_second_sentences, padding=True, truncation=True, max_length=22)输出为:================================================================================================================================================
21['[CLS]', 'Hello', 'I', "'", 'm', 'a', 'single', 'sentence', '[SEP]', 'I', "'", 'm', 'a', 'sentence', 'that', 'goes', 'with', 'the', 'first', 'sentence', '[SEP]']================================================================================================================================================
21['[CLS]', 'And', 'another', 'sentence', '[SEP]', 'And', 'I', 'should', 'be', 'encoded', 'with', 'the', 'second', 'sentence', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']================================================================================================================================================
21['[CLS]', 'And', 'the', 'very', 'very', 'last', 'one', '[SEP]', 'And', 'I', 'go', 'with', 'the', 'very', 'last', 'one', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']Pre-tokenized输入(Pre-tokenized inputsPre-tokenized inputs)tokenizer还接受预标记化的输入。当想要计算命名实体识别(NER)或词性标记(POS标记)中的标签和提取预测时,这一点特别有用。警告:预标记化并不意味着您的输入已经分词好了(如果是这样的话,您不需要通过tokenizer传递它们),而是将它们分割成单词(这通常是子单词标记化算法(如BPE)的第一步)。如果您想使用预标记化的输入,只需在将输入传递给分词器时设置is_split_into_words=True。例如,我们有encoded_input = tokenizer(["Hello", "I'm", "a", "single", "sentence"], is_split_into_words=True)
print(encoded_input)输出为:{'input_ids': [101, 8667, 146, 112, 182, 170, 1423, 5650, 102],
'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0],
'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]}注意,分词器仍然会添加特殊标记的id(如果适用),除非传递add_special_tokens=False。同时,tokenizer同样会进行分词操作,只是在设置is_split_into_words=True后,它不会在词和词直接进行分割。如下示例代码:batch = tokenizer(["Hello", "I'm", "a", "single", "sentence"], is_split_into_words=True)
ids = batch['input_ids'] # 打印输出的代码全部一样,后续省略print("===="*36)
print(len(tokenizer.convert_ids_to_tokens(ids)))
print(tokenizer.convert_ids_to_tokens(ids))
print()输出为:================================================================================================================================================
9['[CLS]', 'Hello', 'I', "'", 'm', 'a', 'single', 'sentence', '[SEP]']可以看到,I'm这个词内部仍被切分为了I,', m三个token。这和之前的一组句子或一组句子对完全一样。你可以像这样编码一批句子batch_sentences = [["Hello", "I'm", "a", "single", "sentence"],
["And", "another", "sentence"],
["And", "the", "very", "very", "last", "one"]]
encoded_inputs = tokenizer(batch_sentences, is_split_into_words=True)或者像这样的一组成对句子batch_of_second_sentences = [["I'm", "a", "sentence", "that", "goes", "with", "the", "first", "sentence"],
["And", "I", "should", "be", "encoded", "with", "the", "second", "sentence"],
["And", "I", "go", "with", "the", "very", "last", "one"]]
encoded_inputs = tokenizer(batch_sentences, batch_of_second_sentences, is_split_into_words=True)你可以添加填充,截断以及像之前那样直接返回张量batch = tokenizer(batch_sentences,
batch_of_second_sentences,
is_split_into_words=True,
padding=True,
truncation=True,
return_tensors="pt")
野生程序员在线
Day 05-结构化数组
本节内容:1、结构化数据类型2、字段访问一、结构化数据类型结构化数据类型可以被认为是一定长度的字节序列(结构的项大小),它被解释为字段的集合。 每个字段在结构中都有一个名称、一个数据类型和一个字节偏移量。 字段的数据类型可以是任何 numpy 数据类型可以使用函数 numpy.dtype 创建结构化数据类型,有 4 种替代形式的规范,它们的灵活性和简洁性各不相同:1.1、元组列表元组列表,每个字段一个元组。每个元组都具有 (fieldname, datatype, shape) 形式,其中 shape 是可选的np.dtype([('x', 'f4'), ('y', np.float32), ('z', 'f4', (2, 2))])
# dtype([('x', '<f4'), ('y', '<f4'), ('z', '<f4', (2, 2))])
#如果 fieldname 是空字符串 '',则该字段将被赋予 f# 形式的默认名称
np.dtype([('x', 'f4'), ('', 'i4'), ('z', 'i8')])
# dtype([('x', '<f4'), ('f1', '<i4'), ('z', '<i8')])1.2、一串以逗号分隔的 dtype 规格在这个速记符号中,任何字符串 dtype 规范都可以在字符串中使用并用逗号分隔,并且字段名称被赋予默认名称 f0、f1 等np.dtype('i8, f4, S3')
# dtype([('f0', '<i8'), ('f1', '<f4'), ('f2', 'S3')])
np.dtype('3int8, float32, (2, 3)float64')
# dtype([('f0', 'i1', (3,)), ('f1', '<f4'), ('f2', '<f8', (2, 3))])1.3、字段参数数组字典该字典有两个必需键,“名称”和“格式”,‘names’ 和 ‘formats’ 的值应分别是相同长度的字段名称列表和 dtype 规范列表可选的“偏移”值应该是整数字节偏移列表,结构中的每个字段一个。 如果未给出“偏移量”,则自动确定偏移量。 可选的“itemsize”值应该是一个整数,以字节为单位描述 dtype 的总大小,它必须足够大以包含所有字段np.dtype({'names': ['col1', 'col2'], 'formats': ['i4', 'f4']})
# dtype([('col1', '<i4'), ('col2', '<f4')])
np.dtype({'names': ['col1', 'col2'],
'formats': ['i4', 'f4'],
'offsets': [0, 4],
'itemsize': 12})
# dtype({'names': ['col1', 'col2'], 'formats': ['<i4', '<f4'],
# 'offsets': [0, 4], 'itemsize': 12})1.4、字段名称字典字典的键是字段名,值是指定类型和偏移量的元组:np.dtype({'col1': ('i1', 0), 'col2': ('f4', 1)})
# dtype([('col1', 'i1'), ('col2', '<f4')])二、字段访问如果 ndarray 对象是结构化数组,则可以通过使用字符串索引数组来访问数组的字段,类似于字典对结构化数组的索引也可以通过字段名称列表来完成,例如 x[['field-name1', 'field-name2']]如果访问的字段是子数组,则子数组的维度将附加到结果的形状中。 例如:2.1、访问单个字段x = np.zeros((2, 2), dtype=[('a', np.int32),
('b', np.float64, (3, 3))
])
x
'''
array([[(0, [[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]]),
(0, [[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]])],
[(0, [[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]]),
(0, [[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]])]],
dtype=[('a', '<i4'), ('b', '<f8', (3, 3))])
'''
x['a'].shape
# (2, 2)
x['a'].dtype
# dtype('int32')
x['b'].shape
# (2, 2, 3, 3)
x['b'].dtype
# dtype('float64')
d = np.dtype([('x', 'i8'), ('y', 'f4')])
d.names
#('x', 'y')
d.fields2.2、访问多个字段可以索引并分配给具有多字段索引的结构化数组,其中索引是字段名称列表。a = np.zeros(3, dtype=[('a', 'i4'), ('b', 'i4'), ('c', 'f4')])
a[['a', 'c']]
array([(0, 0.), (0, 0.), (0, 0.)],
dtype={'names':['a','c'], 'formats':['<i4','<f4'], 'offsets':[0,8], 'itemsize':12})
野生程序员在线
Day 07-进阶(01)
本节重点归一化随机数组生成矩阵乘法分桶寻找常见值相关性1 对一个5x5的随机矩阵做归一化提示: (x - min) / (max - min)答案:import numpy as np
Z = np.random.randint(1,20, size = 25).reshape(5,5)
print(Z)
Zmax, Zmin = Z.max(), Z.min()
Z = (Z - Zmin)/(Zmax - Zmin)
print(Z)2. 如何创建包含5到10之间随机浮动的二维数组?描述:创建一个形状为5x3的二维数组,以包含5到10之间的随机十进制数。思路:1、randint 整数2、uniform 小数 :https://www.runoob.com/python/func-number-uniform.html#Solution Method 1:
rand_arr = np.random.randint(low=5, high=10, size=(5,3)) + np.random.random((5,3))
print(rand_arr)
#Solution Method 2:
rand_arr = np.random.uniform(5,10, size=(5,3))
print(rand_arr)
# > [[ 8.50061025 9.10531502 6.85867783]
# > [ 9.76262069 9.87717411 7.13466701]
# > [ 7.48966403 8.33409158 6.16808631]
# > [ 7.75010551 9.94535696 5.27373226]
# > [ 8.0850361 5.56165518 7.31244004]]3. 一个5x3的矩阵与一个3x2的矩阵相乘,实矩阵乘积是什么?提示: np.dot区别:np.multiply,np.dot答案:Z = np.dot(np.ones((5,3)), np.ones((3,2)))
print(Z)4. 如何将数字转换为分类(文本)数组?问题:将iris_2d的花瓣长度(第3列)加入以形成文本数组,这样如果花瓣长度为:Less than 3 --> 'small'3-5 --> 'medium''>=5 --> 'large'给定:#Input
url = '<https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data>'
iris = np.genfromtxt(url, delimiter=',', dtype='object')
names = ('sepallength', 'sepalwidth', 'petallength', 'petalwidth', 'species')
'''答案:import numpy as np
# Input
url = '<https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data>'
iris = np.genfromtxt(url, delimiter=',', dtype='object')
names = ('sepallength', 'sepalwidth', 'petallength', 'petalwidth', 'species')
#查看数据
iris.shape
# Bin petallength
petal_length_bin = np.digitize(iris[:, 2].astype('float'), [0, 3, 5, 10])
# Map it to respective category
label_map = {1: 'small', 2: 'medium', 3: 'large', 4: np.nan}
petal_length_cat = [label_map[x] for x in petal_length_bin]
# View
petal_length_cat[:4]
# > ['small', 'small', 'small', 'small']
petal_length_cat
5. 如何在numpy数组中找到最常见的值?- 描述:在鸢尾属植物数据集中找到最常见的花瓣长度值(第3列)。**给定:**答案:# **给定:**
url = '<https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data>'
iris = np.genfromtxt(url, delimiter=',', dtype='object')
# Solution:
vals, counts = np.unique(iris[:, 2], return_counts=True)
print(vals[np.argmax(counts)])
vals
# > b'1.5'6. 如何找到numpy数组的两列之间的相关性?- 描述:在iris_2d中找出SepalLength(第1列)和PetalLength(第3列)之间的相关性**给定:**答案:# Input
url = '<https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data>'
iris = np.genfromtxt(url, delimiter=',', dtype='float', usecols=[0,1,2,3])
# Solution 1
#np.corrcoef : <https://blog.csdn.net/qq_39514033/article/details/88931639>
cor = np.corrcoef(iris[:, 0], iris[:, 2])
print(cor)
print(cor[0, 1])
# Solution 2
#输出:r: 相关系数 [-1,1]之间,p-value: p值。
# 注: p值越小,表示相关系数越显著,一般p值在500个样本以上时有较高的可靠性。
#说明:<https://www.osgeo.cn/scipy/reference/generated/scipy.stats.pearsonr.html>
from scipy.stats.stats import pearsonr
corr, p_value = pearsonr(iris[:, 0], iris[:, 2])
print(corr,p_value)
野生程序员在线
10天玩转NumPy
员在短时间内掌握NumPy库,成为高效使用该库进行科学计算和数据处理的专业人士。课程涵盖NumPy的基础概念、数组操作、数学运算、数据处理、随机模块等多个主题。通过每天一课的学习计划,学员将深入了解NumPy的各项功能,并通过实际案例进行实践,提高在数据科学和机器学习领域的应用水平
野生程序员在线
Huggingface Transformers库学习笔记
Huggingface的Transformers库是一个很棒的项目,该库提供了用于自然语言理解(NLU)任务(如分析文本的情感)和自然语言生成(NLG)任务(如用新文本完成提示或用另一种语言翻译)的预先训练的模型。其收录了在100多种语言上超过32种预训练模型。这些先进的模型通过这个库可以非常轻松的调取。同时,也可以通过Pytorch和TensorFlow 2.0进行编写修改等