프로그래밍공부/파이썬

코퍼스 검색툴

중랑구보안관 2020. 6. 30. 11:19

NLU업무중를 하다보면 훈련된 데이터가 실제 코퍼스파일에 어디있는지 찾거나 혹은 문장이 어떤식으로 훈련되어 있는지 액션과 익스퍼트를 직접 찾아야 하는일이 많은데, 사실 txt파일을 일일이 찾는게 좀 번거로워서 tkinter를 이용해서 간단한 검색툴을 만들었었다.

 

사실 만든지는 몇 달 되었고 이거 말고도 업무용으로 만든게 몇개 있는데 계약기간이 끝나기전에 기록으로 남겨둘려고 올려 놓는다 ^^ 이 자료가 누군가에게 도움이 됬으면 좋겠다.

 

참고로 실행이 정상적으로 되려면 config.txt파일이 아래와 같은 형식으로 같은 폴더에 있어야한다.

 

TRAINING_PATH:C:/nlpworkspace/training/training
SERVICE_PATH:C:/nlpworkspace/service/service
EXPERT_PATH:models/ENG/expert/ha
DB_FILE_PATH:DB/EN/ARCH
PATTERNTEMP_PATH:ha_application/EN/domain/svm

 

그리고 이거는 나름대로 짧게 설계?한 것이다.

문장 입력 하면 코퍼스에서 액션 ne 찾아주기.+ 줄수. 파일이름.

마지막 : DB.txt 혹은 패턴템프에서 찾아주기.

1.config 파일에서 training경로를 찾아서 코퍼스를 읽어온다.
               key(문장):data_list(클래스로 만들자)
      1.코퍼스를 한줄 씩 읽는다.
	  2.#처리된것과 줄띄어쓰기를 넘긴다.
	  3.한줄을 \t으로 잘라서 액션을 가지고 와서 class에 넣어준다.
	  4.문장에서 반복문으로 find(사용 해당 문자가 없으면 -1 출력)
	    을 사용하여 <>안에 있는 NE와 Lexical을 가져와서 class에 넣어준다.
	  5.NE태깅을 뺸 문장 만 추출하여 class에 넣어준다.
	  6.위 데이터를 dictionary자료형에 넣어준다.
	  
data_list : 액션명,익스퍼트명,줄수,코퍼스이름,원래 코퍼스 형태
			   
2. Corpus = { 'sentenes':data_list, 반복.... }

3. 문장을 입력하면 dictionary에서 key값으로 value를 찾아와서 화면에 출력.
from tkinter import *
from tkinter.ttk import *
import sys
import os
import re

#코퍼스를 읽은 데이터를 저장하기 위한 클래스
#참조문제로 미사용.
#class Corpus():
#    
#    #액션명
#    actionName=''
#    #익스퍼트명
#    expertName=''
#    #줄수
#    line=-1
#    #NE이름(리스트)
#    neList=[]
#    #NE개수
#    neCount=0
#    #원래 문장 형태
#    originSentence=''
#    #ne를 뺀 문장 형태
#    sentenceNotNeTagging=''
#    #생성자
#    def __init__(self,line):
#        self.line = line
#        pass
#        
#    #getter와 setter
#    def getActionName(self):
#        return self.actionName
#    def setActionName(self, actionName):
#        self.actionName=actionName
#    def getExpertName(self):
#        return self.expertName
#    def setExpertName(self, expertName):
#        self.expertName = expertName
#    def getLine(self):
#        return self.line
#    def setLine(self,line):
#        self.line=line
#    def getNeList(self):
#        return self.neList
#    def setNeList(self,ne,lexical):
#        neData={ne:lexical}
#        self.neList.append(neData)
#        self.neCount+=1
#    def getNeCount(self):
#        return self.neCount
#    def getOriginSentence(self):
#        return self.originSentence
#    def setOriginSentence(self,originSentence):
#        self.originSentence=originSentence
#    def getSentenceNotNeTagging(self):
#        return self.sentenceNotNeTagging
#    def setSentenceNotNeTagging(self,sentenceNotNeTagging):
#        self.sentenceNotNeTagging=sentenceNotNeTagging

#프레임 클래스
class MainFrame(Frame):
    #경로들을 저장할 변수
    training_path=''
    service_path=''
    db_file_path=''
    expert_path=''
    patternTemp_path=''
    #코퍼스를 읽은 데이터를 저장할 딕셔너리. Corpus = { 'sentenes':data_list, 반복.... }
    global corpus_datas
    corpus_datas={}
    #익스퍼트 정보를 저장할 딕셔너리
    global expert_datas
    expert_datas={}
    #패턴템프를 읽은 정보를 저장할 딕셔너리
    global pattern_datas
    pattern_datas={}
    def __init__(self, master):
        
        #설정파일 읽는다.
        self.readConfig()
        #expert.list를 읽어온다.
        self.readExpertList()
        #코퍼스파일을 읽는다
        self.readCorpus()
        #patternTemp를 읽는다.
        self.readPatternTemp()
        #print(corpus_datas)

        #GUI설계부분.
        Frame.__init__(self, master)
        self.master = master
        self.master.title('searchTool v.0.11')
        self.master.geometry('1200x600+100+100')
        self.pack(fill=BOTH, expand=True)
        #액션입력창
        topFrame = Frame(self)
        topFrame.pack(fill=X)
        
        lblAction = Label(topFrame, text='액션입력 ', width=10)
        lblAction.pack(side=LEFT, padx=10,pady=10)
        
        entryAction = Entry(topFrame)
        entryAction.pack(fill=X, padx=10,expand=True)
        
        #검색하기
        def searchStart():
            readSentence=str(entryAction.get())
            readSentence=re.sub('(\?|\,|\.|\-|\_)','',readSentence.lower())
            data=corpus_datas.get(readSentence)
 
            #0 액션명 1 줄수 2 원래 문장형태 3 파일 이름 4 익스퍼트명 5 이후..NE
            #기존창에 입력되어있던것 삭제
            actionNameTxt.delete(1.0,END)
            neNameTxt.delete(1.0,END)
            sentenceTxt.delete(1.0,END)
            fileNameTxt.delete(1.0,END)
            patternTmpTxt.delete(1.0,END)
            #입력된액션에 대한 정보가 없는경우
            if data == None:
                actionNameTxt.delete(1.0,END)
                actionNameTxt.insert(1.0,'없는 문장.')
                return
            #2개이상의 데이터가 있는 경우
            if len(data) >= 2:
                row=0.0
                for d in data:
                    row+=1.0
                    actionNameTxt.insert(row,d[0]+'\t'+str(d[4])+'\n')
                    totalLen = len(d)
                    neData = ''
                    for i in range(5,totalLen):
                        neData += str(d[i])+','
                    neNameTxt.insert(row,neData.rstrip(',')+'\n')
                    sentenceTxt.insert(row,d[2]+'\n')
                    fileNameTxt.insert(row,d[3]+' / '+str(d[1])+'\n')
                    patternTmpTxt.insert(row,str(pattern_datas[d[2]])+'\n')                   
            else:
                actionNameTxt.insert(1.0,data[0]+'\t'+str(data[4]))
                totalLen = len(data)
                neData = ''
                for i in range(5,totalLen):
                    neData += str(data[i])+','
                neNameTxt.insert(1.0,neData.rstrip(','))
                sentenceTxt.insert(1.0,data[2])
                fileNameTxt.insert(1.0,data[3]+' / '+str(data[1]))
                patternTmpTxt.insert(1.0,str(pattern_datas[data[2]])+'\n')     
            
        #검색버튼
        btnFrame = Frame(self)
        btnFrame.pack(fill=X)
        btnSearch = Button(btnFrame, text="검색", command = searchStart)
        btnSearch.pack(side=LEFT, padx=10, pady=10)
        
        #결과창
        middleFrame = Frame(self)
        middleFrame.pack(fill=X)
        
        lblActionName = Label(middleFrame, text='액션 / 익스퍼트', width=15)
        lblActionName.pack(side=LEFT, padx=10,pady=10)
        
        actionNameTxt = Text(middleFrame, height=6)
        actionNameTxt.pack(fill=X, padx=10)
        
        middleFrame2 = Frame(self)
        middleFrame2.pack(fill=X)
        
        lblNeName = Label(middleFrame2, text='NE ', width=15)
        lblNeName.pack(side=LEFT, padx=10,pady=10)
        
        neNameTxt = Text(middleFrame2, height=6)
        neNameTxt.pack(fill=X, padx=10,expand=True)
        
        middleFrame3 = Frame(self)
        middleFrame3.pack(fill=X)
        
        lblSentence = Label(middleFrame3, text='원래 문장 ', width=15)
        lblSentence.pack(side=LEFT, padx=10,pady=10)
        
        sentenceTxt = Text(middleFrame3, height=6)
        sentenceTxt.pack(fill=X, padx=10,expand=True)
        
        middleFrame4 = Frame(self)
        middleFrame4.pack(fill=X)
        
        lblFileName = Label(middleFrame4, text='파일이름 / 줄 수 ', width=15)
        lblFileName.pack(side=LEFT, padx=10,pady=10)
        
        fileNameTxt = Text(middleFrame4, height=6)
        fileNameTxt.pack(fill=X, padx=10,expand=True)
        
        middleFrame5 = Frame(self)
        middleFrame5.pack(fill=X)
        
        lblFileName = Label(middleFrame5, text='패턴템프 ', width=15)
        lblFileName.pack(side=LEFT, padx=10,pady=10)
        
        patternTmpTxt = Text(middleFrame5, height=12)
        patternTmpTxt.pack(fill=X, padx=10,expand=True)
      
    #설정파일 읽기.
    def readConfig(self):
        config_file_name = 'config.txt'
        config = open(config_file_name,'r',encoding='UTF8')
        lines = config.readlines()
        
        for line in lines:
            config_data=line.split(':')
            if config_data[0] == 'TRAINING_PATH' :
                global training_path 
                training_path = config_data[1].rstrip('\n')+':'+config_data[2].rstrip('\n')
            elif config_data[0] == 'DB_FILE_PATH':
                global db_file_path
                db_file_path = config_data[1].rstrip('\n')
            elif config_data[0] == 'EXPERT_PATH':
                global expert_path
                expert_path = config_data[1].rstrip('\n')
            elif config_data[0] == 'SERVICE_PATH':
                global service_path
                service_path = config_data[1].rstrip('\n')+':'+config_data[2].rstrip('\n')
            elif config_data[0] == 'PATTERNTEMP_PATH':
                global patternTemp_path
                patternTemp_path = config_data[1].rstrip('\n')
        config.close()
        
    #코퍼스파일 읽기
    def readCorpus(self):
        #DB디렉토리내의 코퍼스파일 이름들을 읽어옴.
        total_path=training_path+'/'+db_file_path
        corpus_list= os.listdir(total_path)
        #print('corpus_list : '+str(corpus_list))
        for file_name in corpus_list:
            #코퍼스파일이 아닌경우 패스.
            if file_name.find('.txt') == -1:
                continue
            #코퍼스파일들을 하나씩 읽어줌.    
            lines=''            
            try:
                corpus_file = open(total_path+'/'+file_name,'r', encoding='UTF8')
                lines = corpus_file.readlines()
            except UnicodeDecodeError as e:
                print(e)
                print(corpus_file)
            finally:    
                lineNumber=0
                for line in lines:
                    lineNumber+=1
                    #주석이나 줄띄어쓰기 한것은 넘기기
                    if line.find('#') == 0 or line.find('\t') == -1:
                        continue                    
                    #corpus = Corpus(lineNumber) 
                    innerData=[]
                    dataInCorpus=line.split('\t')
                    #읽은 줄에서 액션명, 문장, ne리스트를 읽어와야 한다.
                    #ne리스트를 읽어와 보자.
                    tempSentence=dataInCorpus[5]
                    while tempSentence.find('<')!=-1:
                        firstIndex=tempSentence.find('<')
                        lastIndex=tempSentence.find('>')
                        firstIndex+=1
                        try:
                            #neDatas[0] = Lexical, neDatas[1] = Ne
                            neData=tempSentence[firstIndex:lastIndex]
                            neDatas=neData.split(':')
                            #ne를 corpus클래스에 넣어준다.
                            #corpus.setNeList(neDatas[1],neDatas[0])
                            innerData.append([neDatas[1],neDatas[0]])
                            firstIndex-=1
                            middleIndex = tempSentence.find(':')
                            #검색한 ne를 빼준다.
                            tempSentence=tempSentence[:firstIndex]+tempSentence[firstIndex+1:middleIndex]+tempSentence[lastIndex+1:]
                        except IndexError as e:
                            print(e)
                            print(tempSentence)
                            print(neData)
                        
                    #액션명
                    innerData.insert(0,dataInCorpus[4].strip())
                    #줄수
                    innerData.insert(1,lineNumber)
                    #원래 문장 형태
                    innerData.insert(2,dataInCorpus[5])
                    #파일 이름
                    innerData.insert(3,str(file_name))
                    #익스퍼트 이름
                    expertName=expert_datas.get(dataInCorpus[4])
                    innerData.insert(4,expertName)
                    
                    key = tempSentence.lower()
                    key = re.sub('(\?|\,|\.|\-|\_)','',key)
                    #중복문장에 대한 해결책
                    if key in corpus_datas:
                        ret=corpus_datas[key]
                        newData=[]
                        if str(type(ret[0])) != "<class 'str'>":
                            for data in ret:
                                newData.append(data)
                        else:
                            newData.append(ret)
                        newData.append(innerData)
                        corpus_datas[key] = newData
                    else:
                        corpus_datas[key] = innerData
                corpus_file.close()      
    
    #익스퍼트리스트 읽기
    def readExpertList(self):
        #자료구조 딕셔너리 {액션명 : 익스퍼트명}
        total_path=service_path+'/'+expert_path
        expert_file = open(total_path+'/expert.list','r',encoding='UTF8')
        lines = expert_file.readlines()
        #expert.list에서 액션-익스퍼트 읽기
        for line in lines:
            #exact,공백,주석은 제외
            if line.find('#') == 0 or line.find('\t') == -1 or line.find('exact') > 0 :
                continue
            experts=line.split('\t')
            expert_datas[experts[0]]=experts[1]
        expert_file.close()
            
     #패턴템프읽기
    def readPatternTemp(self):
        pass
        #자료구조 딕셔너리 {문장 : [[레벨,훈련된형태,줄수],[레벨,훈련된형태,줄수],[레벨,훈련된형태,줄수]],....}
        total_path=training_path+'/'+patternTemp_path
        patternTmp_file=open(total_path+'/total.patternTmp','r',encoding='UTF8')
        lines = patternTmp_file.readlines()
        lineNumber=0
        for line in lines:
            lineNumber+=1
            innerData=[]
            #공백,주석은 제외
            if line.find('#') == 0 or line.find('\t') == -1:
                continue
            patternDatas=line.split('\t')
            #삽입할 데이터
            innerData.append(patternDatas[0])
            innerData.append(patternDatas[3])
            innerData.append(lineNumber)
            #키값 = ne태깅 뺴기전의 문장
            key = patternDatas[5]
            #중복되는 문장일 경우
            if key in pattern_datas:
                ret=pattern_datas[key]
                newData=[]
                if str(type(ret[0])) != "<class 'str'>":
                    for data in ret:
                        newData.append(data)
                else:
                    newData.append(ret)
                newData.append(innerData)
                pattern_datas[key] = newData                
            else:
                pattern_datas[key] = innerData
                           
            
def main(self):
    root = Tk()
    mainFrame = MainFrame(root)
    root.mainloop()

if __name__ == '__main__':
    main(sys.argv)