PyTorch:RNN,TensorBoard,部署PyTorch,数据增强

1,循环神经网络(RNN)

1.1,基本概述

大脑区别于机器的一个最大的特征就是有记忆,并且能够根据自己的记忆对未知的事务进行推导,思想拥有持久性的。由于传统的神经网络没有设计记忆结构,因此在处理序列数据上无所适从(即便经过特殊的处理),这不仅导致工作量变大,预测的结果也会收到很大的影响。循环神经网络(RNN)针对BP神经网络的缺点,增加了信息跨时传递的结构。传统的神经网络模型面对许多问题显得无能为力,因为同层节点之间无连接,网络传播也是顺序的。而循环神经网络对序列化数据有很强的的模型拟合能力,因为它相比起ANN来讲是一种具有反馈结构的神经网络,其输出不但与当前输入和网络的权值有关,而且也与先前网络的输入有关。

RNN的起因:现实世界中,很多元素都是相互连接的,比如室外的温度是随着气候的变化而周期性的变化的、我们的语言也需要通过上下文的关系来确认所表达的含义。但是机器要做到这一步就相当得难了。因此,就有了现在的循环神经网络,它的本质是:拥有记忆的能力,并且会根据这些记忆的内容来进行推断。因此,他的输出就依赖于当前的输入和记忆。

RNN的优势:RNN背后的想法是利用顺序的信息。 在传统的神经网络中,假设所有输入(和输出)彼此独立。 如果预测句子中的下一个单词,就要知道它前面有哪些单词,甚至要看到后面的单词才能够给出正确的答案。RNN之所以称为循环,就是因为它们对序列的每个元素都会执行相同的任务,所有的输出都取决于先前的计算。 从另一个角度讲RNN的它是有“记忆”的,可以捕获到目前为止计算的信息。 理论上,RNN可以在任意长的序列中使用信息,但实际上它们仅限于回顾几个步骤。 循环神经网络的提出便是基于记忆模型的想法,期望网络能够记住前面出现的特征.并依据特征推断后面的结果,而且整体的网络结构不断循环,因为得名循环神经网络。

RNN的用途:RNN在许多NLP任务中取得了巨大成功。 在这一点上,最常用的RNN类型是LSTM,它在捕获长期依赖性方面要比RNN好得多。 但不要担心,LSTM与RNN基本相同,它们只是采用不同的方式来计算隐藏状态。 

  • 语言建模与生成文本:通过语言的建模,可以通过给定的单词生成人类可以理解的以假乱真的文本。
  • 机器翻译:机器翻译类似于语言建模,输入源语言中的一系列单词,通过模型的计算可以输出目标语言与之对应的内容。
  • 语音识别:给定来自声波的声学信号的输入序列,可以预测一系列语音片段及其概率,并把语音转化成文字。
  • 生成图像描述:与卷积神经网络一起,RNN可以生成未标记图像的描述。

1.2,网络结构及原理

对于RNN而言,每个时刻的隐藏层除了连接本期的输入层和输出层,还连接着上一时刻和下一时刻的隐藏单元,这也就是历史信息的传递方式。过去的信息正是通过这样的结构影响当期的输出。此外,在模型训练上,RNN虽然也是采用反向传播的方式,但跟BP模型不一样的是,它还包含了最后一个时间将积累的残差传递回来的过程。这种方式被称为基于时间的反向传播(BPTT)。

循环神经网络的基本结构特别简单,就是将网络的输出保存在一个记忆单元中,这个记忆单元和下一次的输入一起进入神经网络中。我们可以看到网络在输入的时候会联合记忆单元一起作为输入,网络不仅输出结果,还会将结果保存到记忆单元中,下图就是一个最简单的循环神经网络在输入时的结构示意图。

RNN 可以被看做是同一神经网络的多次赋值,每个神经网络模块会把消息传递给下一个,将这个图的结构展开

网络中具有循环结构,这也是循环神经网络名字的由来,同时根据循环神经网络的结构也可以看出它在处理序列类型的数据上具有天然的优势。因为网络本身就是 一个序列结构,这也是所有循环神经网络最本质的结构。

循环神经网络具有特别好的记忆特性,能够将记忆内容应用到当前情景下,但是网络的记忆能力并没有想象的那么有效。记忆最大的问题在于它有遗忘性,总是更加清楚地记得最近发生的事情而遗忘很久之前发生的事情,循环神经网络同样有这样的问题。

1.3,搭建循环神经网络

Pytorch 中使用 nn.RNN 类来搭建基于序列的循环神经网络,它的构造函数有以下几个参数:

  • input_size:输入数据X的特征值的数目。
  • hidden_size:隐藏层的神经元数量,也就是隐藏层的特征数量。
  • num_layers:循环神经网络的层数,默认值是 1。
  • bias:默认为 True,如果为 false 则表示神经元不使用 bias 偏移参数。
  • batch_first:如果设置为 True,则输入数据的维度中第一个维度就是 batch 值,默认为 False。默认情况下第一个维度是序列的长度, 第二个维度才是 - - batch,第三个维度是特征数目。
  • dropout:如果不为空,则表示最后跟一个 dropout 层抛弃部分数据,抛弃数据的比例由该参数指定。

RNN 中最主要的参数是 input_size hidden_size,这两个参数务必要搞清楚。其余的参数通常不用设置,采用默认值就可以了。

import torch
rnn = torch.nn.RNN(20, 50, 2)
input = torch.randn(100, 32, 20)
h_0 =torch.randn(2, 32 ,50)
output,hn=rnn(input, h_0)
print(output.size(), hn.size())
=================================================
torch.Size([100, 32, 50]) torch.Size([2, 32, 50])

RNN其实也是一个普通的神经网络,只不过多了一个 hidden_state 来保存历史信息。这个hidden_state的作用就是为了保存以前的状态,我们常说RNN中保存的记忆状态信息,就是这个 hidden_state 。

h_t = \tanh(W_{ih} x_t + b_{ih} + W_{hh} h_{(t-1)} + b_{hh})

其中 x_t 是当前状态的输入值,h_{(t-1)} 就是上一个状态的hidden_state,也就是记忆部分。

整个网络要训练的部分就是 W_{ih} 当前状态输入值的权重 W_{hh},hidden_state也就是上一个状态的权重还有这两个输入偏置值。这四个值加起来使用tanh进行激活,pytorch默认是使用tanh作为激活,也可以通过设置使用relu作为激活函数。

这个步骤与普通的神经网络没有任何的区别,而 RNN 因为多了 序列(sequence) 这个维度,要使用同一个模型跑 n 次前向传播,这个n就是我们序列设置的个数。

class RNN(object):
    def __init__(self,input_size,hidden_size):
        super().__init__()
        self.W_xh = torch.nn.Linear(input_size, hidden_size) #因为最后的操作是相加 所以hidden要和output的shape一致
        self.W_hh = torch.nn.Linear(hidden_size, hidden_size)

    def __call__(self, x, hidden):
        return self.step(x, hidden)
    def step(self, x, hidden):
        #前向传播的一步
        h1 = self.W_hh(hidden)
        w1 = self.W_xh(x)
        out = torch.tanh(h1 + w1)
        hidden = self.W_hh.weight
        return out, hidden
rnn = RNN(20, 50)
input = torch.randn(32 , 20)
h_0 = torch.randn(32, 50) 
seq_len = input.shape[0]
for i in range(seq_len):
    output, hn = rnn(input[i, :], h_0)
print(output.size(), h_0.size())
=========================================
torch.Size([32, 50]) torch.Size([32, 50])

1.4,LSTM

普通RNN虽然具备一定的记忆功能,但是却不能很好地处理长期记忆问题。循环神经网络包含非常复杂的参数动态变化,这导致它非常难训练。RNN也是采用后向传播算法对权重和阀值进行优化,虽然可以将之前的隐藏层状态存储在记忆单元中,但对于处理相隔距离较远的信息却无能为力。当误差的权重大于1时,权重经过多次相乘会导致梯度爆炸的问题;而当误差的权重小于1时,经过多次传播,又会出现梯度消失的情况。RNN训练难度的本质在于参数在时间结构的传递中不断使用,即在不同的时间点反复使用,因此只要权重有微小的变化就有可能造成“蝴蝶效应”。

LSTM 是 Long Short Term Memory Networks 的缩写,翻译就是长的短时记忆网络。LSTM虽然只解决了短期依赖的问题,并且它通过刻意的设计来避免长期依赖问题,这样的做法在实际应用中被证明还是十分有效的,有很多人跟进相关的工作解决了很多实际的问题,所以现在LSTM 仍然被广泛地使用。

标准的循环神经网络内部只有一个简单的层结构,而 LSTM 内部有 4 个层结构:

  • 第一层是个忘记层:决定状态中丢弃什么信息。
  • 第二层tanh层用来产生更新值的候选项,说明状态在某些维度上需要加强,在某些维度上需要减弱。
  • 第三层sigmoid层(输入门层),它的输出值要乘到tanh层的输出上,起到一个缩放的作用,极端情况下sigmoid输出0说明相应维度上的状态不需要更新。
  • 最后一层决定输出什么,输出值跟状态有关。候选项中的哪些部分最终会被输出由一个sigmoid层来决定。

pytorch 中使用 nn.LSTM 类来搭建基于序列的循环神经网络,他的参数基本与RNN类似。

lstm = torch.nn.LSTM(10, 20,2)
input = torch.randn(5, 3, 10)
h0 = torch.randn(2, 3, 20)
c0 = torch.randn(2, 3, 20)
output, hn = lstm(input, (h0, c0))
print(output.size(), hn[0].size(), hn[1].size())
====================================================================
torch.Size([5, 3, 20]) torch.Size([2, 3, 20]) torch.Size([2, 3, 20])

1.5,GRU

GRU 和 LSTM 最大的不同在于 GRU 将遗忘门和输入门合成了一个"更新门",同时网络不再额外给出记忆状态,而是将输出结果作为记忆状态不断向后循环传递,网络的输人和输出都变得特别简单。

rnn = torch.nn.GRU(10, 20, 2)
input = torch.randn(5, 3, 10)
h_0= torch.randn(2, 3, 20)
output, hn = rnn(input, h0)
print(output.size(),hn.size())
=============================================
torch.Size([5, 3, 20]) torch.Size([2, 3, 20])

2,TensorBoard

2.1,安装TensorBoard

TensorBoard是一个Web应用,设计用来对神经网络的不同方面进行可视化。利用TensorBoard可以很容易地实时查看统计信息,如准确度、损失激活值,实际上可以查看你希望通过网络发送的任何信息。尽管它本身是用TensorFolow编写的,但有一个跨系统而且相当简单的API,在PyTorch中使用与在TensorFlow中使用并没有太大差别。

安装:

#要求PyTorch版本在v1.14以上
pip install tensorboard
conda install tensorboard

启动:

tensorboard --logdir=runs
=========================================================================
TensorBoard 1.14.0 at http://DESKTOP-3T77H9A:6006/ (Press CTRL+C to quit)

根据提示进入网站,可以看到如下欢迎界面:

2.2,向TensorBoard发送数据

PyTorch中使用TensorBoard的模块在torch.utils.tensorboard中:

from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter()
writer.add_scalar('example',3)

使用SummaryWriter类与TensorBoard交互,这里使用标准位置./runs记录输出,另外使用add_scalar并提供一个标记来发送一个标量。

在PyCharm中打开TensorBoard:

加一层循环:

from torch.utils.tensorboard import SummaryWriter
import random

value = 10
writer = SummaryWriter()
writer.add_scalar('test_loop', 0)
for i in range(1, 10000):
    value += random.random() - 0.5
    writer.add_scalar('test_loop', value, i)

2.3,PyTorch钩子

PyTorch提供了钩子(hook),这是在向前向后传播时看,可以附加到一个张量或模块的函数。PyTorch在传播中遇到一个带钩子的模块时,它会调用所注册的钩子。计算一个张量的梯度时,就会调用这个张量上注册的钩子。

钩子提供了很多可能很强大的方式来管理模块和张量,因为如果需要,可以完全替换进入钩子的(前一层)输出。可以改变梯度、屏蔽激活、替换模块中的所有偏置等。不过这里只是使用钩子在数据流过时得到网络的有关信息。

import torch.utils.data
import torchvision

def print_hook(module,input,output):
    print(input[0].shape)
net = torchvision.models.resnet18()
x = torch.rand(1,3,224,224)
hook_ref = net.fc.register_forward_hook(print_hook)
net(x)
hook_ref.remove()
net(x)
=================================
torch.Size([1, 512])

程序结果打印出一些文本,显示模型全连接层的输入形状。注意,第二次向模型传入张量,不会看到print语句。向一个模块或张量增加一个钩子时,PyTorch会返回这个钩子的一个引用。总是应当保存这个引用,然后工作完成时调用remove()。如果没有保存这个引用,它就会在内存中“游荡”,而占用宝贵的内存。反向钩子的做法类似,只不过要调用register_backward_hook()。

2.4,均值和标准差

建立一个函数,将一个输出层的均值和标准差发送给TensorBoard:

def send_stats(i, module, input, output):
    writer.add_scalar(f"layer {i}-mean", output.data.mean())
    writer.add_scalar(f"layer {i}-stddev", output.data.std())

不能用这个函数本身建立一个前向钩子,不过通过使用Python函数partial(),可以创建一系列前向钩子,将它们附加到指定i值(partial()的第二个参数)的一个层,从而确保把正确的值传送到TensorBoard中正确的图中:

for i, m in enumerate(model.children()):
    m.register_forward_hook(partial(send_stats, i))

注意,这里使用了model.children(),这样只会附加到模型的各个顶层模块,所以如果有一个nn.Sequential()层(基于ResNet的模型都有这样一个层),钩子只会附加到这个模块,而不会附加到nn.Sequential列表中的各个模块。

如果使用通常的训练函数训练这个模型,应该会看到激活值开始流入TensorBoard。必须切换为WALL,因此不再用钩子将训练的步信息发送回TensorBoard(只有在调用PyTorch钩子时才会得到模块信息)。

2.5,获取模型详细信息

理想情况下神经网络中的层应该均值为0,并且标准差为1,从而保证计算不会变成无穷大或者0。如果不是这样,可能这个网络训练有问题。

import torch
import torch.nn as nn
import torch.utils.data
import torchvision
from functools import partial
from torch import optim
from torch.utils.tensorboard import SummaryWriter
from torchvision import datasets, transforms

# Writer will output to ./runs/ directory by default

writer = SummaryWriter()

transform = transforms.Compose(
    [transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,))]
)
trainset = datasets.MNIST("mnist_train", train=True, download=True, transform=transform)
train_data_loader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True)
model = torchvision.models.resnet50(False)

model.conv1 = torch.nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3, bias=False)
images, labels = next(iter(train_data_loader))

grid = torchvision.utils.make_grid(images)
writer.add_image("images", grid, 0)
writer.add_graph(model, images)


def send_stats(i, module, input, output):
    writer.add_scalar(f"layer {i}-mean", output.data.mean())
    writer.add_scalar(f"layer {i}-stddev", output.data.std())


for i, m in enumerate(model.children()):
    m.register_forward_hook(partial(send_stats, i))

# Now train the model and watch output in Tensorboard

optimizer = optim.Adam(model.parameters(), lr=2e-2)
criterion = nn.CrossEntropyLoss()


def train(model, optimizer, loss_fn, train_loader, val_loader, epochs=20, device="cuda:0"):
    model.to(device)
    for epoch in range(epochs):
        print(f"epoch {epoch + 1}")
        model.train()
        for batch in train_loader:
            optimizer.zero_grad()
            ww, target = batch
            ww = ww.to(device)
            target = target.to(device)
            output = model(ww)
            loss = loss_fn(output, target)
            loss.backward()
            optimizer.step()

        model.eval()
        num_correct = 0
        num_examples = 0
        for batch in val_loader:
            ww, target = batch
            ww = ww.to(device)
            target = target.to(device)
            output = model(ww)
            correct = torch.eq(torch.max(output, dim=1)[1], target).view(-1)
            num_correct += torch.sum(correct).item()
            num_examples += correct.shape[0]
        print("Epoch {}, accuracy = {:.2f}".format(epoch + 1, num_correct / num_examples))


train(model, optimizer, criterion, train_data_loader, train_data_loader, epochs=5)

3,部署PyTorch

3.1,搭建Flask服务

建立模型只是构建深度学习应用的一部分,毕竟一个模型有那么惊人的准确度(或其他优点),但是如果它从不做任何预测,又有什么价值。因为我们希望有一种简单的方法打包我们的模型,使它们能响应请求(可以通过Web或者其他途径),而且可以通过最少的努力使模型在生产环境中运行。

Python允许我们使用Flask框架(Flask是用Python创建Web服务的一个流行框架)快速地部署和运行一个Web服务,接受一个请求(包含一个图像URL),并返回一个JSON响应,指示预测结果。

conda install -c anaconda flask
pip install flask

创建一个名为catfish的新目录,把你的模型定义作为model.py复制到这个目录中:

import torch.nn as nn 
from torchvision import models

CatfishClasses = ["cat","fish"]

CatfishModel = models.resnet50()
CatfishModel.fc = nn.Sequential(nn.Linear(CatfishModel.fc.in_features,500),
                  nn.ReLU(),
                  nn.Dropout(), nn.Linear(500,2))
import os
import requests
import torch
from flask import Flask, jsonify, request
from io import BytesIO
from PIL import Image
from torchvision import transforms
from catfish_model import CatfishModel, CatfishClasses

def load_model():
    m = CatfishModel
    m.eval()
    return m
model = load_model()
img_transforms = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406],
                         std=[0.229, 0.224, 0.225])
])
app = Flask(__name__)

@app.route("/")
def status():
    return jsonify({"status": "ok"})

@app.route("/predict", methods=['GET', 'POST'])
def predict():
    if request.method == 'POST':
        img_url = request.form.image_url
    else:
        img_url = request.args.get('image_url', '')

    response = requests.get(img_url)
    img = Image.open(BytesIO(response.content))
    img_tensor = img_transforms(img).unsqueeze(0)
    prediction = model(img_tensor)
    predicted_class = CatfishClasses[torch.argmax(prediction)]
    return jsonify({"image": img_url, "prediction": predicted_class})
if __name__ == '__main__':
    app.run(host="127.0.0.1",port="8080")

Flask的魅力在于@app.route()注解。这些注解允许我们关联平常的Python函数,用户达到某个特定端点时就会运行这些函数。在我们的predict()方法中,从GET或POST HTTP请求取出img_url参数,打开这个URL(作为一个PIL图像),把它推入一个简单的torchvision转换流水线,调整它的大小并把图像转换为一个张量。

这样我们会得到一个形状为[3, 224, 224]的张量,不过由于这个模型的工作方式,需要把它转换为批量大小为1的一个批次,也就是[1, 3, 224, 224]。所以再使用unsqueeze()扩展这个张量,在现在维度前面插入一个新的空轴(维度)。然后像往常一样把它传入模型,从而得到我们的预测张量。与前面的做法一样,我们使用torch.argmax()找到最大值的张量元素,并用这个元素作为索引来访问CatfishClasses数组。最后返回一个JSON响应,其中包括类别名和做预测的图像URL。

如果现在尝试这个服务,可能发现它的分类性能有些失望。因为这个模型没有经过任何训练,下面将填充load_model()来加载神经网络参数。

3.2,设置模型参数

通过之前的了解,知道了训练之后保存模型的两种方法,可以用torch.save()将整个模型写至磁盘,或者使用state_dict()保存模型的所有权重和偏执(但不包括结构)。对于这个基于生产环境的服务,需要加载一个已训练的模型,应该选择state_dict方法。

尽管保存整个模型是一个很有吸引力的选择,但是你会对模型结构的任何改变非常敏感,甚至训练环境目录结构的改变也会产生很大的影响。在其他地方运行的不同服务中加载这个模型时很可能会出现问题。如果迁移到一个稍有不同的布局(结构),肯定不希望一切从头开始。

另外,如果是用state_dicts()保存模型,加载保存的模型时,最好不要硬编码指定模块的文件名,这样就能将模型更新与服务解耦合。这意味着我们可以用一个新模型重启服务,或者也可以很容易地还原到之前的一个模型。

def load_model():
    m = CatfishModel
    location = "./a.pt"
    m.load_state_dict(torch.load(location))
    m.eval()
    return m
torch.save(net.state_dict(), './data/' + 'model.pt')

3.3,服务器部署

使用Xshell上传Python项目文件:

nohup python Net_Server.py >temp.txt&

注意:

  • 其中IP地址不能为:“127.0.0.1”,否则只能本地访问,应该改为“0.0.0.0”。
  • 端口保证不被占用,在安全组中允许访问,并且注意防火墙(可直接关闭)。

4,数据增强

4.1,基本概念

为了防止过拟合的出现,传统的做法是提供大量数据。通过查看更多数据,模型会对所要解决的问题有一个更一般的认知。如果把这种情况看作是一个压缩问题,通过避免模型存储所有答案(由于数据过多,超出了其存储容量,所以无法全部存储),就会要求压缩输入,相应地可以生成一个更泛化的解决方案,而不只是存储答案。

可以使用的一种方法是数据增强,如果我们有一个图像,可以考虑对这个图像做很多种不同的处理,这样就能避免过拟合,并使模型更泛化。

torchvision提供了一个丰富的转换集合,包含可以用于数据增强的大量转换,包括:

(1)基本

  • Compose:图像预处理包,一般用Compose把多个步骤整合到一起。
  • Lambda:根据用户自定义的方式进行变换。

(2)裁剪

  • RandomCrop:随机进行裁剪。
  • CenterCrop:裁剪一定 size 的图片,以图片的中心往外。
  • RandomResizedCrop:裁剪给定的 PIL 图像到随机的尺寸和长宽比。

  • RandomSizedCrop:已经废弃,由 RandomResizedCrop. 取代了。

  • FiveCrop:对图片进行上下左右以及中心裁剪,获得5张图片,返回一个4D-tensor。

  • TenCrop:对图片进行上下左右以及中心裁剪,然后全部翻转(水平或者垂直),获得10张图片,返回一个4D-tensor。

(3)翻转和旋转

  • RandomHorizontalFlip/RandomVerticalFlip:随机进行水平/竖直翻转。
  • RandomRotation:依degrees随机旋转一定角度。

(4)图像变换

  • Resize:重置图像分辨率。
  • ToTensor:将shape为(hwc)的nump.ndarrayimg转为shape(chw)tensor,其将每一个数值归一化到[0,1],其归一化方法比较简单,直接除以255即可。
  • ToPILImage:将tensor 或者 ndarray的数据转换为 PIL Image 类型数据。
  • Normalize:对数据按通道进行标准化,即先减均值,再除以标准差,注意是 hwc。
  • Scale:在底层的实现上, Resize 直接调用 PIL Image 的 resize 方法,Scale 与 Resize 功能相同, 实际上 Scale 直接继承 Resize。(已被废弃)
  • Pad:填充图片的外部轮廓 PIL 数据格式。

  • ColorJitter:随机改变图像的亮度、对比度和饱和度。

  • Grayscale:转换图像灰度。

  • LinearTransformation:对矩阵做线性变化,可用于白化处理!

  • RandomAffine:仿射变换。

  • RandomGrayscale:依概率p将图片转换为灰度图,若通道数为3,则3 channel with r == g == b。

(5)图像变换

  • RandomApply:给定一定概率从一组 transformations 应用。

  • RandomChoice:随机从一组变换中选择一个。

  • RandomOrder:按随机顺序应用变换列表。

  • RandomPerspective:对给定的PIL图像以给定的概率随机进行透视变换。

4.2,例程

读取图片文件

from torchvision import transforms
from PIL import Image
from torchvision.transforms import functional as TF
import torch
# 读取一张测试图片
path = "test.jpg"
img = Image.open(path)
img.show()

ToPILImage:将tensor 或者 ndarray的数据转换为 PIL Image 类型数据。

# 将 ``PIL Image`` or ``numpy.ndarray`` 转换成 tensor 再转成 PIL Image.
transform = transforms.Compose([
    transforms.ToTensor(), # Convert a ``PIL Image`` or ``numpy.ndarray`` to tensor.
    transforms.ToPILImage() # Convert a tensor or an ndarray to PIL Image.
])
new_img = transform(img)
new_img.show()

Normalize:提供一个所有通道的均值(mean) 和方差(std),会将原始数据进行归一化,操作的数据格式是 Tensor。

mean = [0.5, 0.5, 0.5]
std = [0.5, 0.5, 0.5]
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize(mean, std),
    transforms.ToPILImage() # 这里是为了可视化,故将其再转为 PIL,以下同理
])

new_img = transform(img)
new_img.show()

ToTensor:将shape为(hwc)的nump.ndarrayimg转为shape(chw)tensor,其将每一个数值归一化到[0,1],其归一化方法比较简单,直接除以255即可。

transform = transforms.Compose([
    transforms.ToTensor(), # Convert a ``PIL Image`` or ``numpy.ndarray`` to tensor.
])
new_img = transform(img)
print(new_img)

Resize:重置图像分辨率。

  • size: 一个值的话,高和宽共享,否则对应是 (h, w)
  • interpolation: 插值方式 默认 PIL.Image.BILINEAR
size = (100, 100)
transform = transforms.Compose([
    transforms.Resize(size),
])
new_img = transform(img)
new_img.show()

CenterCrop:裁剪一定 size 的图片,以图片的中心往外。

  • size: 一个值的话,高和宽共享,否则对应是 (h, w),若是该值超过原始图片尺寸,则外围用 0 填充。
size = (200, 500)
transform = transforms.Compose([
    transforms.CenterCrop(size),
])

new_img = transform(img)
new_img.show()

Pad:填充图片的外部轮廓 PIL 数据格式。

  • padding:填充的宽度,可以是一个 值、或者元组,分别对应 4 个边
  • fill:填充的值,可以是一个值(所有通道都用该值填充),或者一个 3 元组(RGB 三通道) 当 padding_mode=constant 起作用
  • padding_mode:填充的模式:constant, edge(填充值为边缘), reflect(从边缘往内一个像素开始做镜像) or symmetric(从边缘做镜像).
padding = (10, 20, 30, 40)
transform = transforms.Compose([
    transforms.Pad(padding, padding_mode="symmetric"), 
])

new_img = transform(img)
new_img.show()

RandomApply:给定一定概率从一组 transformations 应用。

transform = [transforms.Pad(100, fill=(0, 255, 255)), transforms.CenterCrop(100), transforms.RandomRotation(20)]
transform = transforms.Compose([
    transforms.RandomApply(transform, p=0.5)
])

new_img = transform(img)
new_img.show()

RandomChoice:随机从一组变换中选择一个。

transform = [transforms.Pad(100, fill=(0, 255, 255)), transforms.CenterCrop((100, 300))]
transform = transforms.Compose([
    transforms.RandomChoice(transform)
])

new_img = transform(img)
new_img.show()

RandomOrder:按随机顺序应用变换列表。

transform = [transforms.Pad(100, fill=(0, 255, 255)), transforms.CenterCrop((50, 50))]
transform = transforms.Compose([
    transforms.RandomOrder(transform)
])

new_img = transform(img)
new_img.show()

RandomCrop:随机进行裁剪。

  • size
  • padding=None
  • pad_if_needed=False
  • fill=0
  • padding_mode=‘constant’
transform = transforms.Compose([
    transforms.RandomCrop((100, 300))
])

new_img = transform(img)
new_img.show()

RandomHorizontalFlip/RandomVerticalFlip:随机进行水平/竖直翻转。

transform = transforms.Compose([
    transforms.RandomHorizontalFlip(p=0.5),
    transforms.RandomVerticalFlip(p=0.5)
])

new_img = transform(img)
new_img.show()

RandomResizedCrop:裁剪给定的 PIL 图像到随机的尺寸和长宽比。

transform = transforms.Compose([
    transforms.RandomResizedCrop((200, 300))
])

new_img = transform(img)
new_img.show()

FiveCrop:对图片进行上下左右以及中心裁剪,获得5张图片,返回一个4D-tensor。

UNIT_SIZE = 200 # 每张图片的宽度是固定的
size = (100, UNIT_SIZE)
transform = transforms.Compose([
    transforms.FiveCrop(size)
])

new_img = transform(img)
delta = 20  # 偏移量,几个图片间隔看起来比较明显
new_img_2 = Image.new("RGB", (UNIT_SIZE*5+delta, 100))
top_right = 0
for im in new_img:
    new_img_2.paste(im, (top_right, 0)) # 将image复制到target的指定位置中
    top_right += UNIT_SIZE + int(delta/5) # 左上角的坐标,因为是横向的图片,所以只需要 x 轴的值变化就行

new_img_2.show()

TenCrop:对图片进行上下左右以及中心裁剪,然后全部翻转(水平或者垂直),获得10张图片,返回一个4D-tensor。

UNIT_SIZE = 200 # 每张图片的宽度是固定的
size = (100, UNIT_SIZE)

transform = transforms.Compose([
    transforms.TenCrop(size, vertical_flip=True)
])

new_img = transform(img)

delta = 50  # 偏移量,几个图片间隔看起来比较明显
new_img_2 = Image.new("RGB", (UNIT_SIZE*10+delta, 100))
top_right = 0
for im in new_img:
    new_img_2.paste(im, (top_right, 0)) # 将image复制到target的指定位置中
    top_right += UNIT_SIZE + int(delta/10) # 左上角的坐标,因为是横向的图片,所以只需要 x 轴的值变化就行

new_img_2.show()

ColorJitter:随机改变图像的亮度、对比度和饱和度。

  • brightness:亮度
  • contrast:对比度
  • saturation:饱和度
  • hue:色调 0<= hue <= 0.5 or -0.5 <= min <= max <= 0.5.
transform = transforms.Compose([
    transforms.ColorJitter(brightness=(0, 36), contrast=(
        0, 10), saturation=(0, 25), hue=(-0.5, 0.5))
])
new_img = transform(img)
new_img.show()

RandomRotation:依degrees随机旋转一定角度。

transform = transforms.Compose([
    transforms.RandomRotation(30, resample=Image.BICUBIC, expand=False, center=(100, 300))
])

new_img = transform(img)
new_img.show()

RandomAffine:仿射变换。

  • degrees:旋转角度
  • translate:水平偏移
  • scale:
  • shear: 裁剪
  • resample ({PIL.Image.NEAREST, PIL.Image.BILINEAR, PIL.Image.BICUBIC}, optional)
  • fillcolor:图像外部填充颜色 int
transform = transforms.Compose([
    transforms.RandomAffine(degrees=30, translate=(0, 0.2), scale=(0.9, 1), shear=(6, 9), fillcolor=66)
])
new_img = transform(img)
new_img.show()

Grayscale:转换图像灰度。

transform = transforms.Compose([
    transforms.Grayscale(num_output_channels=3)
])
new_img = transform(img)
new_img_array = np.array(new_img)
r, g, b = new_img_array[:, :, 0], new_img_array[:, :, 1], new_img_array[:, :, 2]
print(r == b)
print("shape:", new_img_array.shape)
new_img.show()
[[ True  True  True ...  True  True  True]
 [ True  True  True ...  True  True  True]
 [ True  True  True ...  True  True  True]
 ...
 [ True  True  True ...  True  True  True]
 [ True  True  True ...  True  True  True]
 [ True  True  True ...  True  True  True]]
shape: (400, 400, 3)

RandomGrayscale:依概率p将图片转换为灰度图,若通道数为3,则3 channel with r == g == b。

transform = transforms.Compose([
    transforms.RandomGrayscale(p=0.6)
])
new_img = transform(img)
print(np.array(new_img).shape)
new_img.show()
=========================
(400, 400, 3)

RandomPerspective:对给定的PIL图像以给定的概率随机进行透视变换。

transform = transforms.Compose([
    transforms.RandomPerspective(distortion_scale=1, p=1, interpolation=3)
])
new_img = transform(img)
new_img.show()

Lambda:根据用户自定义的方式进行变换。

lambd = lambda x: TF.rotate(x, 100)
transform = transforms.Compose([
    transforms.Lambda(lambd)
])

new_img = transform(img)
new_img.show()

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值