Y's note

Web技術・プロダクトマネジメント・そして経営について

本ブログの更新を停止しており、今後は下記Noteに記載していきます。
https://note.com/yutakikuchi/

魔法少女まどか☆マギカN-Gram

小説 魔法少女まどか☆マギカ (まんがタイムKRノベルス)

小説 魔法少女まどか☆マギカ (まんがタイムKRノベルス)

まどマギN-Gram抽出

魔法少女まどか☆マギカN-Gramを抽出したデータを記載します。目的はテキスト機械学習用のデータ抽出です。N=3〜7で試してみました。台詞のデータは以下のサイトをPythonでスクレイプ、N-Gramの解析にはC++で行いました。
魔法少女まどか☆マギカ WIKI - トップページ はてなブックマーク - 魔法少女まどか☆マギカ WIKI - トップページ

まどマギ台詞

台詞をスクレイピングするプログラムを記載します。プログラムをmadmagi_scrape.pyとして保存します。Pythonプログラムを実行するとmadmagi.txtというファイルができるので中身を確認します。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys,re,urllib,urllib2
urls = ( 'http://www22.atwiki.jp/madoka-magica/pages/170.html',
         'http://www22.atwiki.jp/madoka-magica/pages/175.html',
         'http://www22.atwiki.jp/madoka-magica/pages/179.html',
         'http://www22.atwiki.jp/madoka-magica/pages/180.html',
         'http://www22.atwiki.jp/madoka-magica/pages/200.html',
         'http://www22.atwiki.jp/madoka-magica/pages/247.html',
         'http://www22.atwiki.jp/madoka-magica/pages/244.html',
         'http://www22.atwiki.jp/madoka-magica/pages/249.html',
         'http://www22.atwiki.jp/madoka-magica/pages/250.html',
         'http://www22.atwiki.jp/madoka-magica/pages/252.html',
         'http://www22.atwiki.jp/madoka-magica/pages/241.html',
         'http://www22.atwiki.jp/madoka-magica/pages/254.html'
         )   
f = open( './madmagi.txt', 'w' )
opener = urllib2.build_opener()
ua = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/534.51.22 (KHTML, like Gecko) Version/5.1.1 Safari/    534.51.22'
referer = 'http://www22.atwiki.jp/madoka-magica/'
opener.addheaders = [( 'User-Agent', ua ),( 'Referer', referer )]
for url in urls:
    content = opener.open( url ).read()
    if re.compile( r'<div class="contents".*?>((.|\n)*?)</div>', re.M ).search( content ) is not None:
        data = re.compile( r'<div class="contents".*?>((.|\n)*?)</div>', re.M ).search( content ).group()
        if re.compile( r'「(.*?)」', re.M ).search( data ) is not None: 
            lines = re.compile( r'「(.*?)」', re.M ).findall( data )
            for line in lines:
                f.write( line + "\n" )
f.close()
$ python madmagi_scrape.py
$ cat madmagi.txt | head -n 30
んっん…あっ…!
あっ…!
ひどい…
仕方ないよ。彼女一人では荷が重すぎた
でも、彼女も覚悟の上だろう
そんな…あんまりだよ、こんなのってないよ
諦めたらそれまでだ
でも、君なら運命を変えられる
避けようのない滅びも、嘆きも、全て君が覆せばいい
そのための力が、君には備わっているんだから
本当なの?
私なんかでも、本当に何かできるの?こんな結末を変えられるの?
もちろんさ。だから僕と契約して、魔法少女になってよ!
私は巴マミ
あなたたちと同じ、見滝原中の3年生
そして
キュゥべえと契約した、魔法少女よ
はあーはぁ。うん
やあ
はい、これ
うわぁ…。いつも本当にありがとう。さやかはレアなCDを見つける天才だね
あっはは、そんな、運がいいだけだよ。きっと
この人の演奏は本当にすごいんだ。さやかも聴いてみる?
う、い、いいのかな?
本当はスピーカーで聴かせたいんだけど、病院だしね
ええぇー…
あっ
あら、上条君のお見舞い?
えっ、あっ、え、えぇ…
あ、ごめんなさいね。診察の予定が繰り上がって、今ちょうどリハビリ室なの。

まどマギ台詞N-Gram抽出

C++ Source

UTF8文字コードで保存されたファイルのマルチバイト文字列を読み込んで、N-Gramを算出するプログラムを掲載します。ファイルの読み込み対象は上で保存したmadmagi.txtというファイルです。ngram.hhというN-Gramを抽出するClassファイルとそれを利用するclient.ccというファイルを定義します。client.ccはコマンドライン引数でN-GramのNを決定します。また抽出されたテキストの数をカウントし多い順にsortしています。
ngram.hh

#include <iostream>
#include <string>
#include <vector>
#include <locale>
#include <algorithm>

using namespace std;
typedef pair<wstring,int> psi;

template<template <typename> class P = greater >
struct comp_pair_second {
  template<class T1, class T2> bool operator()( const std::pair<T1, T2>& left, const std::pair<T1, T2>& right ) {
    return P<T2>()( left.second, right.second );
  }
};

// NgramClass
class Ngram {
  private :
    unsigned int N;
    wstring ldata;
    vector<psi> ngram;

  public :

    // Constructor
    Ngram( unsigned n ) {
      N = n;
    }

    ~Ngram() {} 

    // linedataを設定
    void setLineData( wstring data ) {
      ldata = data;      
    }

    // N-Gram抽出
    void extractNgram() {
      wstring sub;
      bool hit;
      unsigned len = ldata.length();
      for( unsigned i=0; i<len; ++i ) {
        hit = false;
        sub = ldata.substr( i, N );
        for( vector<psi>::iterator itr = ngram.begin(); itr != ngram.end(); ++itr ) {
          if( (*itr).first == sub ) {
            (*itr).second++;
            hit = true;
            break;
          }
        }
        if( hit == false ) {
          ngram.push_back( make_pair( sub, 1 ) );
        }
      } 
    }

    // N-Gramを取得
    vector<psi> getNgram() {
      sort( ngram.begin(), ngram.end(), comp_pair_second<greater>() );
      return ngram;
    }

    // N-Gramを出力
    void printNgram() {
      sort( ngram.begin(), ngram.end(), comp_pair_second<greater>() );
      for( vector<psi>::iterator itr = ngram.begin(); itr != ngram.end(); ++itr ) {
        wcout << (*itr).first + L" ";
        wcout <<  (*itr).second <<  endl;
      }
    }
};

client.cc

#include <iostream>
#include <string>
#include <locale>
#include "ngram.hh"

#define BUF_SIZE 4096 
using namespace std;

int main(int argc, char* argv[] ) {
  
  // Local設定
  locale::global(locale("ja_JP.UTF-8"));
  
  // Nを決定
  unsigned int n = atoi( argv[1] );
  
  // wchar_t型を設定
  wchar_t buf[ BUF_SIZE ];
  wstring wsline;

  // Ngram Classの呼び出し
  Ngram ngram( n );

  // ファイルからの呼び出し
  FILE *fp = fopen( "./madmagi.txt", "r, ccs=UTF-8" );
  if( !fp ) {
   cout << "can not open" << endl;
   return 1;
  }
  
  // ファイルの出力
  while( fgetws( buf, BUF_SIZE, fp ) ) {
    wsline = wstring( buf );
    ngram.setLineData( wsline );
    ngram.extractNgram();
  }

  // file close
  fclose( fp );

  // Ngram出力
  ngram.printNgram();
  return 0;
}
3-Gram
$ ./a.out 3 > 3.txt
$ cat 3.txt
ちゃん 29
だから 26
さやか 23
魔法少 19
法少女 19
ないよ 19
じゃな 17
だって 16
ほむら 15
になっ 15
やかち 15
どうし 15
かちゃ 15
そんな 14
なんだ 14
なって 13
ちょっ 12
って、 12
から、 12
むらち 12
こんな 12
らちゃ 12
ゃない 12
ないの 11
ょっと 11
まどか 11
うして 11
いんだ 11
女にな 10
だった 10
ぇぇぇ 10
あなた 10
鹿目さ 10
んじゃ 10
ちゃっ 10
なくて 10
らない 10
本当に 9
みんな 9
4-Gram
$ ./a.out 4 > 4.txt
$ cat 4.txt
魔法少女 19
さやかち 15
やかちゃ 15
かちゃん 15
ほむらち 12
むらちゃ 12
らちゃん 12
じゃない 12
ちょっと 11
どうして 10
ぇぇぇぇ 9
マミさん 9
鹿目さん 9
だから、 9
少女にな 9
法少女に 9
になって 8
女になっ 8
、魔法少 7
になった 7
んだから 7
キュゥべ 6
ュゥべえ 6
、ほむら 6
と契約し 6
いいんだ 6
んだけど 6
んだって 6
。だから 6
ちゃん、 6
暁美さん 6
5-Gram
$ ./a.out 5 > 5.txt
$ cat 5.txt
さやかちゃ 15
やかちゃん 15
むらちゃん 12
ほむらちゃ 12
魔法少女に 9
法少女にな 9
ぇぇぇぇぇ 8
少女になっ 8
、魔法少女 7
キュゥべえ 6
、ほむらち 5
なんだから 5
女になった 4
ってないよ 4
を変えられ 4
ゃえばいい 4
女になって 4
変えられる 4
、さやかち 4
って言うの 4
6-Gram
$ ./a.out 6 > 6.txt
$ cat 6.txt
さやかちゃん 15
ほむらちゃん 12
魔法少女にな 9
法少女になっ 8
ぇぇぇぇぇぇ 7
、ほむらちゃ 5
少女になって 4
を変えられる 4
少女になった 4
、さやかちゃ 4
やかちゃん、 4
えられるの? 3
、魔法少女に 3
女をやっつけ 3
魔女をやっつ 3
て魔法少女に 3
して魔法少女 3
リーフシード 3
グリーフシー 3
ばいいんだよ 3
えばいいんだ 3
ゃえばいいん 3
7-Gram
$ ./a.out 7 > 7.txt
$ cat 7.txt
魔法少女になっ 8
ぇぇぇぇぇぇぇ 6
、ほむらちゃん 5
、さやかちゃん 4
法少女になった 4
さやかちゃん、 4
法少女になって 4
グリーフシード 3
えばいいんだよ 3
ゃえばいいんだ 3
、魔法少女にな 3
ほむらちゃんも 3
ら、ほむらちゃ 3
から、ほむらち 3
だから、ほむら 3
なのってないよ 3
んなのってない 3
こんなのってな 3
魔女をやっつけ 3
て魔法少女にな 3
して魔法少女に 3
どうしようもな 2

考察

3-Gramだと一般的な日本語単語カウントが多く抽出されてしまっていますが、4、5-Gramの抽出だと「魔法少女」、「キュゥべえ」などのまどマギ特有の名詞が抽出されています。6-Gramだと更に「さやかちゃん」、「ほむらちゃん」等の人物を特定できる単語が抽出されています。6、7-Gramの比較はあまり大差ないように思います。Nの調整により抽出される単語カウントが大きく変わってしまいますが、まどマギの台詞に限って言えば5、6-Gramを抽出すると特徴的な単語が抽出されるという結論になると思います。