add structure tasks: sudoku & text2sql

Link: https://code.alibaba-inc.com/Ali-MaaS/MaaS-lib/codereview/11314581
This commit is contained in:
yichang.zyc
2023-01-09 15:53:49 +08:00
committed by yingda.chen
parent 728a5b3a21
commit d78fe495ee
23 changed files with 1184 additions and 15 deletions

View File

@@ -346,9 +346,11 @@ class Pipelines(object):
image_text_retrieval = 'image-text-retrieval'
ofa_ocr_recognition = 'ofa-ocr-recognition'
ofa_asr = 'ofa-asr'
document_vl_embedding = 'document-vl-embedding'
ofa_sudoku = 'ofa-sudoku'
ofa_text2sql = 'ofa-text2sql'
video_captioning = 'video-captioning'
video_question_answering = 'video-question-answering'
document_vl_embedding = 'document-vl-embedding'
# science tasks
protein_structure = 'unifold-protein-structure'

View File

@@ -209,6 +209,12 @@ class MMSpeechConfig(PretrainedConfig):
use_ofasys=False,
vit_type='vit_base',
vit_drop_path_rate=0.0,
use_gamma_feature=False,
gamma=1.0,
exclude_mlp=True,
temperature_init_value=None,
remove_decoder_type_embedding=False,
mlp_dim=512,
required_seq_len_multiple=2,
encoder_pos_conv_depth=5,
encoder_conv_pos=95,
@@ -279,6 +285,15 @@ class MMSpeechConfig(PretrainedConfig):
self.use_ofasys = use_ofasys
self.vit_type = vit_type
self.vit_drop_path_rate = vit_drop_path_rate
self.use_gamma_feature = use_gamma_feature
# add some new features from ofa
self.use_gamma_feature = use_gamma_feature
self.gamma = gamma
self.exclude_mlp = exclude_mlp
self.temperature_init_value = temperature_init_value
self.remove_decoder_type_embedding = remove_decoder_type_embedding
self.mlp_dim = mlp_dim
# FP16 optimization
self.required_seq_len_multiple = required_seq_len_multiple

View File

@@ -216,6 +216,12 @@ class OFAConfig(PretrainedConfig):
use_ofasys=False,
vit_type='vit_base',
vit_drop_path_rate=0.0,
use_gamma_feature=False,
gamma=1.0,
exclude_mlp=True,
temperature_init_value=None,
remove_decoder_type_embedding=False,
mlp_dim=512,
**kwargs):
self.vocab_size = vocab_size
self.max_position_embeddings = max_position_embeddings
@@ -265,6 +271,14 @@ class OFAConfig(PretrainedConfig):
self.vit_type = vit_type
self.vit_drop_path_rate = vit_drop_path_rate
# add some new features from ofa
self.use_gamma_feature = use_gamma_feature
self.gamma = gamma
self.exclude_mlp = exclude_mlp
self.temperature_init_value = temperature_init_value
self.remove_decoder_type_embedding = remove_decoder_type_embedding
self.mlp_dim = mlp_dim
super().__init__(
pad_token_id=pad_token_id,
bos_token_id=bos_token_id,

View File

@@ -11,12 +11,11 @@
# 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.
""" PyTorch OFA model."""
""" PyTorch OFA-MMSpeech model."""
import math
import random
from dataclasses import dataclass
from typing import Dict, List, Optional, Tuple
from typing import Optional, Tuple
import numpy as np
import torch
@@ -27,22 +26,17 @@ from fairseq.modules import LayerNorm, SamePad, TransposeLast
from fairseq.modules.transformer_sentence_encoder import init_bert_params
from fairseq.utils import index_put
from packaging import version
from torch import Tensor, nn
from torch import nn
from torch.nn import functional as F
from transformers.activations import ACT2FN
from transformers.file_utils import (ModelOutput, add_code_sample_docstrings,
add_start_docstrings,
add_start_docstrings_to_model_forward)
from transformers.modeling_outputs import (
BaseModelOutputWithPastAndCrossAttentions, Seq2SeqLMOutput,
Seq2SeqModelOutput)
from transformers.modeling_utils import PreTrainedModel
from transformers.utils import logging
from .configuration_mmspeech import MMSpeechConfig
from .generate import utils
from .modeling_ofa import (Embedding, OFADecoder, OFAModel, OFAPreTrainedModel,
_expand_mask, shift_tokens_right)
_expand_mask)
logger = logging.get_logger()

View File

@@ -461,6 +461,18 @@ class OFAEncoderLayer(nn.Module):
self.drop_path = DropPath(
drop_path_rate) if drop_path_rate > 0.0 else nn.Identity()
self.use_gamma_feature = config.use_gamma_feature
if self.use_gamma_feature:
gamma = getattr(config, 'gamma', 1.)
# `OFA.from_pretrain()` method will replace the `gamma` to `weight`
# in the model key. Here, change the parameters like `xxx_gamma_xxx`
# to `xxx_weight_xxx` to adapt this transformation.
self.weight_self_attn = nn.Parameter(
torch.ones(self.embed_dim) * gamma, requires_grad=True)
self.weight_ffn = nn.Parameter(
torch.ones(self.embed_dim) * gamma, requires_grad=True)
def residual_connection(self, x, residual):
r"""
Residual connection with drop path.
@@ -499,6 +511,8 @@ class OFAEncoderLayer(nn.Module):
if self.self_attn_mid_layer_norm:
hidden_states = self.self_attn_mid_layer_norm(hidden_states)
hidden_states = self.dropout(hidden_states)
if self.use_gamma_feature:
hidden_states = self.weight_self_attn * hidden_states
hidden_states = self.residual_connection(hidden_states, residual)
if not self.normalize_before:
hidden_states = self.self_attn_layer_norm(hidden_states)
@@ -513,6 +527,8 @@ class OFAEncoderLayer(nn.Module):
hidden_states = self.ffn_layer_norm(hidden_states)
hidden_states = self.fc2(hidden_states)
hidden_states = self.dropout(hidden_states)
if self.use_gamma_feature:
hidden_states = self.weight_ffn * hidden_states
hidden_states = self.residual_connection(hidden_states, residual)
if not self.normalize_before:
hidden_states = self.final_layer_norm(hidden_states)
@@ -578,6 +594,20 @@ class OFADecoderLayer(nn.Module):
self.drop_path = DropPath(
drop_path_rate) if drop_path_rate > 0.0 else nn.Identity()
self.use_gamma_feature = config.use_gamma_feature
if self.use_gamma_feature:
gamma = getattr(config, 'gamma', 1.)
# `OFA.from_pretrain()` method will replace the `gamma` to `weight`
# in the model key. Here, change the parameters like `xxx_gamma_xxx`
# to `xxx_weight_xxx` to adapt this transformation.
self.weight_self_attn = nn.Parameter(
torch.ones(self.embed_dim) * gamma, requires_grad=True)
self.weight_cross_attn = nn.Parameter(
torch.ones(self.embed_dim) * gamma, requires_grad=True)
self.weight_ffn = nn.Parameter(
torch.ones(self.embed_dim) * gamma, requires_grad=True)
def residual_connection(self, x, residual):
r"""
Residual connection with drop path.
@@ -629,6 +659,8 @@ class OFADecoderLayer(nn.Module):
if self.self_attn_mid_layer_norm:
hidden_states = self.self_attn_mid_layer_norm(hidden_states)
hidden_states = self.dropout(hidden_states)
if self.use_gamma_feature:
hidden_states = self.weight_self_attn * hidden_states
hidden_states = self.residual_connection(hidden_states, residual)
if not self.normalize_before:
hidden_states = self.self_attn_layer_norm(hidden_states)
@@ -654,6 +686,8 @@ class OFADecoderLayer(nn.Module):
if self.cross_attn_mid_layer_norm:
hidden_states = self.cross_attn_mid_layer_norm(hidden_states)
hidden_states = self.dropout(hidden_states)
if self.use_gamma_feature:
hidden_states = self.weight_cross_attn * hidden_states
hidden_states = self.residual_connection(hidden_states, residual)
if not self.normalize_before:
hidden_states = self.cross_attn_layer_norm(hidden_states)
@@ -671,6 +705,8 @@ class OFADecoderLayer(nn.Module):
hidden_states = self.ffn_layer_norm(hidden_states)
hidden_states = self.fc2(hidden_states)
hidden_states = self.dropout(hidden_states)
if self.use_gamma_feature:
hidden_states = self.weight_ffn * hidden_states
hidden_states = self.residual_connection(hidden_states, residual)
if not self.normalize_before:
hidden_states = self.final_layer_norm(hidden_states)
@@ -1961,6 +1997,14 @@ class OFAModel(OFAPreTrainedModel):
self.decoder = OFADecoder(config, shared)
self.use_ofasys = config.use_ofasys
# exclude mlp head as default
if not getattr(config, 'exclude_mlp', True):
self.mlp_head = Linear(config.d_model, config.mlp_dim)
# None temperature_init_value as default
if config.temperature_init_value:
self.temp = nn.Parameter(config.temperature_init_value
* torch.ones([]))
# Initialize weights and apply final processing
self.post_init()

View File

@@ -11,5 +11,7 @@ OFA_TASK_KEY_MAPPING = {
Tasks.text_classification: OutputKeys.LABELS,
Tasks.image_classification: OutputKeys.LABELS,
Tasks.visual_entailment: OutputKeys.LABELS,
Tasks.auto_speech_recognition: OutputKeys.TEXT
Tasks.auto_speech_recognition: OutputKeys.TEXT,
Tasks.sudoku: OutputKeys.TEXT,
Tasks.text2sql: OutputKeys.TEXT,
}

View File

@@ -38,6 +38,8 @@ __all__ = ['OfaForAllTasks']
@MODELS.register_module(Tasks.text_summarization, module_name=Models.ofa)
@MODELS.register_module(Tasks.text_classification, module_name=Models.ofa)
@MODELS.register_module(Tasks.auto_speech_recognition, module_name=Models.ofa)
@MODELS.register_module(Tasks.sudoku, module_name=Models.ofa)
@MODELS.register_module(Tasks.text2sql, module_name=Models.ofa)
class OfaForAllTasks(TorchModel):
r"""
All ofa tasks using uniform ofa model structure. So far, we support three types of tasks:
@@ -94,7 +96,7 @@ class OfaForAllTasks(TorchModel):
self.cfg = Config.from_file(
osp.join(model_dir, ModelFile.CONFIGURATION))
multimodal_type = self.cfg.model.get('multimodal_type', 'default')
if multimodal_type == 'default':
if multimodal_type in ['default', 'text2sql']:
model = OFAModel.from_pretrained(model_dir)
elif multimodal_type == 'mmspeech':
model = MMSpeechModel.from_pretrained(model_dir)
@@ -123,6 +125,13 @@ class OfaForAllTasks(TorchModel):
self.tokenizer.add_tokens(
['<audio_{}>'.format(i) for i in range(30000)])
self.cfg.update({'num_bins': 0, 'num_codes': 30000})
elif multimodal_type == 'text2sql':
self.tokenizer.add_tokens(
['<code_{}>'.format(i) for i in range(8192)])
self.tokenizer.add_tokens(
['<bin_{}>'.format(i) for i in range(1000)])
self.cfg.update({'num_bins': 1000, 'num_codes': 8192})
self.tokenizer.add_tokens(['>=', '<='])
self.batch_size = self.cfg.model.get('batch_size', 1)
self.patch_image_size = self.cfg.model.get('patch_image_size', 480)
@@ -177,6 +186,8 @@ class OfaForAllTasks(TorchModel):
Tasks.text_classification: inference_d[self.gen_type],
Tasks.image_classification: inference_d[self.gen_type],
Tasks.auto_speech_recognition: self._text_gen_inference,
Tasks.sudoku: self._text_gen_inference,
Tasks.text2sql: self._text_gen_inference,
}
pattern_str = '((?<=[^ a-zA-Z0-9.,:!?]) +| +(?=[^ a-zA-Z0-9.,:!?]))'
self.pattern = re.compile(pattern_str)

View File

@@ -73,6 +73,8 @@ TASK_OUTPUTS = {
# "text": "电子元器件提供BOM配单"
# }
Tasks.ocr_recognition: [OutputKeys.TEXT],
Tasks.sudoku: [OutputKeys.TEXT],
Tasks.text2sql: [OutputKeys.TEXT],
# document vl embedding for single sample
# {

View File

@@ -201,6 +201,12 @@ TASK_INPUTS = {
'src': InputType.LIST,
'ref': InputType.LIST,
},
Tasks.sudoku:
InputType.TEXT,
Tasks.text2sql: {
'text': InputType.TEXT,
'database': InputType.TEXT
},
# ============ audio tasks ===================
Tasks.auto_speech_recognition:

View File

@@ -0,0 +1,53 @@
# Copyright (c) Alibaba, Inc. and its affiliates.
from typing import Any, Dict, Optional, Union
import torch
from modelscope.metainfo import Pipelines
from modelscope.models.multi_modal import OfaForAllTasks
from modelscope.pipelines.base import Model, Pipeline
from modelscope.pipelines.builder import PIPELINES
from modelscope.pipelines.util import batch_process
from modelscope.preprocessors import OfaPreprocessor, Preprocessor
from modelscope.utils.constant import Tasks
from modelscope.utils.logger import get_logger
logger = get_logger()
@PIPELINES.register_module(Tasks.sudoku, module_name=Pipelines.ofa_sudoku)
class SudokuPipeline(Pipeline):
R"""
pipeline for sudoku solving
"""
def __init__(self,
model: Union[Model, str],
preprocessor: Optional[Preprocessor] = None,
**kwargs):
"""
use `model` and `preprocessor` to create a pipeline for solving sudoku
Args:
model: model id on modelscope hub.
"""
super().__init__(model=model, preprocessor=preprocessor, **kwargs)
self.model.eval()
if preprocessor is None:
if isinstance(self.model, OfaForAllTasks):
self.preprocessor = OfaPreprocessor(self.model.model_dir)
else:
raise 'no preprocessor is provided'
def _batch(self, data):
if isinstance(self.model, OfaForAllTasks):
return batch_process(self.model, data)
else:
return super(SudokuPipeline, self)._batch(data)
def forward(self, inputs: Dict[str, Any],
**forward_params) -> Dict[str, Any]:
with torch.no_grad():
return super().forward(inputs, **forward_params)
def postprocess(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
return inputs

View File

@@ -0,0 +1,51 @@
# Copyright (c) Alibaba, Inc. and its affiliates.
from typing import Any, Dict, Optional, Union
import torch
from modelscope.metainfo import Pipelines
from modelscope.models.multi_modal import OfaForAllTasks
from modelscope.pipelines.base import Model, Pipeline
from modelscope.pipelines.builder import PIPELINES
from modelscope.pipelines.util import batch_process
from modelscope.preprocessors import OfaPreprocessor, Preprocessor
from modelscope.utils.constant import Tasks
from modelscope.utils.logger import get_logger
logger = get_logger()
@PIPELINES.register_module(Tasks.text2sql, module_name=Pipelines.ofa_text2sql)
class TextToSqlPipeline(Pipeline):
R"""
pipeline for text to sql task
"""
def __init__(self,
model: Union[Model, str],
preprocessor: Optional[Preprocessor] = None,
**kwargs):
"""
use `model` and `preprocessor` to create a pipeline for text2sql task
Args:
model: model id on modelscope hub.
"""
super().__init__(model=model, preprocessor=preprocessor, **kwargs)
self.model.eval()
if preprocessor is None:
if isinstance(self.model, OfaForAllTasks):
self.preprocessor = OfaPreprocessor(self.model.model_dir)
def _batch(self, data):
if isinstance(self.model, OfaForAllTasks):
return batch_process(self.model, data)
else:
return super(TextToSqlPipeline, self)._batch(data)
def forward(self, inputs: Dict[str, Any],
**forward_params) -> Dict[str, Any]:
with torch.no_grad():
return super().forward(inputs, **forward_params)
def postprocess(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
return inputs

View File

@@ -56,7 +56,9 @@ class OfaPreprocessor(Preprocessor):
Tasks.text_classification: OfaTextClassificationPreprocessor,
Tasks.text_summarization: OfaSummarizationPreprocessor,
Tasks.text_to_image_synthesis: OfaTextToImageSynthesisPreprocessor,
Tasks.auto_speech_recognition: OfaASRPreprocessor
Tasks.auto_speech_recognition: OfaASRPreprocessor,
Tasks.sudoku: OfaSudokuPreprocessor,
Tasks.text2sql: OfaTextToSqlPreprocessor
}
model_dir = model_dir if osp.exists(model_dir) else snapshot_download(
model_dir, user_agent={Invoke.KEY: Invoke.PREPROCESSOR})

View File

@@ -3,7 +3,9 @@ from .asr import OfaASRPreprocessor
from .image_captioning import OfaImageCaptioningPreprocessor
from .image_classification import OfaImageClassificationPreprocessor
from .ocr_recognition import OfaOcrRecognitionPreprocessor
from .sudoku import OfaSudokuPreprocessor
from .summarization import OfaSummarizationPreprocessor
from .text2sql import OfaTextToSqlPreprocessor
from .text_classification import OfaTextClassificationPreprocessor
from .text_to_image_synthesis import OfaTextToImageSynthesisPreprocessor
from .visual_entailment import OfaVisualEntailmentPreprocessor

View File

@@ -48,6 +48,8 @@ class OfaBasePreprocessor:
# there will be no need to use param: use_bpe
tokenizer.add_tokens(['<code_{}>'.format(i) for i in range(8192)])
tokenizer.add_tokens(['<bin_{}>'.format(i) for i in range(1000)])
if self.cfg.model.get('multimodal_type', 'default') == 'text2sql':
tokenizer.add_tokens(['>=', '<='])
self.tokenizer = tokenizer
self.bos_item = torch.LongTensor([tokenizer.bos_token_id])
self.pad_item = torch.LongTensor([tokenizer.pad_token_id])

View File

@@ -0,0 +1,110 @@
# Copyright (c) Alibaba, Inc. and its affiliates.
from typing import Any, Dict
import numpy as np
import torch
from modelscope.utils.constant import ModeKeys
from .base import OfaBasePreprocessor
class OfaSudokuPreprocessor(OfaBasePreprocessor):
r"""
OFA preprocessor for sudoku tasks
"""
def __init__(self,
cfg,
model_dir,
mode=ModeKeys.INFERENCE,
*args,
**kwargs):
"""preprocess the data
Args:
cfg(modelscope.utils.config.ConfigDict) : model config
model_dir (str): model path,
mode: preprocessor mode (model mode)
"""
super(OfaSudokuPreprocessor, self).__init__(cfg, model_dir, mode,
*args, **kwargs)
self.instruction_text = self.cfg.model.get('prompt',
' solve the sudoku .')
self.seg_embedding = self.cfg.get('seg_embedding', False)
self.max_struct_length = self.cfg.get('max_struct_length', 256)
if self.seg_embedding:
self.input_puzzle_row = []
self.input_puzzle_col = []
for idx in range(9):
for jdx in range(9):
self.input_puzzle_row.append(jdx + 1)
self.input_puzzle_col.append(idx + 1)
if not (idx == 8 and jdx == 8):
self.input_puzzle_row.append(0)
self.input_puzzle_col.append(0)
self.input_puzzle_col = torch.tensor(self.input_puzzle_col)
self.input_puzzle_row = torch.tensor(self.input_puzzle_row)
instruct_seg = torch.zeros_like(
self.tokenize_text(self.instruction_text))
input_puzzle_col = torch.cat([self.input_puzzle_col, instruct_seg])
input_puzzle_row = torch.cat([self.input_puzzle_row, instruct_seg])
self.input_puzzle_col = torch.cat(
[self.bos_item, input_puzzle_col, self.eos_item])
self.input_puzzle_row = torch.cat(
[self.bos_item, input_puzzle_row, self.eos_item])
def __call__(self, data: Dict[str, Any]) -> Dict[str, Any]:
if self.mode == ModeKeys.TRAIN:
return self._build_train_sample(data)
else:
return self._build_infer_sample(data)
def _build_train_sample(self, data: Dict[str, Any]) -> Dict[str, Any]:
r"""
build sample for training tasks.
step 1. execute the `_build_infer_sample` function to get a batch sample
for inference.
step 2. process the label data for training.
"""
sample = self._build_infer_sample(data)
target = sample['label']
target_token_list = target.lower().strip().split()
target = ' '.join(target_token_list[:self.max_tgt_length])
sample['target'] = self.tokenize_text(target, add_bos=False)
sample['prev_output_tokens'] = torch.cat(
[self.bos_item, sample['target'][:-1]])
return sample
def _build_infer_sample(self, data: Dict[str, Any]) -> Dict[str, Any]:
r"""
build sample for inference tasks.
step 1. Get the input random masked sudoku text input, which shold be
generated like below pseudo code.
>>> sudo = np.random.randint(1, 9, size=(9, 9)) # a pseudo sudoku
>>> sudo_text = " | ".join(" : ".join(str(c) for c in row) \
>>> for row in sudo)
step 2. Limit the length, tokenize the input text and add the bos token
to the front of the input as source input.
step 3. Add a pseodo ids for every input.
"""
assert 'text' in self.column_map and 'text' in data, \
'there must be `text` column in task key map and source data'
text = data[self.column_map['text']] # equal data['text']
text = ' '.join(text.lower().strip().split()[:self.max_struct_length])
src_item = self.tokenize_text(text + self.instruction_text)
src_item = src_item[:(self.max_src_length + self.max_struct_length)]
sample = {'id': 0.0, 'source': src_item}
if self.seg_embedding:
sample['seg_row_tokens'] = self.input_puzzle_row
sample['seg_col_tokens'] = self.input_puzzle_col
if 'solution' in self.column_map and self.column_map[
'solution'] in data:
sample['label'] = ' {}'.format(data[self.column_map['solution']])
return sample

View File

@@ -0,0 +1,446 @@
# Copyright (c) Alibaba, Inc. and its affiliates.
import os
import random
import re
from typing import Any, Dict, List
import torch
from modelscope.utils.constant import ModeKeys
from .base import OfaBasePreprocessor
from .utils.bridge_content_encoder import get_database_matches
from .utils.get_tables import dump_db_json_schema
class OfaTextToSqlPreprocessor(OfaBasePreprocessor):
r"""
OFA preprocessor for text to sql tasks
"""
def __init__(self,
cfg,
model_dir,
mode=ModeKeys.INFERENCE,
*args,
**kwargs):
"""preprocess the data
Args:
cfg(modelscope.utils.config.ConfigDict) : model config
model_dir (str): model path,
mode: preprocessor mode (model mode)
"""
super(OfaTextToSqlPreprocessor, self).__init__(cfg, model_dir, mode,
*args, **kwargs)
self.instruction_text = self.cfg.model.get('prompt',
' . generating sql code.')
self.max_struct_length = self.cfg.get('max_struct_length', 256)
self.separator = '\t'
self.db_schema_cache = {}
self.database_path = os.path.join(
os.path.abspath(model_dir), 'database')
def __call__(self, data: Dict[str, Any]) -> Dict[str, Any]:
if self.mode == ModeKeys.TRAIN:
return self._build_train_sample(data)
else:
return self._build_infer_sample(data)
def _build_train_sample(self, data: Dict[str, Any]) -> Dict[str, Any]:
r"""
build sample for training tasks.
step 1. Get the input question and database id from text input
step 2. Get the database structure input
step 3. Add a pseudo ids for every input.
step 4. Calculate the target and previous output items.
"""
assert 'text' in self.column_map and 'text' in data, \
'there must be `text` column in task key map and source data'
text = data[self.column_map['text']] # equal data['text']
texts = text.split(self.separator)
assert len(
texts
) == 3, 'invalid input, should contain query, question and database id'
query, question, db_id = texts
# construct struct input
if db_id not in self.db_schema_cache:
self.db_schema_cache[db_id] = dump_db_json_schema(
self.database_path + '/' + db_id + '/' + db_id + '.sqlite',
db_id)
question = ' '.join(question.strip().split()[:self.max_src_length])
seq_inputs = seq2seq_input(query, question, db_id, self.database_path,
self.db_schema_cache[db_id], self.cfg.model,
True)
struct_in = seq_inputs['struct_in']
text = seq_inputs['text_in']
seq_out = seq_inputs['seq_out']
db_struct = seq_inputs['db_struct']
text = '{} ; structured knowledge: {}'.format(
text, struct_in) + self.instruction_text
src_item = self.tokenize_text(text + self.instruction_text)
src_item = src_item[:(self.max_src_length + self.max_struct_length
+ 20)]
tgt_item = self.tokenize_text(
' {}'.format(seq_out), add_bos=False,
add_eos=False)[:self.max_tgt_length]
target_item = torch.cat([tgt_item, self.eos_item])
prev_output_item = torch.cat([self.bos_item, tgt_item])
sample = {
'id': 0.0,
'source': src_item,
'target': target_item,
'prev_output_tokens': prev_output_item,
'db_struct': db_struct
}
return sample
def _build_infer_sample(self, data: Dict[str, Any]) -> Dict[str, Any]:
r"""
build sample for inference tasks.
step 1. Get the input question and database id from text input
step 2. Get the database structure input
step 3. Add a pseudo ids for every input.
"""
assert 'text' in self.column_map and 'text' in data, \
'there must be `text` column in task key map and source data'
text = data[self.column_map['text']] # equal data['text']
db_id = data.get(self.column_map['database'], 'culture_company')
db_id = db_id.strip()
# construct struct input
if db_id not in self.db_schema_cache:
self.db_schema_cache[db_id] = dump_db_json_schema(
self.database_path + '/' + db_id + '/' + db_id + '.sqlite',
db_id)
text = ' '.join(text.strip().split()[:self.max_src_length])
seq_inputs = seq2seq_input(None, text, db_id, self.database_path,
self.db_schema_cache[db_id], self.cfg.model)
struct_in = seq_inputs['struct_in']
db_struct = seq_inputs['db_struct']
text = '{} ; structured knowledge: {}'.format(
text, struct_in) + self.instruction_text
src_item = self.tokenize_text(text + self.instruction_text)
src_item = src_item[:(self.max_src_length + self.max_struct_length
+ 20)]
sample = {'id': 0.0, 'source': src_item, 'db_struct': db_struct}
if 'solution' in self.column_map and self.column_map[
'solution'] in data:
sample['label'] = ' {}'.format(data[self.column_map['solution']])
return sample
def seq2seq_input(query,
question,
db_id,
db_path,
schema,
args,
is_train=False):
ex = form_input_for_construction(query, question, db_id, db_path, schema)
serialized_schema = spider_add_serialized_schema(
ex, args)['serialized_schema'].strip()
if not is_train:
return {
'struct_in': serialized_schema,
'text_in': question,
'db_struct': ex
}
question, seq_out = spider_pre_process_one_function(ex, args)
return {
'struct_in': serialized_schema,
'text_in': question,
'seq_out': seq_out,
'db_struct': ex
}
def spider_pre_process_one_function(item: dict, args):
prefix = ''
seq_out = spider_get_target(
query=item['query'],
db_id=item['db_id'],
normalize_query=True,
target_with_db_id=args.target_with_db_id,
)
return prefix + item['question'].strip(), seq_out
def spider_get_target(
query: str,
db_id: str,
normalize_query: bool,
target_with_db_id: bool,
) -> str:
_normalize = normalize if normalize_query else (lambda x: x)
return f'{db_id} | {_normalize(query)}' if target_with_db_id else _normalize(
query)
def normalize(query: str) -> str:
def comma_fix(s):
# Remove spaces in front of commas
return s.replace(' , ', ', ')
def white_space_fix(s):
# Remove double and triple spaces
return ' '.join(s.split())
def lower(s):
# Convert everything except text between (single or double) quotation marks to lower case
return re.sub(r"\b(?<!['\"])(\w+)(?!['\"])\b",
lambda match: match.group(1).lower(), s)
return comma_fix(white_space_fix(lower(query)))
def spider_add_serialized_schema(ex: dict, args) -> dict:
if getattr(args, 'schema_serialization_with_nl'):
serialized_schema = serialize_schema_natural_language(
question=ex['question'],
db_path=ex['db_path'],
db_id=ex['db_id'],
db_column_names=ex['db_column_names'],
db_table_names=ex['db_table_names'],
db_primary_keys=ex['db_primary_keys'],
db_foreign_keys=ex['db_foreign_keys'],
schema_serialization_with_db_content=args.
schema_serialization_with_db_content,
normalize_query=True,
)
else:
serialized_schema = serialize_schema(
question=ex['question'],
db_path=ex['db_path'],
db_id=ex['db_id'],
db_column_names=ex['db_column_names'],
db_table_names=ex['db_table_names'],
schema_serialization_type='peteshaw',
schema_serialization_randomized=False,
schema_serialization_with_db_id=True,
schema_serialization_with_db_content=args.
schema_serialization_with_db_content,
normalize_query=True,
)
return {'serialized_schema': serialized_schema}
def serialize_schema_natural_language(
question: str,
db_path: str,
db_id: str,
db_column_names: Dict[str, str],
db_table_names: List[str],
db_primary_keys,
db_foreign_keys,
schema_serialization_with_db_content: bool = False,
normalize_query: bool = True,
) -> str:
overall_description = f'{db_id} contains tables such as ' \
f'{", ".join([name.lower() if normalize_query else name for name in db_table_names])}.'
def table_description_primary_key_template(primary_key):
return f'{primary_key} is the primary key.'
def table_description(name, column_names):
return f'Table {name} has columns such as {", ".join(column_names)}.'
def value_description(cv_pairs):
return f'{"".join(["The {} contains values such as {}.".format(column, value) for column, value in cv_pairs])}'
def foreign_key_description(table_1, column_1, table_2, column_2):
return f'The {column_1} of {table_1} is the foreign key of {column_2} of {table_2}.'
db_primary_keys = db_primary_keys['column_id']
db_foreign_keys = list(
zip(db_foreign_keys['column_id'], db_foreign_keys['other_column_id']))
descriptions = [overall_description]
db_table_name_strs = []
db_column_name_strs = []
value_sep = ', '
for table_id, table_name in enumerate(db_table_names):
table_name_str = table_name.lower() if normalize_query else table_name
db_table_name_strs.append(table_name_str)
columns = []
column_value_pairs = []
primary_keys = []
for column_id, (x, y) in enumerate(
zip(db_column_names['table_id'],
db_column_names['column_name'])):
if column_id == 0:
continue
column_str = y.lower() if normalize_query else y
db_column_name_strs.append(column_str)
if x == table_id:
columns.append(column_str)
if column_id in db_primary_keys:
primary_keys.append(column_str)
if schema_serialization_with_db_content:
matches = get_database_matches(
question=question,
table_name=table_name,
column_name=y,
db_path=(db_path + '/' + db_id + '/' + db_id
+ '.sqlite'),
)
if matches:
column_value_pairs.append(
(column_str, value_sep.join(matches)))
table_description_columns_str = table_description(
table_name_str, columns)
descriptions.append(table_description_columns_str)
table_description_primary_key_str = table_description_primary_key_template(
', '.join(primary_keys))
descriptions.append(table_description_primary_key_str)
if len(column_value_pairs) > 0:
value_description_str = value_description(column_value_pairs)
descriptions.append(value_description_str)
for x, y in db_foreign_keys:
# get the table and column of x
x_table_name = db_table_name_strs[db_column_names['table_id'][x]]
x_column_name = db_column_name_strs[x]
# get the table and column of y
y_table_name = db_table_name_strs[db_column_names['table_id'][y]]
y_column_name = db_column_name_strs[y]
foreign_key_description_str = foreign_key_description(
x_table_name, x_column_name, y_table_name, y_column_name)
descriptions.append(foreign_key_description_str)
return ' '.join(descriptions)
def serialize_schema(
question: str,
db_path: str,
db_id: str,
db_column_names: Dict[str, str],
db_table_names: List[str],
schema_serialization_type: str = 'peteshaw',
schema_serialization_randomized: bool = False,
schema_serialization_with_db_id: bool = True,
schema_serialization_with_db_content: bool = False,
normalize_query: bool = True,
) -> str:
if schema_serialization_type == 'verbose':
db_id_str = 'Database: {db_id}. '
table_sep = '. '
table_str = 'Table: {table}. Columns: {columns}'
column_sep = ', '
column_str_with_values = '{column} ({values})'
column_str_without_values = '{column}'
value_sep = ', '
elif schema_serialization_type == 'peteshaw':
# see https://github.com/google-research/language/blob/master/language/nqg/tasks/spider/append_schema.py#L42
db_id_str = ' | {db_id}'
table_sep = ''
table_str = ' | {table} : {columns}'
column_sep = ' , '
column_str_with_values = '{column} ( {values} )'
column_str_without_values = '{column}'
value_sep = ' , '
else:
raise NotImplementedError
def get_column_str(table_name: str, column_name: str) -> str:
column_name_str = column_name.lower(
) if normalize_query else column_name
if schema_serialization_with_db_content:
# print("testing")
matches = get_database_matches(
question=question,
table_name=table_name,
column_name=column_name,
db_path=(db_path + '/' + db_id + '/' + db_id + '.sqlite'),
)
if matches:
return column_str_with_values.format(
column=column_name_str, values=value_sep.join(matches))
else:
return column_str_without_values.format(column=column_name_str)
else:
return column_str_without_values.format(column=column_name_str)
tables = [
table_str.format(
table=table_name.lower() if normalize_query else table_name,
columns=column_sep.join(
map(
lambda y: get_column_str(
table_name=table_name, column_name=y[1]),
filter(
lambda y: y[0] == table_id,
zip(
db_column_names['table_id'],
db_column_names['column_name'],
),
),
)),
) for table_id, table_name in enumerate(db_table_names)
]
if schema_serialization_randomized:
random.shuffle(tables)
if schema_serialization_with_db_id:
serialized_schema = db_id_str.format(
db_id=db_id) + table_sep.join(tables)
else:
serialized_schema = table_sep.join(tables)
return serialized_schema
def form_input_for_construction(query, question, db_id, db_path, schema):
return {
'query':
query,
'question':
question,
'db_id':
db_id,
'db_path':
db_path,
'db_table_names':
schema['table_names_original'],
'db_column_names': {
'table_id': [
table_id
for table_id, column_name in schema['column_names_original']
],
'column_name': [
column_name
for table_id, column_name in schema['column_names_original']
]
},
'db_column_types':
schema['column_types'],
'db_primary_keys': [{
'column_id': column_id
} for column_id in schema['primary_keys']],
'db_foreign_keys': {
'column_id': [
column_id
for column_id, other_column_id in schema['foreign_keys']
],
'other_column_id': [
other_column_id
for column_id, other_column_id in schema['foreign_keys']
]
},
}

View File

@@ -0,0 +1,266 @@
"""
Copyright (c) 2020, salesforce.com, inc.
All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
Encode DB content.
"""
import difflib
import functools
import sqlite3
from typing import List, Optional, Tuple
from rapidfuzz import fuzz
# fmt: off
_stopwords = {
'who', 'ourselves', 'down', 'only', 'were', 'him', 'at', "weren't", 'has',
'few', "it's", 'm', 'again', 'd', 'haven', 'been', 'other', 'we', 'an',
'own', 'doing', 'ma', 'hers', 'all', "haven't", 'in', 'but', "shouldn't",
'does', 'out', 'aren', 'you', "you'd", 'himself', "isn't", 'most', 'y',
'below', 'is', "wasn't", 'hasn', 'them', 'wouldn', 'against', 'this',
'about', 'there', 'don', "that'll", 'a', 'being', 'with', 'your', 'theirs',
'its', 'any', 'why', 'now', 'during', 'weren', 'if', 'should', 'those',
'be', 'they', 'o', 't', 'of', 'or', 'me', 'i', 'some', 'her', 'do', 'will',
'yours', 'for', 'mightn', 'nor', 'needn', 'the', 'until', "couldn't", 'he',
'which', 'yourself', 'to', "needn't", "you're", 'because', 'their',
'where', 'it', "didn't", 've', 'whom', "should've", 'can', "shan't", 'on',
'had', 'have', 'myself', 'am', "don't", 'under', 'was', "won't", 'these',
'so', 'as', 'after', 'above', 'each', 'ours', 'hadn', 'having', 'wasn',
's', 'doesn', "hadn't", 'than', 'by', 'that', 'both', 'herself', 'his',
"wouldn't", 'into', "doesn't", 'before', 'my', 'won', 'more', 'are',
'through', 'same', 'how', 'what', 'over', 'll', 'yourselves', 'up',
'mustn', "mustn't", "she's", 're', 'such', 'didn', "you'll", 'shan',
'when', "you've", 'themselves', "mightn't", 'she', 'from', 'isn', 'ain',
'between', 'once', 'here', 'shouldn', 'our', 'and', 'not', 'too', 'very',
'further', 'while', 'off', 'couldn', "hasn't", 'itself', 'then', 'did',
'just', "aren't"
}
# fmt: on
_commonwords = {'no', 'yes', 'many'}
def is_number(s: str) -> bool:
try:
float(s.replace(',', ''))
return True
except ValueError:
return False
def is_stopword(s: str) -> bool:
return s.strip() in _stopwords
def is_commonword(s: str) -> bool:
return s.strip() in _commonwords
def is_common_db_term(s: str) -> bool:
return s.strip() in ['id']
class Match(object):
def __init__(self, start: int, size: int) -> None:
self.start = start
self.size = size
def is_span_separator(c: str) -> bool:
return c in "'\"()`,.?! "
def split(s: str) -> List[str]:
return [c.lower() for c in s.strip()]
def prefix_match(s1: str, s2: str) -> bool:
i, j = 0, 0
for i in range(len(s1)):
if not is_span_separator(s1[i]):
break
for j in range(len(s2)):
if not is_span_separator(s2[j]):
break
if i < len(s1) and j < len(s2):
return s1[i] == s2[j]
elif i >= len(s1) and j >= len(s2):
return True
else:
return False
def get_effective_match_source(s: str, start: int, end: int) -> Match:
_start = -1
for i in range(start, start - 2, -1):
if i < 0:
_start = i + 1
break
if is_span_separator(s[i]):
_start = i
break
if _start < 0:
return None
_end = -1
for i in range(end - 1, end + 3):
if i >= len(s):
_end = i - 1
break
if is_span_separator(s[i]):
_end = i
break
if _end < 0:
return None
while _start < len(s) and is_span_separator(s[_start]):
_start += 1
while _end >= 0 and is_span_separator(s[_end]):
_end -= 1
return Match(_start, _end - _start + 1)
def get_matched_entries(
s: str,
field_values: List[str],
m_theta: float = 0.85,
s_theta: float = 0.85
) -> Optional[List[Tuple[str, Tuple[str, str, float, float, int]]]]:
if not field_values:
return None
if isinstance(s, str):
n_grams = split(s)
else:
n_grams = s
matched = dict()
for field_value in field_values:
if not isinstance(field_value, str):
continue
fv_tokens = split(field_value)
sm = difflib.SequenceMatcher(None, n_grams, fv_tokens)
match = sm.find_longest_match(0, len(n_grams), 0, len(fv_tokens))
if match.size > 0:
source_match = get_effective_match_source(n_grams, match.a,
match.a + match.size)
if source_match and source_match.size > 1:
match_str = field_value[match.b:match.b + match.size]
source_match_str = s[source_match.start:source_match.start
+ source_match.size]
c_match_str = match_str.lower().strip()
c_source_match_str = source_match_str.lower().strip()
c_field_value = field_value.lower().strip()
if (c_match_str and not is_number(c_match_str)
and not is_common_db_term(c_match_str)):
if (is_stopword(c_match_str)
or is_stopword(c_source_match_str)
or is_stopword(c_field_value)):
continue
if c_source_match_str.endswith(c_match_str + "'s"):
match_score = 1.0
else:
if prefix_match(c_field_value, c_source_match_str):
match_score = (
fuzz.ratio(c_field_value, c_source_match_str)
/ 100)
else:
match_score = 0
if (is_commonword(c_match_str)
or is_commonword(c_source_match_str)
or is_commonword(c_field_value)
) and match_score < 1: # noqa
continue
s_match_score = match_score
if match_score >= m_theta and s_match_score >= s_theta:
if field_value.isupper(
) and match_score * s_match_score < 1:
continue
matched[match_str] = (
field_value,
source_match_str,
match_score,
s_match_score,
match.size,
)
if not matched:
return None
else:
return sorted(
matched.items(),
key=lambda x: (1e16 * x[1][2] + 1e8 * x[1][3] + x[1][4]),
reverse=True,
)
@functools.lru_cache(maxsize=1000, typed=False)
def get_column_picklist(table_name: str, column_name: str,
db_path: str) -> list:
fetch_sql = 'SELECT DISTINCT `{}` FROM `{}`'.format(
column_name, table_name)
try:
conn = sqlite3.connect(db_path)
conn.text_factory = bytes
c = conn.cursor()
c.execute(fetch_sql)
picklist = set()
for x in c.fetchall():
if isinstance(x[0], str):
picklist.add(x[0].encode('utf-8'))
elif isinstance(x[0], bytes):
try:
picklist.add(x[0].decode('utf-8'))
except UnicodeDecodeError:
picklist.add(x[0].decode('latin-1'))
else:
picklist.add(x[0])
picklist = list(picklist)
finally:
conn.close()
return picklist
def get_database_matches(
question: str,
table_name: str,
column_name: str,
db_path: str,
top_k_matches: int = 2,
match_threshold: float = 0.85,
) -> List[str]:
picklist = get_column_picklist(
table_name=table_name, column_name=column_name, db_path=db_path)
matches = []
if picklist and isinstance(picklist[0], str):
matched_entries = get_matched_entries(
s=question,
field_values=picklist,
m_theta=match_threshold,
s_theta=match_threshold,
)
if matched_entries:
num_values_inserted = 0
for _match_str, (
field_value,
_s_match_str,
match_score,
s_match_score,
_match_size,
) in matched_entries:
if 'name' in column_name and match_score * s_match_score < 1:
continue
if table_name != 'sqlite_sequence': # Spider database artifact
matches.append(field_value)
num_values_inserted += 1
if num_values_inserted >= top_k_matches:
break
return matches

View File

@@ -25,7 +25,7 @@ def collate_fn(samples, pad_idx, eos_idx):
if samples[0].get('source', None) is not None:
batch['net_input']['input_ids'] = merge('source')
if samples[0].get('id', None) is not None:
batch['id'] = np.array([s.get['id'] for s in samples])
batch['id'] = np.array([s.get('id') for s in samples])
if samples[0].get('target', None) is not None:
batch['target'] = merge('target')
tgt_lengths = torch.LongTensor(
@@ -91,6 +91,20 @@ def collate_fn(samples, pad_idx, eos_idx):
batch['phone_length'] = torch.tensor(
[s['phone_target'].size(0) for s in samples], dtype=torch.long)
# for sudoku
if samples[0].get('db_struct', None) is not None:
db_struct = [sample['db_struct'] for sample in samples]
batch['db_struct'] = db_struct
if samples[0].get('mask_ratio', None) is not None:
mask_ratio = [sample['mask_ratio'] for sample in samples]
batch['mask_ratio'] = mask_ratio
if samples[0].get('seg_col_tokens', None) is not None:
seg_col_tokens = merge('seg_col_tokens')
batch['net_input']['seg_col_tokens'] = seg_col_tokens
if samples[0].get('seg_row_tokens', None) is not None:
seg_row_tokens = merge('seg_row_tokens')
batch['net_input']['seg_row_tokens'] = seg_row_tokens
return batch

View File

@@ -1,3 +1,5 @@
# Copyright (c) Alibaba, Inc. and its affiliates.
from modelscope.utils.constant import Tasks
OFA_TASK_KEY_MAPPING = {
@@ -11,4 +13,6 @@ OFA_TASK_KEY_MAPPING = {
Tasks.visual_entailment: ['image', 'text', 'text2'],
Tasks.text_to_image_synthesis: ['text'],
Tasks.auto_speech_recognition: ['wav', 'text'],
Tasks.sudoku: ['text'],
Tasks.text2sql: ['text', 'database'],
}

View File

@@ -0,0 +1,88 @@
# Copyright (c) Alibaba, Inc. and its affiliates.
import sqlite3
import sys
import traceback
EXIST = {'atis', 'geo', 'advising', 'yelp', 'restaurants', 'imdb', 'academic'}
def convert_fk_index(data):
fk_holder = []
for fk in data['foreign_keys']:
tn, col, ref_tn, ref_col = fk[0][0], fk[0][1], fk[1][0], fk[1][1]
ref_cid, cid = None, None
try:
tid = data['table_names_original'].index(tn)
ref_tid = data['table_names_original'].index(ref_tn)
for i, (tab_id,
col_org) in enumerate(data['column_names_original']):
if tab_id == ref_tid and ref_col == col_org:
ref_cid = i
elif tid == tab_id and col == col_org:
cid = i
if ref_cid and cid:
fk_holder.append([cid, ref_cid])
except ValueError:
traceback.print_exc()
print('table_names_original: ', data['table_names_original'])
print('finding tab name: ', tn, ref_tn)
sys.exit()
return fk_holder
def dump_db_json_schema(db, f):
"""read table and column info"""
conn = sqlite3.connect(db)
conn.execute('pragma foreign_keys=ON')
cursor = conn.execute("SELECT name FROM sqlite_master WHERE type='table';")
data = {
'db_id': f,
'table_names_original': [],
'table_names': [],
'column_names_original': [(-1, '*')],
'column_names': [(-1, '*')],
'column_types': ['text'],
'primary_keys': [],
'foreign_keys': [],
}
fk_holder = []
for i, item in enumerate(cursor.fetchall()):
table_name = item[0]
data['table_names_original'].append(table_name)
data['table_names'].append(table_name.lower().replace('_', ' '))
fks = conn.execute(
"PRAGMA foreign_key_list('{}') ".format(table_name)).fetchall()
# print("db:{} table:{} fks:{}".format(f,table_name,fks))
fk_holder.extend([[(table_name, fk[3]), (fk[2], fk[4])] for fk in fks])
cur = conn.execute("PRAGMA table_info('{}') ".format(table_name))
for j, col in enumerate(cur.fetchall()):
data['column_names_original'].append((i, col[1]))
data['column_names'].append((i, col[1].lower().replace('_', ' ')))
# varchar, '' -> text, int, numeric -> integer,
col_type = col[2].lower()
if ('char' in col_type or col_type == '' or 'text' in col_type
or 'var' in col_type):
data['column_types'].append('text')
elif ('int' in col_type or 'numeric' in col_type
or 'decimal' in col_type or 'number' in col_type
or 'id' in col_type or 'real' in col_type
or 'double' in col_type or 'float' in col_type):
data['column_types'].append('number')
elif 'date' in col_type or 'time' in col_type or 'year' in col_type:
data['column_types'].append('time')
elif 'boolean' in col_type:
data['column_types'].append('boolean')
else:
data['column_types'].append('others')
if col[5] == 1:
data['primary_keys'].append(len(data['column_names']) - 1)
data['foreign_keys'] = fk_holder
data['foreign_keys'] = convert_fk_index(data)
return data

View File

@@ -152,6 +152,8 @@ class NLPTasks(object):
extractive_summarization = 'extractive-summarization'
feature_extraction = 'feature-extraction'
translation_evaluation = 'translation-evaluation'
sudoku = 'sudoku'
text2sql = 'text2sql'
class AudioTasks(object):

View File

@@ -1,9 +1,11 @@
ftfy>=6.0.3
librosa
opencv-python
pycocoevalcap>=1.2
pycocotools>=2.0.4
# compatible with taming-transformers-rom1504
pytorch_lightning<=1.7.7
rapidfuzz
# rough-score was just recently updated from 0.0.4 to 0.0.7
# which introduced compatability issues that are being investigated
rouge_score<=0.0.4

View File

@@ -329,6 +329,43 @@ class OfaTasksTest(unittest.TestCase, DemoCompatibilityCheck):
for r in result:
print(r[OutputKeys.TEXT])
@unittest.skipUnless(test_level() >= 0, 'skip test in current test level')
def test_run_with_sudoku_with_name(self):
model = 'damo/ofa_sudoku_kaggle_large'
ofa_pipe = pipeline(Tasks.sudoku, model=model)
# the valid num is 1-9and use 0 represents the empty block
# the separator of column is ` : `, and the separator of row is ` | `
example = '5 : 3 : 0 : 0 : 7 : 0 : 0 : 0 : 0 | \
6 : 0 : 0 : 1 : 9 : 5 : 0 : 0 : 0 | \
0 : 9 : 8 : 0 : 0 : 0 : 0 : 6 : 0 | \
8 : 0 : 0 : 0 : 6 : 0 : 0 : 0 : 3 | \
4 : 0 : 0 : 8 : 0 : 3 : 0 : 0 : 1 | \
7 : 0 : 0 : 0 : 2 : 0 : 0 : 0 : 6 | \
0 : 6 : 0 : 0 : 0 : 0 : 2 : 8 : 0 | \
0 : 0 : 0 : 4 : 1 : 9 : 0 : 0 : 5 | \
0 : 0 : 0 : 0 : 8 : 0 : 0 : 7 : 9'
result = ofa_pipe(example)
print(result[OutputKeys.TEXT])
# test batch infer
result = ofa_pipe([example for _ in range(3)], batch_size=2)
for r in result:
print(r[OutputKeys.TEXT])
@unittest.skipUnless(test_level() >= 0, 'skip test in current test level')
def test_run_with_text2sql_with_name(self):
model = 'damo/ofa_text2sql_spider_large_en'
ofa_pipe = pipeline(Tasks.text2sql, model=model)
text = 'Show all book categories and the number of books in each category.'
database = 'culture_company' # optional, default `culture_company`
example = {'text': text, 'database': database}
result = ofa_pipe(example)
print(result[OutputKeys.TEXT])
# test batch infer
result = ofa_pipe([example for _ in range(3)], batch_size=2)
for r in result:
print(r[OutputKeys.TEXT])
@unittest.skip('demo compatibility test is only enabled on a needed-basis')
def test_demo_compatibility(self):
self.compatibility_check()