419 lines
13 KiB
Python
419 lines
13 KiB
Python
"""
|
|
Copyright (C) 2018-2020 Intel Corporation
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
"""
|
|
import io
|
|
import os
|
|
import struct
|
|
|
|
import numpy as np
|
|
|
|
from mo.utils.error import Error
|
|
from mo.utils.utils import refer_to_faq_msg
|
|
|
|
end_of_nnet_tag = '</Nnet>'
|
|
end_of_component_tag = '<!EndOfComponent>'
|
|
|
|
supported_components = [
|
|
'addshift',
|
|
'affinecomponent',
|
|
'affinecomponentpreconditionedonline',
|
|
'affinetransform',
|
|
'backproptruncationcomponent',
|
|
'batchnormcomponent',
|
|
'clipgradientcomponent',
|
|
'convolutional1dcomponent',
|
|
'convolutionalcomponent',
|
|
'copy',
|
|
'elementwiseproductcomponent',
|
|
'fixedaffinecomponent',
|
|
'fixedscalecomponent',
|
|
'fixedbiascomponent',
|
|
'linearcomponent',
|
|
'logsoftmaxcomponent',
|
|
'lstmnonlinearitycomponent',
|
|
'lstmprojected',
|
|
'lstmprojectedstreams',
|
|
'maxpoolingcomponent',
|
|
'naturalgradientaffinecomponent',
|
|
'naturalgradientperelementscalecomponent',
|
|
'noopcomponent',
|
|
'normalizecomponent',
|
|
'parallelcomponent',
|
|
'pnormcomponent',
|
|
'rectifiedlinearcomponent',
|
|
'rescale',
|
|
'sigmoid',
|
|
'sigmoidcomponent',
|
|
'softmax',
|
|
'softmaxcomponent',
|
|
'splicecomponent',
|
|
'sumgroupcomponent',
|
|
'tanhcomponent',
|
|
]
|
|
|
|
|
|
def get_bool(s: bytes) -> bool:
|
|
"""
|
|
Get bool value from bytes
|
|
:param s: bytes array contains bool value
|
|
:return: bool value from bytes array
|
|
"""
|
|
if str(s) == "b\'F\'":
|
|
return False
|
|
elif str(s) == "b\'T\'":
|
|
return True
|
|
else:
|
|
return struct.unpack('?', s)[0]
|
|
|
|
|
|
def get_uint16(s: bytes) -> int:
|
|
"""
|
|
Get unsigned int16 value from bytes
|
|
:param s: bytes array contains unsigned int16 value
|
|
:return: unsigned int16 value from bytes array
|
|
"""
|
|
return struct.unpack('H', s)[0]
|
|
|
|
|
|
def get_uint32(s: bytes) -> int:
|
|
"""
|
|
Get unsigned int32 value from bytes
|
|
:param s: bytes array contains unsigned int32 value
|
|
:return: unsigned int32 value from bytes array
|
|
"""
|
|
return struct.unpack('I', s)[0]
|
|
|
|
|
|
def get_uint64(s: bytes) -> int:
|
|
"""
|
|
Get unsigned int64 value from bytes
|
|
:param s: bytes array contains unsigned int64 value
|
|
:return: unsigned int64 value from bytes array
|
|
"""
|
|
return struct.unpack('q', s)[0]
|
|
|
|
|
|
def read_binary_bool_token(file_desc: io.BufferedReader) -> bool:
|
|
"""
|
|
Get next bool value from file
|
|
The carriage moves forward to 1 position.
|
|
:param file_desc: file descriptor
|
|
:return: next boolean value in file
|
|
"""
|
|
return get_bool(file_desc.read(1))
|
|
|
|
|
|
def read_binary_integer32_token(file_desc: io.BufferedReader) -> int:
|
|
"""
|
|
Get next int32 value from file
|
|
The carriage moves forward to 5 position.
|
|
:param file_desc: file descriptor
|
|
:return: next uint32 value in file
|
|
"""
|
|
buffer_size = file_desc.read(1)
|
|
return get_uint32(file_desc.read(buffer_size[0]))
|
|
|
|
|
|
def read_binary_integer64_token(file_desc: io.BufferedReader) -> int:
|
|
"""
|
|
Get next int64 value from file
|
|
The carriage moves forward to 9 position.
|
|
:param file_desc: file descriptor
|
|
:return: next uint64 value in file
|
|
"""
|
|
buffer_size = file_desc.read(1)
|
|
return get_uint64(file_desc.read(buffer_size[0]))
|
|
|
|
|
|
def read_binary_float_token(file_desc: io.BufferedReader) -> float:
|
|
"""
|
|
Get next float32 value from file
|
|
The carriage moves forward to 5 position.
|
|
:param file_desc: file descriptor
|
|
:return: next float32 value in file
|
|
"""
|
|
buffer_size = file_desc.read(1)
|
|
s = file_desc.read(buffer_size[0])
|
|
return np.frombuffer(s, dtype=np.float32)[0]
|
|
|
|
|
|
def read_string(file_desc: io.BufferedReader) -> int:
|
|
return collect_until_whitespace(file_desc)
|
|
|
|
|
|
def find_next_tag(file_desc: io.BufferedReader) -> str:
|
|
"""
|
|
Get next tag in the file
|
|
:param file_desc:file descriptor
|
|
:return: string like '<sometag>'
|
|
"""
|
|
tag = b''
|
|
while True:
|
|
symbol = file_desc.read(1)
|
|
if symbol == b'':
|
|
raise Error('Unexpected end of Kaldi model')
|
|
if tag == b'' and symbol != b'<':
|
|
continue
|
|
elif symbol == b'<':
|
|
tag = b''
|
|
tag += symbol
|
|
if symbol != b'>':
|
|
continue
|
|
try:
|
|
return tag.decode('ascii')
|
|
except UnicodeDecodeError:
|
|
# Tag in Kaldi model always in ascii encoding
|
|
tag = b''
|
|
|
|
|
|
def read_placeholder(file_desc: io.BufferedReader, size=3) -> bytes:
|
|
"""
|
|
Read size bytes from file
|
|
:param file_desc:file descriptor
|
|
:param size:number of reading bytes
|
|
:return: bytes
|
|
"""
|
|
return file_desc.read(size)
|
|
|
|
|
|
def find_next_component(file_desc: io.BufferedReader) -> str:
|
|
"""
|
|
Read next component in the file.
|
|
All components are contained in supported_components
|
|
:param file_desc:file descriptor
|
|
:return: string like '<component>'
|
|
"""
|
|
is_start = True
|
|
while True:
|
|
tag = find_next_tag(file_desc)
|
|
# Tag is <NameOfTheLayer>. But we want get without '<' and '>'
|
|
component_name = tag[1:-1].lower()
|
|
if component_name in supported_components or tag == end_of_nnet_tag:
|
|
# There is whitespace after component's name
|
|
read_placeholder(file_desc, 1)
|
|
return component_name
|
|
elif tag == '<ComponentName>':
|
|
raise Error('Component has unsupported or not specified type')
|
|
elif not (is_start and tag == end_of_component_tag) and tag.find('Component') != -1:
|
|
raise Error('Component has unsupported type {}'.format(tag))
|
|
is_start = False
|
|
|
|
|
|
def get_name_from_path(path: str) -> str:
|
|
"""
|
|
Get name from path to the file
|
|
:param path: path to the file
|
|
:return: name of the file
|
|
"""
|
|
return os.path.splitext(os.path.basename(path))[0]
|
|
|
|
|
|
def find_end_of_component(file_desc: io.BufferedReader, component: str, end_tags: tuple = ()):
|
|
"""
|
|
Find an index and a tag of the ent of the component
|
|
:param file_desc: file descriptor
|
|
:param component: component from supported_components
|
|
:param end_tags: specific end tags
|
|
:return: the index and the tag of the end of the component
|
|
"""
|
|
end_tags_of_component = ['</{}>'.format(component),
|
|
end_of_component_tag.lower(),
|
|
end_of_nnet_tag.lower(),
|
|
*end_tags,
|
|
*['<{}>'.format(component) for component in supported_components]]
|
|
next_tag = find_next_tag(file_desc)
|
|
while next_tag.lower() not in end_tags_of_component:
|
|
next_tag = find_next_tag(file_desc)
|
|
return next_tag, file_desc.tell()
|
|
|
|
|
|
def get_parameters(file_desc: io.BufferedReader, start_index: int, end_index: int):
|
|
"""
|
|
Get part of file
|
|
:param file_desc: file descriptor
|
|
:param start_index: Index of the start reading
|
|
:param end_index: Index of the end reading
|
|
:return: part of the file
|
|
"""
|
|
file_desc.seek(start_index)
|
|
buffer = file_desc.read(end_index - start_index)
|
|
return io.BytesIO(buffer)
|
|
|
|
|
|
def read_token_value(file_desc: io.BufferedReader, token: bytes = b'', value_type: type = np.uint32):
|
|
"""
|
|
Get value of the token.
|
|
Read next token (until whitespace) and check if next teg equals token
|
|
:param file_desc: file descriptor
|
|
:param token: token
|
|
:param value_type: type of the reading value
|
|
:return: value of the token
|
|
"""
|
|
getters = {
|
|
np.uint32: read_binary_integer32_token,
|
|
np.uint64: read_binary_integer64_token,
|
|
np.bool: read_binary_bool_token
|
|
}
|
|
current_token = collect_until_whitespace(file_desc)
|
|
if token != b'' and token != current_token:
|
|
raise Error('Can not load token {} from Kaldi model'.format(token) +
|
|
refer_to_faq_msg(94))
|
|
return getters[value_type](file_desc)
|
|
|
|
|
|
def collect_until_whitespace(file_desc: io.BufferedReader):
|
|
"""
|
|
Read from file until whitespace
|
|
:param file_desc: file descriptor
|
|
:return:
|
|
"""
|
|
res = b''
|
|
while True:
|
|
new_sym = file_desc.read(1)
|
|
if new_sym == b' ' or new_sym == b'':
|
|
break
|
|
res += new_sym
|
|
return res
|
|
|
|
|
|
def collect_until_token(file_desc: io.BufferedReader, token, size_search_zone=0):
|
|
"""
|
|
Read from file until the token
|
|
:param file_desc: file descriptor
|
|
:param token: token that we find
|
|
:return:
|
|
"""
|
|
while True:
|
|
# usually there is the following structure <CellDim> DIM<ClipGradient> VALUEFM
|
|
res = collect_until_whitespace(file_desc)
|
|
if res == token or res[-len(token):] == token:
|
|
return
|
|
size = size_search_zone
|
|
if size == 0 and isinstance(file_desc, io.BytesIO):
|
|
size = len(file_desc.getbuffer())
|
|
elif size == 0 and isinstance(file_desc, io.BufferedReader):
|
|
size = os.fstat(file_desc.fileno()).st_size
|
|
if file_desc.tell() >= size:
|
|
raise Error('End of the file. Token {} not found. {}'.format(token, file_desc.tell()))
|
|
|
|
|
|
def collect_until_token_and_read(file_desc: io.BufferedReader, token, value_type: type = np.uint32):
|
|
"""
|
|
Read from file until the token
|
|
:param file_desc: file descriptor
|
|
:param token: token to find and read
|
|
:param value_type: type of value to read
|
|
:return:
|
|
"""
|
|
getters = {
|
|
np.uint32: read_binary_integer32_token,
|
|
np.uint64: read_binary_integer64_token,
|
|
np.bool: read_binary_bool_token,
|
|
np.string_: read_string
|
|
}
|
|
collect_until_token(file_desc, token)
|
|
return getters[value_type](file_desc)
|
|
|
|
|
|
def create_edge_attrs(prev_layer_id: str, next_layer_id: str, in_port=0, out_port=0) -> dict:
|
|
"""
|
|
Create common edge's attributes
|
|
:param prev_layer_id: id of previous layer
|
|
:param next_layer_id: id of next layer
|
|
:param in_port: 'in' port
|
|
:param out_port: 'out' port
|
|
:return: dictionary contains common attributes for edge
|
|
"""
|
|
return {
|
|
'out': out_port,
|
|
'in': in_port,
|
|
'name': next_layer_id,
|
|
'fw_tensor_debug_info': [(prev_layer_id, next_layer_id)],
|
|
'in_attrs': ['in', 'permutation'],
|
|
'out_attrs': ['out', 'permutation'],
|
|
'data_attrs': ['fw_tensor_debug_info']
|
|
}
|
|
|
|
|
|
def read_blob(file_desc: io.BufferedReader, size: int, dtype=np.float32):
|
|
"""
|
|
Read blob from the file
|
|
:param file_desc: file descriptor
|
|
:param size: size of the blob
|
|
:param dtype: type of values of the blob
|
|
:return: np array contains blob
|
|
"""
|
|
dsizes = {
|
|
np.float32: 4,
|
|
np.int32: 4
|
|
}
|
|
data = file_desc.read(size * dsizes[dtype])
|
|
return np.frombuffer(data, dtype=dtype)
|
|
|
|
|
|
def get_args_for_specifier(string):
|
|
"""
|
|
Parse arguments in brackets and return list of arguments
|
|
:param string: string in format (<arg1>, <arg2>, .., <argn>)
|
|
:return: list with arguments
|
|
"""
|
|
open_bracket = 1
|
|
pos = 1
|
|
args = []
|
|
prev_arg_pos = 1
|
|
pos_close = string.rfind(b')')
|
|
string = string[:pos_close+1]
|
|
while pos < len(string):
|
|
pos_open = string.find(b'(', pos)
|
|
pos_close = string.find(b')', pos)
|
|
pos_sep = string.find(b',', pos)
|
|
|
|
if pos_open == -1:
|
|
if open_bracket == 1:
|
|
args = args + string[prev_arg_pos:pos_close].replace(b' ', b'').split(b',')
|
|
pos = len(string)
|
|
else:
|
|
open_bracket = open_bracket - 1
|
|
while open_bracket > 1:
|
|
pos_close = string.find(b')', pos_close+1)
|
|
if pos_close != -1:
|
|
open_bracket = open_bracket - 1
|
|
else:
|
|
raise Error("Syntax error in model: incorrect number of brackets")
|
|
args.append(string[prev_arg_pos:pos_close+1].strip())
|
|
prev_arg_pos = string.find(b',', pos_close+1) + 1
|
|
if prev_arg_pos != 0 and string[prev_arg_pos:-2].replace(b' ', b'').split(b',') != [b'']:
|
|
args = args + string[prev_arg_pos:-1].replace(b' ', b'').split(b',')
|
|
pos = len(string)
|
|
else:
|
|
if pos_sep < pos_open and open_bracket == 1:
|
|
pos_sep = string[pos_sep:pos_open].rfind(b',') + pos_sep
|
|
args = args + string[prev_arg_pos:pos_sep].replace(b' ', b'').split(b',')
|
|
prev_arg_pos = pos_sep + 1
|
|
|
|
if pos_open < pos_close:
|
|
open_bracket = open_bracket + 1
|
|
pos = pos_open + 1
|
|
else:
|
|
open_bracket = open_bracket - 1
|
|
if open_bracket == 1:
|
|
args.append(string[prev_arg_pos:pos_close + 1].strip())
|
|
prev_arg_pos = string.find(b',', pos_close+1) + 1
|
|
pos = prev_arg_pos
|
|
else:
|
|
pos = pos_close + 1
|
|
|
|
return args
|