RNN处理图片分类

依旧是处理验证码问题,虽然学校的验证码已换,我手头只有原来的验证码,但是由于我在学习rnn,因此试着将RNN应用于验证码,熟悉一下

1.结构

必然是多对一结构,我打算将图片的每一列分开,作为rnn的一部分输入,输出字符的独热编码。

2.输入数据

之前的图片集已经整理好了,利用我之前做的Mydataset类提取图片数据,但是之前是使用CNN卷积,直接输入图片,进行ToTensor函数转换就能直接作为输入,现在需要提取图片的每一列。

 图片格式 torch.Size([3, 22, 14])

tensor([[[249., 255., 255., 255., 255., 255., 245., 241., 235., 251., 255.,255., 255., 249.],
         [226., 233., 255., 217., 255., 241., 239., 231., 165., 203., 255., 246., 255., 247.],
         [224., 235., 206., 241., 241., 205., 237., 232., 213., 195., 255.,246., 253., 221.],
         [151., 177., 221., 200., 192., 212., 214., 218., 226., 241., 251.,254., 231., 240.],
         [192., 148., 213., 194.,  95.,   9.,  18., 148., 197., 213., 206.,233., 204., 194.],
         [ 70., 201., 151.,  20.,  22.,  31.,   0.,  11.,  27.,  21.,  73.,195., 223., 195.],
         [  0.,  10., 132.,  48.,  20.,   0.,  35.,   0.,  26.,  11.,  14., 39., 131., 226.],
         [  5., 216., 127.,  41., 118., 206., 185.,  62.,  33.,  20.,  10.,2.,  59., 213.],
         [123., 228., 182., 230., 221., 244., 244., 228., 163.,  17.,   0., 0.,  13., 151.],
         [223., 187., 235., 211., 255., 255., 255., 245., 189.,  49.,  36., 0.,   0., 175.],
         [217., 237., 233., 255., 235., 255., 243., 250., 196.,  47.,   0., 0.,  44., 160.],
         [206., 181., 255., 248., 255., 219., 203., 217.,  77.,  22.,   5., 36.,  71., 207.],
         [221., 207., 255., 255., 240., 181., 198.,  96.,   0.,  25.,  40.,25., 155., 231.],
         [255., 241., 255., 191., 233., 208.,  38.,   5.,  13.,  32.,  19.,142., 190., 248.],
         [248., 207., 242., 229., 116.,  32.,   4.,  11.,  30.,  40., 176., 240., 240., 243.],
         [215., 234., 177.,  48.,  44.,  29.,  22.,   0.,  59., 203., 233.,250., 248., 252.],
         [231.,  92.,  15.,   0.,   0.,   0.,   4., 180., 212., 225., 247.,255., 255., 255.],
         [118.,  51.,   0.,   0.,   0.,   0.,   0.,  96., 196., 209., 225.,224., 231., 222.],
         [ 86.,  55.,   6.,   0.,   0.,   0.,   0.,  15.,  12.,   8.,  22.,52., 157., 203.],
         [204., 109.,   9.,   1.,   6.,  40.,  37.,  14.,  49.,  20.,  13., 38., 183., 227.],
         [255., 236., 224., 217., 218., 186., 203., 177.,  41.,  29.,  39.,64., 187., 232.],
         [255., 253., 255., 239., 255., 255., 255., 249., 228., 206., 199.,198., 240., 249.]])
这只是一个通道的数据,我删除了一部分

数组可视化
这是数组原本的图片。

    img, label, path = train_data[19]

    print(img.shape)
    print(img[0][:, 0])

结果如下,可以看到取出的数是第一通道的第0列,共22个数


torch.Size([3, 22, 14])
tensor([249., 226., 224., 151., 192.,70., 0., 5.,123., 223., 217., 206.,
        221., 255., 248., 215., 231., 118.,  86., 204., 255., 255.])

所以 img[0][:, t] 是x即x第t列,在循环中可以作为输入,共循环14次

问题1. ValueError: too many values to unpack (expected 2)

我自制数据集返回三个参数,但是训练时读取两个参数,因此读取时需要有对应读取格式——————三个参数

数据集部分
    def __getitem__(self, index):
        path, label = self.img[index]
        img = self.loader(path)  # 读取出来维度为3, 22, 14 ?? 无法正常显示图片
        img = np.array(img, dtype=np.float32)
        label_one_hot = one_hot(label)  # 不是独热编码,只是字符都变成了数字
        if self.transform:
            img = self.transform(img)  # Totensor维度改变 torch.Size([3, 22, 14])
        return img, label_one_hot, path  # ------我自制数据集返回三个参数
训练部分
	def rnn_train(dataloader, rnn, loss_fn, grads):
    # print(len(dataloader.dataset))
    # rnn.train()  # 启用 BatchNormalization 和 Dropout

    size = len(dataloader.dataset)  # 为什么娶不到  加载数据返回值出错

    for item, (X, y) in enumerate(dataloader): ## TODO-----更改,(X, y, path)
        x = X.view(-1, 22, 14)
        pred = rnn(x)
        loss = loss_fn(pred, y)

        grads.zero_grad()
        loss.backward()
        grads.step()

        if item % 100 == 0:
            loss, current = loss.item(), item * len(X)
            # accuracy = (pred == y).numpy().sum() / y.size(0)
            print(f"loss: {loss:>7f}  [{current:>5d}/{size:>5d}]")

问题2. ValueError: Expected input batch_size (96) to match target batch_size (32).

ValueError:预期输入批次大小(96)与目标批次大小(32)匹配。
因为我没有考虑通道数问题,输入为3通道图片,
补充一下,因为之前做的是cnn分类,所以先做的cnn数据集,rnn是拿过来稍微改一下直接用,如果想查看CNN数据集,点这里查看 CNN数据集——自己建立数据集要点.

解决办法:

1.将图片二值化,转换成一通道
2.只取某一通道的数值

第一个比较简单,我先试试

[[249., 255., 255., 255., 255., 255., 245., 241., 235., 251., 255.,255., 255., 249.],
[226., 233., 255., 217., 255., 241., 239., 231., 165., 203., 255.,246., 255., 247.],
[224., 235., 206., 241., 241., 205., 237., 232., 213., 195., 255.,246., 253., 221.],
[151., 177., 221., 200., 192., 212., 214., 218., 226., 241., 251.,254., 231., 240.]

[[231., 244., 229., 242., 236., 245., 255., 255., 255., 255., 250.,234., 242., 245.],
[231., 230., 249., 201., 252., 242., 254., 253., 179., 202., 248.,219., 246., 254.],
[254., 255., 226., 255., 255., 230., 255., 255., 223., 191., 239.,227., 247., 239.],
[189., 220., 255., 255., 254., 255., 255., 255., 254., 252., 252.,252., 240., 255.],

[[255., 255., 247., 250., 228., 223., 226., 232., 230., 243., 253.,249., 255., 255.],
 [250., 247., 255., 204., 246., 228., 233., 232., 166., 200., 255.,238., 255., 255.],
[255., 255., 233., 255., 255., 226., 255., 255., 232., 208., 255.,247., 255., 239.],
[198., 226., 255., 255., 253., 255., 255., 255., 255., 255., 255., 255., 249., 255.],

上面是三个通道的数据,还是有很大差别的
图片的三个通道分开表示这是三个通道的数组和图片表示,因为是直接转至的ToTensor数据,所以不太能看出来,这是数字2
数字0
**数字0
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

小知识:subplot画子图时,写总标题时使用 plt.suptitle(‘总标题’)

可以看到各通道不一样,不能使用某一通道作为3通道的替代品。
因此采用第二种方案,
我在搜索的时候发现有人提出一种建议,采用希尔伯特曲线将二维图片一维化,很有想法,但是好像有很多多余计算,另外,没有考虑相邻坐标之间的联系。

1.读取时不再采用RGB读取,采用L读取

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
还可以,好像可以直接用。

def default_loader(path):
    return Image.open(path).convert('L')

网上找了一下
RGB到灰度图转换公式:Y’ = 0.299 R + 0.587 G + 0.114 B
已出现第一个loss值,数据集问题解决了

Epoch 1
-------------------------------
loss: 3.581590  [    0/ 2368]

3.RNN模型


class rnn(nn.Module):
    def __init__(self):
        super(rnn, self).__init__()
        self.lstm1 = nn.LSTM(
            input_size=14,
            hidden_size=64,
            num_layers=1,
            batch_first=True  # 表示在输入数据的时候,数据的维度为(batch,time_step,input)  (3,22,14)
        )
        self.fc1 = nn.Linear(64, 36)

    def forward(self, x):   # x :(batch,time_step,input_size)
        r_out, (h_n, h_c) = self.lstm1(x, None)  # None为隐藏层,第一层无隐藏层
        y = self.fc1(r_out[:, -1, :])  # 每个维度最后一行

        return y

    def train(self, x):
        pass


def rnn_train(dataloader, rnn, loss_fn, grads):
    # print(len(dataloader.dataset))
    # rnn.train()  # 启用 BatchNormalization 和 Dropout

    size = len(dataloader.dataset)  # 为什么娶不到  加载数据返回值出错

    for item, (X, y, path) in enumerate(dataloader):
        x = X.view(-1, 22, 14)
        pred = rnn(x)
        loss = loss_fn(pred, y)

        grads.zero_grad()
        loss.backward()
        grads.step()

        if item % 100 == 0:
            loss, current = loss.item(), item * len(X)
            # accuracy = (pred == y).numpy().sum() / y.size(0)
            print(f"loss: {loss:>7f}  [{current:>5d}/{size:>5d}]")


def rnn_test(dataloader, rnn, loss_fn):
    rnn.eval()
    size = len(dataloader.dataset)
    test_loss, correct = 0, 0

    with torch.no_grad():
        for X, y,path in dataloader:
            test_out = rnn(X)
            pred = torch.max(test_out, 1)[1].data.squeeze()  # torch.max(input,dim)返回每一行中的最大值的标签
            test_loss += loss_fn(pred, y).item()
            accuracy = (pred == y).numpy().sum() / y.size(0)
            # print('step: {} | train loss: {} | test accuracy: {} '.format(step, loss.data, accuracy))
            test_loss /= size
            correct /= size
            print(f"Test Error: \n Accuracy: {(100 * correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")


def model():
    learn_rate = 1e-3

    load_model = False
    device = "cuda" if torch.cuda.is_available() else "cpu"
    print("Using {} device".format(device))
    if load_model:
        rnn2 = rnn()
        rnn2.load_state_dict(torch.load('RNN_model/rnn.pth'))
    else:
        rnn2 = rnn()
    learn_rate = 1e-3
    data_train, data_test = load_data()

    print(len(data_train.dataset))

    loss_fn = nn.CrossEntropyLoss()
    grads = torch.optim.Adam(rnn2.parameters(), lr=learn_rate)

    epochs = 10
    for t in range(epochs):
        print(f"Epoch {t + 1}\n-------------------------------")
        rnn_train(data_train, rnn2, loss_fn, grads)
        rnn_test(data_test, rnn2, loss_fn)
    print("Done!")

    #  保存模型
    torch.save(rnn2.state_dict(), 'RNN_model/rnn.pth')
    print("Saved PyTorch Model State to rnn.pth")
    print("Done!")


问题:

问题1.RuntimeError: input must have 3 dimensions, got 4

运行时错误:输入必须有3个维度,得到4个维度
可以正常训练,但是测试程序有问题,发现测试程序少了一个输入维度转换
错误原因:pytorch训练和测试时维度为[batch_size,数据本身维度],因此,进行测试时需要增加一个batch_size维度

解决:输入数据x没有进行格式转换
    with torch.no_grad():
        for X, y, path in dataloader:
            X = X.view(-1, 22, 14)  ————————少了它
            test_out = rnn(X)
            pred = torch.max(test_out, 1)[1].data.squeeze()  # torch.max(input,dim)返回每一行中的最大值的标签
            test_loss += loss_fn(pred, y).item()
            accuracy = (pred == y).numpy().sum() / y.size(0)
            # print('step: {} | train loss: {} | test accuracy: {} '.format(step, loss.data, accuracy))
            test_loss /= size
            correct /= size
            print(f"Test Error: \n Accuracy: {(100 * correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")

问题2.IndexError: Dimension out of range (expected to be in range of [-1, 0], but got 1)

维度超出范围(预期在[-1,0]范围内,但得到1)
这里的意思实际上是预测值的维度和真实值的维度不匹配,即pred与y的维度不同

解决:CrossEntropyLoss输入pred数据类型为[y的维度,batch_size],不是预测出来的标签
pred_y = torch.max(test_out, 1)[1].data.squeeze()  # torch.max(input,dim)返回每一行中的最大值的标签
# 删除上一行就可以了,我真的是自作聪明
loss_fn = nn.CrossEntropyLoss()
print(pred.shape, y.shape)
test_loss += loss_fn(pred, y).item()

可以得到以下结果,好像一样

torch.Size([32]) torch.Size([32])

链接: 文本分类—使用CrossEntropyLoss时候的一些错误.
这篇文章写了一些关于交叉熵出现的问题,他说出现此类问题是由于预测出的pred维度与label维度不同导致的,我没有这个问题,在计算pred时已经降维了(就是因为降维才出的错
在这里插入图片描述

暂时没有解决,现注释掉测试程序,用train——acc代替
现在明白了,之前这个人完全写反了,交叉熵CrossEntropyLoss的输入必须是从神经网络中获取的数组作为输入,维度为[y的维度, batch_size],,不能将其转换成标签数组!!!

问题3.没有梯度下降

1.rnn本身训练较慢
2.数据没有归一化(标准化)

补充:主要是标准化问题,其他的不应该影响这么大。不过我记得我的cnn没有标准化也挺快的啊(不解.jpg)

查看了下pred出的数据,发现在计算loss前少乐一步数据处理,从每行中提取最大值,即为预测出地标签位置

tensor([[ 0.4091,  0.2488,  0.0026,  ..., -0.1413, -0.3998, -0.0599],
        [ 0.3132,  0.3893,  0.0850,  ..., -0.1707, -0.1380, -0.1432],
        [ 0.3098,  0.4704,  0.0514,  ..., -0.1330, -0.1492, -0.0379],
        ...,
        [ 0.3839,  0.2675,  0.0747,  ..., -0.3380, -0.1887, -0.2636],
        [ 0.3897,  0.2639,  0.1823,  ..., -0.2823, -0.1413, -0.2457],
        [ 0.4143,  0.3597, -0.1770,  ..., -0.2259, -0.5340, -0.2000]],
       grad_fn=<AddmmBackward>)
def rnn_train(dataloader, rnn, loss_fn, grads):
    # print(len(dataloader.dataset))
    rnn.train()  # 启用 BatchNormalization 和 Dropout

    size = len(dataloader.dataset)  # 为什么娶不到  加载数据返回值出错

    for item, (X, y, path) in enumerate(dataloader):
        x = X.view(-1, 22, 14)
        pred = rnn(x)
        pred = torch.max(pred, 1)[1].data.squeeze()
        print(pred)
        loss = loss_fn(pred, y)
        grads.zero_grad()
        loss.backward()
        grads.step()
tensor([12,  1, 12, 12, 12,  2,  1, 12,  1, 12, 12,  1, 12, 12,  1, 35, 12,  1,
         1,  1,  1, 12,  1, 12, 12, 12, 11, 33,  1,  1,  1, 12])

问题是pred转换成这种形式,与label进行交叉熵计算就会出现报错:
IndexError: Dimension out of range (expected to be in range of [-1, 0], but got 1)
什么意思,交叉熵输入为原始各预测值的数据???
后来补充:交叉熵输入应该为原始的未经截取的预测数据,即预测结果概率矩阵,不能输入预测值

认真看了看,发现loss值还是有变化的,只是变化太少了,训练集准确度变化的也不多。不过正好把第二个问题解决了。
在这里插入图片描述

learn_rate = 0.01
训练5次的loss
[0.10761373107497757, 0.10497836144389333, 0.10341259877423982, 0.10248044727219117, 0.1017013682505569]
训练5次的准确率和loss

Using cpu device
Epoch 1
-------------------------------
 train_Accuracy: 9.7%, Avg loss: 0.107614 

Epoch 2
-------------------------------
 train_Accuracy: 8.0%, Avg loss: 0.104978 

Epoch 3
-------------------------------
 train_Accuracy: 8.2%, Avg loss: 0.103413 

Epoch 4
-------------------------------
 train_Accuracy: 8.2%, Avg loss: 0.102480 

Epoch 5
-------------------------------
 train_Accuracy: 8.2%, Avg loss: 0.101701 

可以看到准确率几乎没变化,设置一下学习率试试

# 学习率设置
        if epochs < 5:
            learn_rate = 0.05
        elif epochs > 10:
            learn_rate = 1e-3
        else:
            learn_rate = 0.01
# 梯度下降方法
grads = torch.optim.SGD(rnn2.parameters(), lr=learn_rate)

在这里插入图片描述
这是训练20次的图像,可以看到变化很小
他的准确率变化不大,
这是训练20次的准确率图像,可以看到训练很困难。
在这里插入图片描述

这rnn相比cnn训练太困难了,也太慢了

利用adam方法试一下:
adam50次,0.001
adam50次
接下来继续训练,减小学习速率 到0.0005

sgd 0.001 50次:
sgd 0.001 50次
这loss下不去啊

补充:严重怀疑rnn训练是不是有问题,我训练大概300多次了,准确率还在50%+,这要是cnn都90+了。

Epoch 24
-------------------------------
train Error: 
 train_Accuracy: 54.0%, Avg loss: 0.051337 

Test Error: 
 Accuracy: 47.0%, Avg loss: 0.067447 

看了眼fashionMNIST的训练,好吧,要900步才能精确度达到0,83
一个step2000数据,900个共10

step: 0 | train loss: 2.3006675243377686 | test accuracy: 0.2235 
step: 50 | train loss: 1.1753591299057007 | test accuracy: 0.547 
step: 100 | train loss: 0.8468279838562012 | test accuracy: 0.6335 
step: 150 | train loss: 0.9217376708984375 | test accuracy: 0.6925 
step: 200 | train loss: 0.6689577698707581 | test accuracy: 0.7385 
step: 250 | train loss: 0.6621242165565491 | test accuracy: 0.749 
step: 300 | train loss: 1.0415210723876953 | test accuracy: 0.6525 
step: 350 | train loss: 0.5930203199386597 | test accuracy: 0.712 
step: 400 | train loss: 1.0252635478973389 | test accuracy: 0.7495 
step: 450 | train loss: 0.7461682558059692 | test accuracy: 0.7895 
step: 500 | train loss: 0.8410964608192444 | test accuracy: 0.788 
step: 550 | train loss: 0.479185551404953 | test accuracy: 0.7865 
step: 600 | train loss: 0.5193421840667725 | test accuracy: 0.801 
step: 650 | train loss: 0.5043472051620483 | test accuracy: 0.7845 
step: 700 | train loss: 0.5094617009162903 | test accuracy: 0.803 
step: 750 | train loss: 0.4729616045951843 | test accuracy: 0.817 
step: 800 | train loss: 0.6788201928138733 | test accuracy: 0.809 
step: 850 | train loss: 0.395673543214798 | test accuracy: 0.8285 
step: 900 | train loss: 0.2983940839767456 | test accuracy: 0.8305 

而cnn的minifast:
1各epoch遍历6w数据,就loss很低了

Epoch 1
-------------------------------
D:\soft\anaconda\envs\pytorch\lib\site-packages\torch\nn\functional.py:718: UserWarning: Named tensors and all their associated APIs are an experimental feature and subject to change. Please do not use them for anything important until they are released as stable. (Triggered internally at  ..\c10/core/TensorImpl.h:1156.)
  return torch.max_pool2d(input, kernel_size, stride, padding, dilation, ceil_mode)
loss: 2.300289  [    0/60000]
loss: 1.108720  [ 6400/60000]
loss: 0.649177  [12800/60000]
loss: 0.717834  [19200/60000]
loss: 0.902756  [25600/60000]
loss: 0.738814  [32000/60000]
loss: 0.613899  [38400/60000]
loss: 0.759048  [44800/60000]
loss: 0.505524  [51200/60000]
loss: 0.676004  [57600/60000]
Test Error: 
 Accuracy: 78.6%, Avg loss: 0.009123 

是不是数据没有归一化导致的呢?
好吧,是没有归一化导致的训练过慢,这是归一化后的20次训练,
adam, 0.01
在这里插入图片描述
比我之前训练的快无数倍,这20次训练顶400多次。
训练最后:

Epoch 1
-------------------------------
train Error: 
 train_Accuracy: 99.5%, Avg loss: 0.001139 

Test Error: 
 Accuracy: 82.6%, Avg loss: 0.034709 

有点过拟合,懒得搞dropout了,开始看论文了,拖了好久了

4.梯度修剪

解决:后来训练挺快的,就不想做了

看到出现的loss变化图像,突然想到一个方法——梯度修剪
利用一个函数将梯度限制在一个范围内,以便更快的下降

5.想法:实时显示loss图像

希望有一种实时显示loss图像,当他开始上下摆动就手动停止训练,改变学习速率。
找到了一篇文章,先码上,有时间再看:pytorch用tensorboard实时生成loss图像.

  • 2
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值