当前位置: 首页 > 工具软件 > tf-coreml > 使用案例 >

TextCNN in CoreML

雍嘉勋
2023-12-01

issues

unsupported ops of type gatherv2

Embedding layer: ‘embedding_1’: height dimension of the input blob must be 1.

NSLocalizedDescription = “Failure dynamically resizing for sequence length.”

NSLocalizedDescription = “Neural Network inputs can only be of size 1, 3, or 5.”;

Invalid dst shape when converting from Keras to Coreml

长文本分类

项目中使用 word embedding 和 卷积网络对长文本进行分类,模型之前在Android上已经验证OK,tflite模型在安卓上的准确率有98%+,但在应用到ios上的时候遇到很多问题和坑。

模型设计

textcnn模型

输入一串 sequence_length=3w 长度的汉字,通过 word embedding 转成 (batch_size, sequence_length, word_dim) 的矩阵,然后送进 conv1D 网络进行卷积,卷积结果进行 MaxPooling1D 然后喂进全连接 Dense 层得到结果。

模型本身很简单,
如果只在linux+python+tensorflow环境下训练和使用没有什么问题,
转到android上,输出成 tflite 格式也ok,
主要在应用到 ios 上问题比较多。

IOS 卷积神经网络方案

IOS上使用深度学习模型有两种方案,
· 自编 tflite 框架到 ios 上使用
· 使用 IOS 的 CoreML 框架

coreml 模型以 .mlmodel 后缀描述,模型本身符合 protobuf 协议。
coreml 模型所定义的各个不同layers的协议和数据格式参考
CoreML layers 格式

coreml 本身所支持的所有layers或者ops都在上面这个链接中,任何不属于这些layers的算子都不能支持,意味着即使你能训练处一个模型,也不能成功转成 coreml 并接入到 os 系统中(包括ios和mac os)。

coreml提供了自定义层功能,customlayers虽然可以允许将tensorflow或者其他框架模型成功转成 coreml模型,但这不属于这篇记录讨论的范围。

IOS 卷积神经网络设计和问题
gatherV2 not support

第一个问题是coreml并不支持gatherV2算子。
nlp中必须使用到embedding layer,使用tensorflow的nn包可以这么写

self.embedding_weights = tf.Variable(tf.random_uniform([self.config.vocabulary_size, self.config.embedding_size], -1.0, 1.0), name="embedding_weight")
embedded = tf.nn.embedding_lookup(self.embedding_weights, self.texts)
embedded = tf.expand_dims(embedded, -1)

但在转换到 coreml 的时候会有以下问题

unsupported ops of type gatherv2

查看coreml的proto定义是有embedding层的,解决办法是使用keras框架改写模型结构,关于embedding层定义为

emb = layers.Embedding(max_features, word_dim, dropout=0.2, name='embedding')

到这里解决了第一个问题:gatherV2 not support

Embedding layer: ‘embedding_1’: height dimension of the input blob must be 1.

该问题还有两个类似的问题,分别是

Embedding layer: ‘embedding_1’: channel dimension of the input blob must be 1.
Embedding layer: ‘embedding_1’: weight dimension of the input blob must be 1.

原因:
coreml的embedding层定义导致的,它要求输入的数据维度在 C H W 三个维度上都为1,因此如果embedding的上一层输出维度在这个三个维度上不是1的话就会报错
可以查看上面 Proto 中对 embedding 层的定义

 * A layer that performs a matrix lookup and optionally adds a bias.
 * The weights matrix is stored with dimensions [outputChannels, inputDim].
 *
 * .. code::
 *
 *      y = EmbeddingLayer(x)
 *
 * Requires 1 input and produces 1 output.
 *
 * Input
 *     Input values must be in the range ``[0, inputDim - 1]``.
 *
 *     Input must have rank equal to 4 or 5, such that the last 3 dimensions are all 1.
 *     rank 4: shape (x1, 1, 1, 1). x1 is effectively the batch/sequence length.
 *     rank 5: shape (x1, x2 , 1, 1, 1). x1 * x2 is effectively the combined batch/sequence length.

CoreML比较蛋疼的地方是它本身设计的时候就没有考虑好输入数据维度的问题,在V1版本的时候就倾向于认为数据都必须有 C H W 三个维度,加上 batch 和 sequence 总共构成5个维度,

[batch_size, sequence_length, channel, width, height]

但在对于非图片类数据进行处理的时候就会显得很奇葩,
特么我NLP不需要有CHW这三个维度啊!!!

对于这个问题处理方法是我们不得不在 embedding layer 之前加一层 reshape,把输入数据维度加上 CHW 三个维度。

reshape_1 = layers.Reshape(input_shape=(maxlen, ), target_shape=(maxlen, 1, ))(input_text)
emb = layers.Embedding(max_features, 128, dropout=0.2, name='embedding')(reshape_1)

只加了一层reshape,也就是在Keras模型上只加了一个维度,并不会错误,实际上CoreML在处理这层reshape的时候会自动加上CHW三个维度。

CoreML 的输入维度

用 summary() 把Keras模型结构打出来的话,结构会像下面这样

Layer (type) Output Shape Param #
input_text (InputLayer) (None, 80) 0
reshape_1 (Reshape) (None, 80, 1) 0
embedding (Embedding) (None, 80, 1, 128) 896

对比一下coreml模型的网络结构

Input name(s) and shape(s):
input_text : (C,H,W) = (80, 1, 1)
Neural Network compiler 0: 300 , name = reshape_1, output shape : (C,H,W) = (1, 1, 1)
Neural Network compiler 1: 150 , name = embedding, output shape : (C,H,W) = (128, 1, 1)

注意Coreml的reshape层,output shape是 (1,1,1),
标识输入长度的80在input_text层在 channel 维度上,到了reshape就不见了,这是因为coreml在做模型转换的时候把channel转到了 batch 维度上。

在CoreML3之前的版本里,coreml 模型的输入只接受总维度为 1, 3, 5的数据,当我们使用coremltools转换模型的时候,只能看到 CHW 三个维度,前面的两个维度没有显示出来。rank5的输入分别表示

(sequence length, batch size, channels, height, width)

当输入维度只有1的时候,会自动填充到3,CoreML默认至少要有3个维度。
所以上面的coreml模型实际上是这样子

input_text: (None, 80)
reshape_1: (None, 80, 1, 1, 1)
embedding: (None, 80, 128, 1, 1)

到这里就解决了embedding输入维度的问题。

TextCNN Keras源码
input_text = layers.Input(shape=(maxlen, ), name='input_text')
reshape_1 = layers.Reshape(input_shape=(maxlen, ), target_shape=(maxlen, 1, ))(input_text)
emb = layers.Embedding(max_features, 128, dropout=0.2, name='embedding')(input_text)
reshape_2 = layers.Reshape(target_shape=(maxlen, 128, ))(emb)

filter_size = 2
conv1d = layers.Conv1D(filters=32,
                     kernel_size=filter_size,
                     strides=1,
                     padding='valid',
                     activation='relu',
                     kernel_initializer=keras.initializers.TruncatedNormal(mean=0.0, stddev=0.1),
                     bias_initializer=keras.initializers.constant(value=0.1),
                     name='conv_%d' % filter_size)(reshape_2)
maxpool1d = layers.MaxPool1D(pool_size=maxlen - filter_size + 1,
                            strides=1,
                            padding='valid',
                            name=('maxpool_%d' % filter_size))(conv1d)

dropout = layers.Dropout(0.5)(maxpool1d)
reshape_3 = layers.Reshape(target_shape=(1, 32))(dropout)
dense = layers.Dense(1, name='dense')(reshape_3)

model = Model(inputs=[input_text], outputs=[dense])

model.compile(loss='binary_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])
使用CoreML进行推理预测

CoreML的python工具提供了在python环境下进行预测的接口,我们不需要在ios或者mac os上才可以验证模型。
以textcnn为例,
模型接受一个输入维度是 (1, 80) 的数据,并输出预测结果,
预测代码如下

coreml_model = coremltools.models.MLModel('textcnn.mlmodel')
coreml_predict = np.array([[0, 0, 0, 2]])
coreml_predict = sequence.pad_sequences(coreml_predict, maxlen=maxlen)
coreml_predict = coreml_predict.reshape((maxlen, ))

其中 textcnn.model 是你自己训练出来的模型文件。

 类似资料:

相关阅读

相关文章

相关问答