我們對錯誤消息並不陌生。假新聞和假標題並不是現代發明。甚至早在20世紀初就有了黃色新聞,它只是使用各種道德上有問題的策略來吸引人們購買報紙和其他媒體形式的注意力。在沒有報紙訂閱的情況下,公司必須為每一筆銷售而戰,而當你最好的營銷方式是招牌和報童時,就需要通過新聞標題迅速形成強烈的印象。隨之而來的是大量過度誇張的標題和缺乏研究的文章。聽起來是不是很熟悉?
我們生活在一個真理不再是非黑即白的世界。在我們生活的世界裡,媒體明白,影響人們的最佳方式不是通過邏輯,而是通過情感。他們明白我們人類不是通過有意識的思考和邏輯處理來做決定,而是通過隱藏在我們心靈中的無意識傾向來做決定。對通過媒體賺錢的人來說是好事,對像我們這樣消費媒體的人來說是壞事。
今天,機器學習變得越來越突出,領域越來越進步,特別是自然語言處理,任何人都可以生成虛假內容,而不需要寫一個句子。電腦為我們做了所有的事情!我決定做一個小實驗,看看一個完全由電腦生成內容的新聞網站(比如華爾街日報)會是什麼樣子。
這是成品的樣子。
我知道它很乏味。更重要的是,它的功能非常強大,外觀很容易調整。並且隨意在這個項目上添加你想要的任何內容。
目錄
Gatsby.js設置配置頁面布局機器學習設置與谷歌Colab假文章代Google Drive API編程式頁面生成部署改進的領域如果您想要更深入地了解這個項目,或者想要添加到代碼中,請查看最後的GitHubGatsby.js
為了構建一個可以無限擴展且加載速度非常快的博客,我們需要一個能夠構建和渲染靜態資源的框架,這些靜態資源可以很容易地部署在web伺服器上。Gatsby.js (可以說)是目前最好的開源靜態站點生成器,所以讓我們來看看它。
Gatsby.js是一個基於response .js的用於生成靜態網站的開源框架。Gatsby.js是一個web應用程式生成器。該框架使用一些web資源,如HTML、CSS和JavaScript,通過各種api加載數據,然後將所有這些資源加載到帶有預抓取資源的站點中。最終的結果是,您擁有了一個非常快速、易於伸縮和修改、非常安全的web資源的集合。
在Gatsby.js之前,首先需要安裝Node。這是一個開源的JavaScript運行時環境,用於在瀏覽器之外執行JavaScript代碼。使用Node還可以得到npm,它表示「包管理器」。使用npm,您可以將Gatsby.js安裝到本地機器上。
接下來最好安裝git,這是一種非常強大且流行的版本控制系統。當您使用Gatsby.js站點模板時,Gatsby會使用Git的一些功能。基本上,Gatsby.js將幫你創建一個有完整的骨架的網站,你可以調整和重新配置,而不是從頭開始構建整個東西。
在安裝完成nodejs以後,使用以下命令:
npm install -g gatsby-cli
在Gatsby CLI中有相當數量的命令,可以通過下面的命令行提示符了解更多關於它們的信息:
gatsby --help
對於這個特定的項目,您有多種選擇。
(1)使用gatsby new [yoursite -name]完全從頭開始,
(2)使用gatsby new [yoursite -name] [starter-git-url]的啟動模板,
(3)使用我發布在GitHub上的現成代碼
在本地機器上擁有站點文件和靜態文件之後,就可以使用gatsby develop的本地開發伺服器進行開發。
網站配置
現在我們已經設置了Gatsby站點,並預先打包了基本的靜態web資源後,在實際添加內容之前,我們應該了解站點的基本組件並正確配置它們。
當你設置一個Gatsby網站時,你會得到一堆文件。所有這些資產幫助您創建更好的網絡體驗與更少的麻煩。讓我們逐個介紹。
gatsby-browser.js
此文件用於實現Gatsby瀏覽器api。對於這個項目,我們不需要在這個文件中放入任何東西。
gatsby-config.js
這個文件是網站的基本配置。它是大多數API設置將被存儲的地方。Gatsby附帶了許多插件,您可以通過運行在終端npm install中輕鬆地安裝它們。下載插件後,可以將其添加到gatsby-config.js中。
下面是這個項目的文件。
如你所見,在這個項目中使用了許多不同的插件,這些插件可以幫助我們節省時間和精力。一個重要的插件是Gatsby -source-filesystem,它允許Gatsby從存儲在本地文件系統中的文件中提取數據。稍後,我們將使用Git從GitHub中提取必要的文件,這樣本地文件系統中的所有文件都能與雲伺服器中的資源相匹配,並且可以進行自動部署。gatsby-transformer-sharp和gatsby-transformer-remark也是重要的插件。它們可以自動將markdown 文件轉換為可用於web格式。其他的插件不太重要,就不介紹了。
gatsby-node.js
此文件用於實現api。這些api可以使用GraphQL從數據層中獲取數據。在處理程序化頁面生成時,我們將更深入地研究這個文件的內容。
gatsby-ssr.js
此文件用於實現伺服器端選然的api。我們不會在這個項目中使用。
布局設置
網站布局是一個非常重要的方面。Gatsby構建在React之上,而React是一個JavaScript庫,它使使用稱為「組件」的構建塊構建用戶界面變得更加容易。你不必把你所有的代碼放在一個文件中,你可以把你的網站分解成基本的構建塊,然後把它們堆疊在一起,在你需要的時候重用各種組件。
首頁代碼
import React from 'react'import { Link, graphql } from 'gatsby'import Masonry from 'react-masonry-component'import Img from 'gatsby-image'import Work_Layout from "../components/work_layout"const MainPage = ({ data }) => {return ( <Work_Layout> <Masonry className="showcase"> {data.allMarkdownRemark.edges.map(({ node: work }) => ( <div key={work.id} className="showcase__item"> <figure className="card"> <Link to={`${work.fields.slug}`} className="card__image"> </Link> <figcaption className="card__caption"> <h6 className="card__title"> <Link to={`${work.fields.slug}`}>{work.fields.title}</Link> </h6> <div className="card__description"> <p>{work.excerpt}</p> </div> </figcaption> </figure> </div> ))} </Masonry> </Work_Layout> )}export default MainPageexport const query = graphql` query { allMarkdownRemark { edges { node { id fields { slug title } excerpt html } } } }
Masonry 組件將每個文章變為一個卡片,並允許卡片根據屏幕的打叫進行重排,方便移動端和不同的解析度使用
布局組件看起來像這樣。
import React from 'react';import "../styles/index.sass";import Helmet from './helmet';import Footer from './footer';import Navbar from './navbar';const Work_Layout = ({ children }) => (<div> <Helmet /> <Navbar /> {children} <Footer /> </div>);export default Work_Layout;
可以看到,這就是主頁面的WorkLayout組件的來源。{children}引用您放在父組件(即WorkLayout)中的所有組件。本例中的直接子組件是Masonry 組件。
深入每個組件將花費太長時間。所有組件代碼都在GitHub存儲庫中。
機器學習設置和谷歌Colab
現在我們的網站布局和結構已經建立,是時候真正生成我們的假新聞文章了。我使用谷歌Colaboratory,它可以在瀏覽器中運行Python代碼並可以直接訪問谷歌Driver。
首先,我需要配置我的谷歌Drive,這樣我可以把文章保存到谷歌Drive中。
接下來,我們將建立運行文本生成的參數。很明顯,你不必和我做同樣的事情。我選擇了模仿《華爾街日報》的寫作風格,對於你想模仿的風格,你有很多選擇。(我們以前發布過模仿莎士比亞風格寫作的教程,有興趣的可以查看)
# Fake person who will be slandered/libeled in the fake articlesNAME_TO_SLANDER = "Chucky McChuckster"IMAGE_TO_SLANDER = "images.generated.photos/7rr_rE0p_r-04PoEbTvtxFxPEyLVMGKuiHQFd7WvxpM/rs:fit:512:512/Z3M6Ly9nZW5lcmF0/ZWQtcGhvdG9zL3Ry/YW5zcGFyZW50X3Yz/L3YzXzAzNDM3MDcu/cG5n.png"SLANDEROUS_SEED_HEADLINES = [f"{NAME_TO_SLANDER} convicted of stealing puppies", f"{NAME_TO_SLANDER} caught lying about growing the world's largest watermelon", f"{NAME_TO_SLANDER} single-handedly started the Cold War", f"{NAME_TO_SLANDER} forged priceless works of modern art for decades", f"{NAME_TO_SLANDER} claimed to be Pokemon master, but caught in a lie", f"{NAME_TO_SLANDER} bought fake twitter followers to pretend to be a celebrity", f"{NAME_TO_SLANDER} created the original design for Ultron", f"{NAME_TO_SLANDER} revealed as a foriegn spy for the undersea city of Atlantis", f"{NAME_TO_SLANDER} involved in blackmail scandal with King Trident of Atlantis", f"{NAME_TO_SLANDER} is dumb", f"{NAME_TO_SLANDER} lied on tax returns to cover up past life as a Ninja Turtle", f"{NAME_TO_SLANDER} stole billions from investors in a new pet store", f"{NAME_TO_SLANDER} claims to be a Ninja Turtle but was actually lying", f"{NAME_TO_SLANDER} likely to be sentenced to 20 years in jail for chasing a cat into a tree", f"{NAME_TO_SLANDER} caught in the act of illegal trafficking of Teletubbies", f"{NAME_TO_SLANDER} commits a multitude of crimes against dinosaurs",]# Which news website to 'clone'DOMAIN_STYLE_TO_COPY = "wsj.com"RSS_FEEDS_OF_REAL_STORIES_TO_EMULATE = [ "dj/rss/RSSWorldNews.xml",]
Grover是一個深度學習模型,它實際上是用來抵禦假新聞的。在區分人工生成的新聞和機器生成的新聞方面,它的準確率超過90%。這也意味著,該模式本身就擅長製造假新聞。我們可以在Colab中克隆存儲它的存儲庫並使用它。
我們需要將Grover模型文件下載到Colab文件夾中。幸運的是,Python有一些直接讀寫文件的簡單函數。
import osimport requestsmodel_type = "mega"model_dir = os.path.join('/content/grover/models', model_type)if not os.path.exists(model_dir):os.makedirs(model_dir)for ext in ['data-00000-of-00001', 'index', 'meta']: r = requests.get(f'storage.googleapis/grover-models/{model_type}/model.ckpt.{ext}', stream=True) with open(os.path.join(model_dir, f'model.ckpt.{ext}'), 'wb') as f: file_size = int(r.headers["content-length"]) if file_size < 1000: raise ValueError("File doesn't exist? idk") chunk_size = 1000 for chunk in r.iter_content(chunk_size=chunk_size): f.write(chunk) print(f"Just downloaded {model_type}/model.ckpt.{ext}!", flush=True)
下面的代碼可能會非常複雜。主要的一點是,我們使用前面設置的參數,添加一些可以填充的屬性,使用自然語言處理使文本更易於模型處理,然後使用Grover模型生成文章。最終的結果是一組由html組成的文章,我選擇將其保存為markdown。
%tensorflow_version 1.ximport tensorflow as tfimport numpy as npimport sysimport feedparserimport timefrom datetime import datetime, timedeltaimport requestsimport base64from ttp import ttpsys.path.append('../')from lm.modeling import GroverConfig, samplefrom sample.encoder import get_encoder, _tokenize_article_pieces, extract_generated_targetimport randomdef get_fake_articles(domain):articles = [] headlines_to_inject = SLANDEROUS_SEED_HEADLINES for fake_headline in headlines_to_inject: days_ago = random.randint(1, 7) pub_datetime = datetime.now() - timedelta(days=days_ago) publish_date = pub_datetime.strftime('%m-%d-%Y') iso_date = pub_datetime.isoformat() articles.append({ 'summary': "", 'title': fake_headline, 'text': '', 'authors': ["Staff Writer"], 'publish_date': publish_date, 'iso_date': iso_date, 'domain': domain, 'image_url': IMAGE_TO_SLANDER, 'tags': ['Breaking News', 'Investigations', 'Criminal Profiles'], }) return articlesdef get_articles_from_real_blog(domain, feed_url): feed_data = feedparser.parse(feed_url) articles = [] for post in feed_data.entries: if 'published_parsed' in post: publish_date = time.strftime('%m-%d-%Y', post.published_parsed) iso_date = datetime(*post.published_parsed[:6]).isoformat() else: publish_date = time.strftime('%m-%d-%Y') iso_date = datetime.now().isoformat() if 'summary' in post: summary = post.summary else: summary = None tags = [] if 'tags' in post: tags = [tag['term'] for tag in post['tags']] if summary is None: summary = ", ".join(tags) image_url = None if 'media_content' in post: images = post.media_content if len(images) > 0 and 'url' in images[0]: image_url = images[0]['url'] # Hack for NYT images to fix tiny images in the RSS feed if "-moth" in image_url: image_url = image_url.replace("-moth", "-threeByTwoMediumAt2X") if 'authors' in post: authors = list(map(lambda x: x["name"], post.authors)) else: authors = ["Staff Writer"] articles.append({ 'summary': summary, 'title': post.title, 'text': '', 'authors': authors, 'publish_date': publish_date, 'iso_date': iso_date, 'domain': domain, 'image_url': image_url, 'tags': tags, }) return articlesdef format_generated_body_text_as_html(article_text, image_url=None): p = ttp.Parser() result = p.parse(article_text) article_text = result.html lines = article_text.split("\n") new_lines = [] for line in lines: if len(line) < 80 and not "." in line: line = f"<b>{line}</b>" new_lines.append(line) article_text = "<p>".join(new_lines) if image_url is not None: article_text = f"<img src='{image_url}'><p>{article_text}" return article_textdef generate_article_attribute(sess, encoder, tokens, probs, article, target='article'): # Tokenize the raw article text article_pieces = _tokenize_article_pieces(encoder, article) # Grab the article elements the model careas about - domain, date, title, etc. context_formatted = [] for key in ['domain', 'date', 'authors', 'title', 'article']: if key != target: context_formatted.extend(article_pieces.pop(key, [])) # Start formatting the tokens in the way the model expects them, starting with # which article attribute we want to generate. context_formatted.append(encoder.__dict__['begin_{}'.format(target)]) # Tell the model which special tokens (such as the end token) aren't part of the text ignore_ids_np = np.array(encoder.special_tokens_onehot) ignore_ids_np[encoder.__dict__['end_{}'.format(target)]] = 0 # We are only going to generate one article attribute with a fixed # top_ps cut-off of 95%. This simple example isn't processing in batches. gens = [] article['top_ps'] = [0.95] # Run the input through the TensorFlow model and grab the generated output tokens_out, probs_out = sess.run( [tokens, probs], feed_dict={ # Pass real values for the inputs that the # model needs to be able to run. initial_context: [context_formatted], eos_token: encoder.__dict__['end_{}'.format(target)], ignore_ids: ignore_ids_np, p_for_topp: np.array([0.95]), } ) # The model is done! Grab the results it generated and format the results into normal text. for t_i, p_i in zip(tokens_out, probs_out): extraction = extract_generated_target(output_tokens=t_i, encoder=encoder, target=target) gens.append(extraction['extraction']) # Return the generated text. return gens[-1]
我們在前面定義了函數,現在我們所需要做的就是運行所有的東西來一次生成所有的文章。[警告:此過程將花費很長時間]
一些虛假的文章將完全從我們之前創建的假標題中生成,一些將從《華爾街日報》網站上刮下來,並使用我們的參數進行調整。
# Ready to start grabbing RSS feedsdomain = DOMAIN_STYLE_TO_COPYfeed_urls = RSS_FEEDS_OF_REAL_STORIES_TO_EMULATEarticles = []# Get the read headlines to look more realisticfor feed_url in feed_urls:articles += get_articles_from_real_blog(domain, feed_url)# Toss in the slanderous articlesarticles += get_fake_articles(domain)# Randomize the order the articles are generatedrandom.shuffle(articles)# Load the pre-trained "huge" Grover model with 1.5 billion paramsmodel_config_fn = '/content/grover/lm/configs/mega.json'model_ckpt = '/content/grover/models/mega/model.ckpt'encoder = get_encoder()news_config = GroverConfig.from_json_file(model_config_fn)# Set up TensorFlow session to make predictionstf_config = tf.ConfigProto(allow_soft_placement=True)with tf.Session(config=tf_config, graph=tf.Graph()) as sess: # Create the placehodler TensorFlow input variables needed to feed data to Grover model # to make new predictions. initial_context = tf.placeholder(tf.int32, [1, None]) p_for_topp = tf.placeholder(tf.float32, [1]) eos_token = tf.placeholder(tf.int32, []) ignore_ids = tf.placeholder(tf.bool, [news_config.vocab_size]) # Load the model config to get it set up to match the pre-trained model weights tokens, probs = sample( news_config=news_config, initial_context=initial_context, eos_token=eos_token, ignore_ids=ignore_ids, p_for_topp=p_for_topp, do_topk=False ) # Restore the pre-trained Grover 'huge' model weights saver = tf.train.Saver() saver.restore(sess, model_ckpt) # START MAKING SOME FAKE NEWS!! # Loop through each headline we scraped from an RSS feed or made up for article in articles: print(f"Building article from headline '{article['title']}'") # If the headline is one we made up about a specific person, it needs special handling if NAME_TO_SLANDER in article['title']: # The first generated article may go off on a tangent and not include the target name. # In that case, re-generate the article until it at least talks about our target person attempts = 0 while NAME_TO_SLANDER not in article['text']: # Generate article body given the context of the real blog title article['text'] = generate_article_attribute(sess, encoder, tokens, probs, article, target="article") # If the Grover model never manages to generate a good article about the target victim, # give up after 10 tries so we don't get stuck in an infinite loop attempts += 1 if attempts > 5: continue # If the headline was scraped from an RSS feed, we can just blindly generate an article else: article['text'] = generate_article_attribute(sess, encoder, tokens, probs, article, target="article") # Now, generate a fake headline that better fits the generated article body # This replaces the real headline so none of the original article content remains article['title'] = generate_article_attribute(sess, encoder, tokens, probs, article, target="title") # Grab generated text results so we can post them to WordPress article_title = article['title'] article_text = article['text'] article_date = article["iso_date"] article_image_url = article["image_url"] article_tags = article['tags'] # Make the article body look more realistic - add spacing, link Twitter handles and hashtags, etc. # You could add more advanced pre-processing here if you wanted. article_text = format_generated_body_text_as_html(article_text, article_image_url) print(f" - Generated fake article titled '{article_title}'") filename = '/content/gdrive/My Drive/Articles/' + f"{article_title}.md" with open(filename, 'w' ) as f: f.write(article_text)
這麼一大堆代碼!理想情況下,運行它時不會出現任何故障。如果查看gen.py的底部,將看到我在path /content/gdrive/My Drive/ articles /中編寫了文章。這是我為自己設置的配置,所以它可能與其他人不同。
下面是運行代碼時應該看到的內容。
當我查看驅動器上的文章文件夾時,我會看到一堆包含假文章的markdown 文件。
我們可以使用名為Gatsby -source-drive的插件將文件直接導入到Gatsby的本地文件系統中。這需要在谷歌api中設置一個服務帳戶。然後需要將其添加到gatsby-config.js中,並從谷歌驅動器文件夾中獲得唯一的ID。這個API的好處在於它保存並緩存了谷歌驅動器文件夾的內容,所以即使您的驅動器發生了什麼事情,文件還是安全的。
編程式頁面生成
我們已經使用谷歌Colab生成了文章,並且使用gatsby-source-drive插件將文件直接歸檔到我們的本地文件系統中。現在我們需要使用markdown文件以編程方式生成網頁。
同樣,確保您的gatsby-config.js文件包含 gatsby-source-filesystem和gatsby-transformer-remark。這些對於頁面生成非常重要。
創建頁面的兩個大步驟是:
1)為本地文件系統中的每個標記文件創建slugs(或唯一的url)
2)使用頁面模板使用slugs和通過GraphQL獲取的其他信息創建實際的web頁面。
我們需要創建的兩個文件如下
gatsby-node.js
const path = require(`path`)const { createFilePath } = require(`gatsby-source-filesystem`)exports.onCreateNode = ({ node, getNode, actions }) => {const { createNodeField } = actions if (node.internal.type === 'MarkdownRemark') { const slug = createFilePath({ node, getNode, basePath: 'pages' }) createNodeField({ node, name: 'slug', value: slug, }) createNodeField({ node, name: 'title', value: slug.replace(/\//g, " ") }) }}exports.createPages = async ({ graphql, actions }) => { const { createPage } = actions const result = await graphql(` query { allMarkdownRemark { edges { node { fields { slug } } } } } `) result.data.allMarkdownRemark.edges.map(({ node }) => { createPage({ path: node.fields.slug, component: path.resolve('./src/templates/work.js'), context: { slug: node.fields.slug, }, }) })}
在gatsby-node.js中,為每個markdown文件創建數據節點,然後所有這些節點將與頁面模板一起使用,以創建實際的頁面。
頁面模板代碼如下:
import React from 'react'import Slider from 'react-slick'import Img from 'gatsby-image'import { graphql } from 'gatsby'import Layout from "../components/layout"export default ({ data }) => {return ( <Layout> <article className="sheet"> <div className="sheet__inner"> <h1 className="sheet__title">{data.markdownRemark.fields.title}</h1> <p className="sheet__lead">{data.markdownRemark.excerpt}</p> <div className="sheet__body" dangerouslySetInnerHTML={{ __html: data.markdownRemark.html, }} /> </div> </article> </Layout> )}export const query = graphql` query($slug: String!) { markdownRemark(fields: { slug: { eq: $slug } }) { fields { slug title } html id excerpt } }`
當您運行gatsby develop或gatsby build時,代碼就會自動生成所有內容!
部署
讓我們使用Netlify將我們的站點部署到網際網路上。Netlify是一個建立和部署網站的平臺。它將你的本地資源存儲在雲上以便部署。
我們現在需要做的是更新GitHub庫。我們需要將文件添加到Git上的本地暫存區域,提交這些文件,然後將它們推到GitHub上的遠程存儲庫。
git add .git commit -m "[whatever changes you made]"git push -u origin master
一旦你的GitHub庫被更新,我們就可以設置一個直接從GitHub部署的Netlify站點。
可以改進的領域
美化網站,使其看起來更像新聞網站
多樣化假文章生成的參數
為網站增加更多的交互性
為文章添加更多元數據
總結
感謝您花時間閱讀本文!GitHub在這裡:jerrytigerxu
作者:Jere Xu
deephub翻譯組