12/08/2018, 15:22

Intro to Machine Learning in Ruby

Machine Learning(học máy) là một nhánh của Artificial Intelligence(AI - trí tuệ nhân tạo) liên quan tới thiết kế và phát triển thuật toán cho phép máy tính có thể xử lý và học được thông tin. Đó là một chủ đề vô cùng rộng lớn do đó chúng ta sẽ chỉ tập trung vào một ví dụ đơn giản sử dụng thuật ngữ ...

Machine Learning(học máy) là một nhánh của Artificial Intelligence(AI - trí tuệ nhân tạo) liên quan tới thiết kế và phát triển thuật toán cho phép máy tính có thể xử lý và học được thông tin. Đó là một chủ đề vô cùng rộng lớn do đó chúng ta sẽ chỉ tập trung vào một ví dụ đơn giản sử dụng thuật ngữ phân loại thống kê.

Let's build...

Trong ví dụ dưới đây chúng ta sẽ xây dựng một ứng dụng phân loại thống kê sẽ phân tích và phân loại các bài viết RSS/HTML từ trang báo Times Live Để làm công việc này, chúng ta sẽ sử dụng gem nokogiri và 2 chuẩn thư viện: open-uri và rss/2.0

RSS Parser

Để tìm nguồn của các bài viết để xử lý, chúng ta có thể xây dựng một công nghệ tìm kiếm phức tạp hoặc đơn giản là sử dụng RSS để lấy những bài báo cung cấp cho chúng ta từ một nguồn nào đó. RssParser làm điều đó, bạn khởi tạo với một địa chỉ url và nó sẽ trả về tất cả các liên kết tới tất cả các bài viết mà nó tìm thấy được từ nguồn url đó.

class RssParser  
  attr_accessor :url

  def initialize(url)
    @url = url
  end

  def article_urls
    RSS::Parser.parse(open(url), false).items.map{|item| item.link }
  end
end  

HTML Parser

Khi đã có các liên kết, chúng ta cần phân tích nội dung và trích xuất ra những phần có nghĩa từ những trang đó. HtmlParser có thể khởi tạo với một liên kết tới một trang và dùng DOM selector để làm điều đó. Trong ví dụ này, chúng ta sẽ sử dụng một CSS selector để lấy ra nội dung từ một bài viết - Firebug và jQuery trước đó cũng được sử dụng để trích xuất nội dung từ các bài viết. Trong trường hợp này, chúng ta cũng sẽ sử dụng phương thức clean_whitespace để xóa bỏ những ký tự rỗng từ văn bản trích xuất

class HtmlParser  
  attr_accessor :url, :selector

  def initialize(url, selector)
    @url      = url
    @selector = selector
  end

  def content
    doc = Nokogiri::HTML(open(url))
    html_elements = doc.search(selector)
    html_elements.map { |element| clean_whitespace(element.text) }.join(' ')
  end

  private

    def clean_whitespace(text)
      text.gsub(/s{2,}|	|
/, ' ').strip
    end
end 

Statistical Classifier

Chúng ta sẽ đến với một class có trách nhiệm phân loại các bài viết. Nó được khởi tạo với một mảng chứa các keys của các loại bài viết mà chúng ta sẽ phân loại vào và dữ liệu huấn luyện training với các loại tương ứng. Dữ liệu huấn luyện được sử dụng để khám phá ra mối quan hệ giữa bài viết và thể loại của bài viết đó. Dữ liệu này nên được chọn lọc kỹ và sắp xếp theo thứ tự để có thể có được kết quả phân loại tốt hơn. Nó được tạo ra bởi quyết định giá trị của từng từ trong một ngữ cảnh của tất cả các từ trong mỗi một danh mục bài viết phân loại. Trong ví dụ dưới đây, chúng ta sẽ sử dụng các bài viết Wikipedia để huấn luyện dữ liệu với ba danh mục là kinh tế, thể thao và sức khỏe. Trong khi phân loại các bài viết, chúng ta sẽ chỉ so sánh các từ có nghĩa và bỏ qua các từ khác mà không thêm vào bất kỳ giá trị nào cho mỗi danh mục phân loại. Chúng ta giải quyết vấn đề sử dụng các từ cố định stop words Cuối cùng phương thức scores() sẽ đánh giá tạo điểm với mỗi một danh mục phân loại mà chúng ta kiểm tra

class Classifier  
  attr_accessor :training_sets, :noise_words

  def initialize(data)
    @training_sets = {}
    filename = File.join(File.dirname(__FILE__), 'stop_words.txt')
    @noise_words = File.new(filename).readlines.map(&:chomp)
    train_data(data)
  end

  def scores(text)
    words = text.downcase.scan(/[a-z]+/)

    scores = {}
    training_sets.each_pair do |category, word_weights|
      scores[category] = score(word_weights, words)
    end

    scores
  end

  def train_data(data)
    data.each_pair do |category, text|
      words = text.downcase.scan(/[a-z]+/)
      word_weights = Hash.new(0)

      words.each {|word| word_weights[word] += 1 unless noise_words.index(word)}

      ratio = 1.0 / words.length
      word_weights.keys.each {|key| word_weights[key] *= ratio}

      training_sets[category] = word_weights
    end
  end

  private
    def score(word_weights, words)
      score = words.inject(0) {|acc, word| acc + word_weights[word]}
      1000.0 * score / words.size
    end
end

Lets have a go

Dưới đây là đoạn script chạy trương trình

require 'rubygems'  
require 'nokogiri'  
require 'open-uri'  
require 'rss/2.0'

# training data samples
economy = HtmlParser.new('http://en.wikipedia.org/wiki/Economy', '.mw-content-ltr')  
sport   = HtmlParser.new('http://en.wikipedia.org/wiki/Sport', '.mw-content-ltr')  
health  = HtmlParser.new('http://en.wikipedia.org/wiki/Health', '.mw-content-ltr')

training_data = {  
  :economy => economy.content,
  :sport => sport.content,
  :health => health.content
}

classifier = Classifier.new(training_data)

results = {  
  :economy => [],
  :sport => [],
  :health => []
}

rss_parser = RssParser.new('http://avusa.feedsportal.com/c/33051/f/534658/index.rss')  
rss_parser.article_urls.each do |article_url|  
  article = HtmlParser.new(article_url, '#article .area > h3, #article .area > p, #article > h3')
  scores = classifier.scores(article.content)
  category_name, score = scores.max_by{ |k,v| v }
  # DEBUG info
  # p "category: #{category_name}, score: #{score}, scores: #{scores}, url: #{article_url}"
  results[category_name] << article_url
end

p results

Mặc dù thuật toán phân loại rất đơn giản, nó cũng có thể mang lại kết quả đáng lưu ý cung cấp huấn luyện dữ liệu tốt. Để có thể có kết quả tốt hơn, bạn có thể thử một vài thuật toán phân loại khác giống như Bayesian probability và Latent semantic analysis

Refs

Intro to Machine Learning in Ruby

0