1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Avilenプロダクト課題(コナン映画のレビューを自然言語処理,二値分類)

Posted at

はじめに

E資格取得に向けて受講したAvilenのプロダクト課題についての記事となります。
私個人としてはAvilenのE資格修了プログラムの大きな特徴の一つと考えております。
正直に言いますと長期間かけたプロダクトではないため、あまり完成度が高いと言えません。
また作成から2ヶ月近く経過しているので記憶が薄れている部分もある点はご容赦ください。
あくまで課題のレベル感を掴み、尚且つ4月から聴講開始し、8月受験を目指すためのモデルスケジュールとして参考にしてください。
※就職や転職を目指す方にはこのプログラムで作成したプロダクトをポートフォリオに記載するのも良いかと思います。

<対象読者>
・E資格取得を目指しており、受講プログラムとしてAvilenを候補として考えている方
・現在Avilenのプログラムを受講しており、プロダクト課題に悩まれている方
・Avilen卒業生

スケジュール

はじめにAvilenの修了条件は下記3つになります。

  • コーディング試験全問題修了
  • 修了試験(E資格本番に近い形式):70点以上
  • プロダクト課題の認定

私は6月半ばの段階でコーディング試験が完了し、残すところは修了試験とプロダクト課題になっておりました。この段階で講師の方と相談し、まずは修了試験を確実に合格し、その後プロダクト課題の合格を目指すように言われまし
た。
修了試験はE資格本番相当の難易度があり、相応の準備期間が必要な一方で、プロダクト課題については自身で課題を決めて作成するため難易度調整が可能であるためです。
修了試験は2回目にてなんとか合格点をもぎ取りました。(Avilen修了試験は2回落ちると追試になり、追加料金が発生します。しっかり勉強して臨みましょう。)

プロダクト課題テーマ選定 

  • 画像処理:MNIST、CIFAR10などデータセットが揃っており取り組みやすい。コーディング演習で散々取り組むため、実装もそこまでハードルは高くなさそう。

  • 自然言語処理:実装はそこまでハードル高くなさそうだが、データセットの用意が大変そう?

  • 生成モデル:MNISTやCIFAR10の高解像度化などがテーマとして思い浮かんだが実装に自信が無かった。。。

  • 強化学習:個人的には最も興味があったのですが、講義内で実装について全く触れられず、レベル感がわからないのがネック。

取り組みやすいのは画像かなー、と思ったのですが仕事柄画像分類はこれから先もやる機会はあると思い、折角ならば少し苦手意識のあった自然言語処理による分類問題を取り組むこととしました。

データセットについて

自然言語処理に取り組むのに当たって、データセットをどう集めるかが問題でした。
分類問題を行うに当たってアノテーションの手間が課題になります。個人的にはこのアノテーションに手間をかけたくないなーと思いました。

アノテーション済みのデータを取得するに当たっては、口コミサイトを利用するのが良いと思い、下記のコードで映画.comからコナン映画(ほぼ毎年放映しているため、たくさんデータあるでしょという安易な考えのもと)のレビューとレーティングからデータセットを作成することとしました。
データセットとしては去年までの口コミを訓練データ、今年の口コミを検証データとしました。(ドメインシフトについては深く考えていませんw)

def scrape_reviews(url):
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
    }
    resp = requests.get(url, headers=headers)
    resp.raise_for_status()
    soup = BeautifulSoup(resp.text, "html.parser")

    reviews = []
    # レビュー見出しは <h2> または <h3> タグ
    for heading in soup.find_all(["h2", "h3"]):
        full = heading.get_text(strip=True)  # 例: "3.5小五郎推しは必見" :contentReference[oaicite:0]{index=0}
        m = re.match(r"^(\d+(?:\.\d+)?)(?=\S)", full)
        if not m:
            continue
        rating = float(m.group(1))

        # 次の見出しタグまでの間に出てくる <p> をすべて本文として抽出
        parts = []
        for elem in heading.next_elements:
            if isinstance(elem, Tag) and elem.name in ["h2", "h3"]:
                break
            if isinstance(elem, Tag) and elem.name == "p":
                text = elem.get_text(separator="\n", strip=True)
                if text:
                    parts.append(text)

        # 本文が空ならスキップ
        if not parts:
            continue

        review_text = "\n\n".join(parts)
        reviews.append({
            "rating": rating,
            "text": review_text
        })

    return reviews

モデル構築

Word2vecの使用

今回は日本語のレビューでしたので、下記のトークナイザを利用しました。

j_t = Tokenizer()

def tokenizer_janome(text):
    return [tok for tok in j_t.tokenize(text,wakati=True)]

text = "我輩は猫である。名前はまだ無い。"
tokenizer_janome(text)

正規化

一応、表記揺れの修正のために正規化しましたが、あまり要らなかったかもしれません。

import re

def preprocessing_text(text):
    #改行、半角スペース、全角スペースの削除
    text = re.sub("\r","",text)
    text = re.sub("\n","",text)
    text = re.sub(" ","",text)
    text = re.sub(" ","",text)
    text = re.sub("\n\n","",text)

    return text

正規化と単語分割を合わせて関数化

def tokenizer_with_preprocessing(text):
    text = preprocessing_text(text)
    ret = tokenizer_janome(text)

    return ret

#動作確認
text = "昨日は とても暑く、気温が36℃もあった。"
print(tokenizer_with_preprocessing(text))

単語のベクトル化

ここら辺はコーディング試験を参考にしたような気がします。

from gensim.models import KeyedVectors


# 一度gensimライブラリで読み込んで、word2vecのformatで保存する
model = KeyedVectors.load_word2vec_format(
    './data/entity_vector/entity_vector.model.bin', binary=True)

# 保存(時間がかかります、10分弱)
#model.save_word2vec_format('./data/japanese_word2vec_vectors.vec')3.png)

データフレームに含まれる語彙の作成

出現頻度が2未満の単語は切り捨てて、辞書の作成を実施

from collections import Counter

# 例: df_pos, df_neg の "tokens" 列に、すでにリスト化したトークンが入っている想定
all_tokens = []
for tokens in df_train["tokens"]:
    all_tokens.extend(tokens)
#for tokens in df_neg["tokens"]:
    #all_tokens.extend(tokens)

# 出現頻度カウント
counter = Counter(all_tokens)

# 最低出現回数(min_freq)以上を語彙に残す
min_freq = 2
vocab_tokens = [tok for tok, cnt in counter.items() if cnt >= min_freq]

# 特殊トークンを先頭に
specials = ["<pad>", "<unk>"]
itos = specials + sorted(vocab_tokens)
stoi = {tok: i for i, tok in enumerate(itos)}

print(f"語彙サイズ: {len(stoi)}")

Step1:双方向LSTM@SGD

とりあえず双方向LSTM、最適化モデル:SGDで学習させてみましたが、全く学習が進まず。
スクリーンショット 2025-09-03 23.01.41.png

Step2:Adam導入

とりあえず最適化モデルが良く無かったのかと考えて、Adamに変更。
訓練モデルの学習は進みましたが、検証モデルですぐに過学習し始めてました。
この後DropOutなど試みますが、そんなに精度は変わらず。最終的にアーリーストッピングだけに留めます。
(Attentionモデルも試してみましたが、こちらも精度はほとんど改善されませんでした・・・)
スクリーンショット 2025-09-03 22.43.53.png

Step3:考察

LIME(Local Interpretable Model-agnostic Explanations)で個々の予測に対して、与えた影響を可視化しました。
どの入力が予測に影響を与えたかを可視化する手法になります。

Example20では期待を下回ったという文章にもかかわらず、POSに貢献していることになっています。これは期待という単語が入った文章の多くがPOSだったため、この単語の影響で過学習したなどという考察をしました。
合わせて[unk],即ち未知語・専門用語がPOS要素に貢献している場合が多く、これは熱心なファンはこういった語を使うため未知語の影響により誤判定を起こした可能性を考察しました。

スクリーンショット 2025-09-03 23.09.18.png
スクリーンショット 2025-09-03 23.12.23.png

総評

2ヶ月ぶりに当時作成したプロダクト課題を見直してみて、当時の自身のスキルの無さ、知識の無さを実感しております。
現在は主に画像処理に取り組んでおりますが、Dropout入れたり、有効なデータオーギュメンテーションを選択できるくらいにはなります。
これからプロダクト課題に挑戦する方はこれくらいのレベル感でも良いんだとハードルを下げる効果があれば幸いです。

1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?