SpursGoZmy / IM-TQA

Dataset and Code for ACL 2023 paper: "IM-TQA: A Chinese Table Question Answering Dataset with Implicit and Multi-type Table Structures". We proposed a new TQA problem which aims at real application scenarios, together with a supporting dataset and a baseline method.
Other
15 stars 3 forks source link

请问在生成手动的节点特征时,咱们用的是中文还是英文来生成的呀 #2

Open Czy-family opened 8 months ago

SpursGoZmy commented 7 months ago

是基于中文的单元格内容生成一些手工特征,参考了A Machine Learning Approach for Layout Inference in Spreadsheets这篇论文采用的一些手工特征。

SpursGoZmy commented 7 months ago

因为相关代码之前有产品里用过,我后面尽快完整地整理出来。

基于中文单元格内容构建手工特征的类如下,

import numpy as np
import paddle
import paddle.nn as nn
import pgl
from paddlenlp import Taskflow
from collections import defaultdict

class Manual_CTC_Feats(object):
    def __init__(self) -> None:
        super(Manual_CTC_Feats,self).__init__()
        """
        This class is used to create manual cell feats from the paper "A Machine Learning Approach for Layout Inference in Spreadsheets"
        We adopt 24 feats from the paper's selected feats(section 5.1), 
        note that we do not use some feats like cell style and font because we want to process general tables rather than particular spreadsheet tables,
        and we adapt some feats for chinese language like punctuations
        """
        self.module_name='Manual-CTC-Feats-Module'
        # word_segmentation and pos_tagging tools from paddlenlp Taskflow
        self.seg_model = Taskflow("word_segmentation",batch_size=4)
        self.pos_model=Taskflow("pos_tagging",batch_size=4)
        self.punctuation_list=[',','。','-','——','?','?','!','!','.','“','”',';']
        self.special_token_list=['#','$','¥','%','&','*','~','/','<','>']
        #we select some chinese words expressing the 'total' meaning in the OpenHowNet toolkit.
        self.words_like_total=['共计','合计','总数','总量','全部','总共有','总额度','总额','总值',
                                '总和','共有','总共','累计','累计为','总合','合达','total','sum','all']
        self.content_feature_num=15
        self.spatial_feature_num=9

    def build_content_features(self,cell_values):
        cell_num=len(cell_values)
        content_features=np.zeros((cell_num,self.content_feature_num))  # dtype = 'float64'

        processed_cell_values=["Empty_cell" if cell_text=="" or str.isspace(cell_text) else cell_text for cell_text in cell_values]  ##处理空cell_values
        assert len(processed_cell_values)==len(cell_values)
        # segmentation results of cell_values (a nested list)
        segmented_cell_values=self.seg_model(processed_cell_values)    
        # pos tagging results of cell_values (a nested list)
        cell_tag_results=self.pos_model(processed_cell_values)   
        if len(segmented_cell_values)!=len(cell_values):
            print("len(segmented_cell_values): ",len(segmented_cell_values))
            print("len(cell_values): ",len(cell_values))
            print(cell_values)
            print(segmented_cell_values)
        #considered content feat: 
        for cell_id,cell_text in enumerate(cell_values):
            # LENGTH# 
            text_len=len(cell_text)
            if text_len==0:
                continue
            segmented_cell_text=segmented_cell_values[cell_id]
            pos_tag_results=cell_tag_results[cell_id]
            # NUM OF TOKENS#
            if cell_text=="":
                tokens_num=0
            else:
                tokens_num=len(segmented_cell_text)
            # LEADING SPACES#
            leading_spaces_num=0
            for char in cell_text:
                if char==' ':
                    leading_spaces_num+=1
                else:
                    break
            # IS NUMERIC?
            if str.isnumeric(cell_text):
                is_numeric=1
            else:
                is_numeric=0
            #STARTS WITH NUMBER?
            if str.isnumeric(cell_text[0]):
                starts_with_number=1
            else:
                starts_with_number=0
            # STARTS WITH SPECIAL?
            if cell_text[0] in self.special_token_list:
                starts_with_special=1
            else:
                starts_with_special=0
            #IS CAPITALIZED?
            if str.istitle(cell_text):
                is_capitalized=1
            else:
                is_capitalized=0
            #IS UPPER CASE?
            if str.isupper(cell_text):
                is_upper=1
            else:
                is_upper=0
            #IS ALPHABETIC?
            if str.isalpha(cell_text):
                is_alpha=1
            else:
                is_alpha=0
            #CONTAINS SPECIAL CHARS?
            contain_special_char=0
            for char in cell_text:
                if char in self.special_token_list:
                    contain_special_char=1
                    break
            #CONTAINS PUNCTUATIONS?
            contain_punctuation=0
            for char in cell_text:
                if char in self.punctuation_list:
                    contain_punctuation=1
                    break
            #CONTAINS COLON?
            if cell_text.find(':')>=0:
                contain_colon=1
            elif cell_text.find(':')>=0:
                contain_colon=1
            else:
                contain_colon=0
            #WORDS LIKE TOTAL?
            words_like_total=0
            for word in segmented_cell_text:
                if word in self.words_like_total:
                    words_like_total=1
                    break
            #WORDS LIKE TABLE?
            words_like_table=0
            for word in segmented_cell_text:
                if word in ['表','表格']:
                    words_like_table=1
                    break
            #IN YEAR RANGE?
            in_year_range=0
            for tag_result in pos_tag_results:
                if tag_result[1] in ['t','TIME']:
                    in_year_range=1
                    break
            #build feat vectors
            feat_vector=[]
            feat_vector.append(text_len)
            feat_vector.append(tokens_num)
            feat_vector.append(leading_spaces_num)
            feat_vector.append(is_numeric)
            feat_vector.append(starts_with_number)
            feat_vector.append(starts_with_special)
            feat_vector.append(is_capitalized)
            feat_vector.append(is_upper)
            feat_vector.append(is_alpha)
            feat_vector.append(contain_special_char)
            feat_vector.append(contain_punctuation)
            feat_vector.append(contain_colon)
            feat_vector.append(words_like_total)
            feat_vector.append(words_like_table)
            feat_vector.append(in_year_range)
            feat_vector=np.array(feat_vector)
            content_features[cell_id]=feat_vector
        return content_features

    def build_spatial_feats(self,layout,cell_values):
        cell_num=len(cell_values)
        spatial_features=np.zeros((cell_num,self.spatial_feature_num))  # dtype = 'float64'
        # considered spatial feat: row_id, col_id,has_1_2_3_4_neighbour,more than 5 neighbor,  is_merged_cell,num_of_cells,
        cell_id_to_row_and_col_id={}
        cell_id_to_neighbor_set=defaultdict(set)
        cell_id_to_cell_num=defaultdict(int)
        row_num=len(layout)
        col_num=len(layout[0])

        for row_id in range(row_num):
            for col_id in range(col_num):
                cell_id=layout[row_id][col_id]
                if cell_id not in cell_id_to_row_and_col_id:
                    cell_id_to_row_and_col_id[cell_id]=(row_id,col_id)

                cell_id_to_cell_num[cell_id]+=1
                #top neighbour
                if row_id-1>=0:
                    top_neighbour=layout[row_id-1][col_id]
                    cell_id_to_neighbor_set[cell_id].add(top_neighbour)
                #left neighbour
                if col_id-1>=0:
                    left_neighbour=layout[row_id][col_id-1]
                    cell_id_to_neighbor_set[cell_id].add(left_neighbour)
                #right neighbour
                if col_id+1<col_num:
                    right_neighbour=layout[row_id][col_id+1]
                    cell_id_to_neighbor_set[cell_id].add(right_neighbour)
                # down neighbour
                if row_id+1<row_num:
                    down_neighbour=layout[row_id+1][col_id]
                    cell_id_to_neighbor_set[cell_id].add(down_neighbour)

        for cell_id in range(cell_num):
            row_id=cell_id_to_row_and_col_id[cell_id][0]
            col_id=cell_id_to_row_and_col_id[cell_id][1]
            neighbour_num=len(cell_id_to_neighbor_set[cell_id])
            neighbour_num_feats=[0,0,0,0,0]
            if neighbour_num<=4 and neighbour_num>0:
                neighbour_num_feats[neighbour_num-1]=1
            else:
                neighbour_num_feats[4]=1
            num_cells=cell_id_to_cell_num[cell_id]
            if num_cells>1:
                is_merged_cell=1
            else:
                is_merged_cell=0
            feat_vector=[]
            feat_vector.append(row_id)
            feat_vector.append(col_id)
            feat_vector.extend(neighbour_num_feats)
            feat_vector.append(num_cells)
            feat_vector.append(is_merged_cell)
            feat_vector=np.array(feat_vector)
            spatial_features[cell_id]=feat_vector
        return spatial_features

    def build_features(self,layout,cell_values):
        content_feats=self.build_content_features(cell_values)
        spatial_feats=self.build_spatial_feats(layout,cell_values)
        cell_feats=np.concatenate((content_feats,spatial_feats),axis=1)
        return cell_feats

可以通过类似如下的代码获取单元格的手工特征:

Manual_CTC_Feats_Module=Manual_CTC_Feats()
layout = cell_ID_matrix # read 'cell_ID_matrix' of one table 
cell_values = chinese_cell_value_list # read 'chinese_cell_value_list' of one table
cell_feats=Manual_CTC_Feats_Module.build_features(layout,cell_values)  # np.array, [cell_num,24]
Czy-family commented 6 months ago

十分感谢,那咱们的这个数据集有原始的表格数据吗,比如表格原始的图片之类的

SpursGoZmy commented 6 months ago

十分感谢,那咱们的这个数据集有原始的表格数据吗,比如表格原始的图片之类的

我写脚本把所有表格写入excel文件里,已经上传到Github中了。链接:https://github.com/SpursGoZmy/IM-TQA/blob/main/data/Excel%20tables.zip

Czy-family commented 6 months ago

感谢🙏

---- 回复的原邮件 ---- | 发件人 | Mingyu @.> | | 日期 | 2024年04月12日 20:49 | | 收件人 | @.> | | 抄送至 | @.>@.> | | 主题 | Re: [SpursGoZmy/IM-TQA] 请问在生成手动的节点特征时,咱们用的是中文还是英文来生成的呀 (Issue #2) |

十分感谢,那咱们的这个数据集有原始的表格数据吗,比如表格原始的图片之类的

我写脚本把所有表格写入excel文件里里,已经上传到Github中了。链接:https://github.com/SpursGoZmy/IM-TQA/blob/main/data/Excel%20tables.zip

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you authored the thread.Message ID: @.***>

Czy-family commented 6 months ago

请问咱们这个边特征是怎么生成的呀,我随便生成的,导致预测结果不是很理想

SpursGoZmy commented 6 months ago

请问咱们这个边特征是怎么生成的呀,我随便生成的,导致预测结果不是很理想

我用的是RGCN,不需要边特征,在聚合节点特征之前,不同的边会对应不同的线性映射,用于转换原始节点特征,具体信息可以查看RGCN的介绍。另外24维离散的手工节点特征会通过训练一个自编码器(AutoEncoder)转化为32维的连续特征,与BERT获取的768维单元格文本语义特征(对应[CLS] token)拼接在一起作为结点的最终特征(800维)。

AutoEncoder定义如下,训练之后的AutoEncoder中的Encoder就可以把离散的节点特征转化为连续的特征向量。等这阵忙完我把代码整理出来。

class AutoEncoder(nn.Layer):
    def __init__(self, enc_dim,manual_feat_dim):
        super(AutoEncoder,self).__init__()
        self.model_name='Auto-Encoder-for-CTC-Model'
        self.enc_dim=enc_dim
        self.manual_feat_dim=manual_feat_dim
        self.encoder=nn.Sequential(
            nn.Linear(manual_feat_dim,enc_dim),
            nn.ReLU(),
            nn.Dropout(0.1),
            nn.Linear(enc_dim,enc_dim)
        )
        self.decoder=nn.Sequential(
            nn.Linear(enc_dim,manual_feat_dim),
            nn.ReLU(),
            nn.Dropout(0.1),
            nn.Linear(manual_feat_dim,manual_feat_dim)
        )
    def forward(self, input_vecs):
        assert input_vecs.shape[1]==self.manual_feat_dim
        enc_vec=self.encoder(input_vecs)
        dec_vec=self.decoder(enc_vec)
        return dec_vec,enc_vec 
Czy-family commented 6 months ago

十分感谢,方便问一下您大概什么时候可以把代码整理出来吗,因为我在以您这个项目为参考做项目,如果代码能在一周内整理出来,那真是再好不过了,再次感谢您的帮助

---- 回复的原邮件 ---- | 发件人 | Mingyu @.> | | 日期 | 2024年04月21日 00:26 | | 收件人 | @.> | | 抄送至 | @.>@.> | | 主题 | Re: [SpursGoZmy/IM-TQA] 请问在生成手动的节点特征时,咱们用的是中文还是英文来生成的呀 (Issue #2) |

请问咱们这个边特征是怎么生成的呀,我随便生成的,导致预测结果不是很理想

我用的是RGCN,不需要边特征,在聚合节点特征之前,不同的边会对应不同的线性映射,用于转换原始节点特征,具体信息可以查看RGCN的介绍。另外24维离散的手工节点特征会通过训练一个自编码器(AutoEncoder)转化为32维的连续特征,与BERT获取的768维单元格文本语义特征(对应[CLS] token)拼接在一起作为结点的最终特征(800维)。

AutoEncoder定义如下,训练之后的AutoEncoder中的Encoder就可以把离散的节点特征转化为连续的特征向量。等这阵忙完我把代码整理出来。

class AutoEncoder(nn.Layer): def init(self, enc_dim,manual_feat_dim): super(AutoEncoder,self).init() self.model_name='Auto-Encoder-for-CTC-Model' self.enc_dim=enc_dim self.manual_feat_dim=manual_feat_dim self.encoder=nn.Sequential( nn.Linear(manual_feat_dim,enc_dim), nn.ReLU(), nn.Dropout(0.1), nn.Linear(enc_dim,enc_dim) ) self.decoder=nn.Sequential( nn.Linear(enc_dim,manual_feat_dim), nn.ReLU(), nn.Dropout(0.1), nn.Linear(manual_feat_dim,manual_feat_dim) ) def forward(self, input_vecs): assert input_vecs.shape[1]==self.manual_feat_dim enc_vec=self.encoder(input_vecs) dec_vec=self.decoder(enc_vec) return dec_vec,enc_vec

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you authored the thread.Message ID: @.***>

SpursGoZmy commented 6 months ago

同学,我先整理了单元格分类(CTC)任务的代码,第三方库主要需要paddlepaddle、PGL、paddlenlp、scikit-learn,具体版本号我过几天到学校服务器上找一下发给你,应该自己配一下也是ok的,比如别用特别新版本的paddlepaddle,因为是22年底写的代码。TQA的代码我是基于RCI模型代码写的,可能要过段时间才能整理出来,同学可以先尝试基于CTC模型的输出结果构建论文里使用的表格行/列表示,然后就可以用于RCI模型的训练和预测。

Czy-family commented 6 months ago

OK,实在是太感谢啦

---- 回复的原邮件 ---- | 发件人 | Mingyu @.> | | 日期 | 2024年04月22日 19:10 | | 收件人 | @.> | | 抄送至 | @.>@.> | | 主题 | Re: [SpursGoZmy/IM-TQA] 请问在生成手动的节点特征时,咱们用的是中文还是英文来生成的呀 (Issue #2) |

同学,我先整理了单元格分类(CTC)任务的代码,第三方库主要需要paddlepaddle、PGL、paddlenlp、scikit-learn,具体版本号我过几天到学校服务器上找一下发给你,应该自己配一下也是ok的,比如别用特别新版本的paddlepaddle,因为是22年底写的代码。TQA的代码我是基于RCI模型代码写的,可能要过段时间才能整理出来,同学可以基于CTC模型的输出结果构建论文里使用的表格行/列表示,用于训练RCI模型。

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you authored the thread.Message ID: @.***>

SpursGoZmy commented 6 months ago

不客气~ 另外建议尝试下基于LLM的方法,比如直接把表格转成Markdown格式送入LLM就可以做问答,效果一般会好很多,我当时用ChatGPT测发现效果很好,大概在90%的准确率(也可能是因为IM-TQA里的look-up类问题还是比较简单),还是比RGCN-RCI这种传统pipeline的方法要好不少。

Czy-family commented 6 months ago

好哒

---- 回复的原邮件 ---- | 发件人 | Mingyu @.> | | 日期 | 2024年04月22日 19:14 | | 收件人 | @.> | | 抄送至 | @.>@.> | | 主题 | Re: [SpursGoZmy/IM-TQA] 请问在生成手动的节点特征时,咱们用的是中文还是英文来生成的呀 (Issue #2) |

不客气~ 另外建议尝试下基于LLM的方法,效果一般会好很多,传统pipeline式的方法有点低效了其实。

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you authored the thread.Message ID: @.***>

Czy-family commented 6 months ago

请问,咱们这个项目中,是不是改了RCI中聚合那部分的代码呀,因为我今天用训练的模型去实现聚合的时候会报错,我发现RCI那块的代码逻辑有点问题,因为RCI的问题分类和IM-TQA中对问题的分类不一样,导致聚合那块的代码在处理咱们这个数据集的数据时逻辑上有点不通

SpursGoZmy commented 6 months ago

请问,咱们这个项目中,是不是改了RCI中聚合那部分的代码呀,因为我今天用训练的模型去实现聚合的时候会报错,我发现RCI那块的代码逻辑有点问题,因为RCI的问题分类和IM-TQA中对问题的分类不一样,导致聚合那块的代码在处理咱们这个数据集的数据时逻辑上有点不通

聚合代码是指? 我们仅改掉了训练RCI-row和RCI-col模型时使用的行和列的文本序列表示,即在原始单元格内容的基础上额外增加了RGCN模型识别出来的表头信息,后续代码和RCI是一致的。问题分类的话,IM-TQA只考虑了Look-up类问题,RCI还额外考虑了需要聚合操作的问题,是指这个聚合吗?是的话,IM-TQA是不需要这部分代码的。

Czy-family commented 6 months ago

看来不需要我再改了,那太好了,嘻嘻,感谢🙏

---- 回复的原邮件 ---- | 发件人 | Mingyu @.> | | 日期 | 2024年04月23日 10:31 | | 收件人 | @.> | | 抄送至 | @.>@.> | | 主题 | Re: [SpursGoZmy/IM-TQA] 请问在生成手动的节点特征时,咱们用的是中文还是英文来生成的呀 (Issue #2) |

请问,咱们这个项目中,是不是改了RCI中聚合那部分的代码呀,因为我今天用训练的模型去实现聚合的时候会报错,我发现RCI那块的代码逻辑有点问题,因为RCI的问题分类和IM-TQA中对问题的分类不一样,导致聚合那块的代码在处理咱们这个数据集的数据时逻辑上有点不通

聚合代码是指? 我们仅改掉了训练RCI-row和RCI-col模型时使用的行和列的文本序列表示,即在原始单元格内容的基础上额外增加了RGCN模型识别出来的表头信息,后续代码和RCI是一致的。问题分类的话,IM-TQA只考虑了Look-up类问题,RCI还额外考虑了需要聚合操作的问题,是指这个聚合吗?是的话,IM-TQA是不需要这部分代码的。

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you authored the thread.Message ID: @.***>

WeixuanXiong commented 5 months ago

不客气~ 另外建议尝试下基于LLM的方法,比如直接把表格转成Markdown格式送入LLM就可以做问答,效果一般会好很多,我当时用ChatGPT测发现效果很好,大概在90%的准确率(也可能是因为IM-TQA里的look-up类问题还是比较简单),还是比RGCN-RCI这种传统pipeline的方法要好不少。

您好,想问下您这边是否可以提供将数据转换为md格式的脚本呀?