Amazon Translate を試してみた

ちょっとAmazon Translateに興味が湧いて試してみたのでそのメモです。
結論ですが、非常に簡単に翻訳ができることがわかりました。

準備として、 boto3 が使えるようにしておく必要があります。(アクセスキーやシークレットキーの準備。自分は環境変数に入れています)
また、利用するIAMにAmazon Translateの権限を付与しておく必要があります。

例文はいつも通り「走れメロス」から拝借しています。

翻訳対象のテキストはこちらです。


text = """
メロスは激怒した。
必ず、かの邪智暴虐の王を除かなければならぬと決意した。
メロスには政治がわからぬ。
メロスは、村の牧人である。
笛を吹き、羊と遊んで暮して来た。
けれども邪悪に対しては、人一倍に敏感であった。
きょう未明メロスは村を出発し、野を越え山越え、十里はなれた此このシラクスの市にやって来た。
メロスには父も、母も無い。
女房も無い。
十六の、内気な妹と二人暮しだ。
"""

Amazon Translate をboto3から使う方法は非常に簡単で、client を生成して、
translate_textテキストに、ほんやくしたいテキスト、元の言語、翻訳先の言語を指定するだけです。
今回は 日本語 -> 英語 を指定しました。

ちなみにドキュメントはこちらです。


import boto3
client = boto3.client('translate')

result = client.translate_text(
    Text=text,
    SourceLanguageCode="ja",
    TargetLanguageCode="en",
)

print(result["TranslatedText"])
"""
Melos got furious.
He determined that he must exclude the king of wicked violence.
Melos does not know politics.
Melos is a village shepherd.
I played a whistle and played with the sheep.
However, for evil, people were more sensitive to evil.
Today, the unknown Melos left the village, crossed the field over the mountains, and came to this city of Silax, where the city of Silax is gone.
Melos has no father or mother.
There is no wife.
I live two with sixteen shy sisters.
"""

あっという間にできましたね。
「笛を吹き、羊と遊んで暮して来た。」の部分の主語が「I(私)」になってしまっていたり、邪悪に対して敏感なのがメロスではなく人々(people)になっていたり、
元々の文の主語が省略されていた部分については少し誤訳があるように感じますが、翻訳サービスとしてはやむを得ない部分もあるでしょう。

今回のコードでは、元の言語をSourceLanguageCode="ja"と指定しましたが、ここは”auto”とすることもできます。


result = client.translate_text(
    Text=text,
    SourceLanguageCode="auto",
    TargetLanguageCode="en",
)

この場合、 Amazon Comprehend を使って自動的に元のテキストの言語を推定して翻訳してくれます。
元の言語を何と推定したかは、結果のオブジェクトから取得できます。きちんと日本語(ja)になったようです。


print(result["SourceLanguageCode"])
"ja"

一度に翻訳できるテキストの長さは、5000byteまでです。5000文字ではないので特に僕ら日本人は注意が必要です。
試しに、元のテキスト(191文字)を20回繰り返して長文を作って翻訳にかけてみます。


long_text = text*20
print(len(long_text))
# 3820

try:
    result = client.translate_text(
        Text=long_text,
        SourceLanguageCode="ja",
        TargetLanguageCode="en",
    )
except client.exceptions.TextSizeLimitExceededException as e:
    print(e)

"""
An error occurred (TextSizeLimitExceededException) when calling the TranslateText operation:
Input text size exceeds limit.
Max length of request text allowed is 5000 bytes while in this request the text size is 11020 bytes
"""

出力されたエラーメッセージを読んでいただけるとわかりますが、5000バイトが上限なのに、11020バイト渡されたと言うエラーになっていますね。
しかし、リクエストしたテキストは3820文字です。

明らかに短いテキストを翻訳にかける場合は問題ないですが、そうでなければ、
translate_text を呼び出す前にチェックを入れるか、
上のコードみたいに例外処理を加えた方が良いでしょう。

LightsailのWordPressの開発環境を立てる

このBlogはWordPressの設定を最低限度にしか変更せずに運用しています。(記事執筆時時点)
ただ、それでも少々修正したい部分は発生しており、その一部はどうも管理画面では修正できず、PHPファイルを修正する必要があるようです。
(具体的に挙げと、フッターの、 「プライバシーポリシー Proudly powered by WordPress」 の部分など)

僕自身が普段はPHPを書いておらず、WordPressに不慣れなのに本番環境を直接いじって修正するのはリスクが高すぎるので、
開発環境が欲しいなと思ったので調べてみました。

このBlogはLightsailで動いているので、同じようにLightsailでWordpressインスタンスを立てて、
今入れているテーマやプラグインを追加していくのも方法の一つです。

ただ、そのようなことををしなくてもこのサーバーをそのままコピーできるらしいことがわかりました。

手順は以下の通りです。
1. Lightsail の管理画面に入り、このBlogが動いているインスタンスを選択。
2. スナップショットを選択し、手動スナップショットをとる。(自動スナップショットを取っている人はこの手順は不要)
3. 取得したスナップショットから、「新規インスタンスを作成」

これで、記事等も全部入った状態でインスタンスのコピーが立ち上がります。

あとは、
http://{IPアドレス}/
でアクセスしたら、開発環境にアクセスできて万歳、となると思っていたのですが、そうはならず、
何度試しても、本番環境(このBlog)にリダイレクトされてしまいました。

原因は以前設定したリダイレクト設定です。
参考: httpのアクセスをhttpsにリダイレクトする

開発サーバーにSSHログインし、bitnami.confに参考記事で追記した3行を消し、apacheを再起動すると、開発環境にアクセスできるようになります。

お金がかかるので、必要な修正の検討が終わったら作った開発環境とスナップショットは消しておきましょう。

Amazon Linux 2 (EC2)にphpMyAdminを導入

MySQLやその互換のDBを管理するphpMyAdminという便利なツールがあります。
僕が私用で使っている Aurora Serverless の管理もphpMyAdminで行おうとしたら、予想よりも苦戦したのでそのメモです。
参考: Amazon Aurora Serverlessを使ってみる

昔、Amazon Linux (無印)のインスタンスを建てたときは、ApachとPHPを入れて、phpMyAdminのソースファイルを配置しただけで
当時使っていた同じインスタンス内のMySQLの管理をすぐできるようになりました。
しかし当時の手順を元に、Amazon Linux 2で同様の操作を行ったら全然うまくいきませんでした。

改めて手順を探してみると、「チュートリアル: Amazon Linux 2 に LAMP ウェブサーバーをインストールする
というドキュメントが用意されており、これを参考にすることでうまくいきました。
(このチュートリアルでは、同サーバーにMariaDBを入れますが、僕はRDSのAurora Serverlessを使うので、MariaDB関係の手順は省略します。)

EC2インスタンス作成

最初にインスタンスを作ります。
DBをEC2インスタンスにインストールしている場合はそのインスタンスを使えば良いのでこの手順は不要です。

Amazon Linux 2 の AMIを選択し、通常通りインスタンスを作成します。
sshログインと、Webアクセスができるように、22番と80番のポートが開いたセキュリティグループを設定しておきます。

インスタンスができたら、sshで入って、yumをアップデートします。


$ sudo yum update -y

PHPのインストール

最初のハマりポイントがここです。
普通に、 
$ yum install -y php
とすると、バージョン 5.4.16 の非常に古いPHPが入ってしまいます。

チュートリアルに沿って、 amazon-linux-extras と言うのを使って 7系のPHPをインストールします。


$ sudo amazon-linux-extras install -y php7.2

# 入ったバージョンの確認
$ php -v
# 以下出力
# PHP 7.2.34 (cli) (built: Oct 21 2020 18:03:20) ( NTS )
# Copyright (c) 1997-2018 The PHP Group
# Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies

Apacheのインストール

PHPの次は Apache のインストールと起動設定です。
Amazon Linux 2になって、無印の時とコマンドが変わっているので注意が必要です。


# Apache のインストール
$ sudo yum install -y httpd
# 起動
$ sudo systemctl start httpd
# サーバー起動時に自動的に起動するようにする
$ sudo systemctl enable httpd

以前は、起動は
$ service httpd start
で、自動起動は、
$ chkconfig httpd on
でしたね。

この時点でWebサーバーは立ち上がっているので、ブラウザを起動し、
http://{サーバーのIPアドレス}
にアクセスできることを確認します。

PHPの動作確認

ApacheでPHPが動くことを確認します。

昔は、Apacheの設定ファイル( /etc/httpd/conf/httpd.conf ) に
<FilesMatch \.php$>
SetHandler applocation/x-httpd-php
</FilesMatch>
など書いて設定しないと動かなかった覚えがあるのですが、それをしなくても動きます。

さて、動作確認に進みましょう。
ドキュメントルート(つまり、 /var/www/html)に移動し、phpinfo.phpというテキストファイルを生成します。


$ cd /var/www/html
$ vim phpinfo.php

# phpinfo.php に以下の内容を書き込む。
<?php phpinfo(); ?>

ブラウザから
http://{サーバーのIPアドレス}/phpinfo.php
にアクセスし、PHPのバージョンやシステム情報などのテーブルが表示されることが確認できたらPHPは動作しています。

phpMyAdminのインストール

ここから、phpMyAdmin本体のインストールです。

まず、phpの依存モジュールをインストールします。
(これはyumでいいらしいです。不思議です。)


# 必要な依存ファイルをインストール
$ sudo yum install php-mbstring -y
# Apache を再起動
$ sudo systemctl restart httpd
# php-fpm を再起動
$ sudo systemctl restart php-fpm

次に、phpMyAdminの本体ファイルをダウンロードして配置します。
ec2-userはドキュメントルート配下に書き込み権限を持っていないと思うので、
権限設定を適切にするか、面倒であればルートになって作業しましょう。


# ドキュメントルートに移動
$ cd /var/www/html
# phpMyAdminのファイルを取得する。
$ wget https://www.phpmyadmin.net/downloads/phpMyAdmin-latest-all-languages.tar.gz
# ファイル展開
$ mkdir phpMyAdmin && tar -xvzf phpMyAdmin-latest-all-languages.tar.gz -C phpMyAdmin --strip-components 1
# 不要ファイル削除
$ rm phpMyAdmin-latest-all-languages.tar.gz

以前は、 phpMyAdminのダウンロードページに訪問して、
最新バージョンを調べて、
wget https://files.phpmyadmin.net/phpMyAdmin/5.0.4/phpMyAdmin-5.0.4-all-languages.tar.gz
みたいに、バージョン指定して落としていたのですが、 latest で取れたんですね。
これは知りませんでした。

あとはブラウザで、
http://{IPアドレス}/phpMyAdmin/
にアクセスし、ログイン画面が表示されればインストールできています。

同サーバー内のDBの管理をするのであればこのまま使えます。

RDSへの接続を設定する

さて、冒頭の通り、僕が管理したいサーバーはAurora Serverlessなので、その設定を行います。
方法は設定ファイルにエンドポイントを指定するだけです。
まず、設定ファイルを作成します。
デフォルトでは設定ファイルは存在しておらず、config.sample.inc.phpと言うテンプレートを、
config.inc.php という有効なファイル名にコピーして使います。


# デフォルトの設定ファイルをコピーして設定ファイルを生成する
cd /var/www/html/phpMyAdmin
cp config.sample.inc.php config.inc.php

/var/www/html/phpmyadmin/config.inc.php の以下の行を書き換える
# 元の記述
$cfg['Servers'][$i]['host'] = 'localhost';
# 修正後の記述
$cfg['Servers'][$i]['host'] = '{RDSのエンドポイント}';

ログイン確認

以上の操作が全て終わったら、実際にphpMyAdminにログインして確認します。

ユーザーとパスワードは RDSのものを使います。
Aurora Serverless なので、停止状態の場合は起動に少し時間がかかりますが、数分程度待てば無事にログインできます。

EC2の不要なインスタンスの消し方

とても単純な話なのですがかなり迷ったので備忘録的に残しておきます。

「インスタンスの削除」的なメニューがあるもんだと思ってずっと探していましたが、
実際には 「インスタンスの終了」 がそれにあたります。 (削除という文言がないのでなかなか気づきませんでした。)

消したいインスタンスを右クリックし、「インスタンスの状態」 => 「インスタンスの終了」 と選択することで消せます。
消してもしばらくはコンソール上に残るようです。

「終了保護」が設定されていると、終了ができないので、本当に消したい場合は事前に外しておきましょう。

boto3でEC2インスタンスを起動してIPアドレスを取得する

EC2のインスタンスを操作するとき、(会社ではCLIのバッチファイルを手元に持ってるのですが)私物の環境ではいつもコンソールにログインして起動/停止していたので、
これもバッチ化することにしました。
AWS CLIではなく、 Pythonライブラリの boto3で作ってみます。

職場の環境と異なり、自分の学習環境のEC2はIPアドレスを固定していません。(お金かかるから。)
なので、毎回コンソールから起動して、IPアドレスを確認し、そのIPアドレスに接続する、という作業を行っていました。
これを短縮するため、起動したらIPアドレスを取得して表示できるようにします。

ドキュメントはこの辺りが参考になります。
ec2.html#instance

以下のように、ec2インスタンスのオブジェクトを用意します。


import boto3

ec2 = boto3.resource('ec2')
instance = ec2.Instance('{インスタンスID}')

そして、startで起動してwait_until_running()で起動完了を待ち、
public_ip_addressを取得すれば良いです。

完成したバッチファイルの中身は次のようになります。
これを.pyファイルとして保存して、実行できるように権限を744にします。


#!/usr/bin/env python
import boto3
instance_id = "{インスタンスID}"

ec2 = boto3.resource('ec2')
instance = ec2.Instance(instance_id)
print("インスタンス起動開始")
instance.start()
instance.wait_until_running()
print("インスタンス起動完了")
ip = instance.public_ip_address
print(f"IPアドレス: {ip}")

実行するときちんと、
インスタンス起動開始
インスタンス起動完了
IPアドレス: xx.xxx.xxx.xxx
が表示されました。

ついでですが、停止バッチは次のようになります。


#!/usr/bin/env python
import boto3
instance_id = "{インスタンスID}"

ec2 = boto3.resource('ec2')
instance = ec2.Instance(instance_id)
print("インスタンス停止開始")
instance.stop()
instance.wait_until_stopped()
print("インスタンス停止完了")

Amazon Aurora Serverlessを使ってみる

ずっと前から気になっていたまま、使ったことがなかったのですが重い腰を上げて Aurora Serverless を触ってみることにしました。

用途は仕事ではなく、自分の趣味と勉強でとあるデータを蓄積するDBです。
AWSのアカウント持っていてDBが必要なら、RDSを使うのが順当なのですが、
通常のRDSは個人で使うにはちょっとお高いので、これまではEC2に導入したMySQLを使っていました。
そこにAurora の Serverless が登場ということで、自分の分析作業時のみの課金で使えるとなれば非常にお得な気がします。
完全に移行できるかとコストはトータルどうなるか、といった懸念は残りますが、まずは一度触ってみることにしました。

Aurora Serverless にはいくつか制限事項があるようです。
それを満たせるように事前に準備を進めていきます。

詳細はこちらを参照: Aurora Serverless の制約事項

1. VPC (Virtual Private Cloud)

Aurora Serverless にパブリックなIPアドレスを割り当てることはできず、おなじVPC内からしかアクセスできないそうです。
なので、ローカルの端末から踏み台等使わずに直接繋ぐことはできなさそうですね。
また、EC2インスタンスををクライアントにする場合は、おなじVPCの中に置いておく必要があります。
(僕はVPCはデフォルトの1個しか使っていないので、この制限は特に意識することはありませんでした。
VPCを複数使っている人は注意が必要です。)

2. AWS PrivateLink エンドポイント

僕がこの辺りの用語に疎いので、ドキュメントをそのまま引用します。
各 Aurora Serverless DB クラスターには、2 つの AWS PrivateLink エンドポイントが必要です。VPC 内の AWS PrivateLink エンドポイントが制限に達した場合、その VPC にそれ以上 Aurora Serverless クラスターを作成することはできません。
要は、VPCにサブネットを2つ以上用意しておく必要があります。
これも自分の場合は元々東京リージョンに3個持っていたので特に作業の必要はありませんでした。

3. セキュリティグループ

ドキュメントの通り、3306番のポートを利用するので、「インバウンド」で、3306番ポートが開通したセキュリティグループが必要になります。
セキュリティグループの設定画面で「タイプ」に「MYSQL/Aurora」を選択すると自動的に必要な設定が入るので作っておきます。

4. クライアント

実際の利用にはDBに接続するクライアントが必要です。
MySQLか、その互換のMariaDBクライアントが入ったインスタンスを同じVPC内に立てておきましょう。

さて、準備が終わった後、実際に DBを作成するのですが、これは簡単でした。
手順にすると多いですが画面に沿って進むだけなのでほぼ迷いません。

1. AWSのコンソールにログインし、RDSの管理画面に移動する。
2. [データベースの作成]をクリック
3. 標準作成を選択 (簡単作成でも良い)
4. エンジンのタイプ は [Amazon Aurora]を選択
5. エディション は [MySQLとの互換性を持つ Amazon Aurora]
6. MySQLのバージョンを選ぶ。
  これは、ドキュメントで指定されているバージョンでないと、次の選択肢からサーバレスが消えるので注意が必要です。

7. データベースの機能 は [サーバーレス]
8. DBクラスター識別子 は何か名前をつける。
9. マスターユーザー名 を指定 (デフォルトは admin)
10. パスワードの設定。 (自分はパスワードの自動作成にしました)
11. キャパシティーの設定を変更。(予算を抑えるため気持ち低めにしました。)
12. [データベースの作成]をクリック

ここまで進むとDBの作成が始まり、数分でDBが出来上がります。
出来上がったら、[認証情報の詳細を表示] から、 adminのパスワードを入手します。
また、同時に エンドポイント もわかるのでこれも記録しておきます。

DBが作成できたら、クライアントの環境から以下のコマンドで接続できます。


mysql -h {エンドポイント} -u {ユーザー名} -p

Enter password: 
と聞かれるのでパスワードを入力する。

ポートはデフォルトの3306を使っているので、 -P 3306 はつける必要ありません。(つけても大丈夫ですが。)

これで、通常のDBとして、使用できるようになりました。

もしDBが作成できているのに接続できなかった場合、セキュリティグループを見直してみてください。
(僕は用意していたセキュリティグループと違うグループが付与されていてしばらく詰まりました。)

あとは、残る課題はお値段ですね。
しばらく使ってみて、どのくらいの金額になるのか様子をみようと思います。

※ 以下、追記
2~3日ほど放置した後、様子を見たらメキメキと課金されていました。触っていない間も稼働しっぱなしだったようです。

設定項目の中に、「数分間アイドル状態のままの場合コンピューティング性能を一時停止する」
というがあり、これにチェックを入れないと、期待してた使っていない間は停止する効果が得られないようです。
てっきりデフォルトだと思っていたので危なかったです。
(請求金額アラートに救われました)

Amazon Comprehend でエンティティ認識

Amazon Comprehend シリーズの3記事目です。今回はエンティテイ認識をやってみます。
キーフレーズ抽出とかなりかぶるのですが、抽出した要素に対して人物なのか場所なのか時間なのかのフラグがつく点がメリットと言えるでしょう。

使うboto3のメソッドは、対象の文章が1つなら、detect_entities() で、
複数(ただし25個まで)なら batch_detect_entities()です。

比較のために前回の記事と全く同じテキストに対して実行してみました。


# サンプルテキスト。
# これで括弧内のテキストが連結されて1つの文字列になる。
text= (
    "メロスは激怒した。必ず、かの邪智暴虐の王を除かなければならぬと決意した。"
    "メロスには政治がわからぬ。メロスは、村の牧人である。笛を吹き、羊と遊んで暮して来た。"
    "けれども邪悪に対しては、人一倍に敏感であった。"
    "きょう未明メロスは村を出発し、野を越え山越え、十里はなれた此のシラクスの市にやって来た。"
)

import boto3
comprehend = boto3.client("comprehend")
result = comprehend.detect_key_phrases(Text=text, LanguageCode="ja")

import boto3
comprehend = boto3.client("comprehend")

entities = comprehend.detect_entities(Text=text, LanguageCode="ja")
for entity in entities["Entities"]:
    print(entity)

"""
{'Score': 0.9998800754547119, 'Type': 'PERSON', 'Text': 'メロス', 'BeginOffset': 0, 'EndOffset': 3}
{'Score': 0.9998433589935303, 'Type': 'PERSON', 'Text': 'メロス', 'BeginOffset': 36, 'EndOffset': 39}
{'Score': 0.9998514652252197, 'Type': 'PERSON', 'Text': 'メロス', 'BeginOffset': 49, 'EndOffset': 52}
{'Score': 0.7808283567428589, 'Type': 'QUANTITY', 'Text': '人一倍', 'BeginOffset': 90, 'EndOffset': 93}
{'Score': 0.6928854584693909, 'Type': 'DATE', 'Text': '未明', 'BeginOffset': 104, 'EndOffset': 106}
{'Score': 0.9995416402816772, 'Type': 'PERSON', 'Text': 'メロス', 'BeginOffset': 106, 'EndOffset': 109}
{'Score': 0.506858766078949, 'Type': 'LOCATION', 'Text': '十里', 'BeginOffset': 124, 'EndOffset': 126}
{'Score': 0.9970796704292297, 'Type': 'LOCATION', 'Text': 'シラクス', 'BeginOffset': 132, 'EndOffset': 136}
{'Score': 0.6032651662826538, 'Type': 'LOCATION', 'Text': '市', 'BeginOffset': 137, 'EndOffset': 138}
"""

キーフレーズ抽出に比べて抽出された単語は少ないですが、 PERSON とか、 LOCATION といったTypeが付与されています。
ドキュメントによると、Typeは次の値を取りうる様です。

– ‘PERSON’
– ‘LOCATION’
– ‘ORGANIZATION’
– ‘COMMERCIAL_ITEM’
– ‘EVENT’
– ‘DATE’
– ‘QUANTITY’
– ‘TITLE’
– ‘OTHER’,

Amazon Comprehend でキーフレーズ抽出

前回の記事に続いて Amazon Comprehend の話です。
今度はキーフレーズ抽出をやってみます。

ドキュメントは同じところを参照します。
Comprehend — Boto3 Docs 1.14.32 documentation

Amazon Comprehend の特徴ページによると、
キーフレーズ抽出 API は、キーフレーズまたは会話のポイント、およびそれがキーフレーズであることを裏付ける信頼性スコアを返します。
とのことです。

早速やってみましょう。使うboto3のメソッドは、
detect_key_phrases() か、batch_detect_key_phrases()です。
それぞれ、単一テキストを対象とするか、テキストのリストを対象とするかの違いです。
ほとんど同じなので、今回は1テキストだけやってみることにしました。
サンプルにはいつのもメロスの最初の方の文章を使います。(全テキストで実行したら5000byteの制限によりエラーになりました。)


# サンプルテキスト。
# これで括弧内のテキストが連結されて1つの文字列になる。
text= (
    "メロスは激怒した。必ず、かの邪智暴虐の王を除かなければならぬと決意した。"
    "メロスには政治がわからぬ。メロスは、村の牧人である。笛を吹き、羊と遊んで暮して来た。"
    "けれども邪悪に対しては、人一倍に敏感であった。"
    "きょう未明メロスは村を出発し、野を越え山越え、十里はなれた此のシラクスの市にやって来た。"
)

import boto3
comprehend = boto3.client("comprehend")
result = comprehend.detect_key_phrases(Text=text, LanguageCode="ja")

for key_phrase in result["KeyPhrases"]:
    print(key_phrase)

"""
{'Score': 0.9999992847442627, 'Text': 'メロス', 'BeginOffset': 0, 'EndOffset': 3}
{'Score': 0.5104176998138428, 'Text': 'かの', 'BeginOffset': 12, 'EndOffset': 14}
{'Score': 0.9680508971214294, 'Text': '邪智暴虐の王', 'BeginOffset': 14, 'EndOffset': 20}
{'Score': 0.9999995231628418, 'Text': 'メロス', 'BeginOffset': 36, 'EndOffset': 39}
{'Score': 0.9999402761459351, 'Text': '政治', 'BeginOffset': 41, 'EndOffset': 43}
{'Score': 0.9999988079071045, 'Text': 'メロス', 'BeginOffset': 49, 'EndOffset': 52}
{'Score': 0.9999657869338989, 'Text': '村の牧人', 'BeginOffset': 54, 'EndOffset': 58}
{'Score': 0.9999977350234985, 'Text': '笛', 'BeginOffset': 62, 'EndOffset': 63}
{'Score': 0.9999823570251465, 'Text': '羊', 'BeginOffset': 67, 'EndOffset': 68}
{'Score': 0.9975282549858093, 'Text': '人一倍', 'BeginOffset': 90, 'EndOffset': 93}
{'Score': 0.8310525417327881, 'Text': 'きょう未明', 'BeginOffset': 101, 'EndOffset': 106}
{'Score': 0.9997578263282776, 'Text': 'メロス', 'BeginOffset': 106, 'EndOffset': 109}
{'Score': 0.9997960925102234, 'Text': '村', 'BeginOffset': 110, 'EndOffset': 111}
{'Score': 0.9999336004257202, 'Text': '野', 'BeginOffset': 116, 'EndOffset': 117}
{'Score': 0.9991182088851929, 'Text': '十里', 'BeginOffset': 124, 'EndOffset': 126}
{'Score': 0.9819957613945007, 'Text': '此のシラクスの市', 'BeginOffset': 130, 'EndOffset': 138}
"""

ものすごく簡単に使えましたね。
一応重要な名詞は拾えてる様な気がしますし、王様は「邪智暴虐の王」として抽出できています。
ただ抽出したテキストだけなど意味がわからないので使い道に悩むところです。
BeginOffset と、 EndOffset は、その該当テキストを切り出すスライスに使える様です。

こんな感じです。


print(text[14: 20])
# 邪智暴虐の王

print(text[54: 58])
# 村の牧人

Amazon Comprehend でテキストのセンチメント分析

いつのまにか Amazon Comprehend の感情分析が日本語に対応しているのを見つけたので試してみました。

Amazon Comprehend というのは AWSが提供している自然言語処理の機械学習サービスです。
すでに学習済みのモデルが用意されており、利用者はデータを渡すだけで、感情分析(ポジネガ)や、キーフレーズの抽出ができます。

少し前までは日本語は言語判別のみ対応していて、センチメント分析するには一度対応してる言語に翻訳する必要があったはずなのですが、今は日本語も使えます。

ということで、Pythonからこれを動かしてみましょう。

・準備
AWSのPython SDKである boto3が動くように環境をつくっておきます。
主な作業は、 Comprehend の権限を持ったIAMの作成と、そのAccess Key と Secret Access Keyの設定でしょうか。
(僕は環境変数に入れました。)

これで動くはずなのでやってみます。
ドキュメントはこちらです。
Comprehend — Boto3 Docs 1.14.32 documentation

テキストは青空文庫の走れメロスを拝借しました。
まず、1文のポジネガを判定します。使うメソッドは、 detect_sentimentです。
本当に簡単に使えます。


import boto3

# 試すテキストを準備する
text1 = "メロスは激怒した。必ず、かの邪智暴虐の王を除かなければならぬと決意した。"
text2 = "メロスは、村の牧人である。笛を吹き、羊と遊んで暮して来た。"

comprehend = boto3.client("comprehend")
for t in [text1, text2]:
    comprehend_result = comprehend.detect_sentiment(Text=t, LanguageCode="ja")
    print(t)
    print(comprehend_result["Sentiment"])
    for k, v in comprehend_result["SentimentScore"].items():
        print(f"    {k}: {v}")
    print()

# 以下出力結果
"""
メロスは激怒した。必ず、かの邪智暴虐の王を除かなければならぬと決意した。
NEGATIVE
    Positive: 0.00021381396800279617
    Negative: 0.8228716850280762
    Neutral: 0.17690636217594147
    Mixed: 8.087589776550885e-06

メロスは、村の牧人である。笛を吹き、羊と遊んで暮して来た。
NEUTRAL
    Positive: 0.0011265947250649333
    Negative: 6.331767508527264e-05
    Neutral: 0.9988085031509399
    Mixed: 1.5915205722194514e-06
"""

みて分かる通り、 Positive(肯定的)/ Negative(否定的)/ Neutral(中立的)/ Mixed(混在) の4つの感情の割合を返してくれます。

複数のテキストをまとめて判定する関数も用意されています。
それが、batch_detect_sentimentです。

A list containing the text of the input documents. The list can contain a maximum of 25 documents. Each document must contain fewer that 5,000 bytes of UTF-8 encoded characters.

とある通り、 25個までのテキスト(それぞれ5000バイト未満)を順番に判定してくれます。

走れメロスの中に登場する会話文を順番に判定かけてみましょう。

準備として、以下のコードでメロスの会話文の一覧を取得します。


import requests
from bs4 import BeautifulSoup
import re

# 青空文庫 走れメロスのURL
url = "https://www.aozora.gr.jp/cards/000035/files/1567_14913.html"
response = requests.get(url)
# 文字化け対応
response.encoding = response.apparent_encoding
html = response.text

soup = BeautifulSoup(html)

# ルビを取り除く
for tag in soup.findAll(["rt", "rp"]):
    # タグとその内容の削除
    tag.decompose()

# ルビを取り除いたテキストを取得
text = soup.find(class_="main_text").get_text()

# 改行を消す。
text = text.replace("\r\n", "")
text = text.replace("\n", "")
# 全角スペースを消す
text = text.replace("\u3000", "")

# カッコの内側の文字列を抽出する正規表現パターン
speech_pattern = re.compile("「([^「」]+)」")

# カッコの内側の文字列取得
speech_texts = speech_pattern.findall(text)

全部で62文あるので3回に分けて取得します。
結果は扱いやすい様にPandasのDataFrameに入れておきましょう。


import pandas as pd

sentiment_list = []
positive_list = []
negative_list = []
neutral_list = []
mixed_list = []

for i in range(3):
    target_texts = speech_texts[25*i: 25*(i+1)]
    comprehend_result = comprehend.batch_detect_sentiment(
        TextList=target_texts,
        LanguageCode="ja"
    )
    result_list = comprehend_result["ResultList"]

    for r in result_list:
        sentiment_list.append(r["Sentiment"])

        positive_list.append(r["SentimentScore"]["Positive"])
        negative_list.append(r["SentimentScore"]["Negative"])
        neutral_list.append(r["SentimentScore"]["Neutral"])
        mixed_list.append(r["SentimentScore"]["Mixed"])

# 結果をDataFrameに変換
df = pd.DataFrame(
    {
        "text": speech_texts,
        "sentiment": sentiment_list,
        "positive": positive_list,
        "negative": negative_list,
        "neutral": neutral_list,
        "mixed": mixed_list,
    }
)

こうして、走れメロスの全台詞に対してポジネガのフラグをつけることができました。
ちなみに最もポジティブだったセリフはこちらです。

text ありがとう、友よ。
sentiment POSITIVE
positive 0.998629
negative 1.88228e-05
neutral 0.00135026
mixed 1.96162e-06

そして、最もネガティブだったのがこれ。

text ‘もう、駄目でございます。むだでございます。走るのは、やめて下さい。もう、あの方をお助けになることは出来ません。
sentiment NEGATIVE
positive 0.000276001
negative 0.996889
neutral 0.00283329
mixed 1.40147e-06
Name: 46, dtype: object

全テキストの結果を見ていくと流石に「ん?」と思う様なものもあるのですが、
概ねしっかりと感情分析ができてる様に思います。

LightsailのMySQLに接続する

このブログはAWSのLightsailを使って構築しています。
ちょっと訳あって、そのサーバーのDB(MySQL)に接続したくなったのですが、
少しつまずいたのでメモです。

mysqlは勝手に構築してくれているので初期パスワードに何が設定されているのか、わかりませんでした。

結論から言うと
user : root
pass : bitnami_application_password ファイルの中身
で接続できます。
このwordpressの管理画面と同じ初期パスワードだったんですね。

わかるまで、サーバーにログインし、

\$ mysql
\$ mysql -u user
\$ mysql -u root
など試しましたが、ログインできず。

パスワードとして心当たりがあるのが管理画面のパスワードだけだったので、

\$ mysql -u root -p
とコマンドを打った後、bitnami_application_passwordの中身を入力したら接続できました。

念のため設定変えておこう。