チャットボットの原始的なアプローチに、「入力された文が、あるパターンに合致していれば、予め登録されている応答を返す」という単純な方法がありますが、もしかしたら、人間もある程度はパターンに反応して喋っているのではないかと思うのですよ。自分自身が、いちいち文法解析をしながら会話を解釈している気がしないんですね。聞きなれない言い回しに出くわしたときに、初めて文法解析を始める、みたいな。
で、パターンに反応して喋るチャットボットを作り始めました。とりあえずはスケジュールに関する会話ができるようなものを目指しています。まず、日時表現をパターン化して、それに続いて「〜があります」などのパターンが現われると、スケジュールの登録を行い、日時表現に続いて「の予定を教えてください」などのパターンが現われると、登録されたスケジュールを表示します。
試作したプログラムでは、次のような受け答えをします。
このようなアプローチではネストされたパターンを解釈することが重要になります。今は単純な正規表現ですが、自分でパーサーを書かないとダメですね。以下は現状のパターン解析部分です。
で、パターンに反応して喋るチャットボットを作り始めました。とりあえずはスケジュールに関する会話ができるようなものを目指しています。まず、日時表現をパターン化して、それに続いて「〜があります」などのパターンが現われると、スケジュールの登録を行い、日時表現に続いて「の予定を教えてください」などのパターンが現われると、登録されたスケジュールを表示します。
試作したプログラムでは、次のような受け答えをします。
User: 明日の予定を教えて
Bot: 4月23日は何もありません。
User: 明日は11時から会議です。
Bot: 4月23日に会議の予定を登録しました
User: 明日の6時から8時30分までジムに行きます
Bot: 4月23日にジムの予定を登録しました
User: 明日は何かありますか?
Bot: 4月23日は11時00分から会議、06時00分から08時30分までジム、があります。
User: 23日の会議はキャンセルになったよ
Bot: 4月23日の会議、をキャンセルしました
User: 明日の予定は?
Bot: 4月23日は06時00分から08時00分までジム、があります。
User: 来週の火曜日は5時から会議です。
Bot: 5月1日に会議の予定を登録しました
User: 火曜日は何かあるかな
Bot: 4月24日は何もありません。
XMPPのインターフェイスを被せてGoogle talkにぶら下げてあるので、適当にからかってみてください。takegata.takezo@gmail.comです。Bot: 4月23日は何もありません。
User: 明日は11時から会議です。
Bot: 4月23日に会議の予定を登録しました
User: 明日の6時から8時30分までジムに行きます
Bot: 4月23日にジムの予定を登録しました
User: 明日は何かありますか?
Bot: 4月23日は11時00分から会議、06時00分から08時30分までジム、があります。
User: 23日の会議はキャンセルになったよ
Bot: 4月23日の会議、をキャンセルしました
User: 明日の予定は?
Bot: 4月23日は06時00分から08時00分までジム、があります。
User: 来週の火曜日は5時から会議です。
Bot: 5月1日に会議の予定を登録しました
User: 火曜日は何かあるかな
Bot: 4月24日は何もありません。
このようなアプローチではネストされたパターンを解釈することが重要になります。今は単純な正規表現ですが、自分でパーサーを書かないとダメですね。以下は現状のパターン解析部分です。
import java.util.*;
import java.util.regex.*;
import java.text.*;
public class Bot{
public static String respond(Friend friend,String strMessage){
strMessage = zenNum2hanNum(strMessage);
String strResponse=null;
String strRegexp;
Pattern pattern;
Matcher matcher;
//---------------
if(strMessage.matches("(おはよう|お早う|こんにちは|こんばんは).*")){
Calendar cal = Calendar.getInstance();
int intHour = cal.get(Calendar.HOUR_OF_DAY);
if(4<=intHour && intHour<=10){strResponse="おはようございます";}
else if(11<=intHour && intHour<=17){strResponse="こんにちは";}
else if(18<=intHour || intHour<=3){strResponse="こんばんは";}
return strResponse;
}
//---------------
strRegexp = "(.+)[、は](何|なに)[がか](ありますか|あるか|ある?).*";
pattern = Pattern.compile(strRegexp);
matcher = pattern.matcher(strMessage);
if(matcher.matches()){
String strDate = matcher.group(1);
Calendar cal = resolveDate(strDate);
if(cal!=null){
return Schedule.ask(friend, cal);
}
}
//---------------
strRegexp = "(.+)の予定(を教えて.*|は?)";
pattern = Pattern.compile(strRegexp);
matcher = pattern.matcher(strMessage);
if(matcher.matches()){
String strDate = matcher.group(1);
Calendar cal = resolveDate(strDate);
if(cal!=null){
return Schedule.ask(friend, cal);
}
}
//---------------
strRegexp = "(.+)[、はのに]((\\d+)時((\\d+)分)?(に|から))?((\\d+)時((\\d+)分)?まで)?"
+ "(.+)((です|があります|に行きます|へ行きます|をします).*)";
pattern = Pattern.compile(strRegexp);
matcher = pattern.matcher(strMessage);
if(matcher.matches()){
String strDate = matcher.group(1);
Calendar cal = resolveDate(strDate);
if(cal!=null){
String strFromHour = matcher.group(3);
String strFromMinute = matcher.group(5);
String strToHour = matcher.group(8);
String strToMinute = matcher.group(10);
String strTodo = matcher.group(11);
Schedule sch = new Schedule();
System.out.println(strDate +"/"+ strFromHour +"/"+ strFromMinute
+"/"+ strToHour +"/"+ strToMinute +"/"+ strTodo);
strResponse = sch.add(friend, cal, strFromHour,strFromMinute,strToHour,strToMinute,strTodo);
if(strResponse!=null){
return strResponse;
}
}
}
//---------------
strRegexp = "(.+)の(.+)(は|を)キャンセル.*";
pattern = Pattern.compile(strRegexp);
matcher = pattern.matcher(strMessage);
if(matcher.matches()){
String strDate = matcher.group(1);
Calendar cal = resolveDate(strDate);
if(cal!=null){
return Schedule.cancel(friend, cal,matcher.group(2));
}
}
//---------------
return "意味が分かりません。";
}
static Calendar resolveDate(String strDate){
Calendar cal = Calendar.getInstance();
cal.clear(Calendar.MILLISECOND);
cal.clear(Calendar.SECOND);
cal.clear(Calendar.MINUTE);
cal.set(Calendar.HOUR_OF_DAY,0);
Pattern pattern;
Matcher matcher;
Integer intDate=null;
if(strDate.equals("今日") |strDate.equals("きょう")){
//nop;
}else if(strDate.equals("明日") | strDate.equals("あした") ){
cal.add(Calendar.DATE,1);
}else if(strDate.equals("明後日") | strDate.equals("あさって") ){
cal.add(Calendar.DATE,2);
}else if(strDate.equals("昨日") | strDate.equals("きのう") ){
cal.add(Calendar.DATE,-1);
}else if(strDate.equals("一昨日") | strDate.equals("おととい") ){
cal.add(Calendar.DATE,-2);
}else if(strDate.matches("((\\d+)月)?(\\d+)日")){
pattern = Pattern.compile("((\\d+)月)?(\\d+)日");
matcher = pattern.matcher(strDate);
matcher.matches();
String strMonthTemp=matcher.group(2);
//System.out.println("month:"+strMonthTemp);
if(strMonthTemp!=null){
try{
cal.set(Calendar.MONTH,Integer.parseInt(strMonthTemp)-1);
}catch(NumberFormatException e){
cal=null;
}
}
String strDateTemp=matcher.group(3);
try{
cal.set(Calendar.DATE,Integer.parseInt(strDateTemp));
}catch(NumberFormatException e){
cal=null;
}
}else if(strDate.matches("(来週の|再来週の|今度の|こんどの|次の|つぎの)?(.)曜日?")){
int intDayDelta=0;
pattern = Pattern.compile("(来週の|再来週の|今度の|こんどの|次の|つぎの)?(.)曜日?");
matcher = pattern.matcher(strDate);
matcher.matches();
String strWeek=matcher.group(2);
int intDayToday = cal.get(Calendar.DAY_OF_WEEK);
if(matcher.group(1)!=null){
if(matcher.group(1).equals("来週の")){
intDayDelta = 7-intDayToday+resolveWeek(strWeek);
}else if(matcher.group(1).equals("再来週の")){
intDayDelta = 14-intDayToday+resolveWeek(strWeek);
}
}else{
intDayDelta=resolveWeek(strWeek)-intDayToday;
if(intDayDelta<0){
intDayDelta+=7;
}
}
cal.add(Calendar.DATE,intDayDelta);
}else{
cal = null;
}
return cal;
}
static int resolveWeek( String strWeek ){
final String days = "日月火水木金土";
return days.indexOf(strWeek)+1;
}
public static String zenNum2hanNum(String str) {
StringBuffer sb = new StringBuffer(str);
for (int i = 0; i < sb.length(); i++) {
char c = sb.charAt(i);
if (c >= '0' && c <= '9') {
sb.setCharAt(i, (char)(c - '0' + '0'));
}
}
return sb.toString();
}
}
import java.util.regex.*;
import java.text.*;
public class Bot{
public static String respond(Friend friend,String strMessage){
strMessage = zenNum2hanNum(strMessage);
String strResponse=null;
String strRegexp;
Pattern pattern;
Matcher matcher;
//---------------
if(strMessage.matches("(おはよう|お早う|こんにちは|こんばんは).*")){
Calendar cal = Calendar.getInstance();
int intHour = cal.get(Calendar.HOUR_OF_DAY);
if(4<=intHour && intHour<=10){strResponse="おはようございます";}
else if(11<=intHour && intHour<=17){strResponse="こんにちは";}
else if(18<=intHour || intHour<=3){strResponse="こんばんは";}
return strResponse;
}
//---------------
strRegexp = "(.+)[、は](何|なに)[がか](ありますか|あるか|ある?).*";
pattern = Pattern.compile(strRegexp);
matcher = pattern.matcher(strMessage);
if(matcher.matches()){
String strDate = matcher.group(1);
Calendar cal = resolveDate(strDate);
if(cal!=null){
return Schedule.ask(friend, cal);
}
}
//---------------
strRegexp = "(.+)の予定(を教えて.*|は?)";
pattern = Pattern.compile(strRegexp);
matcher = pattern.matcher(strMessage);
if(matcher.matches()){
String strDate = matcher.group(1);
Calendar cal = resolveDate(strDate);
if(cal!=null){
return Schedule.ask(friend, cal);
}
}
//---------------
strRegexp = "(.+)[、はのに]((\\d+)時((\\d+)分)?(に|から))?((\\d+)時((\\d+)分)?まで)?"
+ "(.+)((です|があります|に行きます|へ行きます|をします).*)";
pattern = Pattern.compile(strRegexp);
matcher = pattern.matcher(strMessage);
if(matcher.matches()){
String strDate = matcher.group(1);
Calendar cal = resolveDate(strDate);
if(cal!=null){
String strFromHour = matcher.group(3);
String strFromMinute = matcher.group(5);
String strToHour = matcher.group(8);
String strToMinute = matcher.group(10);
String strTodo = matcher.group(11);
Schedule sch = new Schedule();
System.out.println(strDate +"/"+ strFromHour +"/"+ strFromMinute
+"/"+ strToHour +"/"+ strToMinute +"/"+ strTodo);
strResponse = sch.add(friend, cal, strFromHour,strFromMinute,strToHour,strToMinute,strTodo);
if(strResponse!=null){
return strResponse;
}
}
}
//---------------
strRegexp = "(.+)の(.+)(は|を)キャンセル.*";
pattern = Pattern.compile(strRegexp);
matcher = pattern.matcher(strMessage);
if(matcher.matches()){
String strDate = matcher.group(1);
Calendar cal = resolveDate(strDate);
if(cal!=null){
return Schedule.cancel(friend, cal,matcher.group(2));
}
}
//---------------
return "意味が分かりません。";
}
static Calendar resolveDate(String strDate){
Calendar cal = Calendar.getInstance();
cal.clear(Calendar.MILLISECOND);
cal.clear(Calendar.SECOND);
cal.clear(Calendar.MINUTE);
cal.set(Calendar.HOUR_OF_DAY,0);
Pattern pattern;
Matcher matcher;
Integer intDate=null;
if(strDate.equals("今日") |strDate.equals("きょう")){
//nop;
}else if(strDate.equals("明日") | strDate.equals("あした") ){
cal.add(Calendar.DATE,1);
}else if(strDate.equals("明後日") | strDate.equals("あさって") ){
cal.add(Calendar.DATE,2);
}else if(strDate.equals("昨日") | strDate.equals("きのう") ){
cal.add(Calendar.DATE,-1);
}else if(strDate.equals("一昨日") | strDate.equals("おととい") ){
cal.add(Calendar.DATE,-2);
}else if(strDate.matches("((\\d+)月)?(\\d+)日")){
pattern = Pattern.compile("((\\d+)月)?(\\d+)日");
matcher = pattern.matcher(strDate);
matcher.matches();
String strMonthTemp=matcher.group(2);
//System.out.println("month:"+strMonthTemp);
if(strMonthTemp!=null){
try{
cal.set(Calendar.MONTH,Integer.parseInt(strMonthTemp)-1);
}catch(NumberFormatException e){
cal=null;
}
}
String strDateTemp=matcher.group(3);
try{
cal.set(Calendar.DATE,Integer.parseInt(strDateTemp));
}catch(NumberFormatException e){
cal=null;
}
}else if(strDate.matches("(来週の|再来週の|今度の|こんどの|次の|つぎの)?(.)曜日?")){
int intDayDelta=0;
pattern = Pattern.compile("(来週の|再来週の|今度の|こんどの|次の|つぎの)?(.)曜日?");
matcher = pattern.matcher(strDate);
matcher.matches();
String strWeek=matcher.group(2);
int intDayToday = cal.get(Calendar.DAY_OF_WEEK);
if(matcher.group(1)!=null){
if(matcher.group(1).equals("来週の")){
intDayDelta = 7-intDayToday+resolveWeek(strWeek);
}else if(matcher.group(1).equals("再来週の")){
intDayDelta = 14-intDayToday+resolveWeek(strWeek);
}
}else{
intDayDelta=resolveWeek(strWeek)-intDayToday;
if(intDayDelta<0){
intDayDelta+=7;
}
}
cal.add(Calendar.DATE,intDayDelta);
}else{
cal = null;
}
return cal;
}
static int resolveWeek( String strWeek ){
final String days = "日月火水木金土";
return days.indexOf(strWeek)+1;
}
public static String zenNum2hanNum(String str) {
StringBuffer sb = new StringBuffer(str);
for (int i = 0; i < sb.length(); i++) {
char c = sb.charAt(i);
if (c >= '0' && c <= '9') {
sb.setCharAt(i, (char)(c - '0' + '0'));
}
}
return sb.toString();
}
}
投稿:竹形 誠司[takegata]/2007年 04月 22日 07時 07分
/更新:2007年 04月 22日 07時 29分
Re^2:パターンで喋る
by KILROY[KILROY]
Q&A型の対話(「人間は喋るコンピュータに出会うと質問したくなる」という話はどこで聞いたんだっけかな?)の場合、
……問題は、自然言語の文をどうやって Prolog プログラム(ホーン節の集合)に置き換えるか、っつーことなんですけどね。(^_^!)
そもそも日本語の場合、「文の中に構造を見出すことができない」という点が最大の問題であるとされてきました。英語などのヨーロッパ語の場合は「文には必ず述語( verb )が存在して、そこに格を持った名詞がぶら下がる」という構造が普遍的にあるとされているわけですが、日本語の場合は
「象は鼻が長い」
のように、「述語(動詞)が一個もないのに主語が二つある(ように見える)」文があたりまえのように存在してしまうため、既存の印欧語の文法理論では箸にも棒にもかからない、ということでした。
でまあ、「強調転置」(「修飾詞(連体形)+名詞」が、名詞が強調されることによって「名詞+“が”+修飾詞」の形になる)というリクツを考えて、
「象は鼻が長い」→「象は長い“鼻”の(動物)です」
といった形で「決まった構造に押し込めるではないか」というところまでは考えました。
ただ、そこから先の「(同じように定型的な構造に押し込んで、体系的な知識として整理された)実際の知識とぶっつけてみて評価し、その結果から応答文を生成する」の部分はまるっきり手付かずです。まあ、それでも「××は○○である」型の文構造に限ってなら、「長い鼻の動物は?」「象」とか、ちょっとヒネって「象は鼻が長い」「鼻が短い象は象ではないのですか?」とかいった程度の程度の応答文作成はできるように思うんですが、うーん、面白くなるかどうかはやってみないと分りませんね。
早めに実装したいと思います。
真偽の判定( yes/no 型疑問文)
未知変数の確定( WHAT 型疑問文)
と看做(みな)してよい、という話はかなり前からあって(たしか橋田浩一さんあたりが謂っていた)、「 Prolog で書いて評価すれば終わり」という意見がありました。……問題は、自然言語の文をどうやって Prolog プログラム(ホーン節の集合)に置き換えるか、っつーことなんですけどね。(^_^!)
そもそも日本語の場合、「文の中に構造を見出すことができない」という点が最大の問題であるとされてきました。英語などのヨーロッパ語の場合は「文には必ず述語( verb )が存在して、そこに格を持った名詞がぶら下がる」という構造が普遍的にあるとされているわけですが、日本語の場合は
「象は鼻が長い」
のように、「述語(動詞)が一個もないのに主語が二つある(ように見える)」文があたりまえのように存在してしまうため、既存の印欧語の文法理論では箸にも棒にもかからない、ということでした。
でまあ、「強調転置」(「修飾詞(連体形)+名詞」が、名詞が強調されることによって「名詞+“が”+修飾詞」の形になる)というリクツを考えて、
「象は鼻が長い」→「象は長い“鼻”の(動物)です」
といった形で「決まった構造に押し込めるではないか」というところまでは考えました。
ただ、そこから先の「(同じように定型的な構造に押し込んで、体系的な知識として整理された)実際の知識とぶっつけてみて評価し、その結果から応答文を生成する」の部分はまるっきり手付かずです。まあ、それでも「××は○○である」型の文構造に限ってなら、「長い鼻の動物は?」「象」とか、ちょっとヒネって「象は鼻が長い」「鼻が短い象は象ではないのですか?」とかいった程度の程度の応答文作成はできるように思うんですが、うーん、面白くなるかどうかはやってみないと分りませんね。
早めに実装したいと思います。
投稿:KILROY[KILROY]/2007年 04月 23日 06時 53分
/更新:2007年 04月 23日 06時 57分