LoginSignup
6
1

概要

はじめに

これは 富士通クラウドテクノロジーズ株式会社 Advent calendar 2022 の10日目の記事になります。
昨日は @nori3636 さんでwslでminikubeの環境構築をする話 とでした。
Kubernetesは自分も少し触った程度でまだ使いこなせていないので、自分も環境を構築していろいろ試してみたくなりましたね。

本題

皆さん、人はふとしたときに本を読みたくなるものですが、明るい内容だったり暗い内容だったりその時の気分によって読みたい本の傾向は変わってきますよね。

書店にあなたが足を運んだならば、きっと店頭に並んでいる本の表紙を見て気になる本を見つけることでしょう。

しかし、ネット上でフリーで公開されている 青空文庫の書籍には表紙がありません。気になった本をジャケ買いならぬ、ジャケ読みすることもできないのです。

だったら小説の内容から自動で本表紙が作成できるようにしましょう。

作ったもの

人類がぱっと見で内容を理解するためにはやはり、本の内容を図示する必要があります。
そのため、書籍のテキストデータから頻出単語を抽出してtext-to-imageの深層学習モデルを使ってその書籍のイメージ画像を生成しました。

結果として「人間失格」では強気な女性が、「人間椅子」では椅子に腰かける女性がそれぞれ出力されました。
あなたが今読みたいのはどちらの本ですか?
人間失格.png人間椅子.png

実装

作業環境

Google Colaboratory

必要なライブラリのインストール

!pip install accelerate
!pip install wordcloud spacy SudachiPy ja-ginza
!pip install git+https://github.com/rinnakk/japanese-stable-diffusion

pythonライブラリのインポート

import urllib.request
from bs4 import BeautifulSoup
import re
import regex
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

from wordcloud import WordCloud
import spacy
from sudachipy import tokenizer
from sudachipy import dictionary

import torch
from torch import autocast
from diffusers import LMSDiscreteScheduler
from japanese_stable_diffusion import JapaneseStableDiffusionPipeline

関数の実装

青空文庫からのテキスト抽出

こちらは 過去に自分が作ったものを流用
青空文庫の書籍のURLを与えると、そのテキスト情報だけを前処理して返してくれます

def get_txt_from_aozorabunko(url):
  html = urllib.request.urlopen(url=url)
  soup = BeautifulSoup(html, "html.parser")
  sentences = soup.find("div","main_text") # 本文を取得する → <div class="main_text">~本文~</div>
  sentences = sentences.get_text().replace("\r", "").replace("\n", "").replace("\u3000", "") # 文字部分のみを抽出する
  sentences = re.sub("(.*?)", "", sentences) # 全角の括弧に囲われた文字と括弧を除去(ルビが括弧文字として存在するため)
  return sentences

テキストデータからの頻出単語抽出

今回は頻出単語として抽出する品詞を「名詞、形容詞、形容動詞」に限定しました。
また、表紙の生成にあたりキーワードとなる単語が多いほうがある程度の複雑さが生じるのではないかという仮説から、1つの小説当たり30個の単語を抽出するようにしています。

書籍の中の文章をそのまま分かち書きすることはできないため、一旦読点(「。」)ごとにテキストを分割して処理を実施しています。

def extract_words(text, nlp, tokenizer_obj, mode):
    p = regex.compile(r'\p{Block=Hiragana}{1,2}') # ひらがな2文字以下で構成されている文字列を指定する正規表現
    words = []
    doc = nlp(text)
    
    for sent in doc.sents:
        for token in sent:
            if p.fullmatch(token.lemma_):
                # ひらがな2文字以下の単語の除去
                continue
            if token.pos_ in ["NOUN", "ADJ", "ADV"]:
                word = token.lemma_
                word = tokenizer_obj.tokenize(noun, mode)[0].normalized_form() # 単語の正規化
                words.append(noun)
    if words == []:
        return None
    return ' '.join(words)

def preprocess_sentences(sentences):
  list_sentence = sentences.split("")
  list_processed_sentence = []

  nlp = spacy.load('ja_ginza')
  tokenizer_obj = dictionary.Dictionary().create()
  mode = tokenizer.Tokenizer.SplitMode.C

  for sentence in list_sentence:
    words = extract_words(sentence, nlp, tokenizer_obj, mode)
    if words == None:
      pass
    else:
      list_processed_sentence.append(words)
  return " ".join(list_processed_sentence)

def extract_keywords_from_sentences(sentences):
  keywords = WordCloud(collocations = False, # 複合語のオプションをオフ
            random_state=1).process_text(sentences)
  return keywords

def get_frequent_words(url):
  sentences = get_txt_from_aozorabunko(url)
  nouns = preprocess_sentences(sentences)
  keywords = extract_keywords_from_sentences(nouns)
  df = pd.DataFrame.from_dict(keywords, orient='index').reset_index()
  df.columns = ["keyword", "frequency"]
  df = df.sort_values("frequency", ascending=False)
  words = df.head(30)["keyword"].values
  words = " ".join(words)
  return words

頻出単語情報からの表紙画像生成

Hugging Faceへのログイン

今回はtext-to-imageモデルとして日本語のキーワードが入力可能なHugging Faceで提供されている、日本語版stable-diffusionを利用しました。利用にあたり環境での認証が必要なので実行します。

# Hugging Face へのログイン ユーザー登録が必要
!huggingface-cli login

実行すると以下のようにTokenの入力を求められるのでリンク先でユーザー登録とログインをしてTokenを取得。一度登録を済ませれば以降は同じTokenで認証が可能なようです。


    _|    _|  _|    _|    _|_|_|    _|_|_|  _|_|_|  _|      _|    _|_|_|      _|_|_|_|    _|_|      _|_|_|  _|_|_|_|
    _|    _|  _|    _|  _|        _|          _|    _|_|    _|  _|            _|        _|    _|  _|        _|
    _|_|_|_|  _|    _|  _|  _|_|  _|  _|_|    _|    _|  _|  _|  _|  _|_|      _|_|_|    _|_|_|_|  _|        _|_|_|
    _|    _|  _|    _|  _|    _|  _|    _|    _|    _|    _|_|  _|    _|      _|        _|    _|  _|        _|
    _|    _|    _|_|      _|_|_|    _|_|_|  _|_|_|  _|      _|    _|_|_|      _|        _|    _|    _|_|_|  _|_|_|_|

    To login, `huggingface_hub` now requires a token generated from https://huggingface.co/settings/tokens .
    
Token: 

関数作成

これで実行環境の準備は整ったので関数を作成します。この際、keywordsは先ほどまでの関数群によってい生成されている想定です。
今回は表紙を抽象的な絵画のようにしてほしいなと思っているので、わざと入力する文字列の頭に抽象画という文字列を追加しています。
また、ColabでGPUの機能をONにしていないとこちらの関数の実行時にエラーが生じます(忘れててcuda用のデバイスが無いぞって言われました)。

def setup_model():
  model_id = "rinna/japanese-stable-diffusion"
  device = "cuda"
  # Use the K-LMS scheduler here instead
  scheduler = LMSDiscreteScheduler(beta_start=0.00085, beta_end=0.012, beta_schedule="scaled_linear", num_train_timesteps=1000)
  pipe = JapaneseStableDiffusionPipeline.from_pretrained(model_id, scheduler=scheduler, use_auth_token=True)
  pipe = pipe.to(device)
  return pipe

def generate_cover(pipe, keywords):
  keywords = "抽象画 " + keywords
  with autocast("cuda"):
      image = pipe(keywords, guidance_scale=7.5).images[0]
  return image

また、余談ですがjapanese-stable-diffusion公式のUsageには以下のようにサンプル画像を参照できるように記載がありますが、

image = pipe(prompt, guidance_scale=7.5)["sample"][0] 

上記を実行するとそんなものは無いとエラーを吐いてくるので、

image = pipe(keywords, guidance_scale=7.5).images[0]

正しくは上記のようにすることで画像を参照できます。

実行

これで準備は整いました。あとは実行していくだけです。

pipe = setup_model()# stable-diffusion のモデルセットアップ
url = "https://www.aozora.gr.jp/cards/000148/files/773_14560.html" # 青空文庫のURL指定、これは「こころ」
words = get_frequent_words(url)# 頻出語の抽出
image = generate_cover(pipe, words)# 画像の生成、確認
plt.imshow(image)

実行例

人間失格

頻出単語は
自分 事 無い 子 人間 女 人 顔 時 気 父 家 酒 者 気持ち 中 所 罪 ちゃん 道化 言葉 頃 実 矢張り 一人 所謂 男 部屋 絵 目
となり、なんだか強そうな女性のイメージが生成されました。
抽象画という入力を加えているからか、しっかり絵画風の出力が得られています。

人間失格.png

風立ちぬ。

頻出単語は
私達 中 自分 少し 目 方 日 気 上 病人 間 顔 手 前 父 雪 達 谷 風 殆ど 山 幸福 一人 サナトリウム 急 小さな 窓 いつも 月 もっと
となり、どこか暖かくもさみしさも感じる一枚に仕上がっています。木の葉なのか空の雲なのか境界が曖昧な描写が個人的には結構好きな出力結果です。
風立ちぬ.png

こころ

頻出単語は
先生 事 奥さん 時 父 自分 人 方 気 母 前 御嬢 中 所 目 顔 上 言葉 間 二人 今 同じ 日 心 通り 妻 少し 口 後 人間
案の定「先生」というフレーズが最も使われていました。そして出力結果にはなんとも言えない表情の二人が出力されています。ここに写っているのは先生なのか奥さんなのか、はたまた他の登場人物とも思わせられる意味深な一枚です。
こころ1.png

人間椅子

頻出単語は
椅子 中 様 時 手紙 人 上 世界 不思議 人間 外 ホテル 間 奥様 方 男 御 肉体 非常 美しい 部屋 夫人 前 身体 余り 膝 若し 目 為 大きな
ご婦人が椅子の上に座っているというシンプルな構図です。この椅子の中に、いえ、この先はやめておきましょう。
人間椅子.png

結果

今回は青空文庫のテキストデータと最近流行りの画像生成AIモデルを使って、「青空文庫の小説の表紙をAIに自動生成させる」 ことにチャレンジしてみました。

結果としてはかなりよさげで、もし自動生成された表紙が一覧でならんでいたら青空文庫の本をジャケ読みすることも可能になるのではないでしょうか。

一方、課題としては出力される画像に余白のような空間が残ってしまう点を感じました。text-to-imageモデルへのテキストデータの出力時には、SNSで「呪文」と呼ばれているように特定のキーワードを用いた画像の調整が必要になってきます。今回は「抽象画」というキーワードを用いてある程度結果を期待に近い形に工夫をしましたが、それ以外にも色々な工夫ができそうです。

気になった方はぜひ今回の記事を参考にご自分の手元で実践してみてください。

終わりに

こちらは富士通クラウドテクノロジーズ株式会社 Advent calendar 2022 の10日目の記事でした。

明日は @ks2022 さんの「チームで障害訓練した話」です。
自分自身、現在はクラウドサービスの品質管理に関わっているので内容が気になるところですね。

また、FJCTのTechブログエンジニアへのインタビュー記事を執筆したりもしているのでよければそちらもご覧ください。

それでは、良い年末を。

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1