في كثير من الأحيان عند حل المشكلات المتعلقة برؤية الكمبيوتر ، يصبح نقص البيانات مشكلة كبيرة. هذا صحيح بشكل خاص عند العمل مع الشبكات العصبية.
كم سيكون رائعًا إذا كان لدينا مصدر غير محدود للبيانات الأصلية الجديدة؟
دفعني هذا الفكر إلى تطوير لغة خاصة بالمجال تسمح لك بإنشاء صور في تكوينات مختلفة. يمكن استخدام هذه الصور لتدريب نماذج التعلم الآلي واختبارها. كما يوحي الاسم ، لا يمكن استخدام صور DSL التي تم إنشاؤها عادةً إلا في منطقة ذات تركيز ضيق.
متطلبات اللغة
في حالتي الخاصة ، أحتاج إلى التركيز على اكتشاف الكائن. يجب أن يقوم برنامج التحويل البرمجي للغة بإنشاء صور تستوفي المعايير التالية:
- تحتوي الصور على أشكال مختلفة (على سبيل المثال ، الرموز) ؛
- يمكن تخصيص عدد وموضع الشخصيات الفردية ؛
- حجم الصورة والأشكال قابلة للتخصيص.
يجب أن تكون اللغة نفسها بسيطة قدر الإمكان. أريد تحديد حجم الصورة الناتجة أولاً ثم حجم الأشكال. ثم أريد أن أعبر عن التكوين الفعلي للصورة. لتبسيط الأمور ، أفكر في الصورة كجدول ، حيث يتناسب كل شكل في خلية. يتم ملء كل صف جديد بالنماذج من اليسار إلى اليمين.
التنفيذ
لقد اخترت مجموعة من ANTLR و Kotlin و Gradle لإنشاء DSL . ANTLR هو مولد محلل. Kotlin هي لغة تشبه JVM تشبه Scala. Gradle هو نظام بناء مشابه لـ
sbt
.
البيئة اللازمة
ستحتاج إلى Java 1.8 و Gradle 4.6 لإكمال الخطوات الموضحة.
الإعداد الأولي
قم بإنشاء مجلد يحتوي على DSL.
> mkdir shaperdsl
> cd shaperdsl
قم بإنشاء ملف
build.gradle
. هذا الملف مطلوب لسرد تبعيات المشروع وتكوين مهام Gradle الإضافية. إذا كنت تريد إعادة استخدام هذا الملف ، فما عليك سوى تغيير مساحات الأسماء والفئة الرئيسية.
> touch build.gradle
يوجد أدناه محتوى الملف:
buildscript {
ext.kotlin_version = '1.2.21'
ext.antlr_version = '4.7.1'
ext.slf4j_version = '1.7.25'
repositories {
mavenCentral()
maven {
name 'JFrog OSS snapshot repo'
url 'https://oss.jfrog.org/oss-snapshot-local/'
}
jcenter()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.1'
}
}
apply plugin: 'kotlin'
apply plugin: 'java'
apply plugin: 'antlr'
apply plugin: 'com.github.johnrengelman.shadow'
repositories {
mavenLocal()
mavenCentral()
jcenter()
}
dependencies {
antlr "org.antlr:antlr4:$antlr_version"
compile "org.antlr:antlr4-runtime:$antlr_version"
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
compile "org.apache.commons:commons-io:1.3.2"
compile "org.slf4j:slf4j-api:$slf4j_version"
compile "org.slf4j:slf4j-simple:$slf4j_version"
compile "com.audienceproject:simple-arguments_2.12:1.0.1"
}
generateGrammarSource {
maxHeapSize = "64m"
arguments += ['-package', 'com.example.shaperdsl']
outputDirectory = new File("build/generated-src/antlr/main/com/example/shaperdsl".toString())
}
compileJava.dependsOn generateGrammarSource
jar {
manifest {
attributes "Main-Class": "com.example.shaperdsl.compiler.Shaper2Image"
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
task customFatJar(type: Jar) {
manifest {
attributes 'Main-Class': 'com.example.shaperdsl.compiler.Shaper2Image'
}
baseName = 'shaperdsl'
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
with jar
}
محلل اللغة
تم إنشاء المحلل اللغوي مثل قواعد ANTLR .
mkdir -p src/main/antlr
touch src/main/antlr/ShaperDSL.g4
بالمحتوى التالي:
grammar ShaperDSL;
shaper : 'img_dim:' img_dim ',shp_dim:' shp_dim '>>>' ( row ROW_SEP)* row '<<<' NEWLINE* EOF;
row : ( shape COL_SEP )* shape ;
shape : 'square' | 'circle' | 'triangle';
img_dim : NUM ;
shp_dim : NUM ;
NUM : [1-9]+ [0-9]* ;
ROW_SEP : '|' ;
COL_SEP : ',' ;
NEWLINE : '\r\n' | 'r' | '\n';
الآن يمكنك أن ترى كيف تصبح بنية اللغة أكثر وضوحًا. لإنشاء التعليمات البرمجية المصدر لقواعد النحو ، قم بتشغيل:
> gradle generateGrammarSource
نتيجة لذلك ، سوف تحصل على الكود الذي تم إنشاؤه بتنسيق
build/generate-src/antlr
.
> ls build/generated-src/antlr/main/com/example/shaperdsl/
ShaperDSL.interp ShaperDSL.tokens ShaperDSLBaseListener.java ShaperDSLLexer.interp ShaperDSLLexer.java ShaperDSLLexer.tokens ShaperDSLListener.java ShaperDSLParser.java
شجرة النحو المجرد
يقوم المحلل اللغوي بتحويل التعليمات البرمجية المصدر إلى شجرة كائن. شجرة الكائن هي ما يستخدمه المترجم كمصدر بيانات. للحصول على AST ، تحتاج أولاً إلى تحديد نموذج الشجرة.
> mkdir -p src/main/kotlin/com/example/shaperdsl/ast
> touch src/main/kotlin/com/example/shaper/ast/MetaModel.kt
MetaModel.kt
يحتوي على تعريفات لفئات الكائنات المستخدمة في اللغة ، بدءًا من الجذر. كلهم يرثون من العقدة . التسلسل الهرمي للشجرة مرئي في تعريف الفئة.
package com.example.shaperdsl.ast
interface Node
data class Shaper(val img_dim: Int, val shp_dim: Int, val rows: List<Row>): Node
data class Row(val shapes: List<Shape>): Node
data class Shape(val type: String): Node
بعد ذلك ، تحتاج إلى مطابقة الفصل مع ASD:
> touch src/main/kotlin/com/example/shaper/ast/Mapping.kt
Mapping.kt
يستخدم لبناء AST باستخدام الفئات المحددة في MetaModel.kt
، باستخدام البيانات من المحلل اللغوي.
package com.example.shaperdsl.ast
import com.example.shaperdsl.ShaperDSLParser
fun ShaperDSLParser.ShaperContext.toAst(): Shaper = Shaper(this.img_dim().text.toInt(), this.shp_dim().text.toInt(), this.row().map { it.toAst() })
fun ShaperDSLParser.RowContext.toAst(): Row = Row(this.shape().map { it.toAst() })
fun ShaperDSLParser.ShapeContext.toAst(): Shape = Shape(text)
الكود الموجود على DSL:
img_dim:100,shp_dim:8>>>square,square|circle|triangle,circle,square<<<
سيتم تحويله إلى ASD التالي:
مترجم
المترجم هو الجزء الأخير. يستخدم ASD للحصول على نتيجة محددة ، في هذه الحالة ، صورة.
> mkdir -p src/main/kotlin/com/example/shaperdsl/compiler
> touch src/main/kotlin/com/example/shaper/compiler/Shaper2Image.kt
هناك الكثير من التعليمات البرمجية في هذا الملف. سأحاول توضيح النقاط الرئيسية.
ShaperParserFacade
عبارة عن غلاف في الأعلى ShaperAntlrParserFacade
يبني AST الفعلي من كود المصدر المقدم.
Shaper2Image
هي فئة المترجم الرئيسي. بعد أن يستقبل AST من المحلل اللغوي ، فإنه يمر عبر جميع الكائنات الموجودة بداخله ويقوم بإنشاء كائنات رسومية ، ثم يتم إدراجها في الصورة. ثم تقوم بإرجاع التمثيل الثنائي للصورة. هناك أيضًا وظيفة main
في الكائن المصاحب للفئة للسماح بالاختبار.
package com.example.shaperdsl.compiler
import com.audienceproject.util.cli.Arguments
import com.example.shaperdsl.ShaperDSLLexer
import com.example.shaperdsl.ShaperDSLParser
import com.example.shaperdsl.ast.Shaper
import com.example.shaperdsl.ast.toAst
import org.antlr.v4.runtime.CharStreams
import org.antlr.v4.runtime.CommonTokenStream
import org.antlr.v4.runtime.TokenStream
import java.awt.Color
import java.awt.image.BufferedImage
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.InputStream
import javax.imageio.ImageIO
object ShaperParserFacade {
fun parse(inputStream: InputStream) : Shaper {
val lexer = ShaperDSLLexer(CharStreams.fromStream(inputStream))
val parser = ShaperDSLParser(CommonTokenStream(lexer) as TokenStream)
val antlrParsingResult = parser.shaper()
return antlrParsingResult.toAst()
}
}
class Shaper2Image {
fun compile(input: InputStream): ByteArray {
val root = ShaperParserFacade.parse(input)
val img_dim = root.img_dim
val shp_dim = root.shp_dim
val bufferedImage = BufferedImage(img_dim, img_dim, BufferedImage.TYPE_INT_RGB)
val g2d = bufferedImage.createGraphics()
g2d.color = Color.white
g2d.fillRect(0, 0, img_dim, img_dim)
g2d.color = Color.black
var j = 0
root.rows.forEach{
var i = 0
it.shapes.forEach {
when(it.type) {
"square" -> {
g2d.fillRect(i * (shp_dim + 1), j * (shp_dim + 1), shp_dim, shp_dim)
}
"circle" -> {
g2d.fillOval(i * (shp_dim + 1), j * (shp_dim + 1), shp_dim, shp_dim)
}
"triangle" -> {
val x = intArrayOf(i * (shp_dim + 1), i * (shp_dim + 1) + shp_dim / 2, i * (shp_dim + 1) + shp_dim)
val y = intArrayOf(j * (shp_dim + 1) + shp_dim, j * (shp_dim + 1), j * (shp_dim + 1) + shp_dim)
g2d.fillPolygon(x, y, 3)
}
}
i++
}
j++
}
g2d.dispose()
val baos = ByteArrayOutputStream()
ImageIO.write(bufferedImage, "png", baos)
baos.flush()
val imageInByte = baos.toByteArray()
baos.close()
return imageInByte
}
companion object {
@JvmStatic
fun main(args: Array<String>) {
val arguments = Arguments(args)
val code = ByteArrayInputStream(arguments.arguments()["source-code"].get().get().toByteArray())
val res = Shaper2Image().compile(code)
val img = ImageIO.read(ByteArrayInputStream(res))
val outputfile = File(arguments.arguments()["out-filename"].get().get())
ImageIO.write(img, "png", outputfile)
}
}
}
الآن بعد أن أصبح كل شيء جاهزًا ، فلنقم ببناء المشروع والحصول على ملف جرة بجميع التبعيات ( uber jar ).
> gradle shadowJar
> ls build/libs
shaper-dsl-all.jar
اختبارات
كل ما علينا فعله هو التحقق مما إذا كان كل شيء يعمل ، لذا حاول إدخال هذا الرمز:
> java -cp build/libs/shaper-dsl-all.jar com.example.shaperdsl.compiler.Shaper2Image \
--source-code "img_dim:100,shp_dim:8>>>circle,square,square,triangle,triangle|triangle,circle|square,circle,triangle,square|circle,circle,circle|triangle<<<" \
--out-filename test.png
سيتم إنشاء ملف:
.png
الذي سيبدو هكذا:
خاتمة
إنه DSL بسيط وغير مؤمن ومن المحتمل أن ينكسر إذا أسيء استخدامه. ومع ذلك ، فهو يناسب هدفي جيدًا ويمكنني استخدامه لإنشاء أي عدد من عينات الصور الفريدة. يمكن تمديده بسهولة لمزيد من المرونة ويمكن استخدامه كنموذج لـ DSLs الأخرى.
يمكن العثور على مثال كامل لـ DSL في مستودع GitHub الخاص بي: github.com/cosmincatalin/shaper .