Wednesday, March 01, 2006

cl-dot

cl-dot คือ library ที่ช่วยในการ generate graphviz dot output

สมมติเรามี structure แบบนี้ใน lisp
(defstruct node name value childs)

แล้วก็มี tree ที่มีหน้าตาแบบนี้
(setq tree (make-node :name '1
:childs (list
(make-node :name '2)
(make-node :name '3
:childs (list
(make-node :name 4))))))


การใช้ cl-dot กับ structure แบบนี้
ทำได้โดยการ implement generic function
ที่ cl-dot เตรียมไว้

ส่วน generic function คืออะไร?
generic function คือ abstract operation
น่าจะเทียบได้กับ abstract method ใน java
เพียงแต่ method ของ java เป็นส่วนหนึ่งของ ojbect
ส่วน lisp method ไม่จำเป็นต้องอยู่ใน object

(note: generic method ทำอะไรได้พิสดารเชียวหล่ะ
เช่น Method combination, Multimethods
อ่านได้ใน Object Reorientation: Generic Functions
ของ Peter Seibel)

c-dot กำหนด generic function ไว้ 4 ตัว แต่เราจะลองแค่ 2 ตัวคือ
(defgeneric object-node (object)
(:documentation
"Return a NODE instance for this object, or NIL. In the latter case
the object will not be included in the graph, but it can still have an
indirect effect via other protocol functions (e.g. OBJECT-KNOWS-OF).
This function will only be called once for each object during the
generation of a graph."))

(defgeneric object-points-to (object)
(:documentation
"Return a list of objects to which the NODE of this object should be
connected. The edges will be directed from this object to the others.
To assign dot attributes to the generated edges, each object can optionally
be wrapped in a instance of ATTRIBUTED.")
(:method ((object t))
nil))


ในตัวอย่าง struture ข้างบน เราจะ implement function object-node
เนื่องจากเรามี object อยู่ชนิดเดียวใน tree จึง implement แค่ function เดียว
(defmethod cl-dot:object-node ((object node))
(make-instance 'cl-dot:node
:attributes `(:label ,(node-name object)
:shape :box)))


ในส่วนของ edge ก็ implement ผ่าน function object-points-to
(defmethod cl-dot:object-points-to ((object node))
(mapcar #'(lambda (child)
(make-instance 'cl-dot:attributed
:object child
:attributes '(:weight 3)))
(node-childs object)))

เวลา run ก็ใช้คำสั่งนี้
(cl-dot:print-graph (cl-dot:generate-graph tree))

ผลลัพท์ที่ได้
digraph {
1 [label="1",shape=box];
3 [label="3",shape=box];
4 [label="4",shape=box];
2 [label="2",shape=box];
1 -> 3 [weight=3];
1 -> 2 [weight=3];
3 -> 4 [weight=3];
}

Related link from Roti

vnc2swf

คุณ OHM ถามเรื่อง screencast ว่าเขาทำอย่างไร
โปรแกรมตัวหนึ่งที่ใช้กัน ก็คือ vnc2swf ครับ
มี 2 version คือ c version (vnc2swf) กับ python version (pyvnc2swf)

หลักการก็คือ vnc2swf ประพฤติตนเป็น vncclient ต่อไปยัง vncserver ที่เราระบุ
(ถ้าจะอัดหน้าจอของเครื่องเราเอง ก็ต้อง start vncserver บนเครื่องเรา)
โดยมันจะแปลงหน้าจอไปเป็น flash movie (swf)

การ start vnc2swf ก็ใช้คำสั่ง
vnc2swf -nowindow out.swf :1 > out.html
:1 คือ display number ของ vncserver

อ่าน tutorial ที่นี่ครับ

ปล. คุณ OHM อย่าลืมเขียนอธิบายเรื่องซ่อมมือถือให้ฟังด้วยนะครับ

Related link from Roti

debug แบบมักง่าย

ปกติถ้าเป็น java เวลาใล่ source code ของ library ต่างๆ
ก็มักจะใช้ debugger ใล่ดู

พอมาใช้ rails, ruby การ debug ก็ใช้วิธีมักง่าย
ก็คือเข้าไปแทรก print statement (puts) ลงใน source
ของ library ตรงๆเลย

วันนี้เรียกใช้งาน web app ตัวหนึ่ง ที่เขียนด้วย rails
เกิด error ประเภท fcgi error
นึกอยู่ตั้งนาน ว่าเกิดอะไรขึ้น
เมื่อก่อนมันยัง run ได้อยู่เลย

โชคดีที่นึกได้ว่า เมื่ออาทิตย์ก่อน เข้าไปแก้ library base.rb ของ ActiveRecord
ก็เลยเข้าไปดู
ปรากฎว่า ลืมเอา puts ที่ใส่ไว้ออก
ไอ้ puts ตัวนั้นก็เลยส่งข้อความออกมากวน fcgi

[Wed Mar 1 15:15:52 2006] [error] [client 127.0.0.1] FastCGI: comm with server
"/Library/WebServer/Documents/snn/dispatch.fcgi" aborted:
error parsing headers: malformed header 'add_conditions SELECT * FROM tags ,'

Related link from Roti

Decision Tree

อ่านเจอใน OnLamp เรื่อง Building Decision Trees in Python
ของ Christopher Roach
ของเดิมเขาเขียนด้วย python ก็เลยลอง implement ด้วย ruby และ clisp ดู

เริ่มด้วยการทำความเข้าใจ Decision Tree ก่อน
ตัว Decision Tree เป็น topic หนึ่งใน maching learning ของ AI
นอกจากนี้ยังเป็น technique ที่ใช้หา ความสัมพันธ์ของข้อมูล ใน Data mining. อีกด้วย

พูดหลักการเฉยๆ จะมองไม่เห็นภาพ ต้องดูตัวอย่างจริง
ยกตัวอย่างว่า เรามี data อยู่ชุดหนึ่ง (sample data)
แล้วเราต้องการหา decision tree จาก data นี้

decision tree ที่ได้ อาจจะมีหน้าตาได้หลายแบบ ขึ้นอยู่กับ
ลำดับของ attribute ที่เราเลือกออกมาทำ category
เช่น ถ้าเลือก house type, district, previous customer
หน้าตาของ tree ก็จะออกมาแบบนี้



แต่ถ้าเลือกลำดับ income ก่อน หน้าตา ก็อาจจะได้ออกมาแบบนี้



จะเห็นได้ว่าเราสามารถจัดกลุ่ม tree ได้หลายแบบมาก
คำถาม ก็คือ tree แบบไหนที่ให้ information ได้ดีที่สุด
คำตอบก็คือ tree ที่สั้นที่สุด (Occam's Razor)
ซึ่งก็คือ tree ที่หน้าตาแบบนี้



algorithm ที่ใช้ในการเลือกลำดับของ attribute ก็คือ
ID3 ที่ใช้การคำนวณ Entropy เข้ามาช่วย
โดยเลือก attribute ที่ทำให้ Entropy ของ tree ลดลงมากที่สุด (entropy สูง, ซับซ้อนสูง)
Tutorial ที่อธิบายวิธีการคำนวณ Entropy ได้ดี อยู่ที่นี่
DMS Tutorial - Decision trees

ผมลองใช้ข้อมูลที่อยู่ใน tutorial ข้างบน [link]
มาลอง implement และ run ด้วย ruby และ lisp ดู

ผลลัพท์ที่ได้ (ใช้เกณฑ์การตัดสินของตัวเอง, bias ล้วนๆ)
  • การลอก(แปลง) python มาเป็น ruby ทำได้ง่ายมาก
  • ตัว code ที่ได้, ทั้ง python และ ruby อ่านง่ายทั้งคู่ ส่วน lisp อ่านยากกว่า
    (ในส่วนของ lisp ถ้าจะให้อ่านง่าย ก็ต้องทำการลดรูป syntax
    โดยใช้ macro เข้ามาช่วย)


ลองดู code ของ ruby กับ lisp ดู (code ของ python ให้ดูที่ tutorial ของ OnLamp)
function entropy
def entropy(datas, attr)
include Math

freqs = Hash.new {|h, k| h[k] = 0.0}

datas.each do |data|
freqs[data[column_by_name(attr)]] += 1
end
summ = 0.0

freqs.values.each do |val|
summ += (-val/datas.length) *
((log (val/datas.length)) / (log 2))
end

summ
end


(defun group-by-attr (data attr)
(let ((hash (make-hash-table)))
(mapc #'(lambda (record)
(let* ((elm (funcall attr record))
(value (gethash elm hash 0)))
(setf (gethash elm hash) (+ value 1))))
data)
hash))

(defun entropy (data attr)
(let ((freqs (group-by-attr data attr))
(total 0))
(maphash #'(lambda (key value)
(let ((frac (/ value (length data))))
(setq total (+ total
(* (- frac)
(log frac 2))))))
freqs)
total))


function gain
def gain(datas, attr, target_attr)
freqs = Hash.new {|h, k| h[k] = 0.0}
subset_entropy = 0.0

datas.each do |data|
freqs[data[column_by_name(attr)]] += 1
end

freqs.keys.each do |key|
prob = freqs[key] / datas.length
filter_datas = datas.find_all {|data|
data[column_by_name(attr)] == key
}

subset_entropy += prob *
entropy(filter_datas, target_attr)
end

entropy(datas, target_attr) - subset_entropy
end


(defun gain (data attr target-attr)
(let* ((freqs (group-by-attr data attr))
(total 0))
(maphash #'(lambda (key value)
(let ((prob (/ value (length data)))
(subset (remove-if-not
#'(lambda (rec)
(eql key (funcall attr rec))) data)))
(setq total (+ total
(* prob
(entropy subset target-attr))))))
freqs)
(- (entropy data target-attr) total)))


ใน lisp ผมลองลดรูปด้วยการใช้ macro มาช่วย
(แต่ยัง design macro ไม่เก่ง ผลเลยไม่ดีเท่าไร)
function entropy ถูกลดรูปมาเป็น
(defmacro summ-loop (lst value &rest body)
`(let ((total 0))
(mapc #'(lambda (,value)
(let ((result ,@body))
(setf total (+ total result))))
,lst)
total))

(defun entropy2 (data attr)
(let* ((freqs (group-by-attr data attr))
(values (hash-values freqs)))
(summ-loop values val
(let ((frac (/ val (length data))))
(* (- frac) (log frac 2))))))


Note: การพยายามลดรูปด้วยการ define macro แบบนี้ ถ้าตั้งชื่อไม่สื่อ หรือไม่มีเอกสารพอ
ก็น่าจะนำไปสู่ code ที่อ่านยากขึ้นแทน

Related link from Roti

Tuesday, February 28, 2006

Maven2

maven2 ออกมานานแล้ว
แต่ผมยังไม่กล้าใช้สักที
เนื่องจากเคยมีประสบการณ์ที่ขมขื่นกับการ upgrade maven มาแล้ว

วันนี้เห็น feature Archetype ของ Maven2 ก็เลยได้ฤกษ์ทบทวนและทดลองใช้เสียที
ใน maven2 ตัว execute file ของ maven ใช้วิธีเปลี่ยนชื่อเป็น mvn
เพื่อหลีกเลี่ยง Name conflict กับ maven 1.0.x

ที่ชอบก็คือ project เก่าเราที่ใช้ maven 1.0.x
สามารถใช้ maven 2.x co-exists กับ maven 1.x พร้อมๆกันได้ด้วย

โดยใน maven 1.x ปกติจะใช้ file ชุดนี้
  • project.xml
  • maven.xml
  • project.properties
  • build.properties

maven 2.x เปลี่ยนไปใช้ file ชุดนี้แทน
  • pom.xml
  • settings.xml


เนื้อหาของ pom.xml ก็คือเนื้อหาเก่าที่เคยอยู่ใน project.xml
เพียงแต่มีการเปลี่ยนชื่อและเพิ่มเติม element ใหม่ๆเข้ามา

default source structure ของ Maven 2.x หน้าตาเป็นแบบนี้
  • src/main/java
  • src/main/resources
  • src/main/filters
    เก็บพวก properties file ที่ใช้ transform resources file
  • src/main/assembly
    (อันนี้เดาว่าเก็บพวก ejb descriptor ทั้งหลาย)
  • src/main/config
  • src/test/java
  • src/test/resources
  • src/test/filters
  • src/sites
  • LICENSE.txt
  • README.txt


output files ทั้งหลายจะถูกสร้างใน sub-directory target
เหมือนใน maven 1.x

ใน maven2 มีการเพิ่ม concept ของ Build Lifecycle เข้ามา
ซึ่งจะมาแทนที่การใช้ tag preGoal, postGoal เดิม ที่เราใช้ใน maven.xml
โดย phase หลักๆใน lifecycle จะประกอบด้วย
  • validate
    ตรวจว่า project เรามีข้อมูลเบื้องต้นครบถ้วนหรือไม่
  • generate-sources
  • process-sources
  • generate-resources
  • process-resources
  • compile
  • process-classes
    ทำพวก bytecode enhacement ที่จุดนี้
  • generate-test-sources
  • generate-test-resources
  • process-test-resouces
  • process-test-resources
  • test-compile
  • test
    unit testing
  • package
  • before-integration-test
  • integration-test
  • after-integration-test
    อันนี้เป็นการ deploy และ test บน test environment
  • verify
    test package ว่า valid หรือไม่
  • install
    install package ไปที่ local repository เพื่อให้ project อื่นๆ
    ที่ต้องการใช้ artifact ตัวนี้ สามารถอ้างใช้งานได้
  • deploy

การ integrate goal จาก plugin ที่ต้องการ
เข้าไปใน project ทำได้โดยการ add
element plugin เข้าไปใน pom.xml
ซึ่ง plugin แต่ละตัว จะรู้บทบาทว่า
ตัวเองควรจะเข้าไป cooperate ใน phase ไหน
(แต่เราสามารถ overwrite ระบุ phase ที่เราต้องการให้ plugin run ได้ด้วย)

ยกตัวอย่าง antlr plugin ที่ปกติจะ run ในช่วง generate-sources
(เพราะต้อง compile .g -> ให้เป็น .java)

<project>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antlr-plugin</artifactId>
<configuration>
<grammars>java.g</grammars>
</configuration>
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>


วิธีการพัฒนา plugin ใน maven 2.x
ก็มีการเปลี่ยนแปลงไปเช่นกัน
เดิมต้องมี jelly script เสมอ
ก็เปลี่ยนไปใช้ java ล้วนๆ
โดยใช้ศัพท์ Mojo (Maven [plain] Old Java Object) สำหรับ plugin แบบนี้

Dependency Management ก็มีการปรับปรุงใหม่
มีการเพิ่ม Transitive Dependencies, Dependency Scope เข้ามา

Feature อื่นๆยังมีอีกเยอะแยะเลย
อย่างนี้ไม่ใช้ไม่ได้แล้ว

Related link from Roti

Sunday, February 26, 2006

GMF (Graphical Modeling Framework) Screencast
feature ใหม่ (หรืออาจจะเก่าแล้ว แต่พึ่งรู้) สามารถ generate diagrame editor สำหรับ ecore model ที่เราต้องการได้
(เดิม EMF สามารถ generate Model Editor ให้เราได้เหมือนกัน แต่เป็นแค่ Tree Editor หน้าตาธรรมดา)

Related link from Roti