Pythonでマルコフ連鎖を使って自分のツイートから新たな文章を生成する(2023年3月版)
はじめに
今年のエイプリルフールに、Twitterで「自分は実はAIでした」というネタをやりました。
そして、その一環として過去の自分のツイートからマルコフ連鎖を使用してツイートを生成してみました[1]。
せっかくなのでそのやり方を簡単にメモしておきます。
基本方針
基本的にこちらの記事を踏襲する形で作業していきます。
ただし、現在はTwitterからダウンロードできる自分の全ツイートはtweets.csv
ではなくtweets.js
に入っているため、主にその仕様変更に従って前処理を変更していく必要があります。
前処理
全ツイートのダウンロード
Twitter Web > 設定とプライバシー > アカウント > データのアーカイブをダウンロード
から、アーカイブをリクエストします。
詳しくは
こちらの記事などを参照してください。
数日後ぐらいにアーカイブが作成されたら通知が届きます。
アーカイブをダウンロードしたら、zipファイルを解凍し、中のdata/tweets.js
を任意の作業ディレクトリにコピーor移動しておいてください。
tweets.js
からツイートの抽出
前述のとおり、全ツイートはtweets.js
にJSON形式で入っていますので、そこから全ツイートを抽出する必要があります。
下記のPythonコードをtweets.js
と同じディレクトリに保存してください。
import json
import re
from datetime import datetime
read_since = '2020-04-01T00:00:00+09:00'
retweet_pattern = re.compile(r'RT .*\n$')
reply_pattern = re.compile(r'@[\w]+\s')
url_pattern = re.compile(r'https?:\/\/[\w/:%#\$&\?\(\)~\.=\+\-]+')
def exclude_specific_pattern(s: str):
s = retweet_pattern.sub('', s)
s = reply_pattern.sub('', s)
s = url_pattern.sub('', s)
return s
def escape_special_chars(s: str):
return (s.replace('\n', '\\n'))
def is_newer_than(twitter_date_str: str, comparison_date: datetime):
date_format = '%a %b %d %H:%M:%S %z %Y'
parsed_date = datetime.strptime(twitter_date_str, date_format)
return parsed_date > comparison_date
def load_tweets_json(tweets_js_file: str = 'tweets.js'):
with open(tweets_js_file, 'r', encoding='utf-8') as file:
content = file.read()
cleaned_content = content.replace('window.YTD.tweets.part0 = ', '', 1)
return json.loads(cleaned_content)
def write_tweets_txt(tweets_json, output_file: str = 'tweets.txt'):
since_date = datetime.fromisoformat(read_since)
with open(output_file, 'w', encoding='utf-8') as output:
for tw in tweets_json:
tweet = tw["tweet"]
if not is_newer_than(tweet["created_at"], since_date):
continue
tweet_text = escape_special_chars(tweet["full_text"]) + '\n'
tweet_text = exclude_specific_pattern(tweet_text)
output.write(tweet_text)
if __name__ == '__main__':
tweets_json = load_tweets_json()
write_tweets_txt(tweets_json)
このコードでは、
def exclude_specific_pattern(s: str):
s = retweet_pattern.sub('', s)
s = reply_pattern.sub('', s)
s = url_pattern.sub('', s)
return s
def escape_special_chars(s: str):
return (s.replace('\n', '\\n'))
の部分で
- 改行
\n
のサニタイズ - リツイートしたツイートの除去
- リプライやメンションに含まれるTwitter IDの除去
- URLの除去
をおこなっています。
このあたりは好みや目的によって適宜パターンを追加・削除してください。
また、「どの日時以降のツイートを抽出するか」を変更したい場合、下記の行を変更してください。
read_since = '2020-04-01T00:00:00+09:00'
ここで指定日時以降のツイートのみを抽出している理由としては、
- 抽出されたツイートが多すぎると、単純にこの後の処理が時間がかかって重くなってしまう
- あまり昔すぎるツイートを含めてしまうと、生活環境の変化などによって使用する単語が異なってきているため、生成する文章に「今の自分っぽさ」が出なくなってしまう[2]
といった理由です。
python extractTweetsJsToText.py
を実行して、同じディレクトリにtweets.txt
が生成されていることを確認します。
tweets.txt
には1行1ツイートの形式でツイートが抽出されています。
データベースの作成
MeCabのインストール
MeCabをインストールしていない方は、事前にこちらからインストールしておいてください。
また、辞書はUTF-8のものをインストールしておいてください。
同様に、PythonでMeCabが利用できるよう、
pip install mecab
などでPython用パッケージをインストールしておいてください。
文章生成プログラムのダウンロード
実際に文章を生成するプログラムとして、
ohshige15/TextGenerator: マルコフ連鎖を使った文章自動生成プログラム
こちらのPythonスクリプトを使用します。
git clone git@github.com:ohshige15/TextGenerator.git
などで上記リポジトリをクローンし[3]、中の
- GenerateText.py
- PrepareChain.py
- schema.sql
を作業ディレクトリにコピーしてください。
基本方針で挙げた参考記事にあるstoreTweetstoDB.py
を使用させていただき、データベースを生成します。
ただし、今回は抽出したツイートがCSVではなくテキストファイルにベタ書きされていますので、それに対応した修正を行っています。
下記は修正後のstoreTweetstoDB.py
です。
from PrepareChain import *
from tqdm import tqdm
def storeTweetstoDB():
with open('tweets.txt', 'r', encoding='utf-8') as f:
tweets = [line.strip() for line in f]
print(len(tweets))
chain = PrepareChain(tweets[0])
triplet_freqs = chain.make_triplet_freqs()
chain.save(triplet_freqs, True)
for i in tqdm(tweets[1:]):
chain = PrepareChain(i)
triplet_freqs = chain.make_triplet_freqs()
chain.save(triplet_freqs, False)
if __name__ == '__main__':
storeTweetstoDB()
上記コードを作業ディレクトリに保存し、
python storeTweetstoDB.py
を実行します。しばらく待つと、chain.db
が生成されます。
文章の生成
これで準備はできました。
試しに100個ほど文章を生成してみましょう。
from GenerateText import GenerateText
with open('generated_tweets.txt', 'w', encoding='utf-8') as f:
for _ in range(100):
generator = GenerateText()
tweet = generator.generate()
f.write(f'{tweet}\n')
上記コードを作業ディレクトリに保存し、
python generateMarkovTweets.py
を実行します。しばらく待つと、generated_tweets.txt
にマルコフ連鎖で生成された文章が100行ぶん保存されています。
あとは生成結果を眺めて楽しんだり、Botを作って自動でツイートさせたり、様々な楽しみ方ができるかと思います[4]。
おわりに
tweets.js
からのツイートの抽出は、他の用途、たとえば「Notionなどのメモアプリにツイートをバックアップしておき、後で検索しやすいようにしておく」など応用が効くかもしれないな、と思いました。
また、マルコフ連鎖による文章生成も、アイディア次第で様々なネタに使えそうです。
参考になれば幸いです。
参考文献
- マルコフ連鎖を使って自分らしい文章をツイートする
- 投稿した全てのツイートのデータをまとめてダウンロードする
- ohshige15/TextGenerator: マルコフ連鎖を使った文章自動生成プログラム
-
最初はChatGPTやNotion AIにツイートする文章を考えさせようと思っていたのですが、自分の過去のツイートを学習させても、あまり「自分っぽい」文章になりませんでした。やはり「自分っぽい」ツイートを生成するのはマルコフ連鎖が一番良いようです。 ↩︎
-
具体的には、たとえば自分の学生時代のツイートから単語を拾ってきてしまい、社会人になってからはめったに使わなくなった「講義」とか「学食」とかみたいな学生っぽい単語が含まれた文章が多数生成されてしまうなど。 ↩︎
-
参考にさせていただいたQiita記事では、Python3への対応が必要と記載されていましたが、自分が試した時点では既にPython3への対応が済んでおり、特に対応不要でした。 ↩︎
-
昨今のTwitter社によるAPIへの締め付け強化の影響もあり、筆者はリアルタイムで生成してツイートするBotを作成する方法をとれませんでした。そこで、泣く泣くあらかじめ生成した文章を公式Webクライアントの予約投稿でツイートする方式をとりました。 ↩︎
Discussion