EasyNLU는 모바일 앱용 Java로 작성된 자연어 이해(NLU) 라이브러리입니다. 문법 기반이기 때문에 범위가 좁지만 엄격한 제어가 필요한 도메인에 적합합니다.
프로젝트에는 자연어 입력에서 미리 알림을 예약할 수 있는 샘플 Android 애플리케이션이 있습니다.
EasyNLU는 Apache 2.0에 따라 라이센스가 부여됩니다.
EasyNLU의 핵심은 CCG 기반 의미 분석기입니다. 의미론적 분석에 대한 좋은 소개는 여기에서 찾을 수 있습니다. 의미 체계 파서는 다음과 같은 입력을 구문 분석할 수 있습니다.
내일 오후 5시에 치과에 가세요
구조화된 형태로:
{task: "Go to the dentist", startTime:{offset:{day:1}, hour:5, shift:"pm"}}
EasyNLU는 재귀적 Java 맵으로 구조화된 형식을 제공합니다. 이 구조화된 형태는 '실행'되는 작업별 개체로 분석될 수 있습니다. 예를 들어 샘플 프로젝트에서 구조화된 양식은 task
, startTime
및 repeat
과 같은 필드가 있는 Reminder
개체로 해석되며 AlarmManager 서비스로 알람을 설정하는 데 사용됩니다.
일반적으로 다음은 NLU 기능을 설정하는 상위 수준 단계입니다.
규칙을 작성하기 전에 작업 범위와 구조화된 형식의 매개변수를 정의해야 합니다. 장난감 예로 우리의 작업이 Bluetooh, Wi-Fi 및 GPS와 같은 전화 기능을 켜고 끄는 것이라고 가정해 보겠습니다. 따라서 필드는 다음과 같습니다.
구조화된 형식의 예는 다음과 같습니다.
{feature: "bluetooth", action: "enable" }
또한 변형을 이해하는 데 몇 가지 샘플 입력이 있으면 도움이 됩니다.
장난감 예제를 계속하면 최상위 수준에는 기능과 작업이 있어야 하는 설정 작업이 있다고 말할 수 있습니다. 그런 다음 규칙을 사용하여 이 정보를 캡처합니다.
Rule r1 = new Rule ( "$Setting" , "$Feature $Action" );
Rule r2 = new Rule ( "$Setting" , "$Action $Feature" );
규칙에는 최소한 LHS와 RHS가 포함됩니다. 관례적으로 카테고리를 표시하기 위해 단어 앞에 '$'를 붙입니다. 카테고리는 단어 또는 기타 카테고리의 모음을 나타냅니다. 위의 규칙에서 $Feature
'어휘' 규칙을 사용하여 캡처하는 bluetooth, bt, wifi 등과 같은 단어를 나타냅니다.
List < Rule > lexicals = Arrays . asList (
new Rule ( "$Feature" , "bluetooth" ),
new Rule ( "$Feature" , "bt" ),
new Rule ( "$Feature" , "wifi" ),
new Rule ( "$Feature" , "gps" ),
new Rule ( "$Feature" , "location" ),
);
기능 이름의 변형을 정규화하기 위해 $Features
하위 기능으로 구성합니다.
List < Rule > featureRules = Arrays . asList (
new Rule ( "$Feature" , "$Bluetooth" ),
new Rule ( "$Feature" , "$Wifi" ),
new Rule ( "$Feature" , "$Gps" ),
new Rule ( "$Bluetooth" , "bt" ),
new Rule ( "$Bluetooth" , "bluetooth" ),
new Rule ( "$Wifi" , "wifi" ),
new Rule ( "$Gps" , "gps" ),
new Rule ( "$Gps" , "location" )
);
$Action
에 대한 유사점:
List < Rule > actionRules = Arrays . asList (
new Rule ( "$Action" , "$EnableDisable" ),
new Rule ( "$EnableDisable" , "?$Switch $OnOff" ),
new Rule ( "$EnableDisable" , "$Enable" ),
new Rule ( "$EnableDisable" , "$Disable" ),
new Rule ( "$OnOff" , "on" ),
new Rule ( "$OnOff" , "off" ),
new Rule ( "$Switch" , "switch" ),
new Rule ( "$Switch" , "turn" ),
new Rule ( "$Enable" , "enable" ),
new Rule ( "$Disable" , "disable" ),
new Rule ( "$Disable" , "kill" )
);
'?'에 주목하세요. 세 번째 규칙에서; 이는 $Switch
범주가 선택 사항임을 의미합니다. 구문 분석이 성공했는지 확인하기 위해 구문 분석기는 루트 범주라는 특수 범주를 찾습니다. 관례적으로 $ROOT
로 표시됩니다. 이 카테고리에 도달하려면 규칙을 추가해야 합니다.
Rule root = new Rule ( "$ROOT" , "$Setting" );
이러한 규칙 세트를 사용하여 파서는 위의 예를 구문 분석하여 소위 구문 트리로 변환할 수 있어야 합니다.
문장의 의미를 추출할 수 없으면 구문 분석은 소용이 없습니다. 이 의미는 구조화된 형식(NLP 전문 용어의 논리 형식)으로 포착됩니다. EasyNLU에서는 의미가 추출되는 방법을 정의하기 위해 규칙 정의에 세 번째 매개변수를 전달합니다. 이를 위해 특수 마커와 함께 JSON 구문을 사용합니다.
new Rule ( "$Action" , "$EnableDisable" , "{action:@first}" ),
@first
파서에게 RHS 규칙의 첫 번째 범주 값을 선택하라고 지시합니다. 이 경우 문장에 따라 '활성화' 또는 '비활성화'가 됩니다. 기타 마커에는 다음이 포함됩니다.
@identity
: ID 함수@last
: 마지막 RHS 카테고리의 값을 선택합니다.@N
: N 번째 RHS 범주의 값을 선택합니다. 예를 들어 @3
세 번째를 선택합니다.@merge
: 모든 카테고리의 값을 병합합니다. 명명된 값(예 {action: enable}
)만 병합됩니다.@append
: 모든 카테고리의 값을 목록에 추가합니다. 결과 목록에는 이름이 지정되어야 합니다. 명명된 값만 허용됩니다.의미 마커를 추가한 후 규칙은 다음과 같습니다.
List < Rule > rules = Arrays . asList (
new Rule ( "$ROOT" , "$Setting" , "@identity" ),
new Rule ( "$Setting" , "$Feature $Action" , "@merge" ),
new Rule ( "$Setting" , "$Action $Feature" , "@merge" ),
new Rule ( "$Feature" , "$Bluetooth" , "{feature: bluetooth}" ),
new Rule ( "$Feature" , "$Wifi" , "{feature: wifi}" ),
new Rule ( "$Feature" , "$Gps" , "{feature: gps}" ),
new Rule ( "$Bluetooth" , "bt" ),
new Rule ( "$Bluetooth" , "bluetooth" ),
new Rule ( "$Wifi" , "wifi" ),
new Rule ( "$Gps" , "gps" ),
new Rule ( "$Gps" , "location" ),
new Rule ( "$Action" , "$EnableDisable" , "{action: @first}" ),
new Rule ( "$EnableDisable" , "?$Switch $OnOff" , "@last" ),
new Rule ( "$EnableDisable" , "$Enable" , "enable" ),
new Rule ( "$EnableDisable" , "$Disable" , "disable" ),
new Rule ( "$OnOff" , "on" , "enable" ),
new Rule ( "$OnOff" , "off" , "disable" ),
new Rule ( "$Switch" , "switch" ),
new Rule ( "$Switch" , "turn" ),
new Rule ( "$Enable" , "enable" ),
new Rule ( "$Disable" , "disable" ),
new Rule ( "$Disable" , "kill" )
);
의미 매개변수가 제공되지 않으면 파서는 RHS와 동일한 기본값을 생성합니다.
파서를 실행하려면 이 저장소를 복제하고 파서 모듈을 Android Studio/IntelliJ 프로젝트로 가져옵니다. EasyNLU 파서는 규칙을 보유하는 Grammar
개체, 입력 문장을 단어로 변환하는 Tokenizer
개체, 숫자, 날짜, 장소 등과 같은 항목에 주석을 추가하는 Annotator
개체의 선택적 목록을 사용합니다. 규칙을 정의한 후 다음 코드를 실행합니다.
Grammar grammar = new Grammar ( rules , "$ROOT" );
Parser parser = new Parser ( grammar , new BasicTokenizer (), Collections . emptyList ());
System . out . println ( parser . parse ( "kill bt" ));
System . out . println ( parser . parse ( "wifi on" ));
System . out . println ( parser . parse ( "enable location" ));
System . out . println ( parser . parse ( "turn off GPS" ));
다음과 같은 결과가 출력되어야 합니다.
23 rules
[{feature=bluetooth, action=disable}]
[{feature=wifi, action=enable}]
[{feature=gps, action=enable}]
[{feature=gps, action=disable}]
다른 변형을 시도해 보세요. 샘플 변형에 대한 구문 분석이 실패하면 출력이 제공되지 않습니다. 그런 다음 규칙을 추가하거나 수정하고 문법 엔지니어링 프로세스를 반복할 수 있습니다.
EasyNLU는 이제 구조화된 형식의 목록을 지원합니다. 위 도메인의 경우 다음과 같은 입력을 처리할 수 있습니다.
위치 bt gps 비활성화
위 문법에 다음 3가지 추가 규칙을 추가하세요.
new Rule("$Setting", "$Action $FeatureGroup", "@merge"),
new Rule("$FeatureGroup", "$Feature $Feature", "{featureGroup: @append}"),
new Rule("$FeatureGroup", "$FeatureGroup $Feature", "{featureGroup: @append}"),
다음과 같은 새 쿼리를 실행합니다.
System.out.println(parser.parse("disable location bt gps"));
다음과 같은 결과가 출력되어야 합니다.
[{action=disable, featureGroup=[{feature=gps}, {feature=bluetooth}, {feature=gps}]}]
이러한 규칙은 쿼리에 두 개 이상의 기능이 있는 경우에만 트리거됩니다.
어노테이터를 사용하면 규칙을 통해 처리하기 까다롭거나 완전히 불가능할 특정 유형의 토큰을 더 쉽게 만들 수 있습니다. 예를 들어 NumberAnnotator
클래스를 사용하세요. 모든 숫자를 $NUMBER
로 감지하고 주석을 추가합니다. 그런 다음 규칙에서 카테고리를 직접 참조할 수 있습니다. 예:
Rule r = new Rule("$Conversion", "$Convert $NUMBER $Unit $To $Unit", "{convertFrom: {unit: @2, quantity: @1}, convertTo: {unit: @last}}"
EasyNLU에는 현재 몇 가지 주석자가 함께 제공됩니다.
NumberAnnotator
: 숫자에 주석을 답니다.DateTimeAnnotator
: 일부 날짜 형식에 주석을 답니다. 또한 DateTimeAnnotator.rules()
사용하여 추가하는 자체 규칙을 제공합니다.TokenAnnotator
: 입력의 각 토큰에 $TOKEN
으로 주석을 답니다.PhraseAnnotator
: 인접한 각 입력 문구에 $PHRASE
로 주석을 답니다. 자신만의 사용자 정의 주석자를 사용하려면 Annotator
인터페이스를 구현하고 이를 파서에 전달합니다. 구현 방법에 대한 아이디어를 얻으려면 내장된 주석자를 참조하세요.
EasyNLU는 텍스트 파일에서 규칙 로드를 지원합니다. 각 규칙은 별도의 줄에 있어야 합니다. LHS, RHS 및 의미 체계는 탭으로 구분되어야 합니다.
$EnableDisable ?$Switch $OnOff @last
탭을 공백으로 자동 변환하는 IDE를 사용하지 않도록 주의하세요.
더 많은 규칙이 파서에 추가됨에 따라 파서가 특정 입력에 대해 여러 개의 구문 분석을 찾는다는 것을 알게 될 것입니다. 이는 인간 언어의 일반적인 모호성 때문입니다. 파서가 작업에 얼마나 정확한지 확인하려면 레이블이 지정된 예제를 통해 파서를 실행해야 합니다.
이전 규칙과 마찬가지로 EasyNLU는 일반 텍스트로 정의된 예제를 사용합니다. 각 줄은 별도의 예여야 하며 탭으로 구분된 원시 텍스트와 구조화된 형식을 포함해야 합니다.
take my medicine at 4pm {task:"take my medicine", startTime:{hour:4, shift:"pm"}}
입력에 허용 가능한 숫자나 변형을 포함하는 것이 중요합니다. 이 작업을 다른 사람에게 맡기면 다양성이 더 높아질 것입니다. 예제의 수는 도메인의 복잡성에 따라 달라집니다. 이 프로젝트에서 제공되는 샘플 데이터 세트에는 100개 이상의 예가 있습니다.
데이터가 있으면 데이터세트로 만들 수 있습니다. 학습 부분은 trainer
모듈에 의해 처리됩니다. 프로젝트로 가져옵니다. 다음과 같이 데이터세트를 로드합니다.
Dataset dataset = Dataset . fromText ( 'filename.txt' )
두 가지 유형의 정확도를 결정하기 위해 파서를 평가합니다.
평가를 실행하기 위해 Model
클래스를 사용합니다.
Model model = new Model(parser);
model.evaluate(dataset, 2);
평가 함수의 두 번째 매개변수는 상세 수준입니다. 숫자가 높을수록 출력이 더 자세해집니다. evaluate()
함수는 각 예제를 통해 파서를 실행하고 최종적으로 정확성을 표시하는 잘못된 구문 분석을 표시합니다. 90년대 높은 정확도를 모두 얻으면 훈련이 필요하지 않습니다. 아마도 사후 처리를 통해 이러한 몇 가지 잘못된 구문 분석을 처리할 수 있을 것입니다. 오라클 정확도는 높지만 예측 정확도가 낮은 경우 시스템 교육이 도움이 됩니다. Oracle 정확도 자체가 낮다면 문법 엔지니어링을 더 많이 수행해야 합니다.
올바른 구문 분석을 얻기 위해 점수를 매기고 가장 높은 점수를 가진 것을 선택합니다. EasyNLU는 간단한 선형 모델을 사용하여 점수를 계산합니다. 훈련은 힌지 손실 기능이 있는 SGD(Stochastic Gradient Descent)를 사용하여 수행됩니다. 입력 기능은 구조화된 형식의 규칙 수와 필드를 기반으로 합니다. 훈련된 가중치는 텍스트 파일에 저장됩니다.
더 나은 정확도를 얻기 위해 일부 모델/훈련 매개변수를 조정할 수 있습니다. 알림 모델의 경우 다음 매개변수가 사용되었습니다.
HParams hparams = HParams . hparams ()
. withLearnRate ( 0.08f )
. withL2Penalty ( 0.01f )
. set ( SVMOptimizer . CORRECT_PROB , 0.4f );
다음과 같이 학습 코드를 실행합니다.
Experiment experiment = new Experiment ( model , dataset , hparams , "yourdomain.weights" );
experiment . train ( 100 , false );
그러면 훈련-테스트 분할이 0.8로 100세대 동안 모델이 훈련됩니다. 테스트 세트의 정확도를 표시하고 모델 가중치를 제공된 파일 경로에 저장합니다. 전체 데이터세트를 학습하려면 배포 매개변수를 true로 설정하세요. 콘솔에서 입력을 받아 대화형 모드를 실행할 수 있습니다.
experiement.interactive();
참고: 많은 수의 예제를 사용하더라도 훈련에서 높은 정확도가 보장되지는 않습니다. 특정 시나리오에서는 제공된 기능이 충분히 차별적이지 않을 수 있습니다. 이러한 경우에 문제가 발생하는 경우 문제를 기록해 주시면 추가 기능을 찾을 수 있습니다.
모델 가중치는 일반 텍스트 파일입니다. Android 프로젝트의 경우 assets
폴더에 배치하고 AssetManager를 사용하여 로드할 수 있습니다. 자세한 내용은 ReminderNlu.java를 참조하세요. 가중치와 규칙을 클라우드에 저장하고 무선으로 모델을 업데이트할 수도 있습니다(Firebase에 계시나요?).
파서가 자연어 입력을 구조화된 형식으로 변환하는 작업을 잘 수행하면 작업별 개체에 해당 데이터가 필요할 것입니다. 위의 장난감 예와 같은 일부 도메인의 경우 매우 간단할 수 있습니다. 다른 경우에는 날짜, 장소, 연락처 등과 같은 항목에 대한 참조를 해결해야 할 수도 있습니다. 미리 알림 샘플에서 날짜는 종종 상대값(예: '내일', '2시간 후' 등)이므로 절대값으로 변환해야 합니다. 해결 방법을 보려면 ArgumentResolver.java를 참조하세요.
팁: 규칙을 정의할 때 해결 논리를 최소한으로 유지하고 대부분의 작업은 사후 처리에서 수행하십시오. 규칙이 더 단순해집니다.