# Project2 for the Biomedical Information Retrieval Course
###### tags: `NCKU` `python` `生醫資訊` `Flask` `jinja2`
## Environment
* macOS
* python3
* Flask
* nltk
* numpy
* matplotlib
* bootstrap5
## Requirement
* 實現zipf distribution(對文字的頻率的認識)
* pudmed XML文件 & Twitter json文件
* 取不同的數量並分析彼此差別
* stopword
* porter's Aigorithm
* edit-matching
## Flask
[Flask](https://flask.palletsprojects.com/en/2.0.x/quickstart/#a-minimal-application) 主要是由[Werkzeug WSGI 工具箱](https://werkzeug.palletsprojects.com/en/2.0.x/)和[Jinja2 模板引擎](https://werkzeug.palletsprojects.com/en/2.0.x/)構成
```
Flask path
/file
/static
|-/img
|-/js
/templates
app.py
```
`file`儲存一些檔案處理過的資料
`static`一般是儲存image、javaScript跟css
`templates` 儲存html格式
要注意不可以寫成`flask.py`,會出錯
```python=
export FLASK_APP=app.py
flask run --host=0.0.0.0
# or python3 run.py
```
### 與html傳遞的方法
可以使用route()來告訴FLASK,並在後面填入要載入的url位址(網路路徑)中。首頁就可以寫成 `@app.route(“/”)`。通常我會把function name跟網路路徑取相同的名稱。
### Jinja2樣版引擎
Jinja2可以將HTML頁面與後台的程式連起來,達到簡化HTML的目的。也可以方便地將參數傳至HTML並顯示出來。
#### render_template
> render_template('name.html', **lacal())
`**local()`是將funciton裡面的所有參數都傳到前端去,也可以使用 `name = name`這個用法選擇指定的參數傳入。而在.html檔中顯示參數的方法為`{{ name }}`。
#### 語法
Jinja2可以使用一些ifelse、for、list、set、dict這些方法。
**if else :**
```htmlembedded=
{% if path|e == '/show_text' %}
<!-- 內容 -->
{% elif path|e == '/show_json' %}
<!-- 內容 -->
{% else %}
<!-- 內容 -->
{% endif %}
```
**for loop :**
```htmlembedded=
{% for word_list in new_list %}
<!-- 內容 -->
{% endfor %}
```
**Dictionary :**
```htmlembedded=
{% for searched_word, article in word_list.items() %}
{{searched_word}} #key值
{{article}} #value值
{% endfor %}
```
**List :**
```htmlembedded=
{{list.0}}
{{list.1}}
```
> 更多用法:[python jinja2 + flask](https://hackmd.io/@shaoeChen/SJ0X-PnkG?type=view)
#### 樣板繼承方法
有時候為了不讓相同的語法重複使用(例如固定列),我們可以用繼承的方法來簡化他。
子樣版通常會寫成
```htmlembedded=
{% extends "父樣版.html" %}
```
並注意extends一定要放在繼承的html中的第一個標籤。
在被繼承的html檔中放入想要填入子樣版的內容,會使用
```htmlembedded=
{% block content %}{% endblock %}
```
content可以是任何變數,但一定要跟子樣版的變數一樣。且同一個頁面中,content不可以重複。
#### GET & POST傳遞資料
> 參考資料:[Python Web Flask — GET、POST傳送資料](https://medium.com/seaniap/python-web-flask-get-post傳送資料-2826aeeb0e28)
當伺服器接受Request且提供對應的Response的溝通行為,是HTTP Request的一個生命週期。
這次search功能是使用POST來實作的
import方法:
`from flask import Flask, render_template, request`
在HTML檔裡頭定義POST以及設定要對應function的名稱
```htmlembedded=
<form class="d-flex" method = "POST" action = "{{url_for('submit_page')}}">
<input class="form-control" name = 'search' type="search" placeholder="Search" aria-label="Search">
<button class="btn submit-btn" type="submit">Search</button>
</form>
```
其對應的function為
```python=
@app.route('/submit_page', methods=['POST'])
def submit_page():
search = request.values['search']
```
由上面可以知道search就是從html傳遞過來的變數。
#### 檔案上傳
首先需要一個enctype屬性設定為 `multipart/form-data` 的HTML表單,將該文提交到指定URL。 URL處理程式從 `request.files[]` 物件中提取檔案並將其儲存到所需的位置。可以從`request.files[file]`物件的filename屬性中獲取。 但建議使用secure_filename()函式獲取它的安全版本。
```python=
IMG_FOLDER = os.path.join('static', 'img')
app.config['IMG_FOLDER'] = IMG_FOLDER #取得檔案路徑
os.path.join(app.config['IMG_FOLDER'], 'image.png')
```
#### Markup方法
[Flask Markup](https://dormousehole.readthedocs.io/en/latest/api.html#flask.Markup)
> **class flask.Markup(base='', encoding=None, errors='strict')**
>
> Def : A string that is ready to be safely inserted into an HTML or XML document, either because it was escaped or because it was marked safe.
>Passing an object to the constructor converts it to text and wraps it to mark it safe without escaping. To escape the text, use the escape() class method instead.
這次專案中我發現這是一個很好用的方法,當你在後台嵌入HTML格式,經過route傳遞到.HTML會被自動轉譯成string格式而不是保留html(例如`<class=...>`會被轉型成 `<class=...>`),此時就需要使用到Markup。
##### 安裝Markup
`pip3 install Markup`
##### import
`from markupsafe import Markup`
##### 範例
```python=
Markup("<em>Hello</em> ") + "<foo>"
# Markup('<em>Hello</em> <foo>')
```
可以看到有Markup的被保留下來.
## Bootstrap 5
> [官方文件](https://getbootstrap.com/docs/5.0/getting-started/introduction/)
> [中文版](https://bootstrap5.hexschool.com)
這次的互動樣式都是使用 **Bootstrap5**建構出來的,他厲害的地方在於Bootstrap將流變隔線規劃的非常完整,要做RWD(響應式設計)會輕鬆許多。
此次專案主要用到幾項小元件
### Narbar
他是一個導覽列,我主要的功能按鈕都放在這個導覽列上

`Choose File: `選擇Xml(download from pubmed)或是Json(download from Twitter)
`Picture: `找出各篇的Zipf distribution、Porter's algo、stop word.
`Search: `搜尋相近字並highlight
### Navs and tabs
頁籤功能,主要是拿來做相似字排列,當點到button時便會對應到存在該單字的文章。

除此之外還有使用到了`Button`、`Alert`、`Dropdowns`等小功能。
另外還可以使用css更改顏色樣式,在使用Bootstrap前記得link css與javaScript
```htmlembedded=
<!- css ->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-wEmeIV1mKuiNpC+IOBjI7aAzPcEZeedi5yW5f2yOq55WWLwNGmvvx4Um1vskeMj0" crossorigin="anonymous">
```
```htmlembedded=
<!- javaScript ->
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/js/bootstrap.min.js" integrity="sha384-lpyLfhYuitXl2zRZ5Bn2fqnhNAKOAaM/0Kr9laMspuaMiZfGmfwRNFh8HlMy49eQ" crossorigin="anonymous"></script>
```
## Text preprocessing
### What is Tokenization?
**Tokenization is the process by which a large quantity of text is divided into smaller parts called tokens.** These tokens are very useful for finding patterns and are considered as a base step for stemming and lemmatization.
### 切割words
#### Python NLTK tokenize.WordPunctTokenizer()
用法:能從字母或是非字母字符流中提取
```python=
# import WordPunctTokenizer() method from nltk
from nltk.tokenize import WordPunctTokenizer
# Create a reference variable for Class WordPunctTokenizer
tk = WordPunctTokenizer()
# Create a string input
gfg = "The price\t of burger \nin BurgerKing is Rs.36.\n"
# Use tokenize method
geek = tk.tokenize(gfg)
print(geek)
## output=
Output = [‘The’, ‘price’, ‘of’, ‘burger’, ‘in’, ‘BurgerKing’, ‘is’, ‘Rs’, ‘.’, ’36’, ‘.’]
```
#### Python nltk.tokenize.word_tokenize
```python=
from nltk.tokenize import word_tokenize
text = "God is Great! I won a lottery."
print(word_tokenize(text))
## output=
Output: ['God', 'is', 'Great', '!', 'I', 'won', 'a', 'lottery', '.']
```
#### Python re.split
這也是我此次專案使用的方法
```python=
re.split(r'[!(\')#$"&…%^*+,./{}[;:<=>?@~ \ ]+', word)
```
### 切割sentences
#### Python nltk.tokenize.PunktWordTokenizer
```python=
from nltk.tokenize import PunktSentenceTokenizer
tokenizer = PunktSentenceTokenizer()
tokenizer.tokenize("Can't is a contraction.")
## output=
Output: ["Can't is a contraction."]
tokenizer.tokenize("Can't is a contraction. So is hadn't.")
## output=
Output: ["Can't is a contraction.", "So is hadn't."]
```
#### Python nltk.tokenize.sent_tokenize
```python=
from nltk.tokenize import sent_tokenize
text = "God is Great! I won a lottery."
print(sent_tokenize(text))
Output: ['God is Great!', 'I won a lottery ']
```
### 詞性標註
#### Python nltk.corpus.treebank
文字和標注的組合是以tuple的方式儲存的
```python=
import nltk
from nltk.corpus import treebank
nltk.download('treebank')
print(treebank.tagged_sents()[0])
Output: [('Pierre', 'NNP'), ('Vinken', 'NNP'), (',', ','), ('61', 'CD'), ('years', 'NNS'), ('old', 'JJ'), (',', ','), ('will', 'MD'), ('join', 'VB'), ('the', 'DT'), ('board', 'NN'), ('as', 'IN'), ('a', 'DT'), ('nonexecutive', 'JJ'), ('director', 'NN'), ('Nov.', 'NNP'), ('29', 'CD'), ('.', '.')]
```
完整的標籤列表[參考](https://www.ling.upenn.edu/courses/Fall_2003/ling001/penn_treebank_pos.html)
## Algorithm
### zipf distribution
> 此次我定義X軸為單字(從左至右frequency由高到低)
> Y軸是出現次數
> 總共取前50個單字來plot
當利用zipd distribution在做frequency words時,通常一個單詞出現的頻率與它在頻率表里的排名成反比。所以,頻率最高的單詞出現的頻率大約是出現頻率第二位的單詞的2倍,而出現頻率第二位的單詞則是出現頻率第四位的單詞的2倍。

分母為Riemann Zeta function.
#### scipy.special.zetac(x)
此函數定義為

### Porter's algo
Porter's algo 主要是將一些動詞類的變化去做統一的處理
例如`testing` `tested` `tests` 在經過Porter處理後,會統一變成`test`,對於字元的搜索會變得更加方便準確。
### Stop words
Stop words主要是表示一些定冠詞、介系詞、代名詞類的單字,因為在文章中出現的頻率會遠大於其他的單字,且並沒有什麼資訊,若是將這些單字留下來的話因為頻率過大的關係,有可能會擾亂到重要的字的分析。
### 相似字搜尋
[6.3. difflib — Helpers for computing deltas](https://docs.python.org/3.6/library/difflib.html#sequencematcher-examples)

這個方法是會將最相近的word都找出來,`cutoff`是相似度,越高則挑選的越嚴格;`n`是會找出的字。
## Demo
`./`
首頁

`./show_text`
選擇.xml或是.json搜尋所有的文本並顯示出來

`./search`
會找到所有的相似字,並highlight起來

`./show_img`
顯示
* zipf Distribution
* Porter's algo
* using stop word

## Reference
https://ithelp.ithome.com.tw/articles/10197223
[Bootstrap 5](https://getbootstrap.com/docs/5.0/getting-started/introduction/)
[Python網頁設計:Flask使用筆記(二)- 搭配HTML和CSS](https://yanwei-liu.medium.com/python網頁設計-flask使用筆記-二-89549f4986de)
[使用 NLTK 搭配 Twitter API 拿取社群資料:以川普的 Twitter資料為例](https://cyeninesky3.medium.com/使用-nltk-搭配-twitter-api-拿取社群資料-以川普的-twitter資料為例-2bd493f452a6)
[iOS Twitter API串接 + JSON解析 - Twrendings](https://medium.com/彼得潘的-swift-ios-app-開發教室/ios-twitter-api串接-json解析-twrendings-4e0da5599398)
[flask許多神奇功能](https://hackmd.io/@shaoeChen/HJiZtEngG/https%3A%2F%2Fhackmd.io%2Fs%2FrkgXYoBeG)
[Flask example with POST](https://stackoverflow.com/questions/22947905/flask-example-with-post
)
[Flask example with loop](https://stackoverflow.com/questions/20317456/looping-over-a-tuple-in-jinja2)
[Python深度學習筆記(五):使用NLTK進行自然語言處理](https://yanwei-liu.medium.com/python深度學習筆記-五-使用nltk進行自然語言處理-24fba36f3896)
[Stemming and Lemmatization in Python](https://www.datacamp.com/community/tutorials/stemming-lemmatization-python)
[詞幹提取](https://zh.wikipedia.org/wiki/词干提取)
[Zipf's Law in NLP](https://iq.opengenus.org/zipfs-law/)
[Another Twitter sentiment analysis with Python — Part 3 (Zipf’s Law, data visualisation)](https://towardsdatascience.com/another-twitter-sentiment-analysis-with-python-part-3-zipfs-law-data-visualisation-fc9eadda71e7)