2020年12月14日月曜日

【チャットボット】チャットボットの仕組みの技術資料

Webサイトで見かけることが多くなったチャットですが、中に人がいて人が対応してくれるもの、ボットが無人で対応してくれるものなどがあります。

ボットが無人対応するものはどんな仕組みになっているのか?どのボットも質問をすると何か返してくれるという動きは同じですが中の仕組みは色々とあるようです。そのような参考記事はこちら。

【チャットボット – その種類別の仕組み】

仕事などでチャットボット製品の調査をするときに、このような仕組みからベンダに質問できると、より自分たちに合ったものを選べそうですね。

種別ごとの詳細もありますので、参考にしてみてください。

以上

2020年12月7日月曜日

【チャットボット】 SharePointと連携して社内チャットを便利に活用

企業内でのコミュニケーションにMicrosoft TeamsやGoogle Chatなどの企業向けチャットをを使ってますという話を聞く機会も、最近多くなって来たように感じます。リモートワークに合わせて企業向けチャットを社内コミュニケーションに使い始めたという会社も多いのだろうなと思います。

リモートワークといえばワードやパワーポイントのなどファイルサーバーにあるものを「あれどこにあるっけ?」と場所を教えてもらっていたものが、それがやりにくくなったのがちょっと困ったことです。チャットで聞いたりしていますが、全体のグループで聞くのも気が引けるしなど、誰にどう聞こうか一瞬迷ってしまいます。

こんな場合に役に立ちそうなツールのβ版がリリースされたとのことなので紹介します。 チャット経由で「○○のファイル」という感じで質問するとSharePointの中に格納されているファイルを探してくれるそうです。 SharePointの通常の検索だとファイル名やキーワードが正確にわからずに探すのにはいつも苦労するのですが、このツールはファイル名が曖昧であっても近しいものを抽出して候補を表示することで、目的のファイルをチャットボットが探してくれるようです。

【「ENOKI5.0 SharePoint連携オプション」のβ版】

今ならβ版ということでトライアルができるようなので、Microsoft365やSharePointでファイル管理されている方は試してみてはいかがでしょうか?

2020年11月30日月曜日

【チャットボット】 無料トライアルの落とし穴

チャットボットの導入検討の際にトライアルをしてみるのはとても有効だと思います。最近は無料トライアルができる製品も増えてきました。導入するのにどんなデータが必要なのか?運用するのにメンテナンス性はどうなのかは検討しておきたいですよね。

トライアルをする場合の注意点がこちらの記事にありますので、ぜひ参考にして下さい。こういうのは実際やってみないと、なかなか気がつかないことかと思いますが、知っているかどうかでも評価にだいぶ差が出てきますね!

【チャットボット – 無料トライアルの落とし穴】

トライアルを考えている方の参考になれば幸いです!

2020年11月26日木曜日

【チャットボット】目的に合ったチャットボットを選ぶ

チャットボットを設置したサイトが最近多くなってきて、チャットボット製品も色々なものが数多く出ています。 見た目は同じなのでちょっと調査しただけでは違いがわかりにくくどれを選んでも同じに見えますが、用途や状況によって選ぶポイントがあるという記事を紹介します。

【お客様にマッチしたチャットボットを選ぶには?~チャットボットマッチングチャート~】

上記のチャートでは以下のような区分けで、有人対応/無人対応どちらにするか、無人対応するならどの方式がいいか説明していますので参考にしてもらえればと思います。

  • 有人チャット
  • 無人チャット
    • シナリオ型チャット
    • 機械学習型チャット
    • ハイブリッド型チャット

以上、チャットボットの選び方についてのご紹介でした。

2020年6月14日日曜日

MyBatisでヘッダ明細型のデータをINSERTする(自動採番もする)

MyBatisでヘッダ明細型のデータを扱うシリーズの3回目。今回はネストしたJavaオブジェクトをデータベースに新規作成する方法について書いてみたいと思う。またIDもデータベースで自動採番させようとちょっと欲張ったものにしている。 今回はSQLの都合上MySQLに特化した内容となっている。

今回のサンプルもいつもと同じ以下のクラスとテーブルを使用する。

public class Student {
  int id;
  String name;
  Result[] result;
}

public class Result {
  String course;
  int score;
}

studentテーブル(ヘッダ)

id name
100 やまだ

resultテーブル(明細)

refid course score
100 英語 80
100 数学 90

データベースはMySQLを使用する。 idは連番を振りたいのでデータベースで自動採番させてしまおう、ということで以下のようにテーブル定義をした。

CREATE TABLE student (
  id INTEGER NOT NULL AUTO_INCREMENT UNIQUE,
  name VARCHAR(100)
);

CREATE TABLE result (
  refid INTEGER NOT NULL,
  course VARCHAR(100),
  score INTEGER
);

MyBatisのmapper定義

さて、ヘッダ明細型のテーブルに対してJavaオブジェクトをINSERTするMyBatisのmapperの定義をする。 INSERTの処理としてまず思いつくのは、MyBatisの設定でstudentテーブルとresultテーブルにINSERTするmapperをそれぞれ作成してJavaコードから順に呼び出す方法だろう。この場合はStudentクラスのオブジェクトをMyBatis経由で書き込んだ後、resultの配列ごとにループを回してResultクラスのオブジェクトをMyBatis経由で書き込む処理となる。

しかし、ネストしたStudentクラスのオブジェクトをMyBatisに渡して、その中でネストした部分も書き込んでもらうことはできないだろうかということで、以下のようなmapperを作成した。

<mapper namespace="mapper.student">
  <resultmap id="student" type="student">
    <id column="id" property="id" />
    <result column="name" property="name" />

    <collection property="result" oftype="Result">
      <result column="course" property="course" />
      <result column="score" property="score" />
    </collection>
  </resultmap>

  <insert id="create" parameterType="student" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO student (name) VALUES (#{name});
    SET @uid = LAST_INSERT_ID();
    <if test="result != null">
      <foreach item="i" collection="result">
        INSERT INTO result (refid, course, source)
          VALUES (@uid, #{i.course}, #{i.source});
      </foreach>
    </if>
    SELECT @uid FROM dual;
  </insert>

</mapper>

まずは最初の "INSERT INTO student ~" でStudentクラスのオブジェクトをstudentテーブルに書き込む。

次の行で "SET @uid = LAST_INSERT_ID()" を行っているが、ここでstudentテーブルに書き込んで自動採番されたIDを取得している。 これはMySQL専用の構文となる。

次にMyBatisの動的SQLを使ってネストしたオブジェクトを1つづつINSERTする。
まず、<if test="result != null"> でStudentクラスのオブジェクトがネストしたResultオブジェクトを持っているか(nullでないか)を調べ、持っているなら <foreach item="i" collection="result"> でひとつづつ順にINSERT処理をしている。
resultテーブルではrefid列に紐づくstudentテーブルのidを保持するが、先にMySQLの変数 "@uid" に自動採番された値を保存しているので、その値を書き込んでいる。

自動採番された値はJavaのStudentクラスのオブジェクトにも反映したい。MyBatisの処理結果として返した値が反映されるので、最後に "SELECT @uid FROM dual" を実行して、自動採番された値を返している。

Javaコードから書き込み

以下のようなJavaコードでネストされたオブジェクトを1回のMyBatis呼び出しでヘッダ明細型のテーブルにINSERTできる。

// 英語の結果
Result result_english = new Result();
result_english.course = "英語";
result_english.score = 50;

// 英語の結果
Result result_math = new Result();
result_math.course = "数学";
result_math.score = 68;

// 生徒の情報
// IDは自動採番されるため
Student student = new Student();
student.name = "たなか";
student.result = new Result[] { result_english, result_math };

// オブジェクトをデータベースに書き込む
sqlSession.insert("mapper.student.create", student);

// 自動採番された生徒のIDを表示
System.out.println("ID=" + student.id);

おわりに:BadSqlGrammarExceptionが出た時

Springから使っていたらこんな例外が出てしまいました。

org.springframework.jdbc.BadSqlGrammarException: 
### Error updating database.  Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'SET @uid = LAST_INSERT_ID();
		 
		SELECT @uid FROM dual' at line 3

今回はMyBatisのmapperで複数のステートメントを一度に実行していますが、MySQLのJDBCドライバの仕様で複数ステートメントを実行するときは "allowMultiQueries=true" オプションを指定する必要があるようです。JDBC接続のURLに以下のようにオプションを追加したらうまく動くようになりました。


jdbc.url=jdbc:mysql://localhost/student?allowMultiQueries=true

2020年5月26日火曜日

JavaのSimpleDateFormatで日付文字列チェックではまる

java.text.SimpleDateFormatを使って日付文字列をチェックしようとしてはまったときのメモ。 parseメソッドに文字列を与えてParseExceptionが出るか出ないかでチェックしようとした。

SimpleDateFormat fmt = new SimpleDateFormat("yyyyMMdd");

// 不正な日付
Date d = fmt.parse("197405112"); // ParseExceptionが発生しない!!

しかし、エラーとはならず、yyyy=1970、MM=05、dd=112と解釈され、5月112日→8月20日として、d=1974/8/20として通ってしまい予想外の動きになってしまった。 SimpleDateFormatはデフォルトだとできるだけ日付として解釈するように動作をするらしい。 日付を3桁で拾ってしまうのは予想外だったが・・・

存在しない日付を厳密にチェックするときはsetLenientをfalseに設定する必要があるようだ。 以下にすることでチェックをすることができた。

SimpleDateFormat fmt = new SimpleDateFormat("yyyyMMdd");

// 厳密な日付チェックを行う設定
fmt.setLenient(false);

// 不正な日付
Date d = fmt.parse("197405112"); // ParseException発生!!

SimpleDateFormatはスレッドセーフではなく使いまわしが難しいので、そろそろ新しいスレッドセーフなDateTimeFormatterに移行するべきとは思うが、Dateを使っている手持ちライブラリも多く使い勝手の悪さからなかなか移行できていない・・・

2020年5月15日金曜日

MySQLでUnknown command '\''.がでる。

MySQL5.7でSQLを流した時にUnknown command '\''.がでる時の対処法。

どういう状況で出るのかはいまいちはっきりしていないが、UTF-8のファイルを流した時に出る? 5.6などでは同じSQLでも出ていなかったように思われる。 このようなときは--default-character-set=utf8オプションを指定してやると成功するようだ。

mysql -umydb -pmypass -Dmydb --default-character-set=utf8 < data.sql

2020年5月12日火曜日

MyBatisでヘッダ明細型のデータを扱う(行範囲を指定)

前回の記事でヘッダ明細型のデータをMyBatisで読み込む方法としてcollectionタグを使いネストしたJavaオブジェクトに読み込む方法を紹介した。ヘッダテーブルと明細テーブルを結合した1つのSQLで読み込みができるため楽な方法ではあるが使い方に癖があるようなので注意しないと思わぬところではまってしまう。その一つが行数を指定して読み込むときである。

例として以下のようなデータをcollectionタグを使いネストしたJavaオブジェクトで読み込んでみよう。

studentテーブル(ヘッダ)

id name
100 やまだ
101 たなか
102 おばた
103 ささおか

resultテーブル(明細)

refid course score
100 英語 80
100 数学 90
101 英語 50
101 数学 68
102 英語 77
102 数学 70
103 英語 98
103 数学 92

読み込むオブジェクトのJavaクラスの定義とMyBasisのmapperは前回の記事と同じものを使用する。

Javaクラス定義

public class Student {
  int id;
  String name;
  Result[] result;
}

public class Result {
  String course;
  int score;
}

MyBasisのmapper定義

<mapper namespace="mapper.student">
  <resultmap id="student" type="student">
    <id column="id" property="id" />
    <result column="name" property="name" />

    <collection property="result" oftype="Result">
      <result column="course" property="course" />
      <result column="score" property="score" />
    </collection>
  </resultmap>

  <select id="find" resultmap="student">
    SELECT id, name, course, score
    FROM student, result
    WHERE id = refid
    ORDER BY id
  </select>
</mapper>

IDの小さい順に2人、やまだ君(100)、たなか君(101)、のデータだけ読み込むことを考えてみる。MyBatisではRowBoundsを使うことで指定した行範囲のデータを読み込むことができるので、以下のコードで0行目から2行の読み込みをしようとした。

// 読み込む行範囲を指定、先頭から2行のデータを読み込む
int offset = 0;
int limit = 2;
RowBounds rowBounds = new RowBounds(offset, limit);

// SQLに渡すパラメータ(特に無し)
Map<String,Object> params = new HashMap();

// SQLを発行してオブジェクトにマッピングする
List<Student> students = sqlSession.selectList("mapper.student.find", params, rowBounds);

しかしcollectionを使っている場合はそんなに単純ではない。RowBoundsへの期待はstudentテーブルの先頭2行だけであるが、SELECTの結果は以下のようにstudentの内容とresultの内容が1行に結合されたものとなり、RowBoundsはその先頭2行を対象としてしまうようである。結果的にstudentsには、やまだ君のデータだけが読み込まれる。

SELECTの結果

id name course score RowBoundで選ばれる行
100 やまだ 英語 80
100 やまだ 数学 90
101 たなか 英語 50 ×
101 たなか 数学 68 ×
102 おばた 英語 77 ×
102 おばた 数学 70 ×
103 ささおか 英語 98 ×
103 ささおか 数学 92 ×

これを回避するにはRowBoundsを使わず、全行読み込んだ後にoffsetとlimitでリストを切り取る処理を行っている。 一旦全行読み込んで処理をするのでメモリ的にもCPU的にも負荷がかかるのが欠点ではあるが、それほどの規模でなければコードの読みやすさでこの方法を使っている。 ただ、行数が多いテーブルではSQLの書き方も工夫するなど何らかの対策が必要がと思われる。

// 読み込む行範囲を指定、先頭から2行のデータを読み込む
int offset = 0;
int limit = 2;

// SQLを発行してオブジェクトにマッピングする
List<Student> students = sqlSession.selectList("mapper.student.find");

// 行範囲で切り取り
int s = offset;
int e = offset+limit;
if (s < 0) { s = 0; }
if (e >= students.size()) { e = students.size(); }
if (s == 0 && e >= students.size()) {
	return students;
} else {
	return students.subList(s, e);
}

2020年4月29日水曜日

MyBatisでヘッダ明細型のデータを扱う

「ヘッダ明細型」のデータはアプリを作るうえでよく登場するデータ構造である。 「ヘッダ」が親データ、「明細」が親に紐づく子データを表しており、例えば成績のデータなどで、各生徒の英語と数学の成績を表す場合に生徒の情報を親としてヘッダテーブルに格納、成績情報は親にidで紐づけて明細テーブルに格納すると以下のようにあらわすことができる。

studentテーブル(ヘッダ)

id name
100 やまだ
101 たなか

resultテーブル(明細)

refid course score
100 英語 80
100 数学 90
101 英語 50
101 数学 68

やまだ君の成績は英語80点で数学90点。たなか君の成績は英語50点で数学68点を表している・・・たなか君はもう少し頑張ったほうがいいですね・・・

このようなデータの持ち方は「Summary」と「Detail」などとも呼ばれたり、呼び方はさまざまであるが、商品カートや伝票など業務の世界でも頻繁に登場する。

このようなデータを扱う場合はStudentクラスがResultクラスのオブジェクトを持つようなネストした構造で表現することができる。 例えばJavaの場合のクラス定義は以下のようになる。setter/getterメソッドは省略して書いているので適宜付与してもらうとよいだろう。

public class Student {
  int id;
  String name;
  Result[] result;
}

public class Result {
  String course;
  int score;
}

ここでMyBatisを使ってstudentテーブルとresultテーブルの内容を上記クラスに読み込むことを考えてみる。studentテーブルをSELECTしてからresultテーブルをSELECTするように2つのSELECTに分けてもよいが、MyBatisではテーブルを結合した1つのSQLでネストしたクラスを読み込むことができる。

mapperでcollectionタグを使いネストした部分を定義する。property属性に子オブジェクトを格納するプロパティ名、oftype属性にそのクラス名を指定する。 SELECTのSQLでは2つのテーブルを結合する。

<mapper namespace="mapper.student">
  <resultmap id="student" type="student">
    <id column="id" property="id" />
    <result column="name" property="name" />

    <collection property="result" oftype="Result">
      <result column="course" property="course" />
      <result column="score" property="score" />
    </collection>
  </resultmap>

  <select id="find" resultmap="student">
    SELECT id, name, course, score
    FROM student, result
    WHERE id = refid
    ORDER BY id
  </select>
</mapper>

SELECTの結果は以下のようにstudentの内容とresultの内容が1行に結合されたものとなるが、親クラスの内容はmapperのidタグで指定された列でまとめられオブジェクトが構築されるようである。

SELECTの結果

id name course score
100 やまだ 英語 80
100 やまだ 数学 90
101 たなか 英語 50
101 たなか 数学 68

以下のコードで読み込みを行うと・・・

List<Student> entities = sqlSession.selectList("mapper.student.find");

下図のようなオブジェクトとして読み込まれる。



使いこなせば複雑なデータ構造をシンプルに扱えそうな方法であるが、実際使ってみると気を付けなければならない点もあり、そのような点は次回以降また紹介していこうと思う。

2020年4月23日木曜日

機械学習と深層学習

「機械学習と深層学習って何が違うの」という質問を受けることが良くあるが、その説明としてこの図がわかりやすいのかなと思う。

「機械学習」というのは、何らかのパターンを自動で覚えて、次に何らかのパターンが発生したときに、過去に覚えたパターンと近いかどうかを判別したり、次にどういうパターンになるかを予測するようなプログラムのこと。
パターンというのは、例えば画像だったり、文字列だったり、株価の毎日の変動だったり。例えば、犬の画像をたくさん覚えた機械学習に動物の写真を入力すると犬かどうか判別したりできるし、株価のデータを入力すると次の日に上がるか下がるかを過去のパターンから予測してくれるようになる。 自動で覚えてとは書いたが人がそれらのデータを集めて機械学習のプログラムに覚えさせてもいいと思う。

一方で「深層学習」とは「ディープラーニング」や「ディープニューラルネットワーク(DNN)」とも呼ばれるが、機械学習をするための一つの手法のこと。これのベースになっている「ニューラルネット」という技術は神経細胞に似た網目構造を使って計算する方式で何十年も前から知られていて、網目構造を増やす(層を深くする)と性能が上がることはわかっていた。しかし昔はコンピューターが非力だったので少し増やすと計算が終わらず使い物にならなかったのが、最近はまともに動くようになってきて性能も高く実用的になったので一気に知名度が上がってきたというものである。

ただ、深層学習は一気に知名度が上がったので、世の中的には機械学習=深層学習と見てしまわれることが多い。そんな感じで冒頭の質問をされた時には、ちょっと古いが、ヘッドホンステレオ=ウォークマンと同じイメージと説明している。

深層学習の中にも層の作り方やデータの処理の仕方でいろいろな方法があって、画像判定が得意とか、予測が得意とか、用途に応じて使い分けられている。 例えば、最後に紹介するDEEPという深層学習では文章を解析して分類することができるものとなっていて、ここの研究論文にあるような飲食店のレビュー文章を分析することをしている。