深度学习图像处理入门

mac2024-05-07  37

1、环境搭建

在云服务器中开启Docker服务

systemctl start docker

systemctl start nvidia-docker

git clone https://github.com/Jinglue/DL4Img

安装驱动和cuda ......

nvidia-docker pull hubq/dl4img

nvidia-docker run -d -v ~/dl4img/notebook/:/srv -p 8888:8888 -p 6006:6006 hubq/dl4img

登录jupyter:http://xxx:8888,密码jizhitencent

2、机器学习基础知识

3、数形结合---图像处理基础知识

读取图像文件进行基本操作

提取特征

4、使用深度神经网络框架

简单计算图实现P95

5、深度神经网络框架的模型元件

CNN,RNN,Dense

6、深度神经网络框架的输入处理

批量生成训练数据

7、深度神经网络框架的模型训练

优化算法

8、使用深度神经网络进行CIFAR-10数据分类

keras实验

9、使用迁移学习提高准确率

kaggle猫狗图片

10、使用深度神经网络进行文字识别

       首先使用卷积神经网络加多个全连接分类器的方法来识别,然后使用CNN结合RNN的方式来识别,不经能够准确识别字符,不需要进行切割,还能够根据上下文以及语法规则猜测字符。

验证码识别,用captcha库,生成验证码P169,模型最后输出4个结果,每个36个字符

$ pip install captcha

生成样本

from captcha.image import ImageCaptcha import matplotlib.pyplot as plt import numpy as np import random import string characters = string.digits + string.ascii_uppercase print(characters) # 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ width, height, n_len, n_class = 170, 80, 4, len(characters) generator = ImageCaptcha(width=width, height=height) # 生成图片大小 random_str = ''.join([random.choice(characters) for j in range(4)]) img = generator.generate_image(random_str) plt.imshow(img) plt.title(random_str) plt.show()

编写数据生成器,两种方式:

一次性生成几万张图,好处是显卡利用率高,如果需要经常调参,可以一次生成,然后多次训练。定义一个数据生成器,然后利用fit_generator函数进行训练,好处是不生成大量数据,训练时CPU生成,可以无限生成,提高泛化能力。

X:(batch_size, height, width, 3)

y:4个(batch_size, n_class),即(n_len, batch_size, n_class),使用next函数

def gen(batch_size=32): X = np.zeros((batch_size, height, width, 3), dtype=np.uint8) y = [np.zeros((batch_size, n_class), dtype=np.uint8) for i in range(n_len)] generator = ImageCaptcha(width=width, height=height) while True: for i in range(batch_size): random_str = ''.join([random.choice(characters) for j in range(4)]) X[i] = generator.generate_image(random_str) for j, ch in enumerate(random_str): y[j][i, :] = 0 y[j][i, characters.find(ch)] = 1 yield X, y def decode(y, index=0): # [0,0,...,1,0] y = np.argmax(np.array(y), axis=2)[:, index] return ''.join([characters[x] for x in y]) X, y = next(gen(1)) plt.imshow(X[0]) plt.title(decode(y)) plt.show()

构建深度CNN,最后连接4个分类器,每个分类器是36个神经元,输出36个字符的概率,可视化需要pydot和graphviz库

from keras.models import * from keras.layers import * from keras.optimizers import * input_tensor = Input((height, width, 3)) x = input_tensor for i in range(4): x = Convolution2D(32*2**i, 3, activation='relu')(x) x = Convolution2D(32*2**i, 3, activation='relu')(x) x = MaxPooling2D(2)(x) x = Flatten()(x) x = Dropout(0.25)(x) x = [Dense(n_class, activation='softmax', name='c%d'%(i+1))(x) for i in range(4)] model = Model(inputs=input_tensor, outputs=x) model.compile(loss='categorical_crossentropy', optimizer='adadelta', metrics=['accuracy']) # 模型可视化 from keras.utils.vis_utils import plot_model from IPython.display import Image plot_model(model, to_file="model.png", show_shapes=True) Image('model.png')

训练模型

h = model.fit_generator(gen(128), steps_per_epoch=400, epochs=20, workers=4, pickle_safe=True, validation_data=gen(128), validation_steps=10)

可视化loss和acc曲线图

plt.figure(figsize=(10, 4)) plt.subplot(1, 2, 1) plt.plot(h.history['loss']) plt.plot(h.history['val_loss']) plt.legend(['loss', 'val_loss']) plt.ylabel('loss') plt.xlabel('epoch') plt.ylim(0, 1) plt.subplot(1, 2, 2) for i in range(4): plt.plot(h.history['val_c%d_acc' % (i+1)]) plt.legend(['val_c%d_acc' % (i+1) for i in range(4)]) plt.ylabel('acc') plt.xlabel('epoch') plt.ylim(0.9, 1)

模型评估,准确率97.8%

from tqdm import tqdm def evaluate(batch_size=128, steps=20): batch_acc = 0 generator = gen(batch_size) for i in tqdm(range(steps)): X, y = next(generator) y_pred = model.predict(X) y_pred = np.argmax(y_pred, axis=-1) y_true = np.argmax(y, axis=-1) batch_acc += np.equal(y_true, y_pred).all(axis=0).mean() return batch_acc / steps

测试模型

X, y = next(gen(12)) y_pred = model.predict(X) plt.figure(figsize=(16, 8)) for i in range(12): plt.subplot(3, 4, i+1) plt.title('real: %s\npred:%s'%(decode(y, i), decode(y_pred, i))) plt.imshow(X[i]) model.save('model.h5') # 16MB

使用循环神经网络改进模型

对于这种按照顺序书写的文字,还有一种方法可以使用,那就RNN来识别序列。变成37个字符。

from captcha.image import ImageCaptcha import matplotlib.pyplot as plt import numpy as np import random %matplotlib inline %config InlineBackend.figure_format = 'retina' import string characters = string.digits + string.ascii_uppercase print(characters) width, height, n_len, n_class = 170, 80, 4, len(characters)+1 generator = ImageCaptcha(width=width, height=height) random_str = ''.join([random.choice(characters) for j in range(4)]) img = generator.generate_image(random_str) plt.imshow(img) plt.title(random_str)

CTC Loss,可以在只直到序列的顺序,不知道具体位置的情况下让模型收敛(warp-ctc)。

在Keras中,内置了CTC Loss

from keras import backend as K def ctc_lambda_func(args): y_pred, labels, input_length, label_length = args y_pred = y_pred[:, 2:, :] # input_length=15, label_length=4 return K.ctc_batch_cost(labels, y_pred, input_length, label_length)

模型,

from keras.models import * from keras.layers import * from keras.optimizers import * rnn_size = 128 input_tensor = Input((width, height, 3)) x = input_tensor x = Lambda(lambda x:(x-127.5)/127.5)(x) for i in range(3): for j in range(2): x = Conv2D(32*2**i, 3, kernel_initializer='he_uniform')(x) x = BatchNormalization()(x) x = Activation('relu')(x) x = MaxPooling2D((2, 2))(x) conv_shape = x.get_shape().as_list() rnn_length = conv_shape[1] rnn_dimen = conv_shape[2]*conv_shape[3] print(conv_shape, rnn_length, rnn_dimen) x = Reshape(target_shape=(rnn_length, rnn_dimen))(x) rnn_length -= 2 x = Dense(rnn_size, kernel_initializer='he_uniform')(x) x = BatchNormalization()(x) x = Activation('relu')(x) x = Dropout(0.2)(x) gru_1 = GRU(rnn_size, return_sequences=True, kernel_initializer='he_uniform', name='gru1')(x) gru_1b = GRU(rnn_size, return_sequences=True, kernel_initializer='he_uniform', go_backwards=True, name='gru1_b')(x) x = add([gru_1, gru_1b]) gru_2 = GRU(rnn_size, return_sequences=True, kernel_initializer='he_uniform', name='gru2')(x) gru_2b = GRU(rnn_size, return_sequences=True, kernel_initializer='he_uniform', go_backwards=True, name='gru2_b')(x) x = concatenate([gru_2, gru_2b]) x = Dropout(0.2)(x) x = Dense(n_class, activation='softmax')(x) base_model = Model(inputs=input_tensor, outputs=x) labels = Input(name='the_labels', shape=[n_len], dtype='float32') input_length = Input(name='input_length', shape=[1], dtype='int64') label_length = Input(name='label_length', shape=[1], dtype='int64') loss_out = Lambda(ctc_lambda_func, output_shape=(1,), name='ctc')([x, labels, input_length, label_length]) model = Model(inputs=[input_tensor, labels, input_length, label_length], outputs=[loss_out]) model.compile(loss={'ctc': lambda y_true, y_pred: y_pred}, optimizer='adam') # 可视化 from keras.utils.vis_utils import plot_model from IPython.display import Image plot_model(model, to_file="model.png", show_shapes=True) Image('model.png')

       从Input到最后一个MaxPooling2D,是一个很深的卷积神经网络,它负责学习字符的各个特征,尽可能区分不同的字符。它输出shape是[None, 17, 6, 128],这个形状相当于把一张170x80的彩色图(170,80,3),压缩成17x6的128维特征的特征图(17,6,128)。然后我们把图像reshape为(17,768),也就是把高和特征放在一个维度,再降维成(17,128),也就是从左到右有17条特征,每个特征128个维度。

     这128个维度就是一条图像的非常高维、抽象的概括,然后将17个特征向量依次输入到GRU中,GRU有能力学会不同特征向量的组合会代表什么字符,即使是字符之间有粘连也不怕。这里使用了双向GRU,最后Dropout接一个全连接层,作为分类器输出每个字符的概率。

       最后计算CTC Loss。

       可以看到模型比上一个模型复杂了许多,但实际上只是因为输入比较多,所以它显得很大,还有一个值得注意的地方,我们的图片在输入时是经过了transpose函数旋转的,这是因为希望以水平方向输入RNN。

数据生成器,input_length表示模型输出的长度,这里是15。

def gen(batch_size=128): X = np.zeros((batch_size, width, height, 3), dtype=np.uint8) y = np.zeros((batch_size, n_len), dtype=np.uint8) generator = ImageCaptcha(width=width, height=height) while True: for i in range(batch_size): random_str = ''.join([random.choice(characters) for j in range(n_len)]) X[i] = np.array(generator.generate_image(random_str)).transpose(1, 0, 2) y[i] = [characters.find(x) for x in random_str] yield [X, y, np.ones(batch_size)*rnn_length, np.ones(batch_size)*n_len], np.ones(batch_size) (X_vis, y_vis, input_length_vis, label_length_vis), _ = next(gen(1)) print(X_vis.shape, y_vis, input_length_vis, label_length_vis) plt.imshow(X_vis[0].transpose(1, 0, 2)) plt.title(''.join([characters[i] for i in y_vis[0]]))

评估模型,只有全部正确,才算预测正确。这里有个坑,就是模型最开始训练的时候,并不一定会输出4个字符,所以如果遇到所有的字符都不到4个的时候,就不用计算了,一定是全错。遇到多于4个字符的时候,只取前4个。因为Keras没有针对CTC模型计算准确率的选项,因此需要自定义一个回调函数,它会在每一代训练完成的时候计算模型的准确率。

def evaluate(batch_size=128, steps=10): batch_acc = 0 generator = gen(batch_size) for i in range(steps): [X_test, y_test, _, _], _ = next(generator) y_pred = base_model.predict(X_test) shape = y_pred[:,2:,:].shape ctc_decode = K.ctc_decode(y_pred[:,2:,:], input_length=np.ones(shape[0])*shape[1])[0][0] out = K.get_value(ctc_decode)[:, :n_len] if out.shape[1] == n_len: batch_acc += (y_test == out).all(axis=1).mean() return batch_acc / steps from keras.callbacks import * class Evaluator(Callback): def __init__(self): self.accs = [] def on_epoch_end(self, epoch, logs=None): acc = evaluate(steps=20)*100 self.accs.append(acc) print('') print('acc: %f%%' % acc) evaluator = Evaluator()

训练模型,先按照Adam(1e-3)的学习率训练20代,让模型快速收敛,然后以Adam(1e-4)的学习率再训练。这里设置每迭代400个step,也就是400*128=51200个样本,验证集设置的是20*128=2048个样本。准确率99.46%

h = model.fit_generator(gen(128), steps_per_epoch=400, epochs=20, callbacks=[evaluator], validation_data=gen(128), validation_steps=20) model.compile(loss={'ctc': lambda y_true, y_pred: y_pred}, optimizer=Adam(1e-4)) h2 = model.fit_generator(gen(128), steps_per_epoch=400, epochs=20, callbacks=[evaluator], validation_data=gen(128), validation_steps=20) plt.figure(figsize=(10, 4)) plt.subplot(1, 2, 1) plt.plot(h.history['loss'] + h2.history['loss']) plt.plot(h.history['val_loss'] + h2.history['val_loss']) plt.legend(['loss', 'val_loss']) plt.ylabel('loss') plt.xlabel('epoch') plt.ylim(0, 1) plt.subplot(1, 2, 2) plt.plot(evaluator.accs) plt.ylabel('acc') plt.xlabel('epoch')

测试模型

(X_vis, y_vis, input_length_vis, label_length_vis), _ = next(gen(12)) y_pred = base_model.predict(X_vis) shape = y_pred[:,2:,:].shape ctc_decode = K.ctc_decode(y_pred[:,2:,:], input_length=np.ones(shape[0])*shape[1])[0][0] out = K.get_value(ctc_decode)[:, :4] plt.figure(figsize=(16, 8)) for i in range(12): plt.subplot(3, 4, i+1) plt.imshow(X_vis[i].transpose(1, 0, 2)) plt.title('pred:%s\nreal :%s' % (''.join([characters[x] for x in out[i]]), ''.join([characters[x] for x in y_vis[i]])))

再次评估模型,我们可以尝试计算模型的总体准确率,以及查看模型到底错在哪里。首先生成1024个样本,再用base_model进行预测,然后裁剪并进行ctc解码,最后裁剪到4个label并与真实值进行对比。

(X_vis, y_vis, input_length_vis, label_length_vis), _ = next(gen(10000)) y_pred = base_model.predict(X_vis, verbose=1) shape = y_pred[:,2:,:].shape ctc_decode = K.ctc_decode(y_pred[:,2:,:], input_length=np.ones(shape[0])*shape[1])[0][0] out = K.get_value(ctc_decode)[:, :4] (y_vis == out).all(axis=1).mean() # 0.99460000000000004 # 对预测错的样本进行统计 from collections import Counter Counter(''.join([characters[i] for i in y_vis[y_vis != out]])) # Counter({'0': 37, 'O': 14, 'Q': 1, 'T': 1, 'W': 1})

可以发现模型在O和0的准确率稍微低一点,其他的只是个例。O和0确实是很难分辨的,可以尝试用代码生成一个”O0O0“的图像,然后用模型进行预测。

base_model.save('model.h5') characters2 = characters + ' ' generator = ImageCaptcha(width=width, height=height) random_str = '0O0O' X_test = np.array(generator.generate_image(random_str)) X_test = X_test.transpose(1, 0, 2) X_test = np.expand_dims(X_test, 0) y_pred = base_model.predict(X_test) shape = y_pred[:,2:,:].shape ctc_decode = K.ctc_decode(y_pred[:,2:,:], input_length=np.ones(shape[0])*shape[1])[0][0] out = K.get_value(ctc_decode)[:, :4] out = ''.join([characters[x] for x in out[0]]) plt.imshow(X_test[0].transpose(1, 0, 2)) plt.title('pred:' + str(out)) argmax = np.argmax(y_pred, axis=2)[0] list(zip(argmax, ''.join([characters2[x] for x in argmax])))

识别四则混合运算验证码(初赛)

百度云和魅族联合办了一个深度学习比赛,识别四则混合运算

初赛数据集一共包含10万张180*60的图片和一个labels.txt的文本文件。

评价指标是准确率,要求序列与运算结果都正确才会判定为正确。我们本地还会使用CTC Loss来评估模型。

数据生成器,由于比赛减号都是细的,但captcha库会得到粗的,所以修改captcha库中的image.py,在_draw_character函数增加了一句判断,如果是减号就不进行resize操作,这样能够防止减号变粗。

if c != '-': im = im.resize((w2, h2)) im = im.transform((w, h), Image.QUAD, data) import string import os digits = string.digits operators = '+-*' characters = digits + operators + '() ' print(characters) width, height, n_len, n_class = 180, 60, 7, len(characters) + 1 print(n_class) # 原图片 from captcha.image import ImageCaptcha generator = ImageCaptcha(width=width, height=height, font_sizes=range(35, 56), fonts=['fonts/%s'%x for x in os.listdir('fonts') if '.tt' in x]) generator.generate_image('(1-2)-3') # 改进 from image import ImageCaptcha generator = ImageCaptcha(width=width, height=height, font_sizes=range(35, 56), fonts=['fonts/%s'%x for x in os.listdir('fonts') if '.tt' in x]) generator.generate_image('(1-2)-3')

模型结构,与之前一样,只是把卷积核的个数改多了一点,并且做了一点小改动,支持多GPU训练。如果是单卡,可以直接去掉base_model2 = make_parallel(base_model, 4)。

识别四则混合运算验证码(决赛)

多行,公式,大小不一

需要先经过图像处理,然后才能输入到神经网络中进行端到端的文本序列识别。

评价指标,决赛的数字通常都有5位数,并且会有很多乘法和加法,以及一定会存在的一个分数,所以结果很容易超出64位浮点数所能表示的范围,因此官方在经过讨论后只考虑文本序列的识别,不评价运算结果。

我们本地除了会使用官方的准确率作为评估标准以外,还会使用CTC Loss来评估模型。

示例:流=42072;圳=86;(圳-(97510*45921))*流/35864

中文除了“不”字出现了两次,概率翻倍,其他字概率基本相等。中文字取自于下面两句诗:“君不见,黄河之水天上来,奔流到海不复回 烟锁池塘柳,深圳铁板烧”,所以也可以推断出是按字直接随机取的。

数据预处理

主要使用以下几种技术,在第3章图像处理入门中基本都有所涉及:

转灰度图直方图均衡中值滤波开闭运算二值化轮廓查找边界矩形

首先进行初步的关键区域提取,操作步骤如下:

去噪;连接公式;关键区域提取;微调;连接三个公式;并行预处理;

模型与之前差不多,修改了base_model

为什么使用RNN

l2正则化的参数直接参考了Xception论文5.3节给的参数:

生成器,

模型的训练,Adam(1e-3)和Adam(1e-4)

预测结果,0.99868

其他尝试

不定长图像识别,也就是不定长的跨度;分别识别,考虑每行之间有上下文关系;生成器尝试,很难与官方给的图像一样。

其他CNN模型的尝试,除了自己搭模型,还尝试过用ResNet,DenseNet替换CNN,但是这些模型本身就很大,训练起来速度很慢,前面的val_loss一直在抖动,并且最终提交的效果又和浅层模型没有太大差别,所以为了快速尝试更多方案,舍弃了类似ResNet复杂模型。

替换GRU为LSTM,准确率有轻微下降,理论上这个序列长度并没有很长,GRU和LSTM影响不大。

11、使用全卷积神经网络分割病理切片中的癌细胞

赛题来自数愿组织的病理切片AI识别挑战赛初赛赛题(www.datadreams.org/race-race-3.html)

由于需要实现像素级别的图像分割,因此考虑使用全卷积神经网络(FCN)

在这种架构基础之上,有以下想法:

图形分辨率很高,可以考虑将一张图片拆分为多张小图片进行模型训练。由于只有700张图片,并且是RGB图片,考虑引入迁移学习样本数量有限,可能会有过拟合,考虑在反卷积层中引入l2正则化数据增强

 

12、如何写一个深度学习APP

制作一个可以在iPhone上利用摄像头识别猫狗,并且可视化卷积神经网络关注的区域的一个应用程序。

CAM可视化(Class Activation Mapping,类激活图),原理是全局平均池化层(GlobalAveragePooling, GAP)通常用在CNN最后一层,用于降维,使用GAP层可以极大地降低特征的维度,使用全连接层参数不至于过多,保留了特征的强度信息,丢弃了特征的位置信息,因为是分类问题,对位置信息不敏感,所以使用GAP层来降维效果很好。周博磊指出,可以对CNN的输出加权平均,权重是GAP层到这个分类的权重。这样就能得到一个CAM可视化图。简单来说,可以在最后一层卷积层后面加一层。

在iOS 11中,可以直接使用Keras的模型,只需要使用苹果的模型转换库Core ML Tools将Keras以HDF5格式存储的模型转换为mlmodel格式即可。

使用Xcode编辑APP

 

最新回复(0)