自作で対話システム作成日記:日付を返す処理の実装

背景

  • 自然言語処理の研究を学生時代行っていたが,形態素解析の辞書作成と基礎よりだった.
  • 応用部分の一分野である対話システムをやってみたいと思った.

やったこと

  • Python で今日,明日,昨日の日付を聞くと返してくれるプログラムの作成
  • 上記の日付以外については答えられないと返答するようにした.ただ,少しの入力文でしか確認していないので100%ではない.

処理概要

  • 「今日に日時は?」,「明日の日時は?」,「昨日の日時は?」の三つそれぞれを形態素解析し分散表現に変換
  • 分散表現への変換
  • 内容語だけ取り出す
  • 取り出した語の分散表現を足し合わせる
  • 取り出した内容後の数で割る
  • 入力文を分散表現に変換
  • 入力文の分散表現と作成した三つの分散表現の誤差を求める
    • 誤差の求め方
      • 入力文と三つの文とで各次元の値の差の絶対値の合計を求める
      • 求めた合計値を次元数で割る
  • 最も入力文との誤差が小さかった文のタスクを実行
    • ただし,誤差が閾値を超えた場合には実行しない
    • 閾値は,今回人手で決めたので改善したい

コード

from gensim.models import word2vec, KeyedVectors
from gensim.models.wrappers import FastText
from janome.tokenizer import Tokenizer
import numpy as np
import datetime

def sum_vec_error(A, B):
    result = 0
    for a, b in zip(A, B):
        #print(a,b)
        x = abs(a-b)
        #print(x)
        result += x

    result = result / A.shape[0]

    return result

def sentense_ave_vec(sentense):
    cnt_sentense = 0
    vec_sentence = np.zeros_like(w2v_model.wv['今日'])


    for token in tokenizer.tokenize(sentense):
        #print(token.surface)
        #print(w2v_model[token.surface])
        if token.part_of_speech.split(',')[0] in ['名詞', '動詞', '形容詞','形容動詞','副詞']:
            cnt_sentense += 1
            vec_sentence += w2v_model.wv[token.surface]

    vec_sentence = vec_sentence / cnt_sentense

    return vec_sentence


if __name__ == '__main__':
    # しろやぎコーポレーションが公開してくれている分散表現
    #w2v_model = word2vec.Word2Vec.load('data/latest-ja-word2vec-gensim-model/word2vec.gensim.model')
    # fatstext 読み込みに10分程度かかる
    w2v_model = KeyedVectors.load_word2vec_format('data/cc.ja.300.vec')

    #print(w2v_model['今日'])

    tokenizer = Tokenizer()

    while True:
        #input_sentense = '本日の日付は?'
        input_sentense = input('御用は何でしょうか?:')
        input_sentense = str(input_sentense)
        #print(input_sentense)
        #input_sentense = [input_sentense]
        input_sentense_ave_vec = sentense_ave_vec(input_sentense)

        sentenses = ['今日の日付は?','明日の日付は?','昨日の日付は?']
        list_sentenses_error = []

        for sentense in sentenses:
            sav = sentense_ave_vec(sentense)
            list_sentenses_error.append(sum_vec_error(input_sentense_ave_vec, sav))

        #print(list_sentenses_error)


        #返答
        min_sum_vec_error = min(list_sentenses_error)
        #print(min_sum_vec_error)
        min_sum_vec_error_index = list_sentenses_error.index(min_sum_vec_error)

        if min_sum_vec_error < 0.04: #閾値は人手なので改善の余地あり
            if sentenses[min_sum_vec_error_index] == sentenses[0]:
                now = datetime.datetime.now()
                print(now.strftime('%Y/%m/%d です'))
                exit
            elif sentenses[min_sum_vec_error_index] == sentenses[1]:
                now = datetime.datetime.now()
                yesterday = now + datetime.timedelta(days=1)
                print(yesterday.strftime('%Y/%m/%d です'))
                exit
            elif sentenses[min_sum_vec_error_index] == sentenses[2]:
                now = datetime.datetime.now()
                tommorow = now - datetime.timedelta(days=1)
                print(yesterday.strftime('%Y/%m/%d です'))
                exit
        else:
            print('お答えできません')

FTPからSFTPに変更する際に気づいたことまとめ

動機

  • FTPからSFTPに移行する必要があり,その際調べたことをまとめようと思ったため.

調べたこと

  • データ送信時の形式について
    • FTPでは指定するオプションがあるが,SFTPにはなくバイナリとなっている.
  • 接続時のコマンドファイル作成の可否
    • FTPには無いが,SFTPにはオプション "-b コマンドファイル名" でコマンドファイル内に記載されたコマンドを実行することが可能.コマンドファイルにはコマンドを一行ごとに記載する.
  • オプション "-b" を使用した際のSFTP挙動
    • もしコマンドの途中でエラーが見られた場合にはその時点でSFTPが終了する.その際のSTATUSはPIPESTATUSから参照することが可能であり,正常であれば0,以上があればそれ以外の値をとる.
  • コマンドファイルの注意点
    • コマンドファイルに環境変数を書いても使用できないのでファイルまでのパスは記述する必要がある.
  • 送信中の参照を防ぐために複数ファイルを名前を変更して送信する方法
    • 送信自体はPUTコマンドで一度に任意のファイルを送信できるが,ファイル名を変更しての送信は1ファイルずつしか行えない.
      • 解決するために,1ファイルずつ送信し,RENAMEコマンドで元に戻す必要がある.

Kaggle スタートブック読了

Kaggle スタートブック

背景

  • kaggle のようなコンペに興味がでたため.
  • やり方について検索する時間が面倒なので本を購入

目的

  • Kaggle に参加する流れを知る

印象に残った点

  • Kaggle に参加する方法が記載されていて,同じ手順で行える点が良かった.
  • Kaggle に参加する方法以外にも,特徴量エンジニアリングや交差検証,パラメタチューニング等も記載されており,詳細について調べる足掛かりになる.

次に読みたい本

  • Kaggleで勝つデータ分析の技術

Kaggleで勝つデータ分析の技術

Kaggleで勝つデータ分析の技術

Google Todo リストを Google Chat に毎日連絡させてみた

概要

Google Apps Script (GAS) と GASのToDoの拡張機能を用いて BotGoogle Chat に毎朝残ってる ToDo を通知するようにしてみた.

手順

まず,マイドライブにGASファイルを作成し,以下のコードをコピペする.

function report_todo() {
  var url = '自身のチャットボット (Webhook)のURLを記入' //あとで変更するので一旦そのままで.
  var summary_todo = get_todoList();
  
  if (summary_todo) {
    var botMassage = {'text' : summary_todo}
    
    var options = {
      'method' : 'POST',
      'heders' : {
        'Content-Type' : 'application/json: charset=UTF-8'
    },
      'payload':JSON.stringify(botMessage)
  };
    
    var result = UrlFetchApp.fetch(url, options);
    Logger.log(result);
  }
}

function get_todoList() {
  var taskLists = Task.Tasklists.list();
  var summary_msg = '';
  if (taskLists.items) {
    for (var i = 0; i < taskLists.items.length; i++) {
      var taskList = taskLists.items[i];
      summary_msg = getTasks(taskList.id);
    }
  }
  
  return summary_msg;
}

function getTasks(taskListId) {
  var tasks = Tasks.Tasks.list(taskListId);
  var msg = '';
  
  if (tasks.items) {
    for (var i = 0; i < tasks.items.length; i++) {
      var task = task.items[i];
      msg += task.due.split('T')[0] + '\t' + task.title + '\n';
    }
  }
  
  return msg;
}

コピペしたままでは使えないので,GASのコードをコピペしたウィンドウの上部にある「リソース→googleの拡張サービス...」を順にクリックする. もし, "Edit Project Name" と出てきたらわかりやすい名前を適当につける.(例:todo_report) 新たに出てきた ”Advanced Google Services” の下のほうに "Task API" があるので右の部分をクリックして ON にしてOKをクリックする.

google chat で新たにチャットルームを作成し, webhook の設定を行い,URLを取得する.

取得したURLをGASのコードの該当部分に記入する.

GASのウィンドウで上部の時計アイコンをクリックし,トリガーを用いて通知が来るタイミングを設定

※ToDoリストに入れるタスクに締切がないとエラーが出る

GASでGmailをスプレッドシートにまとめる方法

動機

メールのラベル付けがめんどう.自動化できないかと思った.

 

方法

  1. スプレッドシートにGAS (Google Apps Script)でメールの情報をまとめる
  2. Pyhonでまとめた情報からメールをクラスタリングする
  3. クラスタリング結果をGmailに反映する  

今回書くこと

方法1で行ったことをまとめる.

 

手順

まず,スプレッドシートを新たに作成し,A・B・C列の一行目にそれぞれ,ヘッダーとして「id, subject, plainBody」と記入する.
次に,作成したスプレッドシートの上段にあるツールをクリックした際に表示されるスクリプトエディタをクリックし,スクリプトウィンドウを立ち上げる.
以下のコードを張り付ける.また,メールは一度に500件以上は取得できないようになっているため,500件以上集める場合には,取得するメールの日時期間を設定するなどが必要である.

function setInfoMail() {
  var sheet = SpreadsheetApp.getActiveSheet();
  Logger.log(sheet.getName());
  
  //YYYY/MM/DDは適宜,年月日に変更する
  var threads = GmailApp.search('after:YYYY/MM/DD', 0, 500);
  var message = '';
  var subject = '';
  var id = '';
  var plainBody;

  threads.forEach(function(thread, i, array) {
    message = thread.getMessages()[0];
    subject = message.getSubject();
    id = thread.getId();
    body = message.getBody();
    plainBody = message.getPlainBody();
    
    //複数回実行した際に,同じメールがスプレッドシートに記述されないようにする
    var settedId = sheet.getRange('A:A').getValues().flat();
    if (settedId.indexOf(id) == -1) {
      sheet.appendRow([id, subject, plainBody]);
    }  
  });
}

スクリプトウィンドウの上部にある「関数を選択」をクリックし,「setInfoMail」をクリックして選択する.
「▶」ボタンをクリックして,実行する.もし,実行が初回であれば,権限を与えるかどうかの確認が行われるため,承認する.
処理が終わるまで待ち,スプレッドシートを確認するとメールのIDと件名,本文がまとまっていることがわかる.