トップ «前の日記(2013-06-08) 最新 次の日記(2013-06-10)» 編集

ヨタの日々

2001|08|09|10|11|12|
2002|01|02|03|04|05|06|07|08|09|10|11|12|
2003|01|02|03|04|05|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|
2021|01|02|03|04|05|06|07|08|09|10|11|12|
2022|01|02|03|04|05|06|07|08|09|10|11|12|
2023|01|02|03|04|05|06|07|08|12|
2024|01|02|03|04|

2013-06-09 :-)

_ 午前

0900 起床

1010 おひる。カルボナーラ

1030 コーヒー

1100 アニメ消化

IMG_1918

IMG_1920

_ 午後

1400 スクレイピング

1500 散歩

スイカが成っている。

IMG_1925

IMG_1927

IMG_1921

_

1700 係り受け解析

2130 飯。豚の角煮

2200 マンゴー

IMG_1930

IMG_1931

_ [はてな][スクレイピング][ruby][mechanize]はてなブログをスクレイピングする

# coding: utf-8

# hatenablog をスクレイピング
#
# 使い方:
#   hatenablog.rb <hatenablog URI> [カテゴリ]
#
# 例:
#  ruby hatenablog.rb http://jkondo.hatenablog.com/ > jkondo.txt
#
#  ruby hatenablog.rb http://dennou-kurage.hatenablog.com/ 仕事観 > kurage.txt
#


require 'mechanize'
require 'uri'
require 'pp'

def get_text(uri)
  agent = Mechanize.new
  agent.get(uri)
  
  texts = ""
  
  while true
    agent.page.at("//div[@class='entry-content']").children.each {|node|
      text = node.text
      texts << text
    }
    link = agent.page.link_with(:text => '次のページ')
    break if link == nil
    agent.page.link_with(:text => '次のページ').click
  end

  return texts

end


def main(argv)
  uri_base = argv[0]
  cat = ""
  if argv[1] != nil
    cat = "category/" + URI::encode(argv[1])
  end
  uri = uri_base + cat
  text = get_text(uri)
  puts text
end

main(ARGV)

_ [自然言語処理][係り受け解析][構文解析][ruby][NLP]テキストを係り受け解析する

前提

テキスト解析サンプルコード:係り受け解析 - Yahoo!デベロッパーネットワーク

日本語係り受け解析API の制限が以下のようになっている。

日本語係り受け解析Web APIは、24時間以内で1つのアプリケーションIDにつき50000件のリクエストが上限となっています。また、1リクエストの最大サイズを4KBに制限しています。詳しくは「利用制限」をご参照ください。

UTF-8 エンコード 1 文字は最大 4 バイトでエンコードされるとする。

それを URI.encode すると最大 12 バイトになると見積もる

( たとえば 4 バイト 0xAA 0xBB 0xCC 0xDD であるとすると、これが URI.encode されるとテキストで表現されるので %AA %BB %CC %DD の 12 文字、つまり 12 バイトになる )

↓リクエストパラメータのうち &sentence= までで 152 バイトある。

http://jlp.yahooapis.jp/DAService/V1/parse?appid=<あなたのアプリケーションID>&sentence=

↓リクエストに指定できる sentence としては 3848 バイト。

(4 * 1000) - 152 = 3848

上述のように 1 文字 12 バイトとして 320 文字まで指定できる。

3848 / 12 = 320

320 文字はギリギリなので、これをプログラムで扱うときは。まあざっくり 300 文字までとしてみる。

与えられたテキストが長文だった場合を考慮して、最初に「。」でぶったぎっておいて、300 文字を超えないように文を再度連結していく。しかしそもそも「。」までが 300 文字を超えるような文もあるわけだが、それは無視する(どこで区切るのか割りと重要かもそれんけどよく分からないのでスルー)。

実装

とりあえず作ったのがこちら。

syntactic.rb としておく。

# coding: utf-8

# Yahoo デベロッパーネットワーク の 日本語係り受け解析API を使う
# http://developer.yahoo.co.jp/webapi/jlp/da/v1/parse.html

require 'uri'
require 'open-uri'
require 'rexml/document'
require 'pp'

module NLP
  module Syntactic

    SENTENCE_LENGTH_MAX = 300

    def parse(xml)
      syntactic ||= []
      
      doc = REXML::Document.new(xml)
      doc.elements.each('ResultSet/Result/ChunkList/Chunk') do |chunk|
        chunks = ""
        chunk.elements.each('MorphemList') do |ml|
          ml.elements.each("Morphem/Surface") do |mo|
            chunks << mo.text
          end
        end
        syntactic << chunks
      end
      
      return syntactic
    end

    def build_sweep(text)
      text.gsub!("\n", "")
      text.rstrip!
      text.lstrip!
      text.gsub!(/\A +/, "")
      text.gsub!(/ /, "")
      text.chomp!
      text.gsub!(/[\r\n]/, "")
      return text
    end

    def build_sentence(text)

      text = build_sweep(text)

      request_sentence ||= []
      
      if text.length < SENTENCE_LENGTH_MAX
        request_sentence << text
      else
        lump = ""
        text.split(/。/).each {|sentence|
          s = sentence + "。"
          if (lump + s).length < SENTENCE_LENGTH_MAX
            lump << s
          else
            request_sentence << lump
            lump = ""
            lump << s
          end
        }
      end
      
      return request_sentence
    end    

    # 文章が長いのは無視
    def _analysis(text)
      apiuri = "http://jlp.yahooapis.jp/DAService/V1/parse"
      appid = "?appid=" + ここにあたなのAPI IDを入れる
      sentence = "&sentence=" + URI.encode(text)
      request_uri = apiuri + appid + sentence

      syntactics = ""

      begin
        response = open(request_uri).read()
        syntactics = parse(response)
      rescue => e
      end
      return syntactics
    end


    def analysis(text)
      syntactics ||= []
      sentences = build_sentence(text)
      sentences.each {|s|
        syntactics << _analysis(s)
      }

      return syntactics
    end

  end
end

使うときはこう。

# coding: utf-8

#
# 与えられたファイルを係り受け解析する
#

require 'pp'

require './syntactic'
include NLP::Syntactic

def build(text)
  return NLP::Syntactic::analysis(text)
end

def read(text)
  File.open(text).read
end

def main(argv)
  infile = argv[0]
  text = read(infile)
  syntactic = build(text)
  puts syntactic.join("\n")
end

main(ARGV)

_ [ベイズ][自然言語処理][ナイーブベイズ][ruby][NLP]文章をナイーブベイズする

以前作業しておいたナイーブベイズ[ 20130505#p04 ] を利用する。

# -*- encoding: utf-8 -*-

#
# ナイーブベイズを用いたテキスト分類 - 人工知能に関する断創録
# http://aidiary.hatenablog.com/entry/20100613/1276389337
#


def maxint()
  return 2 ** ((1.size) * 8 -1 ) -1
end

def sum(data)
  return data.inject(0) {|s, i| s + i}
end

include Math
require 'pp'
require 'json'
require 'yaml'

module NLP

  # Multinomial Naive Bayes
  class NaiveBayes


    def initialize()
      # カテゴリの集合
      @categories = []

      # ボキャブラリの集合
      @vocabularies = []
      
      # wordcount[cat][word] カテゴリでの単語の出現回数
      @wordcount = {}

      # catcount[cat] カテゴリの出現回数
      @catcount = {}
      
      # denominator[cat] P(word|cat)の分母の値
      @denominator = {}
    end
    
    # ナイーブベイズ分類器の訓練
    def train(data)
      data.each {|d|
        cat = d[0]
        @categories << cat
      }
      
      @categories.each {|cat|
        @wordcount[cat] ||= {}
        @wordcount[cat].default = 0
        @catcount[cat] ||= 0
      }
      
      # 文書集合からカテゴリと単語をカウント
      data.each {|d|
        cat, doc = d[0], d[1, d.length-1]

        @catcount[cat] += 1
        doc.each {|word|
          @vocabularies << word
          @wordcount[cat][word] += 1
        }
      }
      
      @vocabularies.uniq!
      
      # 単語の条件付き確率の分母の値をあらかじめ一括計算しておく(高速化のため)
      @categories.each {|cat|
        s = sum(@wordcount[cat].values)
        @denominator[cat] =  s + @vocabularies.length
      }

    end
    
    
    # 事後確率の対数 log(P(cat|doc)) がもっとも大きなカテゴリを返す
    def classify(doc)
      best = nil
      max = -maxint()
      @catcount.each_key {|cat|
        _p = score(doc, cat)
        if _p > max
          max = _p
          best = cat
        end
      }
      
      return best
    end
    
    # 単語の条件付き確率 P(word|cat) を求める
    def wordProb(word, cat)
      return (@wordcount[cat][word] + 1).to_f / (@denominator[cat]).to_f
    end
    
    # 文書が与えられたときのカテゴリの事後確率の対数 log(P(cat|doc)) を求める
    def score(doc, cat)
      # 総文書数
      total = sum(@catcount.values)
      
      # log P(cat)
      sc = Math.log((@catcount[cat]) / total.to_f)
      doc.each {|word|
        # log P(word|cat
        sc += Math.log(wordProb(word, cat))
      }
      return sc
    end
    
    # 総文書数
#    def to_s()
#      total = sum(@catcount.values) 
#      return "documents: #{total}, vocabularies: #{@vocabularies.length}, categories: #{@categories.length}"
#    end
  end


end # end of module


if __FILE__ == $0

  # Introduction to Information Retrieval 13.2の例題
  data = [
    ["yes", "Chinese", "Beijing", "Chinese"],
    ["yes", "Chinese", "Chinese", "Shanghai"],
    ["yes", "Chinese", "Macao"],
    ["no", "Tokyo", "Japan", "Chinese"]
  ]
  
  # ナイーブベイズ分類器を訓練
  nb = NLP::NaiveBayes.new
  nb.train(data)
  p nb
  puts "P(Chinese|yes) = #{nb.wordProb('Chinese', 'yes')}"
  puts "P(Tokyo|yes) = #{nb.wordProb('Tokyo', 'yes')}"
  puts "P(Japan|yes) = #{nb.wordProb('Japan', 'yes')}"
  puts "P(Chinese|no) = #{nb.wordProb('Chinese', 'no')}"
  puts "P(Tokyo|no) = #{nb.wordProb('Tokyo', 'no')}"
  puts "P(Japan|no) = #{nb.wordProb('Japan', 'no')}"
  
  # テストデータのカテゴリを予測
  test = ['Chinese', 'Chinese', 'Chinese', 'Tokyo', 'Japan']
  puts "log P(yes|test) = #{nb.score(test, 'yes')}"
  puts "log P(no|test) = #{nb.score(test, 'no')}"
  puts nb.classify(test)
end

_ [構文解析][係り受け解析][社畜][ベイズ][自然言語処理][ナイーブベイズ][ruby][NLP]文章が社畜的かどうかをナイーブベイズ推定する

準備

社畜的文章の学習データとして 脱社畜ブログ のカテゴリ 仕事観 を利用する。

非社畜的文章の学習データとして jkondoのはてなブログ を利用する。

先ほどのコード[ 20130609#p04 ]でスクレイピングする。

% ruby hatenablog.rb http://jkondo.hatenablog.com/ > jkondo.txt
% ruby hatenablog.rb http://dennou-kurage.hatenablog.com/ 仕事観 > kurage.txt

先ほどのコード[ 20130609#p05 ]で係り受け解析しておく

% ruby parse.rb kurage.txt > kurage.2.txt
% ruby parse.rb jkondo.txt > jkondo.2.txt

判定の実装

先ほどのナイーブベイズのコード [ 20130609#p06 ] を利用する。

企業戦士として名高い クラウド・ストライフさん のセリフを社畜判定してみる。( FFシリーズ セリフ人気投票 )

# coding: utf-8

require './naivebayes'
require './syntactic'
include NLP::Syntactic

def get_words(text)
  words = NLP::Syntactic::analysis(text)
  words.flatten!
  words.map! {|w|
    w.gsub(/[、。\n]/, "")
  }
  return words
end


def build_learning(filepath, cat)
  lines = File.open(filepath).readlines()
  lines.map! {|w|
    w.gsub(/[、。\n]/, "")
  }
  data = [cat, *lines]
  return data
  
end

def classify(nb, text)
  words = get_words(text)
  cat = nb.classify(words)
#  puts nb.score(words, "社畜")
#  puts nb.score(words, "人間")
  return "#{text} => #{cat}"
end


def main(argv)

  shatiku_file = argv[0]
  not_shatiku_file = argv[1]
  shatiku_data = build_learning(shatiku_file, "社畜")
  not_shatiku_data = build_learning(not_shatiku_file, "人間")
  
  nb = NLP::NaiveBayes.new
  nb.train([shatiku_data])
  nb.train([not_shatiku_data])

  text = %w(
    興味ないね
    エアリスはもう喋らない・・・笑わない・・・泣かない、怒らない・・・!
    大切じゃない物なんか無い!
    俺は俺の現実を生きる
    俺は、お前の生きた証だ
    指先がチリチリする。口の中はカラカラだ。目の奥が熱いんだ!
    オレが・・・お前の生きた証・・・
    引きずりすぎて少しすり減ったかな・・・
    お前の分まで生きよう。そう決めたんだけどな…
    もう・・・揺るがないさ・・
    帰るぞ
    俺は幻想の世界の住人だった。でも、もう幻想はいらない……俺は俺の現実を生きる
    ここに女装に必要な何かがある。おれにはわかるんだ。いくぜ!
    星よ・・・降り注げ!!
    罪って…許されるのかな?
    まだ終わりじゃない…終わりじゃないんだ!
    俺はクラウド、ソルジャークラス1st
  )
  
  text.each {|t|
    puts classify(nb, t)
  }

end

main(ARGV)

実行

% ruby shatiku.rb kurage.2.txt jkondo.2.txt
興味ないね => 社畜
エアリスはもう喋らない・・・笑わない・・・泣かない、怒らない・・・! => 社畜
大切じゃない物なんか無い! => 社畜
俺は俺の現実を生きる => 社畜
俺は、お前の生きた証だ => 社畜
指先がチリチリする。口の中はカラカラだ。目の奥が熱いんだ! => 社畜
オレが・・・お前の生きた証・・・ => 社畜
引きずりすぎて少しすり減ったかな・・・ => 人間
お前の分まで生きよう。そう決めたんだけどな… => 社畜
もう・・・揺るがないさ・・ => 社畜
帰るぞ => 社畜
俺は幻想の世界の住人だった。でも、もう幻想はいらない……俺は俺の現実を生きる => 社畜
ここに女装に必要な何かがある。おれにはわかるんだ。いくぜ! => 人間
星よ・・・降り注げ!! => 社畜
罪って…許されるのかな? => 社畜
まだ終わりじゃない…終わりじゃないんだ! => 人間
俺はクラウド、ソルジャークラス1st => 社畜

ふむ。