1. 程式人生 > >RNN入門(三)利用LSTM生成旅遊點評

RNN入門(三)利用LSTM生成旅遊點評

介紹

  前幾天,某個公眾號發文質疑馬蜂窩網站,認為它搬運其它網站的旅遊點評,對此,馬蜂窩網站迅速地做出了迴應。相信大多數關注時事的群眾已經瞭解了整個事情的經過,在這裡,我們且不論這件事的是是非非,也不關心它是否是通過爬蟲等其他技術手段實現的。本文將會展示一種自動生成旅遊點評的技術手段。我們用到的模型為LSTM模型。
  LSTM模型是深度學習中一種重要的模型,全稱為Long Short-Term Memory,中文譯為長短期記憶網路,是RNN家族中的重要成員,它模擬了人的大腦,具有一定的記憶功能,適合於處理和預測時間序列中間隔和延遲相對較長的重要事件,在翻譯語言、控制機器人、影象分析、文件摘要、語音識別影象識別、手寫識別、控制聊天機器人、預測疾病、點選率和股票、合成音樂等方面有較多應用。
  在本文中,你將會看到LSTM在自動生成文字(在這裡就是旅遊點評)方面的應用,如果你感到好奇的話,請繼續閱讀~

獲取資料集

  第一步,就是獲取資料集,我們利用Python爬蟲來實現。我們需要爬取的旅遊評論來自於攜程網站上的旅遊評論,在本文中,我們以杭州西湖景點的旅遊評論為例,頁面如下:

攜程網站上關於杭州西湖的評論

我們爬取這些評論中的第1至10頁,採用concurrent.futures模組實現併發爬取。完整的Python程式碼如下:

import requests
import pandas as pd
from bs4 import BeautifulSoup
from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED

# 評論列表
comments = [] # 提取評論,傳入引數為網址url def get_comment(url): global comments try: # 傳送HTTP請求 headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 \ (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36'} r = requests.get(
url=url, headers=headers) # 解析網頁,定位到評論部分 soup = BeautifulSoup(r.text, 'lxml') main_content = soup.find_all('div', class_='comment_single') # 提取評論 for para in main_content: comment = para.find('span', class_='heightbox') #print(comment.text) comments.append(comment.text.replace('&quot', '')) except Exception as err: print(err) def main(): # 請求網址 urls = ["http://you.ctrip.com/sight/hangzhou14/49894-dianping-p%d.html"%x for x in range(1,11)] urls[0] = urls[0].replace('-p1', '') # 利用多執行緒爬取景點評論 executor = ThreadPoolExecutor(max_workers=10) # 可以自己調整max_workers,即執行緒的個數 # submit()的引數: 第一個為函式, 之後為該函式的傳入引數,允許有多個 future_tasks = [executor.submit(get_comment, url) for url in urls] # 等待所有的執行緒完成,才進入後續的執行 wait(future_tasks, return_when=ALL_COMPLETED) # 建立DataFrame並儲存到csv檔案 comments_table = pd.DataFrame({'id': range(1, len(comments)+1), 'comments': comments}) print(comments_table) comments_table.to_csv(r"E://LSTM/hangzhou.csv", index=False) main()

執行完該程式碼,就會得到hangzhou.csv檔案,在這個檔案中,我們需要把旅遊評論中的文字做一些修改,比如去掉特殊字元,新增掉電,去掉換行,修改個別錯別字等。修改完後的csv檔案(部分)如下:

西湖經典評論csv檔案(部分)

得到該csv檔案後,我們需要將這些評論(只包含評論)轉移到txt檔案,以便後續的操作,利用下面的Python
程式碼即可完成:

import pandas as pd

# 讀取csv檔案
df = pd.read_csv('E://LSTM/hangzhou.csv')['comments']
# 將pandas中的評論修改後,寫入txt檔案
for item in df:
    comments = item.replace('\n','').replace('&quot','') \
        .replace(r' ', '').replace(r'#', '').replace(r'&','') \
        .replace(r'<', '《').replace(r'>', '》') \
        .replace(r'↑', '').replace(r'[', '').replace(r']', '') \
        .replace(r'❤', '')
    with open('E://LSTM/comments.txt', 'a', encoding='utf-8') as f:
        f.write(comments)
        f.write('\n')
    #print(comments)

txt檔案部分如下:

西湖經典評論txt檔案(部分)

該txt檔案一共含有41412個文字。我們將會以這些評論為資料集,在此基礎上利用Keras建立LSTM模型,訓練完模型後,能自動生成其他的旅遊點評。

LSTM模型

  Keras是一個高階的神經網路API,利用它能夠輕鬆地搭建一些複雜的神經網路模型,是一個不錯的深度學習框架。對於剛才得到的旅遊點評,為了能夠生成其他的旅遊點評(人類可讀),我們將會用到LSTM模型,之所以使用這個模型,是因為LSTM具有長短時記憶功能,能夠很好地處理文字中的文字之間的聯絡,而不是將文字看成是獨立的個體。
  在搭建LSTM模型之前,我們需要做一些準備工作。首先我們需要將每個文字對應到一個數字,該模型的輸入特徵向量為前10個文字對應的數字組成的向量,目標變數為該10個文字的下一個文字對應的數字。該txt檔案中一共有1949個文字(包括漢子和標點符號),按照我們的處理模式,共有41402個樣本,將這些樣本傳入到LSTM模型中。我們建立的模型很簡單,先是一個LSTM層,利用含有256個LSTM結構,然後是一個Dropout層,能有效防止模型發生過擬合,最後是Softmax層,將它轉化為多分類的問題,採用交叉熵作為模型的損失函式。
  訓練模型的Python程式碼如下:

# 搭建簡單的LSTM模型用於生成旅遊評論
import numpy
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Dropout
from keras.layers import LSTM
from keras.callbacks import ModelCheckpoint
from keras.utils import np_utils

# 讀取txt檔案
filename = "E://LSTM/comments.txt"
with open(filename, 'r', encoding='utf-8') as f:
	raw_text = f.read().lower()

# 建立文字和對應數字的字典
chars = sorted(list(set(raw_text)))
#print(chars)
char_to_int = dict((c, i) for i, c in enumerate(chars))
int_to_char = dict((i, c) for i, c in enumerate(chars))
# 對載入資料做總結
n_chars = len(raw_text)
n_vocab = len(chars)
print("總的文字數: ", n_chars)
print("總的文字類別: ", n_vocab)

# 解析資料集,轉化為輸入向量和輸出向量
seq_length = 10
dataX = []
dataY = []
for i in range(0, n_chars-seq_length, 1):
	seq_in = raw_text[i:i + seq_length]
	seq_out = raw_text[i + seq_length]
	dataX.append([char_to_int[char] for char in seq_in])
	dataY.append(char_to_int[seq_out])
n_patterns = len(dataX)
print("Total Patterns: ", n_patterns)
# 將X重新轉化為[samples, time steps, features]的形狀
X = numpy.reshape(dataX, (n_patterns, seq_length, 1))
# 正則化
X = X/n_vocab
# 對輸出變數做one-hot編碼
y = np_utils.to_categorical(dataY)

# 定義LSTM模型
model = Sequential()
model.add(LSTM(256, input_shape=(X.shape[1], X.shape[2])))
model.add(Dropout(0.2))
model.add(Dense(y.shape[1], activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam')
#print(model.summary())

# 定義checkpoint
filepath="E://LSTM/weights-improvement-{epoch:02d}-{loss:.4f}.hdf5"
checkpoint = ModelCheckpoint(filepath, monitor='loss', verbose=1, save_best_only=True, mode='min')
callbacks_list = [checkpoint]
# 訓練模型,每批訓練128個樣本,總共訓練1000次
epochs = 1000
model.fit(X, y, epochs=epochs, batch_size=128, callbacks=callbacks_list)

首先讓我們看一下模型的結構及引數情況, 使用程式碼中的print(model.summary())即可,輸出如下:

Layer (type)                 Output Shape              Param #   
=================================================================
lstm_1 (LSTM)                (None, 256)               264192    
_________________________________________________________________
dropout_1 (Dropout)          (None, 256)               0         
_________________________________________________________________
dense_1 (Dense)              (None, 1949)              500893    
=================================================================
Total params: 765,085
Trainable params: 765,085
Non-trainable params: 0
_________________________________________________________________

雖然是一個很簡單的LSTM模型,但也有76萬多個引數,深度學習的引數的個數可見一斑~
  執行程式碼,訓練該模型,在訓練了漫長的4,5個小時後,在613次的時候,損失值為0.3040,我們就以這個檔案作為模型訓練的結果,而不是1000次,因為1000次太費時了。檔案如下:

Keras訓練613次後生成的HDF5檔案

請注意刪除沒用的檔案,因為這些生成的檔案都很大。

生成旅遊點評

  好不容易訓練完模型後,下一步,我們將要利用這個模型來生成旅遊點評。怎麼樣,是不是有點期待?生成旅遊點評的完整Python如下(我們以輸入的句子“杭州西湖天下聞名,西”為例,請注意,每次輸入正好10個文字,因為模型訓練的輸入向量為含10個元素的向量):

# 搭建簡單的LSTM模型用於生成旅遊評論
import numpy
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Dropout
from keras.layers import LSTM
from keras.callbacks import ModelCheckpoint
from keras.utils import np_utils

# 讀取txt檔案
filename = "E://LSTM/comments.txt"
with open(filename, 'r', encoding='utf-8') as f:
	raw_text = f.read().lower()

# 建立文字和對應數字的字典
chars = sorted(list(set(raw_text)))
#print(chars)
char_to_int = dict((c, i) for i, c in enumerate(chars))
int_to_char = dict((i, c) for i, c in enumerate(chars))
# 對載入資料做總結
n_chars = len(raw_text)
n_vocab = len(chars)
print("總的文字數: ", n_chars)
print("總的文字類別: ", n_vocab)

# 解析資料集,轉化為輸入向量和輸出向量
seq_length = 10
dataX = []
dataY = []
for i in range(0, n_chars-seq_length, 1):
	seq_in = raw_text[i:i + seq_length]
	seq_out = raw_text[i + seq_length]
	dataX.append([char_to_int[char] for char in seq_in])
	dataY.append(char_to_int[seq_out])
n_patterns = len(dataX)
print("Total Patterns: ", n_patterns)
# 將X重新轉化為[samples, time steps, features]的形狀
X = numpy.reshape(dataX, (n_patterns, seq_length, 1))
# 正則化
X = X/n_vocab
# 對輸出變數做one-hot編碼
y = np_utils.to_categorical(dataY)

# 定義LSTM模型
model = Sequential()
model.add(LSTM(256, input_shape=(X.shape[1], X.shape[2])))
model.add(Dropout(0.2))
model.add(Dense(y.shape[1], activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam')

# 匯入訓練完後的檔案
filename = "E://LSTM/weights-improvement-613-0.3040.hdf5"
model.load_weights(filename)
# 示例的輸入句子
input = '杭州西湖天下聞名,西'
pattern = [char_to_int[value] for value in input]
print("輸入:")
print(''.join([int_to_char[value] for value in pattern]))
print("輸出:")
# 生成1000個文字
for i in range(1000):
	x = numpy.reshape(pattern, (1, len(pattern), 1))
	x = x / float(n_vocab)
	prediction = model.predict(x, verbose=0)
	index = numpy.argmax(prediction)
	result = int_to_char[index]
	print(result, end='')
	seq_in = [int_to_char[value] for value in pattern]
	pattern.append(index)
	pattern = pattern[1:len(pattern)]
print("\n生成完畢。")

執行該程式碼,就能看到生成的文字如下:

輸入:
杭州西湖天下聞名,西
輸出:
湖一年四季特色紛呈,西湖有湖光山色交相輝應,既可看湖又可看山,西湖上的橋也是兼具南北特色,長橋不長,斷橋不斷。
西湖古蹟遍佈,山水秀麗,景色宜人,西湖處處有勝景。最著名的西湖十景,西湖十景”是指浙江省杭州市著名旅遊景點西湖上的十處特色風景,分別是蘇堤春曉、曲苑風荷、平湖秋月、斷橋殘雪、柳浪聞鶯、花港觀魚、雷峰夕照、雙峰插雲、南屏晚鐘、三潭印月等十個景點是杭州遊覽的熱點,不用按照目前的時髦話說什麼“非看不可”、“非去不可”,但是,如果去了杭州不看這些景點,不看這些景點的碑刻,就有點可惜了,特別是有康熙爺、乾隆爺的親筆題詞,不去看看,多少會對老祖宗的“大不敬”。
如果天氣晴朗下午五點以後去西湖,如果拍視訊你就會拍到天藍藍水藍藍的西湖和夕陽西下漸黃昏的完美,西湖邊上有小凳子可以坐下來靜靜的欣賞西湖美景也可和同行的夥伴聊聊接下來的行程,目的是等待晚上更值得期待的音樂噴泉,得早早坐下來否則就看不到了,看噴泉不允許站著凳子坐滿其餘人站凳子後面。
西湖遊船是遊西湖必不可少的。另外早晨可以早點去,人少,而且涼爽。花港觀魚的景色也是不錯。雷峰塔就看你需求了,可以俯瞰整個西湖(其實去雷峰塔也是為了看西湖吧,雷峰塔本身貌似沒什麼看的)吃飯的話,別在景區吃,強烈推薦去弄堂裡。好吃又便宜。
8月份出差路過杭州,可以輕鬆的偷閒半天,順便預約了離西湖比較近便的酒店入住。8月底的清晨已經有了絲絲的涼意,順著西湖邊開始溜達。早上人還是比較少,本地起來鍛鍊的大爺大媽比較多,有蘸水在地上練字的,有在小公園練嗓子的,還有坐在長條凳上練二胡的。9點多點,遊客開始多了,各種遊船排隊等候,暑假期間帶小孩匆匆而過的人也不少,基本都是到此一遊,很少有仔細的欣賞和解說景點的出處。乘涼的地方還是不少,但是蚊子也多的嚇人。蘇堤的景色不錯,特別是兩邊的高樹遮擋了大多數的陽光,即使在這裡溜達也不會覺得熱,如果時間允許的話,建議溜達過去,步行約30分鐘左右,南邊的出口還有蘇東坡博物館,比較小,免費開放。
西湖的範圍蠻大的,從北山路(北山路上的老建築,浙江博物館,斷橋殘雪,錦帶橋,保俶塔,樓外樓,孤山,西冷印社,平湖秋月等。)到南線景區(南山路,西湖天地,御碼頭,錢王祠,柳浪聞鶯,萬鬆書院,長橋,南屏晚鐘,淨慈寺,雷鋒塔太子灣等)以及三堤,外西湖,西里湖等,小南湖,小瀛洲,從白堤_蘇堤_麴院風荷_楊公堤_鵒鴣灣_茅家埠。飛來峰景
生成完畢。

讓我們來看一下這段生成的文字,首先,這段文字的可讀性還是很高的,基本上人類能夠理解,其次,與原文相對比,這段文字並不是一味地抄襲原文,而是有選擇地將原檔案中的旅遊點評組合起來,雖然每部分的旅遊點評與原先的相差不多,但重新組合後,是能夠生成新的旅遊點評的,並不算真正意義上的抄襲。
  用LSTM訓練出來的模型生成的文字,是能夠作為新的旅遊點評的,並不是完全的抄襲,但是對於未在原文中出現的輸入向量,可能訓練出來的效果就不會太理想,這是需要改進的地方。

總結

  本文純屬自娛自樂,如果不當之處,還望大家批評指正~~
  當然,對於該專案,還有一些值得改進的地方,比如資料集不夠大,這個可以爬取更多的評論;資料預處理過於簡單,僅僅做了文字與向量的一一對應以及輸入向量的正則化;模型過於簡單,讀者可以嘗試搭建多個LSTM層或其他結構;模型訓練過於耗時,可以嘗試GPU或改進模型結構或資料預處理方式,等等等等。希望讀者在閱讀完本文後,能對LSTM模型在文字生成方面有一定的瞭解,歡迎擁抱LSTM~~

參考文獻

Text Generation With LSTM Recurrent Neural Networks in Python with Keras: https://machinelearningmastery.com/text-generation-lstm-recurrent-neural-networks-python-keras/

注意:本人現已開通微信公眾號: Python爬蟲與演算法(微訊號為:easy_web_scrape), 歡迎大家關注哦~~