<標簽名 style="屬性1:屬性值1; 屬性2:屬性值2; 屬性3:屬性值3;"> 內容 </標簽名>
實際上任何HTML標簽都擁有style屬性,用來設置行內式。
<div style="color: red; font-size: 12px;">青春不常在,抓緊談戀愛</div>
<head>
<style type="text/CSS">
選擇器(選擇的標簽) {
屬性1: 屬性值1;
屬性2: 屬性值2;
屬性3: 屬性值3;
}
</style>
</head>
<style>
div {
color: red;
font-size: 12px;
}
</style>
<style>
/*選擇器{屬性:值;}*/
p {
color:#06C;
font-size:14px;
} /*文字的顏色是 藍色*/
h4 {
color:#900;
}
h1 {
color:#090;
font-size:16px;
}
body {
background:url(bg2.jpg);
}
</style>
<head>
<link rel="stylesheet" type="text/css" href="css文件路徑">
</head>
屬性 | 作用 |
rel | 定義當前文檔與被鏈接文檔之間的關系,在這里需要指定為“stylesheet”,表示被鏈接的文檔是一個樣式表文件。 |
type | 定義所鏈接文檔的類型,在這里需要指定為“text/CSS”,表示鏈接的外部文件為CSS樣式表。我們都可以省略 |
href | 定義所鏈接外部樣式表文件的URL,可以是相對路徑,也可以是絕對路徑。 |
樣式表 | 優點 | 缺點 | 使用情況 | 控制范圍 |
行內樣式表 | 書寫方便,權重高 | 沒有實現樣式和結構相分離 | 較少 | 控制一個標簽(少) |
內部樣式表 | 部分結構和樣式相分離 | 沒有徹底分離 | 較多 | 控制一個頁面(中) |
外部樣式表 | 完全實現結構和樣式相分離 | 需要引入 | 最多,強烈推薦 | 控制整個站點(多) |
標簽名{屬性1:屬性值1; 屬性2:屬性值2; 屬性3:屬性值3; }
類選擇器使用“.”(英文點號)進行標識,后面緊跟類名.
命名規范: 見附件(Web前端開發規范手冊.doc)
命名是我們通俗約定的,但是沒有規定必須用這些常用的命名。
課堂案例:
<head>
<meta charset="utf-8">
<style>
.blue {
color: blue;
font-size: 100px;
}
.red {
color: red;
font-size: 100px;
}
.orange {
color: orange;
font-size: 100px;
}
.green {
color: green;
font-size: 100px;
}
</style>
</head>
<body>
<span class="blue">G</span>
<span class="red">o</span>
<span class="orange">o</span>
<span class="blue">g</span>
<span class="green">l</span>
<span class="red">e</span>
</body>
我們可以給標簽指定多個類名,從而達到更多的選擇目的。
注意:
<div class="pink fontWeight font20">亞瑟</div>
<div class="font20">劉備</div>
<div class="font14 pink">安其拉</div>
<div class="font14">貂蟬</div>
id選擇器使用#進行標識,后面緊跟id名
id選擇器和類選擇器最大的不同在于 使用次數上。
總結他們**
* { 屬性1:屬性值1; 屬性2:屬性值2; 屬性3:屬性值3; }
例如下面的代碼,使用通配符選擇器定義CSS樣式,清除所有HTML標記的默認邊距。
* {
margin: 0; /* 定義外邊距*/
padding: 0; /* 定義內邊距*/
}
選擇器 | 作用 | 缺點 | 使用情況 | 用法 |
標簽選擇器 | 可以選出所有相同的標簽,比如p | 不能差異化選擇 | 較多 | p { color:red;} |
類選擇器 | 可以選出1個或者多個標簽 | 可以根據需求選擇 | 非常多 | .nav { color: red; } |
id選擇器 | 一次只能選擇器1個標簽 | 只能使用一次 | 不推薦使用 | #nav {color: red;} |
通配符選擇器 | 選擇所有的標簽 | 選擇的太多,有部分不需要 | 不推薦使用 | * {color: red;} |
選擇器
p {
font-size:20px;
}
注意:
p{ font-family:"微軟雅黑";}
p{font-family: Arial,"Microsoft Yahei", "微軟雅黑";}
常用技巧:
1. 各種字體之間必須使用英文狀態下的逗號隔開。
2. 中文字體需要加英文狀態下的引號,英文字體一般不需要加引號。當需要設置英文字體時,英文字體名必須位于中文字體名之前。
3. 如果字體名中包含空格、#、$等符號,則該字體必須加英文狀態下的單引號或雙引號,例如font-family: "Times New Roman";。
4. 盡量使用系統默認字體,保證在任何用戶的瀏覽器中都能正確顯示。
字體名稱 | 英文名稱 | Unicode 編碼 |
宋體 | SimSun | B8BF53 |
新宋體 | NSimSun | B0B8BF53 |
黑體 | SimHei | ED1F53 |
微軟雅黑 | Microsoft YaHei | FAEF6FC5ED1 |
楷體_GB2312 | KaiTi_GB2312 | 77F53_GB2312 |
隸書 | LiSu | B6E66 |
幼園 | YouYuan | E7C06 |
華文細黑 | STXihei | 4E87EC6ED1 |
細明體 | MingLiU | EC60EF53 |
新細明體 | PMingLiU | B0EC60EF53 |
屬性值 | 描述 |
normal | 默認值(不加粗的) |
bold | 定義粗體(加粗的) |
100~900 | 400 等同于 normal,而 700 等同于 bold 我們重點記住這句話 |
提倡:
我們平時更喜歡用數字來表示加粗和不加粗。
font-style屬性用于定義字體風格,如設置斜體、傾斜或正常字體,其可用屬性值如下:
屬性 | 作用 |
normal | 默認值,瀏覽器會顯示標準的字體樣式 font-style: normal; |
italic | 瀏覽器會顯示斜體的字體樣式。 |
小技巧:
平時我們很少給文字加斜體,反而喜歡給斜體標簽(em,i)改為普通模式。
font屬性用于對字體樣式進行綜合設置
選擇器 { font: font-style font-weight font-size/line-height font-family;}
屬性 | 表示 | 注意點 |
font-size | 字號 | 我們通常用的單位是px 像素,一定要跟上單位 |
font-family | 字體 | 實際工作中按照團隊約定來寫字體 |
font-weight | 字體粗細 | 記住加粗是 700 或者 bold 不加粗 是 normal 或者 400 記住數字不要跟單位 |
font-style | 字體樣式 | 記住傾斜是 italic 不傾斜 是 normal 工作中我們最常用 normal |
font | 字體連寫 | 1. 字體連寫是有順序的 不能隨意換位置 2. 其中字號 和 字體 必須同時出現 |
表示表示 | 屬性值 |
預定義的顏色值 | red,green,blue,還有我們的御用色 pink |
十六進制 | #FF0000,#FF6600,#29D794 |
RGB代碼 | rgb(255,0,0)或rgb(100%,0%,0%) |
屬性 | 解釋 |
left | 左對齊(默認值) |
right | 右對齊 |
center | 居中對齊 |
一般情況下,行距比字號大7.8像素左右就可以了。
line-height: 24px;
1em 就是一個字的寬度 如果是漢字的段落, 1em 就是一個漢字的寬度
p {
/*行間距*/
line-height: 25px;
/*首行縮進2個字 em 1個em 就是1個字的大小*/
text-indent: 2em;
}
text-decoration 通常我們用于給鏈接修改裝飾效果
值 | 描述 |
none | 默認。定義標準的文本。 取消下劃線(最常用) |
underline | 定義文本下的一條線。下劃線 也是我們鏈接自帶的(常用) |
overline | 定義文本上的一條線。(不用) |
line-through | 定義穿過文本下的一條線。(不常用) |
屬性 | 表示 | 注意點 |
color | 顏色 | 我們通常用 十六進制 比如 而且是簡寫形式 #fff |
line-height | 行高 | 控制行與行之間的距離 |
text-align | 水平對齊 | 可以設定文字水平的對齊方式 |
text-indent | 首行縮進 | 通常我們用于段落首行縮進2個字的距離 text-indent: 2em; |
text-decoration | 文本修飾 | 記住 添加 下劃線 underline 取消下劃線 none |
Emmet的前身是Zen coding,它使用縮寫,來提高html/css的編寫速度。
emment語法
近每天都帶一個蘋果加餐,有次吃到一個十分漂亮的紅蘋果,突然覺得,可以試著畫一個蘋果看看。
之前確實無法想象,可以使用CSS能畫出一個蘋果。
先看效果:
你沒有看錯,這個不是真蘋果。僅僅一百多行代碼就可以實現的CSS的紅蘋果。我自己看到最終效果都驚呆了。
第一步畫出蘋果的輪廓:
.apple {
position: relative;
width: 300px;
height: 270px;
background-color: #bf2934;
border-radius: 160px 150px 145px 150px/160px 140px 210px 190px;
}
第二步:將蘋果變得更加立體
1)右上角使用亮色內陰影,畫出高光效果
2)左下角使用兩層暗色內陰影,畫出背光的暗色效果
3)左上角增加一個亮色高光
4)整體增加徑向漸變的背景,將重心高光,四周暗色
.apple {
position: relative;
width: 300px;
height: 270px;
background: radial-gradient(#0000, rgba(0, 0, 0, .1)),
radial-gradient(rgba(239, 156, 109, .2), #0000);
background-color: #bf2934;
border-radius: 160px 150px 145px 150px/160px 140px 210px 190px;
box-shadow: inset 30px -20px 30px 20px rgba(0, 0, 0, .15),
inset 10px -10px 15px 0 rgba(0, 0, 0, .15),
inset 10px 10px 5px 0 rgba(255, 255, 255, .05),
inset -10px 10px 30px 10px rgba(237, 115, 84, .3);
}
第三步:增加光源反光的高亮效果
使用before偽元素,畫一個亮色的橢圓。橘色的陰影。然后整體模糊一下
.apple::before {
position: absolute;
left: 135px;
top: 50px;
content: '';
width: 80px;
height: 30px;
transform: skew(-20deg) rotate(10deg);
border-radius: 45%;
background: rgba(255, 255, 255, .65);
box-shadow: 2px 10px 30px 22px rgba(239, 156, 109, 1);
filter: blur(5px);
}
哇,一不小心,畫出來了一個西紅柿
好吧,這個時候, 我其實可以停下來,先發一篇畫西紅柿的文章了。
第四步:準備一下頂部葉柄的凹陷
這個凹陷效果使用after偽元素,畫一個黑色透明色漸變的橢圓,然后使用clip-path,截取頂部扇形部分。
.apple::after {
position: absolute;
left: 90px;
top: 2px;
width: 120px;
background: radial-gradient(rgba(0, 0, 0, .3), rgba(239, 156, 109, .2));
height: 30px;
content: '';
border-radius: 50%;
filter: blur(2px);
clip-path: polygon(50% 55%, 150% 0, -50% 0);
filter: blur(1px);
}
真是一個漂亮的西紅柿。。。。。。。
蘋果和西紅柿的區別,主要是,蘋果沒有那么的光滑,顏色沒有這么完美。我們需要一些雜色。
第五步:增加雜色條紋
好吃的蘋果,都有縱向的條紋雜色。這里我們是模糊后的border來模擬。由于需要很多雜色,這里使用js添加,稍微做點隨機。
.stripe {
position: absolute;
width: 300px;
height: 270px;
border-radius: 160px 150px 145px 150px/160px 140px 210px 190px;
border-right: solid 2px rgba(239, 156, 109, .2);
transform: scaleX(.9);
filter: blur(4px);
}
function addStripe() {
var fragment = document.createDocumentFragment()
var count = 15
for (var i = -count; i < count; i++) {
var stripe = document.createElement('div')
stripe.className = 'stripe'
stripe.style.opacity = Math.max(1, 0.5 + Math.random())
stripe.style.transform = `scaleX(${(i / count + Math.random() * 0.2).toFixed(2)})`
fragment.appendChild(stripe)
}
document.querySelector('.apple').appendChild(fragment)
}
是不是突然就像蘋果了
第六步:增加噪點
好吃的蘋果,會有很多小小的斑點。這里,我們使用js增加一些隨機的斑點。
.spot {
position: absolute;
width: 4px;
height: 4px;
border-radius: 50%;
background: rgba(239, 156, 109, .6);
filter: blur(1px);
}
function addSpot() {
var count = 60
var fragment = document.createDocumentFragment()
for (var i = 0; i < count; i++) {
var spot = document.createElement('div')
spot.className = 'spot'
spot.style.left = 5 + ~~(Math.random() * 90) + '%'
spot.style.top = 5 + ~~(Math.random() * 90) + '%'
spot.style.opacity = 0.2 + Math.random() * 0.4
spot.style.transform = `scaleX(${Math.random() * 0.8 + 0.2}) scaleY(${Math.random() * 0.8 + 0.2}) rotate(${~~(360 * Math.random())}deg)`
fragment.appendChild(spot)
}
document.querySelector('.apple').appendChild(fragment)
}
蘋果變得更加誘人了。
再做一些細節優化。
第七部:給蘋果加個葉柄。
買過西瓜的都知道,葉柄是證明西瓜新鮮與否的標志。蘋果亦然。
.petiole {
position: absolute;
transform: translate(155px, -48px) rotate(35deg);
width: 20px;
height: 60px;
border-top: solid 15px transparent;
border-left: solid 10px rgb(162, 76, 9);
border-bottom: solid 8px transparent;
border-radius: 50%;
}
.petiole.left {
transform: translate(154px, -49px) rotate(35deg);
border-left-color: rgb(123, 48, 24);
}
<div class="apple">
<div class="petiole left"></div>
<div class="petiole"></div>
</div>
是不是新鮮很多。
最后,我們給蘋果加個陰影,看著更加立體一點。
.shadow {
position: absolute;
width: 300px;
height: 270px;
background-color: rgba(0, 0, 0, .15);
transform-origin: 50% 100%;
transform: translate(5px, 5px) skew(60deg) scaleY(.25);
border-radius: 160px 150px 145px 150px/160px 140px 210px 190px;
box-shadow: 0 0 20px rgba(0, 0, 0, .15);
filter: blur(4px);
}
嬌艷欲滴的大蘋果,完成!!!!
代碼倉庫地址:
https://github.com/shb190802/html5
演示地址:
http://suohb.com/demo/win/apple.html
選自Floydhub
作者:Emil Wallner
機器之心編譯
如何用前端頁面原型生成對應的代碼一直是我們關注的問題,本文作者根據 pix2code 等論文構建了一個強大的前端代碼生成模型,并詳細解釋了如何利用 LSTM 與 CNN 將設計原型編寫為 HTML 和 CSS 網站。
項目鏈接:github.com/emilwallner…
在未來三年內,深度學習將改變前端開發。它將會加快原型設計速度,拉低開發軟件的門檻。
Tony Beltramelli 在去年發布了論文《pix2code: Generating Code from a Graphical User Interface Screenshot》,Airbnb 也發布Sketch2code(airbnb.design/sketching-i…)。
目前,自動化前端開發的最大阻礙是計算能力。但我們已經可以使用目前的深度學習算法,以及合成訓練數據來探索人工智能自動構建前端的方法。在本文中,作者將教神經網絡學習基于一張圖片和一個設計模板來編寫一個 HTML 和 CSS 網站。以下是該過程的簡要概述:
1)向訓練過的神經網絡輸入一個設計圖
2)神經網絡將圖片轉化為 HTML 標記語言
3)渲染輸出
我們將分三步從易到難構建三個不同的模型,首先,我們構建最簡單地版本來掌握移動部件。第二個版本 HTML 專注于自動化所有步驟,并簡要解釋神經網絡層。在最后一個版本 Bootstrap 中,我們將創建一個模型來思考和探索 LSTM 層。
代碼地址:
所有 FloydHub notebook 都在 floydhub 目錄中,本地 notebook 在 local 目錄中。
本文中的模型構建基于 Beltramelli 的論文《pix2code: Generating Code from a Graphical User Interface Screenshot》和 Jason Brownlee 的圖像描述生成教程,并使用 Python 和 Keras 完成。
核心邏輯
我們的目標是構建一個神經網絡,能夠生成與截圖對應的 HTML/CSS 標記語言。
訓練神經網絡時,你先提供幾個截圖和對應的 HTML 代碼。網絡通過逐個預測所有匹配的 HTML 標記語言來學習。預測下一個標記語言的標簽時,網絡接收到截圖和之前所有正確的標記。
這里是一個簡單的訓練數據示例:docs.google.com/spreadsheet…。
創建逐詞預測的模型是現在最常用的方法,也是本教程使用的方法。
注意:每次預測時,神經網絡接收的是同樣的截圖。也就是說如果網絡需要預測 20 個單詞,它就會得到 20 次同樣的設計截圖。現在,不用管神經網絡的工作原理,只需要專注于神經網絡的輸入和輸出。
我們先來看前面的標記(markup)。假如我們訓練神經網絡的目的是預測句子「I can code」。當網絡接收「I」時,預測「can」。下一次時,網絡接收「I can」,預測「code」。它接收所有之前單詞,但只預測下一個單詞。
神經網絡根據數據創建特征。神經網絡構建特征以連接輸入數據和輸出數據。它必須創建表征來理解每個截圖的內容和它所需要預測的 HTML 語法,這些都是為預測下一個標記構建知識。把訓練好的模型應用到真實世界中和模型訓練過程差不多。
我們無需輸入正確的 HTML 標記,網絡會接收它目前生成的標記,然后預測下一個標記。預測從「起始標簽」(start tag)開始,到「結束標簽」(end tag)終止,或者達到最大限制時終止。
Hello World 版
現在讓我們構建 Hello World 版實現。我們將饋送一張帶有「Hello World!」字樣的截屏到神經網絡中,并訓練它生成對應的標記語言。
首先,神經網絡將原型設計轉換為一組像素值。且每一個像素點有 RGB 三個通道,每個通道的值都在 0-255 之間。
為了以神經網絡能理解的方式表征這些標記,我使用了 one-hot 編碼。因此句子「I can code」可以映射為以下形式。
在上圖中,我們的編碼包含了開始和結束的標簽。這些標簽能為神經網絡提供開始預測和結束預測的位置信息。以下是這些標簽的各種組合以及對應 one-hot 編碼的情況。
我們會使每個單詞在每一輪訓練中改變位置,因此這允許模型學習序列而不是記憶詞的位置。在下圖中有四個預測,每一行是一個預測。且左邊代表 RGB 三色通道和之前的詞,右邊代表預測結果和紅色的結束標簽。
#Length of longest sentence max_caption_len = 3 #Size of vocabulary vocab_size = 3 # Load one screenshot for each word and turn them into digits images = [] for i in range(2): images.append(img_to_array(load_img('screenshot.jpg', target_size=(224, 224)))) images = np.array(images, dtype=float) # Preprocess input for the VGG16 model images = preprocess_input(images) #Turn start tokens into one-hot encoding html_input = np.array( [[[0., 0., 0.], #start [0., 0., 0.], [1., 0., 0.]], [[0., 0., 0.], #start <HTML>Hello World!</HTML> [1., 0., 0.], [0., 1., 0.]]]) #Turn next word into one-hot encoding next_words = np.array( [[0., 1., 0.], # <HTML>Hello World!</HTML> [0., 0., 1.]]) # end # Load the VGG16 model trained on imagenet and output the classification feature VGG = VGG16(weights='imagenet', include_top=True) # Extract the features from the image features = VGG.predict(images) #Load the feature to the network, apply a dense layer, and repeat the vector vgg_feature = Input(shape=(1000,)) vgg_feature_dense = Dense(5)(vgg_feature) vgg_feature_repeat = RepeatVector(max_caption_len)(vgg_feature_dense) # Extract information from the input seqence language_input = Input(shape=(vocab_size, vocab_size)) language_model = LSTM(5, return_sequences=True)(language_input) # Concatenate the information from the image and the input decoder = concatenate([vgg_feature_repeat, language_model]) # Extract information from the concatenated output decoder = LSTM(5, return_sequences=False)(decoder) # Predict which word comes next decoder_output = Dense(vocab_size, activation='softmax')(decoder) # Compile and run the neural network model = Model(inputs=[vgg_feature, language_input], outputs=decoder_output) model.compile(loss='categorical_crossentropy', optimizer='rmsprop') # Train the neural network model.fit([features, html_input], next_words, batch_size=2, shuffle=False, epochs=1000) 復制代碼
在 Hello World 版本中,我們使用三個符號「start」、「Hello World」和「end」。字符級的模型要求更小的詞匯表和受限的神經網絡,而單詞級的符號在這里可能有更好的性能。
以下是執行預測的代碼:
# Create an empty sentence and insert the start token sentence = np.zeros((1, 3, 3)) # [[0,0,0], [0,0,0], [0,0,0]] start_token = [1., 0., 0.] # start sentence[0][2] = start_token # place start in empty sentence # Making the first prediction with the start token second_word = model.predict([np.array([features[1]]), sentence]) # Put the second word in the sentence and make the final prediction sentence[0][1] = start_token sentence[0][2] = np.round(second_word) third_word = model.predict([np.array([features[1]]), sentence]) # Place the start token and our two predictions in the sentence sentence[0][0] = start_token sentence[0][1] = np.round(second_word) sentence[0][2] = np.round(third_word) # Transform our one-hot predictions into the final tokens vocabulary = ["start", "<HTML><center><H1>Hello World!</H1></center></HTML>", "end"] for i in sentence[0]: print(vocabulary[np.argmax(i)], end=' ') 復制代碼
輸出
我走過的坑:
在 FloydHub 上運行代碼
FloydHub 是一個深度學習訓練平臺,我自從開始學習深度學習時就對它有所了解,我也常用它訓練和管理深度學習試驗。我們能安裝它并在 10 分鐘內運行第一個模型,它是在云 GPU 上訓練模型最好的選擇。若果讀者沒用過 FloydHub,可以花 10 分鐘左右安裝并了解。
FloydHub 地址:www.floydhub.com/
復制 Repo:
https://github.com/emilwallner/Screenshot-to-code-in-Keras.git 復制代碼
登錄并初始化 FloydHub 命令行工具:
cd Screenshot-to-code-in-Keras floyd login floyd init s2c 復制代碼
在 FloydHub 云 GPU 機器上運行 Jupyter notebook:
floyd run --gpu --env tensorflow-1.4 --data emilwallner/datasets/imagetocode/2:data --mode jupyter 復制代碼
所有的 notebook 都放在 floydbub 目錄下。一旦我們開始運行模型,那么在 floydhub/Helloworld/helloworld.ipynb 下可以找到第一個 Notebook。更多詳情請查看本項目早期的 flags。
HTML 版本
在這個版本中,我們將關注與創建一個可擴展的神經網絡模型。該版本并不能直接從隨機網頁預測 HTML,但它是探索動態問題不可缺少的步驟。
概覽
如果我們將前面的架構擴展為以下右圖展示的結構,那么它就能更高效地處理識別與轉換過程。
該架構主要有兩個部,即編碼器與解碼器。編碼器是我們創建圖像特征和前面標記特征(markup features)的部分。特征是網絡創建原型設計和標記語言之間聯系的構建塊。在編碼器的末尾,我們將圖像特征傳遞給前面標記的每一個單詞。隨后解碼器將結合原型設計特征和標記特征以創建下一個標簽的特征,這一個特征可以通過全連接層預測下一個標簽。
設計原型的特征
因為我們需要為每個單詞插入一個截屏,這將會成為訓練神經網絡的瓶頸。因此我們抽取生成標記語言所需要的信息來替代直接使用圖像。這些抽取的信息將通過預訓練的 CNN 編碼到圖像特征中,且我們將使用分類層之前的層級輸出以抽取特征。
我們最終得到 1536 個 8*8 的特征圖,雖然我們很難直觀地理解它,但神經網絡能夠從這些特征中抽取元素的對象和位置。
標記特征
在 Hello World 版本中,我們使用 one-hot 編碼以表征標記。而在該版本中,我們將使用詞嵌入表征輸入并使用 one-hot 編碼表示輸出。我們構建每個句子的方式保持不變,但我們映射每個符號的方式將會變化。one-hot 編碼將每一個詞視為獨立的單元,而詞嵌入會將輸入數據表征為一個實數列表,這些實數表示標記標簽之間的關系。
上面詞嵌入的維度為 8,但一般詞嵌入的維度會根據詞匯表的大小在 50 到 500 間變動。以上每個單詞的八個數值就類似于神經網絡中的權重,它們傾向于刻畫單詞之間的聯系(Mikolov alt el., 2013)。這就是我們開始部署標記特征(markup features)的方式,而這些神經網絡訓練的特征會將輸入數據和輸出數據聯系起來。
編碼器
我們現在將詞嵌入饋送到 LSTM 中,并期望能返回一系列的標記特征。這些標記特征隨后會饋送到一個 Time Distributed 密集層,該層級可以視為有多個輸入和輸出的全連接層。
和嵌入與 LSTM 層相平行的還有另外一個處理過程,其中圖像特征首先會展開成一個向量,然后再饋送到一個全連接層而抽取出高級特征。這些圖像特征隨后會與標記特征相級聯而作為編碼器的輸出。
標記特征
如下圖所示,現在我們將詞嵌入投入到 LSTM 層中,所有的語句都會用零填充以獲得相同的向量長度。
為了混合信號并尋找高級模式,我們運用了一個 TimeDistributed 密集層以抽取標記特征。TimeDistributed 密集層和一般的全連接層非常相似,且它有多個輸入與輸出。
圖像特征
對于另一個平行的過程,我們需要將圖像的所有像素值展開成一個向量,因此信息不會被改變,它們只會用來識別。
如上,我們會通過全連接層混合信號并抽取更高級的概念。因為我們并不只是處理一個輸入值,因此使用一般的全連接層就行了。
級聯圖像特征和標記特征
所有的語句都被填充以創建三個標記特征。因為我們已經預處理了圖像特征,所以我們能為每一個標記特征添加圖像特征。
如上,在復制圖像特征到對應的標記特征后,我們得到了新的圖像-標記特征(image-markup features),這就是我們饋送到解碼器的輸入值。
解碼器
現在,我們使用圖像-標記特征來預測下一個標簽。
在下面的案例中,我們使用三個圖像-標簽特征對來輸出下一個標簽特征。注意 LSTM 層不應該返回一個長度等于輸入序列的向量,而只需要預測預測一個特征。在我們的案例中,這個特征將預測下一個標簽,它包含了最后預測的信息。
最后的預測
密集層會像傳統前饋網絡那樣工作,它將下一個標簽特征中的 512 個值與最后的四個預測連接起來,即我們在詞匯表所擁有的四個單詞:start、hello、world 和 end。密集層最后采用的 softmax 函數會為四個類別產生一個概率分布,例如 [0.1, 0.1, 0.1, 0.7] 將預測第四個詞為下一個標簽。
# Load the images and preprocess them for inception-resnet images = [] all_filenames = listdir('images/') all_filenames.sort() for filename in all_filenames: images.append(img_to_array(load_img('images/'+filename, target_size=(299, 299)))) images = np.array(images, dtype=float) images = preprocess_input(images) # Run the images through inception-resnet and extract the features without the classification layer IR2 = InceptionResNetV2(weights='imagenet', include_top=False) features = IR2.predict(images) # We will cap each input sequence to 100 tokens max_caption_len = 100 # Initialize the function that will create our vocabulary tokenizer = Tokenizer(filters='', split=" ", lower=False) # Read a document and return a string def load_doc(filename): file = open(filename, 'r') text = file.read() file.close() return text # Load all the HTML files X = [] all_filenames = listdir('html/') all_filenames.sort() for filename in all_filenames: X.append(load_doc('html/'+filename)) # Create the vocabulary from the html files tokenizer.fit_on_texts(X) # Add +1 to leave space for empty words vocab_size = len(tokenizer.word_index) + 1 # Translate each word in text file to the matching vocabulary index sequences = tokenizer.texts_to_sequences(X) # The longest HTML file max_length = max(len(s) for s in sequences) # Intialize our final input to the model X, y, image_data = list(), list(), list() for img_no, seq in enumerate(sequences): for i in range(1, len(seq)): # Add the entire sequence to the input and only keep the next word for the output in_seq, out_seq = seq[:i], seq[i] # If the sentence is shorter than max_length, fill it up with empty words in_seq = pad_sequences([in_seq], maxlen=max_length)[0] # Map the output to one-hot encoding out_seq = to_categorical([out_seq], num_classes=vocab_size)[0] # Add and image corresponding to the HTML file image_data.append(features[img_no]) # Cut the input sentence to 100 tokens, and add it to the input data X.append(in_seq[-100:]) y.append(out_seq) X, y, image_data = np.array(X), np.array(y), np.array(image_data) # Create the encoder image_features = Input(shape=(8, 8, 1536,)) image_flat = Flatten()(image_features) image_flat = Dense(128, activation='relu')(image_flat) ir2_out = RepeatVector(max_caption_len)(image_flat) language_input = Input(shape=(max_caption_len,)) language_model = Embedding(vocab_size, 200, input_length=max_caption_len)(language_input) language_model = LSTM(256, return_sequences=True)(language_model) language_model = LSTM(256, return_sequences=True)(language_model) language_model = TimeDistributed(Dense(128, activation='relu'))(language_model) # Create the decoder decoder = concatenate([ir2_out, language_model]) decoder = LSTM(512, return_sequences=False)(decoder) decoder_output = Dense(vocab_size, activation='softmax')(decoder) # Compile the model model = Model(inputs=[image_features, language_input], outputs=decoder_output) model.compile(loss='categorical_crossentropy', optimizer='rmsprop') # Train the neural network model.fit([image_data, X], y, batch_size=64, shuffle=False, epochs=2) # map an integer to a word def word_for_id(integer, tokenizer): for word, index in tokenizer.word_index.items(): if index == integer: return word return None # generate a description for an image def generate_desc(model, tokenizer, photo, max_length): # seed the generation process in_text = 'START' # iterate over the whole length of the sequence for i in range(900): # integer encode input sequence sequence = tokenizer.texts_to_sequences([in_text])[0][-100:] # pad input sequence = pad_sequences([sequence], maxlen=max_length) # predict next word yhat = model.predict([photo,sequence], verbose=0) # convert probability to integer yhat = np.argmax(yhat) # map integer to word word = word_for_id(yhat, tokenizer) # stop if we cannot map the word if word is None: break # append as input for generating the next word in_text += ' ' + word # Print the prediction print(' ' + word, end='') # stop if we predict the end of the sequence if word == 'END': break return # Load and image, preprocess it for IR2, extract features and generate the HTML test_image = img_to_array(load_img('images/87.jpg', target_size=(299, 299))) test_image = np.array(test_image, dtype=float) test_image = preprocess_input(test_image) test_features = IR2.predict(np.array([test_image])) generate_desc(model, tokenizer, np.array(test_features), 100) 復制代碼
輸出
訓練不同輪數所生成網站的地址:
我走過的坑:
Bootstrap 版本
在最終版本中,我們使用 pix2code 論文中生成 bootstrap 網站的數據集。使用 Twitter 的 Bootstrap 庫(getbootstrap.com/),我們可以結合 HTML 和 CSS,降低詞匯表規模。
我們將使用這一版本為之前未見過的截圖生成標記。我們還深入研究它如何構建截圖和標記的先驗知識。
我們不在 bootstrap 標記上訓練,而是使用 17 個簡化 token,將其編譯成 HTML 和 CSS。數據集(github.com/tonybeltram…)包括 1500 個測試截圖和 250 個驗證截圖。平均每個截圖有 65 個 token,一共有 96925 個訓練樣本。
我們稍微修改一下 pix2code 論文中的模型,使之預測網絡組件的準確率達到 97%。
端到端方法
從預訓練模型中提取特征在圖像描述生成模型中效果很好。但是幾次實驗后,我發現 pix2code 的端到端方法效果更好。在我們的模型中,我們用輕量級卷積神經網絡替換預訓練圖像特征。我們不使用最大池化來增加信息密度,而是增加步幅。這可以保持前端元素的位置和顏色。
存在兩個核心模型:卷積神經網絡(CNN)和循環神經網絡(RNN)。最常用的循環神經網絡是長短期記憶(LSTM)網絡。我之前的文章中介紹過 CNN 教程,本文主要介紹 LSTM。
理解 LSTM 中的時間步
關于 LSTM 比較難理解的是時間步。我們的原始神經網絡有兩個時間步,如果你給它「Hello」,它就會預測「World」。但是它會試圖預測更多時間步。下例中,輸入有四個時間步,每個單詞對應一個時間步。
LSTM 適合時序數據的輸入,它是一種適合順序信息的神經網絡。模型展開圖示如下,對于每個循環步,你需要保持同樣的權重。
加權后的輸入與輸出特征在級聯后輸入到激活函數,并作為當前時間步的輸出。因為我們重復利用了相同的權重,它們將從一些輸入獲取信息并構建序列的知識。下面是 LSTM 在每一個時間步上的簡化版處理過程:
理解 LSTM 層級中的單元
每一層 LSTM 單元的總數決定了它記憶的能力,同樣也對應于每一個輸出特征的維度大小。LSTM 層級中的每一個單元將學習如何追蹤句法的不同方面。以下是一個 LSTM 單元追蹤標簽行信息的可視化,它是我們用來訓練 bootstrap 模型的簡單標記語言。
每一個 LSTM 單元會維持一個單元狀態,我們可以將單元狀態視為記憶。權重和激活值可使用不同的方式修正狀態值,這令 LSTM 層可以通過保留或遺忘輸入信息而得到精調。除了處理當前輸入信息與輸出信息,LSTM 單元還需要修正記憶狀態以傳遞到下一個時間步。
dir_name = 'resources/eval_light/' # Read a file and return a string def load_doc(filename): file = open(filename, 'r') text = file.read() file.close() return text def load_data(data_dir): text = [] images = [] # Load all the files and order them all_filenames = listdir(data_dir) all_filenames.sort() for filename in (all_filenames): if filename[-3:] == "npz": # Load the images already prepared in arrays image = np.load(data_dir+filename) images.append(image['features']) else: # Load the boostrap tokens and rap them in a start and end tag syntax = '<START> ' + load_doc(data_dir+filename) + ' <END>' # Seperate all the words with a single space syntax = ' '.join(syntax.split()) # Add a space after each comma syntax = syntax.replace(',', ' ,') text.append(syntax) images = np.array(images, dtype=float) return images, text train_features, texts = load_data(dir_name) # Initialize the function to create the vocabulary tokenizer = Tokenizer(filters='', split=" ", lower=False) # Create the vocabulary tokenizer.fit_on_texts([load_doc('bootstrap.vocab')]) # Add one spot for the empty word in the vocabulary vocab_size = len(tokenizer.word_index) + 1 # Map the input sentences into the vocabulary indexes train_sequences = tokenizer.texts_to_sequences(texts) # The longest set of boostrap tokens max_sequence = max(len(s) for s in train_sequences) # Specify how many tokens to have in each input sentence max_length = 48 def preprocess_data(sequences, features): X, y, image_data = list(), list(), list() for img_no, seq in enumerate(sequences): for i in range(1, len(seq)): # Add the sentence until the current count(i) and add the current count to the output in_seq, out_seq = seq[:i], seq[i] # Pad all the input token sentences to max_sequence in_seq = pad_sequences([in_seq], maxlen=max_sequence)[0] # Turn the output into one-hot encoding out_seq = to_categorical([out_seq], num_classes=vocab_size)[0] # Add the corresponding image to the boostrap token file image_data.append(features[img_no]) # Cap the input sentence to 48 tokens and add it X.append(in_seq[-48:]) y.append(out_seq) return np.array(X), np.array(y), np.array(image_data) X, y, image_data = preprocess_data(train_sequences, train_features) #Create the encoder image_model = Sequential() image_model.add(Conv2D(16, (3, 3), padding='valid', activation='relu', input_shape=(256, 256, 3,))) image_model.add(Conv2D(16, (3,3), activation='relu', padding='same', strides=2)) image_model.add(Conv2D(32, (3,3), activation='relu', padding='same')) image_model.add(Conv2D(32, (3,3), activation='relu', padding='same', strides=2)) image_model.add(Conv2D(64, (3,3), activation='relu', padding='same')) image_model.add(Conv2D(64, (3,3), activation='relu', padding='same', strides=2)) image_model.add(Conv2D(128, (3,3), activation='relu', padding='same')) image_model.add(Flatten()) image_model.add(Dense(1024, activation='relu')) image_model.add(Dropout(0.3)) image_model.add(Dense(1024, activation='relu')) image_model.add(Dropout(0.3)) image_model.add(RepeatVector(max_length)) visual_input = Input(shape=(256, 256, 3,)) encoded_image = image_model(visual_input) language_input = Input(shape=(max_length,)) language_model = Embedding(vocab_size, 50, input_length=max_length, mask_zero=True)(language_input) language_model = LSTM(128, return_sequences=True)(language_model) language_model = LSTM(128, return_sequences=True)(language_model) #Create the decoder decoder = concatenate([encoded_image, language_model]) decoder = LSTM(512, return_sequences=True)(decoder) decoder = LSTM(512, return_sequences=False)(decoder) decoder = Dense(vocab_size, activation='softmax')(decoder) # Compile the model model = Model(inputs=[visual_input, language_input], outputs=decoder) optimizer = RMSprop(lr=0.0001, clipvalue=1.0) model.compile(loss='categorical_crossentropy', optimizer=optimizer) #Save the model for every 2nd epoch filepath="org-weights-epoch-{epoch:04d}--val_loss-{val_loss:.4f}--loss-{loss:.4f}.hdf5" checkpoint = ModelCheckpoint(filepath, monitor='val_loss', verbose=1, save_weights_only=True, period=2) callbacks_list = [checkpoint] # Train the model model.fit([image_data, X], y, batch_size=64, shuffle=False, validation_split=0.1, callbacks=callbacks_list, verbose=1, epochs=50) 復制代碼
測試準確率
找到一種測量準確率的優秀方法非常棘手。比如一個詞一個詞地對比,如果你的預測中有一個詞不對照,準確率可能就是 0。如果你把百分百對照的單詞移除一個,最終的準確率可能是 99/100。
我使用的是 BLEU 分值,它在機器翻譯和圖像描述模型實踐上都是最好的。它把句子分解成 4 個 n-gram,從 1-4 個單詞的序列。在下面的預測中,「cat」應該是「code」。
為了得到最終的分值,每個的分值需要乘以 25%,(4/5) × 0.25 + (2/4) × 0.25 + (1/3) × 0.25 + (0/2) ×0.25 = 0.2 + 0.125 + 0.083 + 0 = 0.408。然后用總和乘以句子長度的懲罰函數。因為在我們的示例中,長度是正確的,所以它就直接是我們的最終得分。
你可以增加 n-gram 的數量,4 個 n-gram 的模型是最為對應人類翻譯的。我建議你閱讀下面的代碼:
#Create a function to read a file and return its content def load_doc(filename): file = open(filename, 'r') text = file.read() file.close() return text def load_data(data_dir): text = [] images = [] files_in_folder = os.listdir(data_dir) files_in_folder.sort() for filename in tqdm(files_in_folder): #Add an image if filename[-3:] == "npz": image = np.load(data_dir+filename) images.append(image['features']) else: # Add text and wrap it in a start and end tag syntax = '<START> ' + load_doc(data_dir+filename) + ' <END>' #Seperate each word with a space syntax = ' '.join(syntax.split()) #Add a space between each comma syntax = syntax.replace(',', ' ,') text.append(syntax) images = np.array(images, dtype=float) return images, text #Intialize the function to create the vocabulary tokenizer = Tokenizer(filters='', split=" ", lower=False) #Create the vocabulary in a specific order tokenizer.fit_on_texts([load_doc('bootstrap.vocab')]) dir_name = '../../../../eval/' train_features, texts = load_data(dir_name) #load model and weights json_file = open('../../../../model.json', 'r') loaded_model_json = json_file.read() json_file.close() loaded_model = model_from_json(loaded_model_json) # load weights into new model loaded_model.load_weights("../../../../weights.hdf5") print("Loaded model from disk") # map an integer to a word def word_for_id(integer, tokenizer): for word, index in tokenizer.word_index.items(): if index == integer: return word return None print(word_for_id(17, tokenizer)) # generate a description for an image def generate_desc(model, tokenizer, photo, max_length): photo = np.array([photo]) # seed the generation process in_text = '<START> ' # iterate over the whole length of the sequence print('\nPrediction---->\n\n<START> ', end='') for i in range(150): # integer encode input sequence sequence = tokenizer.texts_to_sequences([in_text])[0] # pad input sequence = pad_sequences([sequence], maxlen=max_length) # predict next word yhat = loaded_model.predict([photo, sequence], verbose=0) # convert probability to integer yhat = argmax(yhat) # map integer to word word = word_for_id(yhat, tokenizer) # stop if we cannot map the word if word is None: break # append as input for generating the next word in_text += word + ' ' # stop if we predict the end of the sequence print(word + ' ', end='') if word == '<END>': break return in_text max_length = 48 # evaluate the skill of the model def evaluate_model(model, descriptions, photos, tokenizer, max_length): actual, predicted = list(), list() # step over the whole set for i in range(len(texts)): yhat = generate_desc(model, tokenizer, photos[i], max_length) # store actual and predicted print('\n\nReal---->\n\n' + texts[i]) actual.append([texts[i].split()]) predicted.append(yhat.split()) # calculate BLEU score bleu = corpus_bleu(actual, predicted) return bleu, actual, predicted bleu, actual, predicted = evaluate_model(loaded_model, texts, train_features, tokenizer, max_length) #Compile the tokens into HTML and css dsl_path = "compiler/assets/web-dsl-mapping.json" compiler = Compiler(dsl_path) compiled_website = compiler.compile(predicted[0], 'index.html') print(compiled_website ) print(bleu) 復制代碼
輸出
樣本輸出的鏈接:
我走過的坑:
下一步
前端開發是深度學習應用的理想空間。數據容易生成,并且當前深度學習算法可以映射絕大部分邏輯。一個最讓人激動的領域是注意力機制在 LSTM 上的應用。這不僅會提升精確度,還可以使我們可視化 CNN 在生成標記時所聚焦的地方。注意力同樣是標記、可定義模板、腳本和最終端之間通信的關鍵。注意力層要追蹤變量,使網絡可以在編程語言之間保持通信。
但是在不久的將來,最大的影響將會來自合成數據的可擴展方法。接著你可以一步步添加字體、顏色和動畫。目前為止,大多數進步發生在草圖(sketches)方面并將其轉化為模版應用。在不到兩年的時間里,我們將創建一個草圖,它會在一秒之內找到相應的前端。Airbnb 設計團隊與 Uizard 已經創建了兩個正在使用的原型。下面是一些可能的試驗過程:
實驗
開始
進一步實驗
原文鏈接:blog.floydhub.com/turning-des…
本文為機器之心編譯,轉載請聯系本公眾號獲得授權。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。