fastai官方教程的第二课分为两个部分,第一个部分主要介绍了如何通过google图片构建自己的图片数据集,方便自己进行一些算法实验。第二部分则着重介绍了一些机器学习训练中会遇到的一些问题,例如过拟合,欠拟合,等产生的原因,并且如何尽可能的避免这些情况的发生的技巧,最后还简单介绍了一下随机梯度下降
如何用Google构建自己的数据集
课程中教授了一种利用google图片搜索来获取数据集的方式
首先,打开google图片的网站,搜索想要下载的图片内容,并且打开开发者工具,输入以下JS脚本,通过该脚本可以保存当前google图片页面下搜索到的图片路径到本地文件,之后可以自己为不同类型的文件命名
urls = Array.from(document.querySelectorAll('.rg_di .rg_meta')).map(el=>JSON.parse(el.textContent).ou);
window.open('data:text/csv;charset=utf-8,' + escape(urls.join('\n')));
接下来我们利用下载的包含图片路径的文件创建自己的数据集
#导入fastai库
from fastai import *
from fastai.vision import *
#定义类别,课程中的例子为泰迪熊,灰熊和黑熊
classes = ['teddys','grizzly','black']
#定义黑熊的文件夹,以及黑熊的图片路径文件
folder = 'black'
file = 'urls_black.txt'
#定义熊的图片下载路径,并且创建黑熊的图片目录
path = Path('data/bears')
dest = path/folder
dest.mkdir(parents=True, exist_ok=True)
#这里执行了shell,是jupyter notebook的用法,将上级目录中input下的路径文件,
#也就是之前通过脚本下载并重命名的文件,都复制到图片下载主目录下
!cp ../input/* {path}/
#根据文件中的路径下载图片到指定目录,并且不超过200张
download_images(path/file, dest, max_pics=200)
#与上面类似的下载泰迪熊数据和灰熊数据
folder = 'teddys'
file = 'urls_teddys.txt'
path = Path('data/bears')
dest = path/folder
dest.mkdir(parents=True, exist_ok=True)
download_images(path/file, dest, max_pics=200)
folder = 'grizzly'
file = 'urls_grizzly.txt'
path = Path('data/bears')
dest = path/folder
dest.mkdir(parents=True, exist_ok=True)
download_images(path/file, dest, max_pics=200)
#最后遍历每个类别的图片文件夹,并且删除无法打开的文件
for c in classes:
print(c)
verify_images(path/c, delete=True, max_size=500)
至此,我们将三种不同类别的图片从google上下载并且放到了指定的文件夹下,建立一个简单的图片数据集
使用自己的数据集进行训练
#从文件夹建立数据集,根据文件夹的名称来标记数据,将图片变换为尺寸64,同时对图片进行归一化
np.random.seed(42)
data = ImageDataBunch.from_folder(path, train=".", valid_pct=0.2,
ds_tfms=get_transforms(), size=64, num_workers=0).normalize(imagenet_stats)
data.classes
#展示了前几张图片
data.show_batch(rows=3, figsize=(3,4))
#下面的步骤主要是创建resnet34网络,并且进行训练
learn = create_cnn(data, models.resnet34, metrics=error_rate)
learn.fit_one_cycle(4)
#保存模型
learn.save('stage-1')
#寻找合适的学习率重新训练
learn.unfreeze()
learn.lr_find()
learn.recorder.plot()
learn.fit_one_cycle(2, max_lr=slice(1e-4,1e-3))
#再次保存模型
learn.save('stage-2')
#读取上面保存的模型,并且显示错误的分类
learn.load('stage-2');
interp = ClassificationInterpretation.from_learner(learn)
interp.plot_confusion_matrix()
通过上面的训练我们会发现一些错误的分类结果,这些结果产生的原因有一部分是因为我们的图片数据集本事有错误的数据,因此我们需要对数据进行清理。
清理数据
#引入fastai小工具
from fastai.widgets import *
#找出训练数据中与分错的图片损失函数差别最大的前100张图片
ds, idxs = DatasetFormatter().from_toplosses(learn, n_imgs=100)
#打开图片清理工具,手动选择标记错误的图片
ImageCleaner(ds, idxs, path)
#也可以通过寻找最相似的图片来进行数据清理
ds, idxs = DatasetFormatter().from_similars(learn)
清理数据后,会产生一个csv的文件,包含了新的数据集,在进行重新训练之前,需要通过这个csv文件来对数据进行重新的构建,通过csv构建数据的方法可以参考第一课。
如何在项目中使用你的模型
#导入fastai的库,由于实际项目中,我们运行的设备可能不支持gpu计算,因此我们将设备设置为cpu
import fastai
fastai.defaults.device = torch.device('cpu')
#打开需要预测的图片
img = open_image(path/'black'/'00000021.jpg')
img
#这里首先定义了预测的类别,并且初始化了一个空的数据集用于预测,
#数据集定义了图片变化的尺寸为224,并且做了归一化
classes = ['black', 'grizzly', 'teddys']
data2 = ImageDataBunch.single_from_classes(path, classes, tfms=get_transforms(), size=224).normalize(imagenet_stats)
#使用上面定义的空数据集初始化resnet34网络,并且读取训练好的模型
learn = create_cnn(data2, models.resnet34).load('stage-2')
#对输入的图片进行预测,返回预测的类别
pred_class,pred_idx,outputs = learn.predict(img)
pred_class
如何处理一些错误情况
- 学习率太低
#这里采用了一个较小的学习率进行训练,导致的结果是训练的速度非常慢,准确率提升很慢
learn = create_cnn(data, models.resnet34, metrics=error_rate)
learn.fit_one_cycle(5, max_lr=1e-5)
learn.recorder.plot_losses()
- 训练轮数太少
#这里只训练了一轮,容易引起欠拟合,结果就是准确率不够高,没有收敛到底
learn = create_cnn(data, models.resnet34, metrics=error_rate, pretrained=False)
learn.fit_one_cycle(1)
- 训练轮数太多
#这里课程中在数据集上加入了大量参数,并且进行了40轮的训练,为了能够达到过拟合,
#一般过拟合的表现是准确率上下浮动。不过深度学习视乎过拟合的情况比较少见,大多数情况都是欠拟合。
np.random.seed(42)
data = ImageDataBunch.from_folder(path, train=".", valid_pct=0.9, bs=32,
ds_tfms=get_transforms(do_flip=False, max_rotate=0, max_zoom=1, max_lighting=0, max_warp=0
),size=224, num_workers=4).normalize(imagenet_stats)
learn = create_cnn(data, models.resnet50, metrics=error_rate, ps=0, wd=0)
learn.unfreeze()
learn.fit_one_cycle(40, slice(1e-6,1e-4))
随机梯度下降(SGD)
随机梯度下降法相对于梯度下降有着一些非常好的优势,不仅减少了计算的时间,同时还增强了模型的泛化度,减少了过拟合。课程里针对线性回归,采用梯度下降的方法做了一个简单的案例。
%matplotlib inline
from fastai.basics import *
#初始化数据,x为一个100x2的矩阵,其中每一行的第一列为一个-1到1区间的浮点数,第二列为1
#第一列表示x的横坐标,而第二列则表示常数项系数
n=100
x = torch.ones(n,2)
x[:,0].uniform_(-1.,1)
x[:5]
tensor([[-0.9436, 1.0000],
[ 0.6772, 1.0000],
[-0.0243, 1.0000],
[-0.3188, 1.0000],
[-0.7737, 1.0000]])
#初始化一个tensor向量,第一列为3,第二列为2,表示x的系数为3,常数项为2
a = tensor(3.,2); a
tensor([3., 2.])
#构建x与y的线性关系,同时加上一些扰动
#其中'@'运算为pytorch中矩阵与tensor相乘的运算,类似于点乘,令矩阵的每一行逐行与tensor进行点乘
y = x@a + torch.rand(n)
plt.scatter(x[:,0], y);
#定义损失函数,均方误差
def mse(y_hat, y): return ((y_hat-y)**2).mean()
#初始化参数a
a = tensor(-1.,1)
#初始化预测结果
y_hat = x@a
#初始误差
mse(y_hat, y)
#展示初始的直线与观测的数据
plt.scatter(x[:,0],y)
plt.scatter(x[:,0],y_hat);
下面采用梯度下降法
#初始化参数
a = nn.Parameter(a); a
Parameter containing:
tensor([-1., 1.], requires_grad=True)
#参数更新
def update():
y_hat = x@a
loss = mse(y, y_hat)
if t % 10 == 0: print(loss)
loss.backward() #反向传播计算梯度
with torch.no_grad(): #使得参数不进行梯度下降
a.sub_(lr * a.grad) #手动更新参数
a.grad.zero_() #令梯度置零,用于下次计算
lr = 1e-1
for t in range(100): update()
tensor(7.0736, grad_fn=<MeanBackward1>)
tensor(1.5100, grad_fn=<MeanBackward1>)
tensor(0.4826, grad_fn=<MeanBackward1>)
tensor(0.1939, grad_fn=<MeanBackward1>)
tensor(0.1110, grad_fn=<MeanBackward1>)
tensor(0.0872, grad_fn=<MeanBackward1>)
tensor(0.0803, grad_fn=<MeanBackward1>)
tensor(0.0784, grad_fn=<MeanBackward1>)
tensor(0.0778, grad_fn=<MeanBackward1>)
tensor(0.0776, grad_fn=<MeanBackward1>)
#展示训练结果
plt.scatter(x[:,0],y)
plt.scatter(x[:,0],x@a);
最后课件里还贴出了一个动画展示训练的过程,简单贴一下代码
from matplotlib import animation, rc
rc('animation', html='jshtml')
a = nn.Parameter(tensor(-1.,1))
fig = plt.figure()
plt.scatter(x[:,0], y, c='orange')
line, = plt.plot(x[:,0], x@a)
plt.close()
def animate(i):
update()
line.set_ydata(x@a)
return line,
animation.FuncAnimation(fig, animate, np.arange(0, 100), interval=20)