nattoとclassifier-rebornを利用してRubyで文書分類
Pythonの統計・数学ライブラリを利用した文書分類はあるが、Rubyで実現できる方法を模索。
目的
文書(データ) の内容から、いくつかのグループ毎に自動的に分類する
手順
Gemfile
gem 'natto' gem 'classifier-reborn'
訓練データ (db/fixtures/train.csv)
内容は、分類, 文章
となる。
- データのバリデーションが多ければ多いほど精度が向上した
- 各分類間で訓練データ数に偏りがあるとうまく分類してくれないでの、A,B,C でそれぞれデータ数に偏りがないようにした
"A","ほげほげ..." "B","ほげほげ..." "C","ほげほげ..." "A","ほげほげ..." "C","ほげほげ..." ...
実装
require 'csv' namespace :classifier do desc 'classify' task classify: :environment do # 品詞分解して名詞を取得する関数 mecab = Natto::MeCab.new def mecab.filter(text) words = [] parse(text) do |n| next unless n.feature.match(/名詞/) next if n.feature.match(/(非自立|数|代名詞)/) next if n.surface.match(/\./) words << n.surface end words end features = ['A', 'B', 'C'] classifier = ClassifierReborn::Bayes.new(features, auto_categorize: true) # 訓練データから学習 csv = CSV.read('db/fixtures/train.csv') csv.each do |data| classifier.train(data[0], mecab.filter(data[1]).join(' ')) end # 分類 result = [] Product.all.each do |product| next if product.description.blank? # Product.descriptionカラムの内容から適切なfeatureに分類する feature_id = classifier.classify(mecab.filter(product.description).join(' ')) result << { product_id: product.id, feature_id: feature_id } end result end end
参考
- 作者: 土屋誠司
- 出版社/メーカー: 森北出版
- 発売日: 2015/12/01
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
- 作者: Willi Richert,Luis Pedro Coelho,斎藤康毅
- 出版社/メーカー: オライリージャパン
- 発売日: 2014/10/25
- メディア: 大型本
- この商品を含むブログ (5件) を見る
RubyでMarkdownのImgタグのサイズ指定
Markdownエディタを作成したので、次は表示をつくる。
前提
以下のGemを利用
実現したい事
![image](http://url.to/image.png = 250x250) ![image](http://url.to/image.png = 250x)
のようなMarkdownを、
<img src="http://url.to/image.png" height="250px" width="250px"> <img src="http://url.to/image.png" height="250px">
と、したい
参考
Resize image in the wiki of GitHub using Markdown - Stack Overflow
実装
Decorator
class HogeDecorator < Draper::Decorator ... def markdown_content markdown = Redcarpet::Markdown.new(CustomRender.new(with_toc_data: true), autolink: true, space_after_headers: true, tables: true, fenced_code_blocks: true) markdown.render(object.content).html_safe end ... end ... class CustomRender < Redcarpet::Render::HTML def image(link, title, alt_text) if link =~ /(.*)=(\d+)x(\d+|)/ "<img src='#{$1.strip}' width='#{$2}px' height='#{$3}px' title='#{title}' alt='#{alt_text}'>" else "<img src='#{link}' title='#{title}' alt='#{alt_text}'>" end end end
view
<div> <%= @hoge.markdown_content %> </div>
参考
- Image resizing · Issue #487 · vmg/redcarpet · GitHub
- Railsでカスタムmarkdownを実装する - k0kubun's blog
- RedcarpetでTOC表示 | | Scimpr Blog
こんなアプローチもあった
ruby on rails - Markdown external image links with Redcarpet - Stack Overflow
RailsでsimplemdeをつかってMarkdownエディタをつくる
RailsでMarkdownのエディタを実装するために、simplemdeを利用する。
前提
インストール
npm
でフロントのライブラリは管理しているので、npm install
でインストール。
$ npm install simplemde --save
実装
app/assets/javascripts/application.js
//= require simplemde/dist/simplemde.min.js
app/assets/stylesheets/application.scss
@import 'simplemde/dist/simplemde.min.css';
form
なぜかスペルチェックでエラーが出力されていたので無効にする
<%= form_for(hoge) do |f| %> <div class="form-group"> <%= f.text_area :foo, class: 'form-control', size: '20x15' %> </div> <div class='row justify-content-md-center'> <%= f.submit('投稿', class: 'btn btn-primary btn-lg') %> </div> <% end %> <script> var simplemde = new SimpleMDE({ element: document.getElementById('hoge_foo'), spellChecker: false}); </script>
スクリーンショット
はじめてのMarkdown―軽量マークアップ言語の記法と使い方 (I・O BOOKS)
- 作者: 清水美樹
- 出版社/メーカー: 工学社
- 発売日: 2014/05
- メディア: 単行本
- この商品を含むブログ (1件) を見る
include_all_helpersについて
include_all_helpersについてのメモ。
以下にあるとおりController名と同名のhelperしか読み込まないように設定をすると、ちょっと厄介なことがある。 qiita.com
HelperをもつGemを利用する場合。例えば、font-awesome-rails
。
github.com
課題
include_all_helpers
でhelperの読み込みに制限をかけるfont-awesome-rails
nofa_icon
ヘルパーを利用- エラーが発生
config/application.rb
module Hoge class Application < Rails::Application ... config.action_controller.include_all_helpers = false end end
view
<%= fa_icon 'envelope-o' %>
エラー
NoMethodError - undefined method
fa_icon' for …` が発生
解決
application_helper
に font-awesome-rails
に include
をすれば良い。
app/helpers/application_helper.rb
module ApplicationHelper include FontAwesome::Rails::IconHelper ...
ただGemのHelperを利用する度に、このを設定していくのは面倒なので、ひとまず include_all_helpers = false
はやめる。
ActiveModelのベストプラクティスを考える
ActiveModelとは
ActiveModelとは こちら にあるように Modelと直接紐付けないような、フロントのフォームに密接に関わるバリデーションを実装する場合に便利な Module。
課題
ActiveModel
を利用シーンの一つとして、一つフォームで複数のモデルを扱うことがある。
その際によく迷うことがある。
- モデルのバリデーションが
ActiveModel
とApplicationRecord
のモデルとで重複する - データの作成と編集で
ActiveModel
のintialize
にどうやって、何を渡すか(ここらへん情報が検索してもあまり見つからなかった)
解決方法
そこで、とりあえず考えた解決方法
ActiveModel
に対象のモデル群を内包するfind
,save
,valid?
を実装ActiveModel
のバリデーションはフォーム固有のものだけを実装し、モデルで実装されているバリデーションはモデルに任せるController
はcreate
,update
はフォームの内容を,edit
の場合は保存されている内容からフォームを作成できるようにするsave
時にvalid?
を実行し、内包するモデルのバリデーションエラーとフォームのバリデーションエラーを全てフォームのエラーに追加する
以下イメージ。実際の動作確認はしてないです
class HogeController < ApplicationController der new @hoge_form = HogeForm.new() end der create @hoge_form = HogeForm.new(params[:hoge_form]) if @hoge_form.save ... else ... end end def edit @hoge_form = HogeForm.find(parmas['id']) end def update ... end ... calss HogeForm include ActiveModel attr_accessor :hoge_a, :foo_b validates :hoge_a, presence: true validates :foo_b, presence: true class << self def find(id) hoge = Hoge.find id new(hoge: hoge.attributes, foo: hoge.foo.attributes) end end def intialize(params = {}) @hoge = Hoge.new(params[:hoge]) @foo = Foo.new(params[:foo]) end def valid?(context = nil) super @hoge.valid? @hoge.errors.each do |k, v| errros.add(k, v) end @foo.valid? @foo.errors.each do |k, v| ... end def save return unless valid? return false unless @hoge.save @foo.save end end
関連のおすすめ本
- 作者: 掌田津耶乃
- 出版社/メーカー: 秀和システム
- 発売日: 2016/12/17
- メディア: 単行本
- この商品を含むブログを見る
Mechanizeでリンクをスクレイピング
Mechanize を利用したときのメモ。
Mechanize
を試した理由
- ちょっと前は anemone を利用していたが、最近はメンテしてないみたい。
- 内部で
Nokogiri
を利用しているので、学習・導入コストが個人的に低かった。
よく利用する関数と戻り値の型をメモ
> mechanize = Mechanize.new > search_page = mechanize.get(url) > search_page.class => Mechanize::Page > search_page.links.first.class => Mechanize::Page::Link > search_page.links.second.uri => #<URI::Generic /a/b.html> > search_page.links.second.resolved_uri => #<URI::HTTP http://hoge.jp/a/b.html> > div = search_page.search('.hoge') > div.class => Nokogiri::XML::NodeSet > div.first.class => Nokogiri::XML::Element
利用例
links
関数でドキュメント内の a
タグを簡単に取得できる。
Mechanize
をラッパーしたリンク取集クラス
class Scraper attr_accessor :mechanize def initialize @mechanize = Mechanize.new end def scraping_links(url:, links_pattern:, sleep: 1) result_links = {} loop do Rails.logger.debug(url) search_page = mechanize.get(url) target_links = search_page.links.select { |link| link.uri.to_s =~ links_pattern } links = target_links.map { |link| [link.resolved_uri.to_s, link] }.to_h result_links.merge!(links) next_link = block_given? ? yield(search_page) : nil break if next_link.blank? url = next_link.resolved_uri.to_s sleep(sleep) end result_links end def search_document(url) search_page = mechanize.get(url) yield(search_page) end end
呼び出し例
# http://hoge.jp ページ内の /foo/ のリンクを全て取得したい場合 links = Scraper.new.scraping_links(url: 'http://hoge.jp', links_pattern: /foo/) # http://hoge.jp ページ内の /foo/ のリンクをページングして全て取得したい場合。この場合は「次へ」のリンクがなくなるまでクロール links = Scraper.new.scraping_links(url: 'https://hoge.co.jp/foos/?page=1', links_pattern: /foos\/[A-Z]/) do |search_page| search_page.links.find { |link| link.text == '次へ' } end
おすすめ本
Rubyによるクローラー開発技法 巡回・解析機能の実装と21の運用例
- 作者: 佐々木拓郎,るびきち
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2014/08/22
- メディア: 単行本
- この商品を含むブログ (10件) を見る
データを集める技術 最速で作るスクレイピング&クローラー (Informatics&IDEA)
- 作者: 佐々木拓郎
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2016/11/29
- メディア: 単行本
- この商品を含むブログを見る
- 作者: Ryan Mitchell,嶋田健志,黒川利明
- 出版社/メーカー: オライリージャパン
- 発売日: 2016/03/18
- メディア: 大型本
- この商品を含むブログ (2件) を見る
ransackのinをカンマ区切りでする
やりたいこと
ransack を利用して、 in
の検索をカンマ区切りで指定できるようにする。
手順
Controller
class HogeController < ApplicationController ... def index @q = Hoge.ransack(ransack_queries) @hoges = @q.result.page(params[:page]) end ... def ransack_queries queries = params[:q] if queries.present? && queries[:id_in].present? # ページネーション等で2回目のアクセスが来た場合には既にarrayになっているので return。 return queries if queries[:id_in].is_a?(Array) queries[:id_in] = queries[:id_in].split(',').map(&:strip) end queries end ...
View
= search_form_for(@q) do |f| ... = f.search_field(:id_in, class: 'form-control', value: params[:q].try(:fetch, :id_in, nil).try(:join, ','))
参考
関連する書籍
- 作者: 掌田津耶乃
- 出版社/メーカー: 秀和システム
- 発売日: 2016/12/17
- メディア: 単行本
- この商品を含むブログを見る