是時候給你的產品配一個AI問答助手了!
> 本文由雲 + 社群發表
| 導語 問答系統是資訊檢索的一種高階形式,能夠更加準確地理解使用者用自然語言提出的問題,並通過檢索語料庫、知識圖譜或問答知識庫返回簡潔、準確的匹配答案。相較於搜尋引擎,問答系統能更好地理解使用者提問的真實意圖, 進一步能更有效地滿足使用者的資訊需求。問答系統是目前人工智慧和自然語言處理領域中一個倍受關注並具有廣泛發展前景的研究方向。
一、引言
問答系統處理的物件主要包括使用者的問題以及答案。根據問題所屬的知識領域,問答系統可分為面向限定域的問答系統、面向開放域的問答系統、以及面向常用問題集(Frequently Asked Questions, FAQ)的問答系統。依據答案來源,問答系統可分為基於結構化資料的問答系統如 KBQA、基於文字的問答系統如機器閱讀理解、以及基於問答對的問答系統如 FAQ 問答。此外,按照答案的反饋機制劃分,問答系統還可以分為基於檢索式的問答系統和基於生成式的問答系統。
本文主要闡述 FAQBot 檢索型問答系統的相關研究和處理框架,以及深度學習在其中的應用。FAQ 檢索型問答是根據使用者的新 Query 去 FAQ 知識庫找到最合適的答案並反饋給使用者。如圖所示:
其中,Qi 是知識庫裡的標準問,Ai 是標準問對應的答案。
具體處理流程為:
- 候選集離線建好索引。採用 Lucene 引擎,為數萬個相似問集合建立字級別倒排索引。Lucene 引擎的效能能夠將召回時間控制在毫秒級別,大大減輕後續模組的計算壓力;
- 線上收到使用者 query 後,初步召回一批候選集作為粗排結果傳入下一模組進行進一步精確排序;
- 利用 matching 模型計算使用者 query 和 FAQ 知識庫中問題或答案的匹配程度;
- 利用 ranking 模型對候選集做 rerank 並返回 topk 個候選答案。
可以看出,FAQ 問答系統的核心任務可以抽象為文字匹配任務。傳統文字匹配方法如資訊檢索中的 BM25,向量空間模型 VSM 等方法,主要解決字面相似度問題。然而由於中文含義的豐富性,通常很難直接根據關鍵字匹配或者基於機器學習的淺層模型來確定兩個句子之間的語義相似度。近幾年,利用神經網路,尤其是深度學習模型學習文字中深層的語義特徵,對文字做語義表示後進行語義匹配的方法開始被提出並應用於檢索式問答系統。基於深度學習的模型一方面能夠節省人工提取特徵的大量人力物力。此外,相比於傳統方法,深度文字匹配模型能夠從大量的樣本中自動提取出詞語之間的關係,並能結合短語匹配中的結構資訊和文字匹配的層次化特性,發掘傳統模型很難發掘的隱含在大量資料中含義不明顯的特徵,更精細地描述文字匹配問題。
二、深度學習文字匹配
FAQ 問答系統一般有兩種解決思路,一種是相似問題匹配,即對比使用者問題與現有 FAQ 知識庫中問題的相似度,返回使用者問題對應的最準確的答案,這種思路類似於 text paraphrase;另一種是問題答案對匹配,即對比使用者問題與 FAQ 知識庫中答案的匹配度,返回使用者問題對應的最準確的答案,這種思路為答案選擇,即 QA 匹配。這兩個型別相通的地方在於都可以看作文字語義匹配,很多模型能同時在兩個任務上都得到很好的效果,區別在於 QA 匹配存在問題與答案不同質的問題。
下面總結一些基於深度學習的文字匹配工作,希望能夠拋磚引玉,如有遺漏或錯誤,歡迎補充或指出。
2.1 模型框架
概括來講,深度語義匹配模型可以分為兩大類,分別是 representation-based method 和 interaction-based method。
1) Represention-based Method
框架圖如下:
這類演算法首先將待匹配的兩個物件通過深度學習模型進行表示,之後計算這兩個表示之間的相似度便可輸出兩個物件的匹配度。這種方式下,更加側重對錶示層的構建,使其儘可能充分地將待匹配的兩個物件都轉換成等長的語義表示向量。然後在兩個物件對應的兩個語義表示向量基礎上,進行匹配度的計算。針對匹配度函式 f(x,y) 的計算,通常有兩種方法,如下圖所示:一種是通過相似度度量函式進行計算,實際使用過程中最常用的就是 cosine 函式,這種方式簡單高效,並且得分割槽間可控意義明確;另一種方法是將兩個向量再接一個多層感知器網路(MLP),通過資料去訓練擬合出一個匹配度得分,更加靈活擬合能力更強,但對訓練的要求也更高。
Represention-based Extended
上述的 representation-based method 存在的問題是直接基於句子的表示太粗糙,無法準確進行文字匹配任務。受資訊檢索領域的啟發,結合主題級別和單詞級別的匹配資訊通常可以獲得更好的表現。於是進一步對句子表示進行擴充套件,加入細粒度匹配資訊。框架圖如下:
2) Interaction-based Method
框架圖如下:
基於互動的方法是通過 Interaction 來對文字相似性建模。該方式更強調待匹配的兩個句子得到更充分的互動,以及互動後的匹配。在表示層不會將句子轉換成一個整體表示向量,一般情況下會保留和詞位置相對應的一組表示向量。首先基於表示層採用 DNN 或直接由 word embedding 得到的句子表示,和詞位置對應的每個向量體現了以本詞語為核心的一定的全域性資訊;然後對兩個句子按詞對應互動,由此構建兩段文字之間的 matching pattern,這裡麵包括了更細緻更區域性的文字互動資訊;基於該匹配矩陣,可以進一步使用 DNN 等來提取更高層次的匹配特徵,最後計算得到最終匹配得分。Interaction-based 方法匹配建模更加細緻、充分,一般來說效果更好,但計算成本增加,更加適合一些效果精度要求高但對計算效能要求不高的場景。
下面總結了不同型別的深度學習文字匹配模型。可以看出,深度文字匹配現有工作很多,本文將對近幾年的部分工作進行詳細介紹,其他可參考對應文獻進行深入閱讀。
- representation-based:DSSM[1]; CDSSM[2]; ARC I[3]; CNTN[4]; LSTM-RNN[5]
- representation-based extension:MultiGranCNN[6]; MV-LSTM[7]
- interaction-based:ARC II[8]; MatchPyramid[9]; Match-SRNN[10]; DeepMatch[11]; ABCNN[12]; QA-LSTM/CNN-attention[13,14]; AP[15]; AICNN[16]; MVFNN[17]; BiMPM[18]; DQI[22]; DIIN[23]
2.2 模型介紹
2.2.1 ABCNN[12]
首先介紹 BCNN,它是 ABCNN 模型的基礎,即未新增 Attention 的模型。模型結構如圖所示:
輸入層:將輸入句子進行 padding 後轉化成詞向量即可; 卷積層:對句子表示進行卷積,使用 wide conv 的方式; pooling 層:論文中使用了兩種 pooling 方式,一種是最後一個 pooling 層為 all-ap,還有一種是中間 pooling 層為 w-ap。區別就是池化時的視窗大小不同; 輸出層:接 logistic 迴歸層做 2 分類。
ABCNN 是在 BCNN 的基礎上加了兩種 attention 機制。模型結果如下圖:
(1) 在輸入層加入 attention
其原理為將輸入擴充成雙通道。新新增的通道是 attention feature map,即上圖中的藍色部分。首先計算 attention matrix A,其每個元素 Aij 代表句子 1 中第 i 個單詞對句子二中第 j 個單詞的 match_score,這裡使用了 Euclidean 距離計算。然後再分別計算兩個句子的 attention feature map。使用兩個矩陣 W0,W1 分別和 A 還有 A 的轉置相乘,得到與原本 feature 尺寸相同的 feature map。W0 和 W1 都是模型引數,可以使用相同的 W,即共享兩個矩陣。這樣我們就將原始的輸入擴充成了兩個通道。
(2) 在 pooling 層加入 attention
Attention matrix A 的計算方法與上述相同,得到 A 後需要為兩個句子分別計算 attention 權重向量,如上圖中的兩個虛線部分 col-wise sum 和 row-wise sum。這兩個向量中的每個元素分別代表了相應單詞在做 Average Pooling 時的權重。相當於 pooling 不再是簡單的 Average Pooling,而是根據計算出的 Attention 權重向量得到的 pooling。
2.2.2LSTM/CNN,attention[13,14]
給定一個 (q,a) pair,q 是問題,a 是候選答案。首先得到它們的詞向量,再使用 biLSTM 進行 encoder,生成問題和答案的分散式表示,然後利用餘弦相似度來衡量它們的距離。訓練目標是 hinge loss。
在 biLSTM 表示輸出的基礎上進一步使用 CNN,CNN 可以獲取 biLSTM 輸出的向量之間的區域性資訊。從而給出問題和答案的更多複合表示。
當 biLSTM 模型在問題和答案上長距離傳播依賴關係時,隱藏向量的固定寬度成為瓶頸。通過動態調整問題答案的更多資訊部分,可以使用注意力機制來緩解這種弱點。在 max/mean pooling 前,每個 biLSTM 輸出向量將乘以 softmax 權重,該權重由 biLSTM 的問題嵌入得到。
2.2.3 Attentive Pooling Networks[15]
QA_LSTM with attention 中 attention 的設計是通過問題對答案的影響進行特徵加權,但是它忽略了答案對問題的影響。Attentive pooling networks 同時將 attention 應用到問題和答案,提高演算法的準確率。通過同時學習兩種輸入的表示以及它們之間的相似性測量,其創新點在於將 Q 和 A 這兩個輸入通過引數矩陣 U 投射到一個共同的表示空間,用 Q 和 A 的 representation 構造了一個矩陣 G,分別對 G 的 row 和 column 做 max pooling, 這樣就能分別能得到 Q 和 A 的 attention vector。AP_BILSTM 模型框架圖如下:
AP_BILSTM 模型的設計首先將問題和答案經過 BILSTM 抽取特徵,然後通過兩者的特徵計算 soft alignment,得到的 G 矩陣表示了問題和答案相互作用的結果。對該矩陣的列取最大,即為答案對問題的重要性得分,同理對該矩陣行取最大即為問題對答案的重要性得分。這兩個向量再作為 attention 向量分別和問題和答案表示相乘後得到問題和答案新的表示,最後再做匹配。
2.2.4 AICNN[16]
之前關於答案選擇的研究通常忽略了資料中普遍存在的冗餘和噪聲問題。 在本文中,設計一種新穎的注意力互動式神經網路(AI-NN),以專注於那些有助於回答選擇的文字片段。 問題答案的表示首先通過卷積神經網路(CNN)或其他神經網路架構來學習。然後 AI-NN 學習兩個文字的每個配對片段的相互作用。 之後使用逐行和逐列池化來收集互動資訊。之後採用注意機制來衡量每個細分的重要性,並結合相互作用來獲得問答的固定長度表示。 模型框架圖如下:
2.2.5 MVFNN[17]
上述基於神經網路的方法通過計算注意力來考慮資訊的幾個不同方面。 這些不同型別的注意力總是簡單地總結並且可以被視為 “單一檢視”,不能從多個方面來審視問題和候選答案,導致嚴重的資訊丟失。 要克服這個問題,此模型提出了一種多檢視融合神經網路,其中每個關注元件生成 QA 對的不同 “檢視”,並且融合 QA 本身的特徵表示以形成更整體的表示。模型框架圖如下:
對於一個問題,可能會有一堆檢視來模擬其相應的答案。 在此模型中,根據直覺構建了四個檢視。 這四個檢視被命名為查詢型別檢視,查詢主動詞檢視,查詢語義檢視和 co-attention 檢視。最後使用 fusion RNN 模型來對這些檢視進行融合。通過不同檢視的融合,能對兩個物件進行更準確的建模。
2.2.6 BiMPM[18]
針對基於互動這一類方法,一般是先對兩個句子的單元相互匹配,之後再聚集為一個向量後做匹配。這種方式可以捕捉到兩個句子之間的互動特徵,但是之前的方式只是基於詞級別的匹配但是忽略了其他層級的資訊。此外,匹配只是基於一個方向忽略了相反的方向。一種雙向的多角度匹配模型 bilateral multi-perspective matching(BiMPM) 解決了這方面的不足。模型框架如下圖:
模型自下而上一共包含五層,分別為單詞表示層、上下文表示層、匹配層、聚合層和預測層,其中匹配層為模型的核心,共提出了四種匹配策略,這裡的匹配可以看成是 attention 機制。
單詞表示層:使用 GloVe 模型訓練向量,對字元 embedding 進行隨機初始化,單詞中的字元組成單詞的向量表示作為 LSTM 網路的輸入。
上下文表示層:使用 BiLSTM 對 p 和 q 進行表示。
匹配層:模型的核心層,包含四種匹配策略,分別是:Full-Matching、Maxpooling-Matching、Attentive-Matching 和 Max-Attentive-Matching。四種匹配策略如下圖:
聚合層:利用 BiLSTM 對匹配層的輸出向量進行處理,取得 p、q 前向和後向最後一個 time step 的輸出進行連線後輸入到預測層。
預測層:softmax 層,softmax 函式分類。
上述是對近幾年部分深度文字匹配模型的總結,接下來則介紹基於深度模型的 FAQBot。
三、基於深度學習的 FAQBot 實現
3.1 模型化流程
3.2 資料獲取及構造
3.2.1 資料獲取
對於有大量問答記錄的場景例如智慧客服,這些記錄裡面有很多高頻的知識點 (知識點包括問題和答案)。這些高頻的知識點對應的問法通常並不唯一。即知識庫的結構為一個問題集合對應同一個答案。針對 FAQ 資料有以下三種資料型別:
- 標準問 q:FAQ 中問題的標準使用者 query
- 答案 A: FAQ 中標準問對應的的標準回答
- 相似問 q1,q2...: 跟標準問語義相似可用同一答案回答的 query
其中,標準問 q、對應答案 A 以及該標準問 q 對應的所有相似問 q1,q2,...,一起組成一個知識點。一個知識點的樣例見下圖:
3.2.2 資料構造
資料構造包含了兩個方面:
(1)訓練集測試集構造
測試集:將相似問中的第一條相似問 q1 作為 query,從 FAQ 知識庫的所有知識點中通過 Lucene 召回 30 個知識點作為候選集
訓練集:包含兩部分,一部分是正例的構造,另一部分是負例的構造,這兩部分資料的構造方式將直接影響到最終的效果。在正例的構造中,因為每個知識點的第一個相似問是作為測試集中出現的,所以在構造訓練集的時候排除掉所有知識點中的第一條相似問 q1。這樣的話,有多於 2 個相似問的知識點還有多於的其他相似問可以用來構造訓練集。將這些識點中的標準問和從相似問的第二條開始(即 [q2,q3,...,qn])可以按照不同方式構造出正例和負例。
訓練集正例的構造:去除所有知識點中的第一條相似問 q1,其他相似問及標準問兩兩組合成正例 pair 對;對於相似問多的知識點進行剪下。
訓練集負例的構造的方式包括:
- 按 Jaccard 距離召回;
- 按 Lucene 召回;
- 從其他知識點中隨機選擇;
- 按照正例中各問題出現的比例從其他知識點中取樣選擇;
- 每個句子和句子中的名詞/動詞構成 pair 對;
- 針對知識點分佈不均衡的問題,對相似問很多的知識點進行相似問剪下。
(2)資料增強策略
由於深度學習需要較多的資料,為了增強資料,我們採用了以下策略:
- 交換兩個句子之間的順序;
- 對句子進行分詞,重新組合生成新的句子;
- 打亂句子的順序,隨機抽取句子。
3.3 模型建立
3.3.1 模型框架
基本框架一般都是將待匹配的兩個句子分別使用兩個 encoder 來獲取對應 context 資訊,然後將二者的 context 資訊進行匹配,得到匹配後的特徵資訊。也可以在匹配之後的特徵後面加上一些其他的傳統文字特徵,將所有這些特徵進行 concat。最後接上 softmax 層,做最終的分類。模型的框架如下圖所示:
3.3.2 模型建立及迭代優化
Embedding 層:使用 word2vec 和 fasttext 訓練詞向量和字元向量。
Encoder 層:卷積具有區域性特徵提取的功能, 所以可用 CNN 來提取句子中類似 n-gram 的關鍵資訊,考慮文字的上下文資訊。於是我們採用 textCNN[19] 來對句子進行編碼表示,encoder 過程見下圖:
Matching 層:在得到兩個句子的表示後,要針對兩個句子的表示進行 matching 操作。可以根據需要構造出很多種型別的 matching 方式如下圖 [20],我們採用相對比較簡單的 element-wise 相加和相乘的方式來進行 matching。
join 層:在 matching 層之後得到的兩個句子的共同表示之後,進一步引入額外的傳統特徵進行 join 操作,類似於下圖 [21]。
引入 interaction:上述步驟對兩個句子 encoder 時沒有考慮兩個句子之間的關聯。於是進一步引入更細緻更區域性的句子互動資訊,從而能捕捉到兩個句子之間的互動特徵,根據互動得到的矩陣獲取兩個句子新的表示。如圖:
引入 attention 機制:採用注意機制使用權重向量來衡量句子不同部分重要性的不同。attention 的計算主要思想沿用了 AICNN 和 ABCNN 中的幾種 attention,分別是 feature 的 attention,interaction 後新的表示和句子原表示之間的 attention。
四、總結與展望
4.1 資料層面
- 建立更加合理的知識庫:每個知識點只包含一個意圖,且知識點之間沒有交叉,歧義,冗餘等容易造成混淆的因素
- 標註:為每個 FAQ 積累一定數量的有代表性的相似問
- 後期的持續維護:包括新 FAQ 發現,原 FAQ 的合併、拆分、糾正等
4.2 模型層面
- 進一步捕捉 syntactic level 和 semantic level 的知識如語義角色標註(SRL, semantic role labelling)和詞性標註(POS, part of speech tagging)等,引入到文字的表示之中,提高文字語義匹配的效果
- 目前大部分檢索行問答的工作做的是問題和問題匹配,或是問題和答案匹配。後續可以同時引入問題和答案的資訊進行建模,如圖:
參考文獻
[1] Huang P S, He X, Gao J, et al. Learning deep structured semantic models for web search using clickthrough data[C]// ACM International Conference on Conference on Information & Knowledge Management. ACM, 2013:2333-2338.
[2] Shen Y, He X, Gao J, et al. A Latent Semantic Model with Convolutional-Pooling Structure for Information Retrieval[C]// Acm International Conference on Conference on Information & Knowledge Management. ACM, 2014:101-110.
[3] Hu B, Lu Z, Li H, et al. Convolutional Neural Network Architectures for Matching Natural Language Sentences[J]. Advances in Neural Information Processing Systems, 2015, 3:2042-2050.
[4] Qiu X, Huang X. Convolutional neural tensor network architecture for community-based question answering[C]// International Conference on Artificial Intelligence. AAAI Press, 2015:1305-1311.
[5] Palangi H, Deng L, Shen Y, et al. Deep Sentence Embedding Using Long Short-Term Memory Networks: Analysis and Application to Information Retrieval[J]. IEEE/ACM Transactions on Audio Speech & Language Processing, 2016, 24(4):694-707.
[6] Yin W, Schütze H. MultiGranCNN: An Architecture for General Matching of Text Chunks on Multiple Levels of Granularity[C]// Meeting of the Association for Computational Linguistics and the, International Joint Conference on Natural Language Processing. 2015:63-73.
[7] Wan S, Lan Y, Guo J, et al. A Deep Architecture for Semantic Matching with Multiple Positional Sentence Representations[J]. 2015:2835-2841.
[8] Hu B, Lu Z, Li H, et al. Convolutional Neural Network Architectures for Matching Natural Language Sentences[J]. Advances in Neural Information Processing Systems, 2015, 3:2042-2050.
[9] Pang L, Lan Y, Guo J, et al. Text Matching as Image Recognition[J]. 2016.
[10] Wan S, Lan Y, Xu J, et al. Match-SRNN: Modeling the Recursive Matching Structure with Spatial RNN[J]. Computers & Graphics, 2016, 28(5):731-745.
[11] Lu Z, Li H. A deep architecture for matching short texts[C]// International Conference on Neural Information Processing Systems. Curran Associates Inc. 2013:1367-1375.
[12] Yin W, Schütze H, Xiang B, et al. ABCNN: Attention-Based Convolutional Neural Network for Modeling Sentence Pairs[J]. Computer Science, 2015.
[13] Tan M, Santos C D, Xiang B, et al. LSTM-based Deep Learning Models for Non-factoid Answer Selection[J]. Computer Science, 2015.
[14] Tan M, Santos C D, Xiang B, et al. Improved Representation Learning for Question Answer Matching[C]// Meeting of the Association for Computational Linguistics. 2016:464-473.
[15] Santos C D, Tan M, Xiang B, et al. Attentive Pooling Networks[J]. 2016.
[16] X Zhang , S Li , L Sha , H Wang. Attentive Interactive Neural Networks for Answer Selection in Community Question Answering[C]// International Conference on Artificial Intelligence.
[17] L Sha , X Zhang , F Qian , B Chang , Z Sui. A Multi-View Fusion Neural Network for Answer Selection[C]// International Conference on Artificial Intelligence.
[18] Wang Z, Hamza W, Florian R. Bilateral Multi-Perspective Matching for Natural Language Sentences[C]// Twenty-Sixth International Joint Conference on Artificial Intelligence. 2017:4144-4150.
[19] Kim Y. Convolutional Neural Networks for Sentence Classification[J]. Eprint Arxiv, 2014.
[20] Wang S, Jiang J. A Compare-Aggregate Model for Matching Text Sequences[J]. 2016.
[21] Severyn A, Moschitti A. Learning to Rank Short Text Pairs with Convolutional Deep Neural Networks[C]// The International ACM SIGIR Conference. ACM, 2015:373-382.
[22] Xiaodong Zhang, Xu Sun, Houfeng Wang. Duplicate Question Identification by Integrating FrameNet with Neural Networks[C]//In the Thirty-Second AAAI Conference on Artificial Intelligence (AAAI-18)
[23] Gong Y, Luo H, Zhang J. Natural Language Inference over Interaction Space[J]. 2018.
此文已由作者授權騰訊雲 + 社群在各渠道釋出
獲取更多新鮮技術乾貨,可以關注我們騰訊雲技術社群-雲加社群官方號及知乎機構號
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- “懶癌”患者福音:是時候找一個AI幫你做家務了AI
- 是時候擁有一個你自己的命令列工具了命令列
- DrawIO 二開 —— 是時候給你的 ProcessOn 充值終身 VIP 了
- 🚀提升生產力:是時候升級你的命令列工具了命令列
- 程式設計師:你是一個產品程式設計師
- WebSocket是時候展現你優秀的一面了Web
- 是時候給糟糕的技術面試來場革命了面試
- APK瘦身-是時候給App進行減負了APKAPP
- 實時數倉是一個產品還是解決方案?
- 求你了,再問你Java記憶體模型的時候別再給我講堆疊方法區了…Java記憶體模型
- 用OceanBase試了一下ChatGPT開源文件問答助手ChatGPT
- 平安AI護航,是時候來次教育升級了AI
- Spring Boot 面試,一個問題你就答不上來了Spring Boot面試
- 是時候,升級你的 Windows 了「GitHub 熱點速覽」WindowsGithub
- 無人機“黑飛”屢禁不止,是時候給它套上“金箍”了無人機
- AI與雲這門To B生意,是時候“娘化”一下了AI
- 一個AI產品經理怎麼看AI的發展AI
- 是時候該學JavaScript了JavaScript
- 如何成為一個AI產品經理?AI
- 是時候瞭解一下 UILayoutGuide 了GUIIDE
- 你的專案剛剛啟動?是時候考慮Globalization了!
- 利用js寫一個分時問候JS
- 北京與你科技招聘 Golang 中高階工程師,#年底了,是時候打算挪個窩了#Golang工程師
- 使用entity bean時候的一個問題?helpBean
- 什麼是AI產品經理AI
- 面試時這麼問你Spring Boot,你能答對幾個?面試Spring Boot
- 是時候捋一捋 Java 的深淺複製了Java
- 是時候理清 React 開發中的一些疑惑了React
- 川普啟動美國AI計劃後,是時候瞭解美國眼中的「中國AI戰略」了AI
- 什麼是 websocket 以及是時候推廣一波 swoole 了Web
- 設計模式總是學不會?是時候換個姿勢了設計模式
- [排期問題] 今天又把產品跟專案經理給罵了
- 是時候開發你自己的vscode擴充套件外掛了VSCode套件
- 是時候扔掉 Postman 了,Apifox 真香!PostmanAPI
- 是時候瞭解React Native了React Native
- 是時候向Chrome說再見了Chrome
- 是時候學習真正的 spark 技術了Spark
- 還在寫iOS?是時候學一下Flutter了iOSFlutter