流氓鄰居!新研究:中國築壩攔水重創湄公河下游國水情

摘錄自2020年4月14日自由時報報導

中國近年積極對鄰國恩威並施,手段繁多,彰顯該國地緣政治方面的野心,最新一份研究指出,中國在湄公河上游瀾滄江築壩攔水的做法,加劇湄公河下游國家越南、泰國、柬埔寨、寮國及緬甸的旱情,進一步重創其經濟。

根據《美國之音》報導,研究人員透過特殊技術建立「表面濕度指數模型」,發現自2012年中國瀾滄江糯扎渡水電站開始運作後,當局又興建了多座大壩,導致該河流自然流量受到很大限制,進而影響下游國家。

這份報告的共同作者,美國「Eyes on Earth」公司獨立研究員貝斯特(Alan Basist)表示,簡而言之,中方的說法就是在雨季時擋水、在旱季放水,試圖穩定生產水電,然而,這次研究卻找到與其說法不符的證據。

貝斯特舉去年的數據為例,2019年湄公河下游水位降至過去半世紀最低點時,上游卻有著高於平均值的表面濕度,代表該處存在高於過往同期自然流量。他說,一過中國攔水區域後,下游的泰國、越南的水平面卻低於平均值,代表中國在上游的限制加劇了下游國家的乾旱。

土地水文
生物多樣性
土地利用
國際新聞
中國新聞
中國
攔水壩
湄公河
水庫

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※推薦台中搬家公司優質服務,可到府估價

美國人忙抗疫 川普悄悄對環境政策上下其手

環境資訊中心外電;姜唯 翻譯;林大利 審校;稿源:ENS

非營利組織「西部優先中心(Center for Western Priorities)」發現,過去一個月,美國人忙著與新冠病毒作戰,川普的內政部一邊悄悄影響環境相關政策制定。自川普簽署第一份緊急新冠病毒法案後一個月,內政部啟動數十項與武漢肺炎(COVID-19)無關的政策行動,取消瀕危野生動植物保護法令,並在全國各地擴大辦理採礦和油氣租約銷售。

西部優先中心列出3月6日川普簽署首份新冠病毒緊急法案後,內政部採取的57項行動,像是34個民意徵詢期被啟動或結束,儘管地方民代和國會多次要求內政部長大衛.伯恩哈特(David Bernhardt)在疫情期間暫停立法程序。

西部優先中心列出3月6日川普簽署首份新冠病毒緊急法案後,內政部採取的57項行動。照片來源:Gage Skidmore(CC BY-SA 2.0)

「美國忙著應付全球性傳染病,但伯恩哈特可沒有忘記油氣和採礦公司付了他多年薪水、交付給他的使命。」西部優先中心執行董事羅卡拉(Jennifer Rokala)說,「看看他在新冠病毒危機惡化時做的事,他拒絕關閉國家公園、無視這對國家公園管理員帶來的危險,也是不意外。」

3月6日以來,美國內政部已完成至少九項對公共和部落土地有永久性影響的行動,包括擴大亞利桑那州和內華達州的硬岩採礦業務,以及撤回麻薩諸塞州Mashpee Wampanoag部落的信託土地。

Mashpee Wampanoag部落在該地區生活超過1萬2000年。由於內政部下印地安事務局的決策,該部落很可能會失去僅存的家園。這個決策嚴重打擊部落主權,影響部落未來和永續性。該部落正在要求國會保護其保留土地,並提出《馬什皮.旺帕諾格部落保留法案》(H.R.312)。

美國內政部還針對《瀕危物種法》,提出削弱弓背鮭(Gila cypha)和波多黎各蘭花保育工作的政策,並取消了北美松雞(Centrocercus urophasianus)保育提案。

儘管石油生產過剩導致全球石油價格暴跌,但美國內政部3月份仍舉行了七次油氣租約銷售。

儘管疫情導致煤炭暫停生產、礦場關閉,煤炭需求也大減,美國內政部仍宣布4月將在科羅拉多州舉行煤炭租約銷售活動。

3月27日,內政部核准在阿拉斯加修建一條會穿過北極門國家公園保護區的私人採礦道路。伯恩哈特之前任職的法律事務所,正是該道路所屬加拿大礦業公司Trilogy Metals的遊說代表。

3月27日,內政部核准在阿拉斯加修建一條會穿過北極門國家公園保護區的私人採礦道路。照片來源:
維基百科/美國漁業與野生動物局

此外,伯恩哈特還拒絕展延第一份新冠病毒法案簽署前,才剛剛開啟的幾個重要民意徵詢期,其中有爭議性的計畫,核准在新墨西哥州查科文化國家歷史公園附近的鑽探活動。新墨西哥州國會代表團和自然保護組織已呼籲伯恩哈特延長民意徵詢期。

內政部土地管理局(Bureau of Land Management)僅在4月6日展延了一個民意徵詢期,讓公眾可以在5月21日之前對影響整個西部北美松雞棲地的六份環境影響聲明草案發表評論。

除了內政部的57項行動之外,美國行政管理和預算局(Office of Management and Budget, OBM)還繼續與油氣公司舉行會議,討論內政部未來的法規制定問題。

3月18日,OMB和內政部官員會見了殼牌、埃克森美孚、康菲石油公司和美國石油學會代表,討論油氣和煤炭公司支付納稅人特許權使用費時,如何對鑽取自公有土地上的產品估價。 

川普政府於2017年試圖廢除歐巴馬時代訂定的法規。該法規關閉了一個漏洞,不再讓企業以低於市價的價格賣煤炭給自己。最後因法院阻止而沒有廢除成功。

Trump Erodes Eco-Protections as Americans Die of Virus DENVER, Colorado, April 7, 2020 (ENS)

As America fought the new coronavirus over the past 30 days, President Donald Trump’s Interior Department rushed through dozens of attacks on the environment, finds a new analysis by the nonprofit Center for Western Priorities. In the month since Trump signed the first emergency coronavirus bill, the Interior Department has taken dozens of policy actions unrelated to COVID-19, removing protections for endangered wildlife, plus expanding mining operations and oil and gas leasing on public lands across the country.

The Center for Western Priorities’ analysis identified 57 separate actions taken by Interior Department agencies since March 6, when President Trump signed the first coronavirus emergency bill.

Those actions include 34 public comment periods that were opened or closed by the Interior Department despite numerous requests from local elected officials and members of Congress that Interior Secretary David Bernhardt suspend rulemakings during the coronavirus pandemic.

“While the country was focused on slowing a global pandemic, Secretary Bernhardt did not lose sight of his singular mission to deliver favors for the oil, gas, and mining companies that paid his salary for years,” commented Jennifer Rokala, executive director at the Center for Western Priorities.

“When you look at the audacious scope of what he was doing as the coronavirus crisis accelerated, it’s no wonder he was so blind to the risk that he created for America’s park rangers by refusing to close park gates,” Rokala said.

Since March 6, Interior has finalized at least nine actions that will have lasting effects on public and tribal lands, including expansions of hardrock mining operations in Arizona and Nevada, and the revocation of tribal trust lands from the Mashpee Wampanoag Tribe in Massachusetts.

The Mashpee Wampanoag have occupied the same region for over 12,000 years. The very tribe that welcomed the Pilgrims in the 1600s is at risk of losing what is left of their homelands due to a determination made by the Bureau of Indian Affairs, a part of the Interior Department.

The decision is a blow to tribal sovereignty and undermines the future and sustainability of the tribal nation. In response, the tribe is asking Congress to protect its reservation lands and has proposed the Mashpee Wampanoag Tribe Reservation Reaffirmation Act (H.R.312).

The Department of the Interior also continued its efforts to undermine the Endangered Species Act, advancing policies that would reduce protections for the humpback chub fish and a Puerto Rican orchid, and eliminating a proposal that would have established protections for the sage-grouse.

In March, the Interior Department held seven oil and gas lease sales, despite a glut of oil production leading to the collapse of global oil prices.

Interior also announced an April coal lease sale in Colorado despite falling coal production and mine closures due to the risk of COVID-19, the dearly respiratory disease caused by the novel coronavirus, as well as low demand for coal.

On March 27, the agency paved the way for a private mining road to be built through Gates of the Arctic National Preserve in Alaska.

Secretary Bernhardt’s former lobbying and law firm, Brownstein Hyatt Farber Schreck, has lobbied the Interior Department to approve the project on behalf of the Canadian mining corporation Trilogy Metals, based in Vancouver.

In addition to the 32 public comment periods opened or closed since March 6, Secretary Bernhardt has refused to extend several key comment periods that opened just prior to the president’s signature on the first coronavirus bill. This includes the controversial plan to allow additional drilling near New Mexico’s Chaco Culture National Historic Park.

New Mexico’s congressional delegation and conservation groups have urged Bernhardt to extend the window for public comment, which opened on February 28th.

The Bureau of Land Management did extend one comment period on April 6, giving the public until May 21 to comment on six draft environmental impact statements that affect sage-grouse habitat across the West.

In addition to the 57 actions taken by the Interior Department, the White House Office of Management and Budget, OMB, also continued to hold meetings with oil and gas companies regarding future rulemakings by the Interior Department.

On March 18, OMB and Interior officials met with representatives of Shell, ExxonMobil, ConocoPhillips, and the American Petroleum Institute regarding a proposed rule on how oil, gas, and coal companies are allowed to value products extracted from public land when paying royalties to taxpayers.

The Trump administration in 2017 attempted to repeal an Obama-era rule that closed a loophole allowing companies to essentially sell coal to themselves at below-market prices, but that repeal was blocked by the courts.

※ 全文及圖片詳見:ENS

環境政策
動物與大環境變遷
武漢肺炎
國際新聞
美國
全球變遷
氣候變遷

作者

姜唯

如果有一件事是重要的,如果能為孩子實現一個願望,那就是人類與大自然和諧共存。

林大利

於特有生物研究保育中心服務,小鳥和棲地是主要的研究對象。是龜毛的讀者,認為龜毛是探索世界的美德。

延伸閱讀

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

網頁設計最專業,超強功能平台可客製化

學者:野火致觀光客減少 有助控制澳洲疫情

摘錄自2020年4月13日中央社報導

一位澳洲學者說,幾個月前的森林大火雖然造成重大傷亡和損失,但減少可能染疫的觀光客到訪,意外地降低2019新型冠狀病毒(COVID-19,武漢肺炎)疫情對澳洲的衝擊。

澳洲國立大學傳染病教授柯里諾(Peter Collignon)說,要是沒有這場災難,澳洲的新型冠狀病毒疫情恐怕一發不可收拾。澳洲觀光及交通業界論壇組織(Tourism and Transport Forum)執行長奧斯蒙(Margy Osmond)表示,早在武漢肺炎疫情爆發之前,澳洲觀光業本就因為森林大火而受到重創。

澳洲衛生部最新的數據顯示,截至澳洲東部標準時間(AEST)13日下午3時止,澳洲2019冠狀病毒疾病確診病例24小時內增加46例,累計達6359例,其中61人死亡。確診人數最多的新南威爾斯州有2863個病例,約占45%。全國有36萬2000多人已接受病毒檢測。

生活環境
國際新聞
澳洲
澳洲野火
地方觀光
控制疫情
武漢肺炎
動物與大環境變遷

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計最專業,超強功能平台可客製化

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家公司費用怎麼算?

中國傳統市場照賣 澳:全球健康威脅

摘錄自2020年4月15日自由時報報導

武漢肺炎疫情被認為起源自中國傳統市場販售的野味,最早傳出疫情的武漢市華南海鮮批發市場目前仍被關閉。但另一個武漢市最大的傳統市場「白沙洲大市場」,不僅在封城期間維持基本的運作,解封後也很快就恢復疫情爆發前的熱絡景況。

為防堵疫情因市場活動二度爆發,民眾必須持有健康認證「綠碼」、量測體溫後才能進入白沙洲大市場,載貨的車輛也需經過消毒。白沙洲大市場販賣蔬菜、魚貨、冷藏雞肉與當季食品,當地攤販指出,該市場禁止宰殺雞、豬、羊等家禽畜,必須在專門的屠宰場完成宰殺後,再送至市場販賣。

澳洲總理莫里森14日指出,他難以理解世衛組織竟坐視中國的傳統市場繼續營運,並表示這不是第一次出現源自市場的病毒,稱傳統市場為「全球健康威脅」。

但世衛組織上週表示,傳統市場為中國人民重要的收入與食物來源,不支持中國關閉所有傳統市場,且中國目前只允許傳統市場銷售安全的食物,衛生水準已經提升。世衛組織特使納巴羅也於14日受訪時說,為防止下一次的全球大流行,世衛組織建議各國應該關閉「危險的」傳統市場,但也強調,世衛組織無法強制任何國家關閉市場。

生活環境
國際新聞
中國新聞
中國
武漢
傳統市場
食品安全

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價

仰賴自然卻摧毀自然 珍古德:導致病毒大爆發

摘錄自2020年4月14日自由時報報導

世界著名的英國靈長類動物學家珍古德(Jane Goodall)11日於電話訪談中指出,武漢肺炎(COVID-19)的大爆發是由於人類長期以來對自然的漠視和對動物的不尊重。

《法新社》報導,珍古德認為,病毒的大流行從很久以前就被預言了。以砍伐森林為例,動物棲地減少,不同物種被迫靠近,因而互相傳染疾病,久之,當生活空間少到牠們不得已向人類居住地接近時,人類便被傳染。她補充:「當然,野生動物的捕食也是原因之一,特別是中國和非洲。」

她進一步表示,中國禁止野生動物市場是一件好事,「希望是永久性的」,且期待亞洲其他國家能夠跟進。但珍古德也明白,在非洲會相對難以實施該禁令,因為許多民眾賴以為生,「當人們沒有其他管道養活自己和家人時,你不可能直接禁止他做(野生動物買賣)。」

土地水文
土地利用
國際新聞
珍古德
冠狀病毒
災害

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※回頭車貨運收費標準

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※推薦評價好的iphone維修中心

※教你寫出一流的銷售文案?

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

德動物園面臨斷炊 列安樂死名單

摘錄自2020年4月16日醒報報導

自德國於3月15日下達封城令後,遊客銳減,新明斯特動物園財務吃緊,為了解決動物捱餓的問題,園方已列出緊急應變方案,考慮將部分動物安樂死,並宰殺作為其他動物的食物。

柏林動物園協會(VdZ)強調,動物園在休館期間無法大幅削減開支,每日仍須支出一定人力與金錢成本餵食並照顧動物,維持宜居的環境及溫度條件,一旦失去門票收入,便面臨龐大財務壓力。德國政府已推出高達7500億歐元的紓困方案,VdZ也已籲請總理梅克爾撥款1億歐元,作為動物園產業的緊急支應金。

國際新聞
德國
動物園
展示動物
動物福利

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

網頁設計最專業,超強功能平台可客製化

※別再煩惱如何寫文案,掌握八大原則!

日本大鯢數量急減 雜交問題嚴重 水族館推大鯢布偶提升國民關注

文:宋瑞文

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

※回頭車貨運收費標準

調優 | Apache Hudi應用調優指南

通過Spark作業將數據寫入Hudi時,Spark應用的調優技巧也適用於此。如果要提高性能或可靠性,請牢記以下幾點。

輸入并行性:Hudi對輸入進行分區默認併發度為1500,以確保每個Spark分區都在2GB的限制內(在Spark2.4.0版本之後去除了該限制),如果有更大的輸入,則相應地進行調整。我們建議設置shuffle的併發度,配置項為hoodie.[insert|upsert|bulkinsert].shuffle.parallelism,以使其至少達到input_data_size/500MB。

Off-heap(堆外)內存:Hudi寫入parquet文件,需要使用一定的堆外內存,如果遇到此類故障,請考慮設置類似spark.yarn.executor.memoryOverheadspark.yarn.driver.memoryOverhead的值。

Spark 內存:通常Hudi需要能夠將單個文件讀入內存以執行合併或壓縮操作,因此執行程序的內存應足以容納此文件。另外,Hudi會緩存輸入數據以便能夠智能地放置數據,因此預留一些spark.memory.storageFraction通常有助於提高性能。

調整文件大小:設置limitFileSize以平衡接收/寫入延遲與文件數量,並平衡與文件數據相關的元數據開銷。

時間序列/日誌數據:對於單條記錄較大的數據庫/ nosql變更日誌,可調整默認配置。另一類非常流行的數據是時間序列/事件/日誌數據,它往往更加龐大,每個分區的記錄更多。在這種情況下,請考慮通過.bloomFilterFPP()/bloomFilterNumEntries()來調整Bloom過濾器的精度,以加速目標索引查找時間,另外可考慮一個以事件時間為前綴的鍵,這將使用範圍修剪並顯着加快索引查找的速度。

GC調優:請確保遵循Spark調優指南中的垃圾收集調優技巧,以避免OutOfMemory錯誤。[必須]使用G1 / CMS收集器,其中添加到spark.executor.extraJavaOptions的示例如下:

-XX:NewSize=1g -XX:SurvivorRatio=2 -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:CMSInitiatingOccupancyFraction=70 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCApplicationConcurrentTime -XX:+PrintTenuringDistribution -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/hoodie-heapdump.hprof

OutOfMemory錯誤:如果出現OOM錯誤,則可嘗試通過如下配置處理:spark.memory.fraction = 0.2,spark.memory.storageFraction = 0.2允許其溢出而不是OOM(速度變慢與間歇性崩潰相比)。

以下是完整的生產配置

spark.driver.extraClassPath /etc/hive/conf
spark.driver.extraJavaOptions -XX:+PrintTenuringDistribution -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCTimeStamps -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/hoodie-heapdump.hprof
spark.driver.maxResultSize 2g
spark.driver.memory 4g
spark.executor.cores 1
spark.executor.extraJavaOptions -XX:+PrintFlagsFinal -XX:+PrintReferenceGC -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintAdaptiveSizePolicy -XX:+UnlockDiagnosticVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/hoodie-heapdump.hprof
spark.executor.id driver
spark.executor.instances 300
spark.executor.memory 6g
spark.rdd.compress true
 
spark.kryoserializer.buffer.max 512m
spark.serializer org.apache.spark.serializer.KryoSerializer
spark.shuffle.service.enabled true
spark.sql.hive.convertMetastoreParquet false
spark.submit.deployMode cluster
spark.task.cpus 1
spark.task.maxFailures 4
 
spark.yarn.driver.memoryOverhead 1024
spark.yarn.executor.memoryOverhead 3072
spark.yarn.max.executor.failures 100

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準

HashMap解析(主要JDK1.8,附帶1.7出現的問題以及區別)

按問題的形式來吧,這些大多是我自己總結的,如有錯誤請及時指正謝謝

1.你了解HashMap么,可以說說么?

  首先,HashMap是一種數據結構,可以快速的幫我們存取數據。它的底層數據結構在1.7和1.8有了一些變化,1.7版本及以前他是數組+鏈表的形式,1.8及以後數組+鏈表+紅黑樹,如果鏈表長度大於等於8就會轉化為紅黑樹,如果長度降至6紅黑樹會轉化為鏈表紅黑樹的出現解決了因為鏈表過長導致查詢速度變慢的問題,因為鏈表的查詢時間複雜度是O(n),而紅黑樹的查詢時間複雜度是O(logn)。

2.它的數組+鏈表是怎麼實現的?

  

 

 

 這個代碼是1.8的(1.7是Entry,就是名字不一樣),其實我們每一個放進去的(key,value)到最後都會封裝成這樣的Node對象。Hashmap的數組就是以一系列這樣的Node對象構成的數組,鏈表就是把next指向下一個Node對象。

 

 

3.為什麼要有鏈表,紅黑樹?只有數組不可以么?

首先我們要知道什麼是Hash算法。

這裏放出一段官方的話:

 

Hash,一般翻譯做散列、雜湊,或音譯為哈希,是把任意長度的輸入(又叫做預映射pre-image)通過散列算法變換成固定長度的輸出,該輸出就是散列值。這種轉換是一種壓縮映射,也就是,散列值的空間通常遠小於輸入的空間,不同的輸入可能會散列成相同的輸出,所以不可能從散列值來確定唯一的輸入值。簡單的說就是一種將任意長度的消息壓縮到某一固定長度的消息摘要的函數。

 

簡單點來說:就是把一個大数字經過運算變為固定範圍的輸出,最簡單的算法就是對你的數組長度取模。

但是這樣就會出現一個問題,你這麼算難免會出現算出來的数字是一樣的:

比如數組長度為16,我們要放入数字1和17,那麼他們經過對數組長度取模后位置是一樣的,這樣就產生了Hash衝突。我們就可以在數組下拉出一個鏈表去存儲這個数字

4.知道哪些常見的解決hash衝突算法么?

1、開放定址法(就是往下找空餘地方)
     用開放定址法解決衝突的做法是:當衝突發生時,使用某種探查(亦稱探測)技術在散列表中形成一個探查(測)序列。沿此序列逐個單元地查找,直到找到給定 的關鍵字,或者碰到一個開放的地址(即該地址單元為空)為止(若要插入,在探查到開放的地址,則可將待插入的新結點存人該地址單元)。查找時探查到開放的 地址則表明表中無待查的關鍵字,即查找失敗。

2、 再哈希法(再進行hash直到無衝突)
再哈希法又叫雙哈希法,有多個不同的Hash函數,當發生衝突時,使用第二個,第三個,….,等哈希函數
計算地址,直到無衝突。雖然不易發生聚集,但是增加了計算時間。

3、拉鏈法(hashmap用的)

鏈地址法的基本思想是:每個哈希表節點都有一個next指針,多個哈希表節點可以用next指針構成一個單向鏈表,被分配到同一個索引上的多個結點用單向鏈表連接起來

4、建立公共溢出區: 
這種方法的基本思想是:將哈希表分為基本表和溢出表兩部分,凡是和基本表發生衝突的元素,一律填入溢出表

5.為什麼閾值就是8和6呢?中間的7是有什麼作用的呢?直接就是紅黑樹不可以么?

HashMap中有這樣一段註釋(主要看数字):

/* * Because TreeNodes are about twice the size of regular nodes, we * use them only when 鏈表s contain enough nodes to warrant use * (see TREEIFY_THRESHOLD). And when they become too small (due to * removal or resizing) they are converted back to plain 鏈表s. In * usages with well-distributed user hashCodes, tree 鏈表s are * rarely used. Ideally, under random hashCodes, the frequency of * nodes in 鏈表s follows a Poisson distribution * (http://en.wikipedia.org/wiki/Poisson_distribution) with a * parameter of about 0.5 on average for the default resizing * threshold of 0.75, although with a large variance because of * resizing granularity. Ignoring variance, the expected * occurrences of list size k are (exp(-0.5) * pow(0.5, k) / * factorial(k)). The first values are: * * 0: 0.60653066 * 1: 0.30326533 * 2: 0.07581633 * 3: 0.01263606 * 4: 0.00157952 * 5: 0.00015795 * 6: 0.00001316 * 7: 0.00000094 * 8: 0.00000006 * more: less than 1 in ten million */

TreeNodes佔用空間是普通Nodes的兩倍(相較於鏈表結構,鏈表只有指向下一個節點的指針,二叉樹則需要左右指針,分別指向左節點和右節點),所以只有當鏈表包含足夠多的節點時才會轉成TreeNodes(考慮到時間和空間的權衡),而是否足夠多就是由TREEIFY_THRESHOLD的值決定的。當紅黑樹中節點數變少時,又會轉成普通的鏈表。並且我們查看源碼的時候發現,鏈表長度達到8就轉成紅黑樹,當長度降到6就轉成普通鏈表。

這樣就解釋了為什麼不是一開始就將其轉換為TreeNodes,而是需要一定節點數才轉為TreeNodes,說白了就是trade-off,空間和時間的權衡。

當hashCode離散性很好的時候,樹型鏈表用到的概率非常小,因為數據均勻分佈在每個鏈表中,幾乎不會有鏈表中鏈表長度會達到閾值。但是在隨機hashCode下,離散性可能會變差,然而JDK又不能阻止用戶實現這種不好的hash算法,因此就可能導致不均勻的數據分佈。不過理想情況下隨機hashCode算法下所有鏈表中節點的分佈頻率會遵循泊松分佈,我們可以看到,一個鏈表中鏈表長度達到8個元素的概率為0.00000006,幾乎是不可能事件。這種不可能事件都發生了,說明鏈表中的節點數很多,查找起來效率不高。至於7,是為了作為緩衝,可以有效防止鏈表和樹頻繁轉換。

之所以選擇8,不是拍拍屁股決定的,而是根據概率統計決定的。由此可見,發展30年的Java每一項改動和優化都是非常嚴謹和科學的。

泊松分佈適合於描述單位時間(或空間)內隨機事件發生的次數。如某一服務設施在一定時間內到達的人數,電話交換機接到呼叫的次數,汽車站台的候客人數,機器出現的故障數,自然災害發生的次數,一塊產品上的缺陷數,顯微鏡下單位分區內的細菌分佈數等等。如果有興趣的,可以研究一下,概率是怎麼算出來的!

個人總結:

  1. 選擇8是因為空間和時間的權衡,再一個是因為鏈表中節點的分佈頻率會遵循泊松分佈,達到8的概率很小
  2. 選擇7是為了作為緩衝,可以有效防止鏈表和樹頻繁轉換
  3. 你的紅黑樹查詢時間複雜度低,但你的維持平衡的操作代價是大的,所以不會直接是紅黑樹(這一點是個人理解)

6.HashMap的初始容量,加載因子,擴容增量是多少?如果加載因子變大變小會怎麼樣?

HashMap的初始容量16,加載因子為0.75,擴容增量是原容量的1倍。如果HashMap的容量為16,一次擴容后容量為32。HashMap擴容是指元素個數(包括數組和鏈表+紅黑樹中)超過了16*0.75=12(容量×加載因子)之後開始擴容。

這個就是源碼里的聲明

//默認初始容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 //最大容量
static final int MAXIMUM_CAPACITY = 1 << 30; //加載因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;

加載因子越大,填滿的元素越多,空間利用率越高,但衝突的機會加大了。
反之,加載因子越小,填滿的元素越少,衝突的機會減小,但空間浪費多了(因為需要經常擴容)。

所以這是一個時間和空間的均衡。

7. 如果我默認初始大小為100,那麼元素個數到達75會擴容么?

這個問題我以前見到過,所以拿出來說一下。

首先HashMap的構造方法有四個

    public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity); } public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
  
  public HashMap(Map<!--? extends K, ? extends V--> m) {       this.loadFactor = DEFAULT_LOAD_FACTOR;       putMapEntries(m, false);   }  

簡單點來說就是你可以自定義加載因子和初始容量。但是這個初始容量不是說你設置多少就是多少,他是會有個計算的,到最後Hashmap的容量一定是2的n次方

 

 

 簡單說一下putMapEntries

final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) { //獲取該map的實際長度
        int s = m.size(); if (s > 0) { //判斷table是否初始化,如果沒有初始化
            if (table == null) { // pre-size
                /**求出需要的容量,因為實際使用的長度=容量*0.75得來的,+1是因為小數相除,基本都不會是整數,容量大小不能為小數的,後面轉換為int,多餘的小數就要被丟掉,所以+1,例如,map實際長度22,22/0.75=29.3,所需要的容量肯定為30,有人會問如果剛剛好除得整數呢,除得整數的話,容量大小多1也沒什麼影響**/
                float ft = ((float)s / loadFactor) + 1.0F; //判斷該容量大小是否超出上限。
                int t = ((ft < (float)MAXIMUM_CAPACITY) ? (int)ft : MAXIMUM_CAPACITY); /**對臨界值進行初始化,tableSizeFor(t)這個方法會返回大於t值的,且離其最近的2次冪,例如t為29,則返回的值是32**/
                if (t > threshold) threshold = tableSizeFor(t); } //如果table已經初始化,則進行擴容操作,resize()就是擴容。
            else if (s > threshold) resize(); //遍歷,把map中的數據轉到hashMap中。
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { K key = e.getKey(); V value = e.getValue(); putVal(hash(key), key, value, false, evict); } } }

 

所以說這個答案就是不會擴容的,因為你初始它的容量是100,tableSizeFor也會自動變成128,128×0.75是93遠遠大於75.

8. HashMap中為什麼數組的長度為2的冪次方?

主要是為了計算hash值時散列性更好。

我們看一下HashMap的數組下標如何計算的

 

// 將(數組的長度-1)和hash值進行按位與操作:
i = (n - 1) & hash  // i為數組對應位置的索引 n為當前數組的大小

假定HashMap的長度為默認的16,則n – 1為15,也就是二進制的01111

可以說,Hash算法最終得到的index結果完全取決於hashCode的最後幾位。

那麼說為什麼別的数字不行呢?

假設,HashMap的長度為10,則n-1為9,也就是二進制的1001

我們來試一個hashCode:1110時,通過Hash算法得到的最終的index是8

 

再比如說:1000得到的index也是8。

也就是說,即使我們把倒數第二、三位的0、1變換,得到的index仍舊是8,說明有些index結果出現的幾率變大!

這樣,顯然不符合Hash算法均勻分佈的要求。

反觀,長度16或其他2的冪次方,Length – 1的值的二進制所有的位均為1,這種情況下,Index的結果等於hashCode的最後幾位。只要輸入的hashCode本身符合均勻分佈,Hash算法的結果就是均勻的。

一句話,HashMap的長度為2的冪次方的原因是為了減少Hash碰撞,盡量使Hash算法的結果均勻分佈。

9.put方法

在講解put方法之前,先看看hash方法,看怎麼計算哈希值的。

    static final int hash(Object key) { int h; /**先獲取到key的hashCode,然後進行移位再進行異或運算,為什麼這麼複雜,不用想肯定是為了減少hash衝突**/
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }

put方法實際調用了putVal方法

    public V put(K key, V value) { /**四個參數,第一個hash值,第四個參數表示如果該key存在值,如果為null的話,則插入新的value,最後一個參數,在hashMap中沒有用,可以不用管,使用默認的即可**/
        return putVal(hash(key), key, value, false, true); } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { //tab 哈希數組,p 該哈希桶的首節點,n hashMap的長度,i 計算出的數組下標
        Node<K,V>[] tab; Node<K,V> p; int n, i; //獲取長度並進行擴容,使用的是懶加載,table一開始是沒有加載的,等put后才開始加載
        if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; /**如果計算出的該哈希桶的位置沒有值,則把新插入的key-value放到此處,此處就算沒有插入成功,也就是發生哈希衝突時也會把哈希桶的首節點賦予p**/
        if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); //發生哈希衝突的幾種情況
        else { // e 臨時節點的作用, k 存放該當前節點的key 
            Node<K,V> e; K k; //第一種,插入的key-value的hash值,key都與當前節點的相等,e = p,則表示為首節點
            if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; //第二種,hash值不等於首節點,判斷該p是否屬於紅黑樹的節點
            else if (p instanceof TreeNode) /**為紅黑樹的節點,則在紅黑樹中進行添加,如果該節點已經存在,則返回該節點(不為null),該值很重要,用來判斷put操作是否成功,如果添加成功返回null**/ e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); //第三種,hash值不等於首節點,不為紅黑樹的節點,則為鏈表的節點
            else { //遍歷該鏈表
                for (int binCount = 0; ; ++binCount) { //如果找到尾部,則表明添加的key-value沒有重複,在尾部進行添加
                    if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); //判斷是否要轉換為紅黑樹結構
                        if (binCount >= TREEIFY_THRESHOLD - 1) treeifyBin(tab, hash); break; } //如果鏈表中有重複的key,e則為當前重複的節點,結束循環
                    if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } //有重複的key,則用待插入值進行覆蓋,返回舊值。
            if (e != null) { V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } //到了此步驟,則表明待插入的key-value是沒有key的重複,因為插入成功e節點的值為null //修改次數+1
        ++modCount; //實際長度+1,判斷是否大於臨界值,大於則擴容
        if (++size > threshold) resize(); afterNodeInsertion(evict); //添加成功
        return null; }

大概如下幾步:

①. 判斷鍵值對數組table[i]是否為空或為null,否則執行resize()進行擴容,初始容量是16;

②. 根據鍵值key計算hash值得到插入的數組索引i,如果table[i]==null,直接新建節點添加,轉向⑥,如果table[i]不為空,轉向③;

③. 判斷table[i]的首個元素是否和key一樣,如果相同直接覆蓋value,否則轉向④,這裏的相同指的是hashCode以及equals;

④. 判斷table[i] 是否為TreeNode,即table[i] 是否是紅黑樹,如果是紅黑樹,遍歷發現該key不存在  則直接在樹中插入鍵值對;遍歷發現key已經存在直接覆蓋value即可;

⑤. 如果table[i] 不是TreeNode則是鏈表節點,遍歷發現該key不存在,則先添加在鏈表結尾, 判斷鏈表長度是否大於8,大於8的話把鏈錶轉換為紅黑樹;遍歷發現key已經存在直接覆蓋value即可;

⑥. 插入成功后,判斷實際存在的鍵值對數量size是否超多了最大容量threshold,如果超過,進行擴容。

10.resize方法

何時進行擴容?

HashMap使用的是懶加載,構造完HashMap對象后,只要不進行put 方法插入元素之前,HashMap並不會去初始化或者擴容table。

當首次調用put方法時,HashMap會發現table為空然後調用resize方法進行初始化
,當添加完元素后,如果HashMap發現size(元素總數)大於threshold(閾值),則會調用resize方法進行擴容

    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table; //old的長度
        int oldCap = (oldTab == null) ? 0 : oldTab.length; //old的臨界值
        int oldThr = threshold; //初始化new的長度和臨界值
        int newCap, newThr = 0; //oldCap > 0也就是說不是首次初始化,因為hashMap用的是懶加載
        if (oldCap > 0) { //大於最大值
            if (oldCap >= MAXIMUM_CAPACITY) { //臨界值為整數的最大值
                threshold = Integer.MAX_VALUE; return oldTab; } //標記##,其它情況,擴容兩倍,並且擴容后的長度要小於最大值,old長度也要大於16
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) //臨界值也擴容為old的臨界值2倍
                newThr = oldThr << 1; } /**如果oldCap<0,但是已經初始化了,像把元素刪除完之後的情況,那麼它的臨界值肯定還存在, 如果是首次初始化,它的臨界值則為0 **/
        else if (oldThr > 0) newCap = oldThr; //首次初始化,給與默認的值
        else { newCap = DEFAULT_INITIAL_CAPACITY; //臨界值等於容量*加載因子
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } //此處的if為上面標記##的補充,也就是初始化時容量小於默認值16的,此時newThr沒有賦值
        if (newThr == 0) { //new的臨界值
            float ft = (float)newCap * loadFactor; //判斷是否new容量是否大於最大值,臨界值是否大於最大值
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } //把上面各種情況分析出的臨界值,在此處真正進行改變,也就是容量和臨界值都改變了。
        threshold = newThr; //表示忽略該警告
        @SuppressWarnings({"rawtypes","unchecked"}) //初始化
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; //賦予當前的table
        table = newTab; //此處自然是把old中的元素,遍歷到new中
        if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { //臨時變量
                Node<K,V> e; //當前哈希桶的位置值不為null,也就是數組下標處有值,因為有值表示可能會發生衝突
                if ((e = oldTab[j]) != null) { //把已經賦值之後的變量置位null,當然是為了好回收,釋放內存
                    oldTab[j] = null; //如果下標處的節點沒有下一個元素
                    if (e.next == null) //把該變量的值存入newCap中,e.hash & (newCap - 1)並不等於j
                        newTab[e.hash & (newCap - 1)] = e; //該節點為紅黑樹結構,也就是存在哈希衝突,該哈希桶中有多個元素
                    else if (e instanceof TreeNode) //把此樹進行轉移到newCap中
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { /**此處表示為鏈表結構,同樣把鏈錶轉移到newCap中,就是把鏈表遍歷后,把值轉過去,在置位null**/ Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } //返回擴容后的hashMap
        return newTab; }

其實主要就是兩步:1.創建新的數組 2.複製元素

但是在新的下標位置計算上1.8做了很大的優化,後面會說到。

11.get方法

    public V get(Object key) { Node<K,V> e; 9 //調用getNode方法來完成的
        return (e = getNode(hash(key), key)) == null ? null : e.value; } final Node<K,V> getNode(int hash, Object key) { //first 頭結點,e 臨時變量,n 長度,k key
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k; //頭結點也就是數組下標的節點
        if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { //如果是頭結點,則直接返回頭結點
            if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k)))) return first; //不是頭結點
            if ((e = first.next) != null) { //判斷是否是紅黑樹結構
                if (first instanceof TreeNode) //去紅黑樹中找,然後返回
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key); do { //鏈表節點,一樣遍歷鏈表,找到該節點並返回
                    if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } //找不到,表示不存在該節點
        return null; }

主要就是利用equals和hashcode方法找到並返回

12.HashMap在JDK1.7和1.8除了數據結構的區別

(1)插入數據方式不同:

JDK1.7用的是頭插法,而JDK1.8及之後使用的都是尾插法,那麼他們為什麼要這樣做呢?因為JDK1.7認為最新插入的應該會先被用到,所以用了頭插法,但當採用頭插法時會容易出現逆序且環形鏈表死循環問題。但是在JDK1.8之後是因為加入了紅黑樹使用尾插法,能夠避免出現逆序且鏈表死循環的問題。

  說一下為什麼會產生死循環問題:

  問題出現在了這個移動元素的transfer方法里

  

 主要問題就出在了這行代碼上

Entry<K,V> next = e.next

如果兩個線程A,B都要對這個map進行擴容

A和B都已經創建了新的數組,假設線程A在執行到Entry < K,V > next = e.next之後,cpu時間片用完了,這時變量e指向節點a,變量next指向節點b。

此時A的狀態:e=a ,next=b

線程B繼續執行,很不巧,a、b、c節點rehash之後又是在同一個位置,開始移動節點, 因為頭插法,複製后順序是反的,結束后B的狀態:

 

 

 此時A開始執行,此時變量e指向節點a,變量next指向節點b,開始執行循環體的剩餘邏輯

if (rehash) { e.hash = null == e.key ? 0 : hash(e.key); } int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next;

執行到

newTable[i] = e;

此時A的狀態

 

執行到

e = next;

 

此時e=b

再執行一波循環,Entry<K,V> next = e.next 但是此時b的next是a,就出現了死循環問題

 

 

(2)擴容后數據存儲位置的計算方式也不一樣:

在JDK1.7的時候是重新計算數組下標

而在JDK1.8的時候直接用了JDK1.7的時候計算的規律,也就是擴容前的原始位置+擴容的大小值=JDK1.8的計算方式,而不再是JDK1.7的那種異或的方法。但是這種方式就相當於只需要判斷Hash值的新增參与運算的位是0還是1就直接迅速計算出了擴容后的儲存方式。

就比如說:數組大小是4,hash算法是對長度取模

 

 擴容后是這樣的

我們可以把這三個數的二進制和擴容后的length-1進行按位與,可以看到只有数字5新增位為1

 

 

 

 因此,我們在擴充HashMap的時候,不需要像JDK1.7的實現那樣重新計算hash,只需要看看原來的hash值新增的那個bit是1還是0就好了,是0的話索引沒變,是1的話索引變成“原索引+oldCap”

(3)擴容的條件不同,1.7需要容量超過閾值且發生hash衝突,1.8超過閾值即會擴容

(4)JDK1.7的時候是先進行擴容後進行插入,而在JDK1.8的時候則是先插入後進行擴容

(5)1.8中沒有區分鍵為null的情況,而1.7版本中對於鍵為null的情況調用putForNullKey()方法。但是兩個版本中如果鍵為null,那麼調用hash()方法得到的都將是0,所以鍵為null的元素都始終位於哈希表table【0】中。

(6)jdk1.7中當哈希表為空時,會先調用inflateTable()初始化一個數組;而1.8則是直接調用resize()擴容

(7)jdk1.7中的hash函數對哈希值的計算直接使用key的hashCode值,而1.8中則是採用key的hashCode異或上key的hashCode進行無符號右移16位的結果,避免了只靠低位數據來計算哈希時導致的衝突,計算結果由高低位結合決定,使元素分佈更均勻

13、HashMap是線程安全的么?如果想線程安全怎麼辦?

不是線程安全的,多線程下會出現死循環和put操作時可能導致元素丟失

死循環原因:上邊已經分析過了

丟失原因:當多個線程同時執行addEntry(hash,key ,value,i)時,如果產生哈希碰撞,導致兩個線程得到同樣的bucketIndex去存儲,就可能會發生元素覆蓋丟失的情況

 

想實現線程安全的解決方法:

1.使用Hashtable 類,Hashtable 是線程安全的(不建議用,就是利用了synchronized進行加鎖);

2.使用併發包下的java.util.concurrent.ConcurrentHashMap,ConcurrentHashMap實現了更高級的線程安全;

3.或者使用synchronizedMap() 同步方法包裝 HashMap object,得到線程安全的Map,並在此Map上進行操作。

 

參考:

https://blog.csdn.net/m0_37914588/article/details/82287191

https://www.jianshu.com/p/7cf2d6f1096b

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※推薦台中搬家公司優質服務,可到府估價

卷積生成對抗網絡(DCGAN)—生成手寫数字

深度卷積生成對抗網絡(DCGAN)

—- 生成 MNIST 手寫圖片

1、基本原理

生成對抗網絡(GAN)由2個重要的部分構成:

  • 生成器(Generator):通過機器生成數據(大部分情況下是圖像),目的是“騙過”判別器
  • 判別器(Discriminator):判斷這張圖像是真實的還是機器生成的,目的是找出生成器做的“假數據”

訓練過程

  • 1、固定判別器,讓生成器不斷生成假數據,給判別器判別,開始生成器很弱,但是隨着不斷的訓練,生成器不斷提升,最終騙過判別器。此時判別器判斷假數據的概率為50%
  • 2、固定生成器,訓練判別器。判別器經過訓練,提高鑒別能力,最終能準確判斷雖有的假圖片
  • 3、循環上兩個階段,最終生成器和判別器都越來越強。然後就可以使用生成器來生成我們想要的圖片了

2、相關數學原理

  • 判別器在這裡是一種分類器,用於區分樣本的真偽,因此我們常常使用交叉熵(cross entropy)來進行判別分佈的相似性

\[H(p, q) := -\sum_i p_i \log q_i \]

公式中 \(p_i\)\(q_i\) 為真實的樣本分佈和生成器的生成分佈

假定 \(y_1\) 為正確樣本分佈,那麼對應的( \(1-y_1\) )就是生成樣本的分佈。\(D\) 表示判別器,則 \(D(x_1)\) 表示判別樣本為正確的概率, \(1-D(x_1)\) 則對應着判別為錯誤樣本的概率。則有如下式子(這裏僅僅是對當前情況下的交叉熵損失的具體化)。

\[H((x_i, y_i)_{i=1}^N, D) = – \sum_{i=1}^N y_i\log D(x_i) – \sum_{i=1}^N(1-y_i)\log (1 – D(x_i)) \]

對於GAN中的樣本點 \(x_i\) ,對應於兩個出處,要麼來自於真實樣本,要麼來自於生成器生成的樣本 $\tilde{x} – G(z) $ ( 這裏的 \(z\) 是服從於投到生成器中噪聲的分佈)。

對於來自於真實的樣本,我們要判別為正確的分佈 \(y_i\) 。來自於生成的樣本我們要判別其為錯誤分佈( \(1-y_i\) )。將上面式子進一步使用概率分佈的期望形式寫出(為了表達無限的樣本情況,相當於無限樣本求和情況),並且讓 \(y_i\) 為 1/2 且使用 \(G(z)\) 表示生成樣本可以得到如下公式:

\[H \left( (x_i, y_i)_{i=1}^\infty, D \right) = -\frac{1}{2}E_{x-p_{data}}\left[ \log D(x) \right] – \frac{1}{2}E_z\left[ \log (1-D(G(z))) \right] \\\ GAN損失函數期望形式 \]

對於論文中的公式

\[min_G max_D V(D, G) = E_{x-p_{data}(x)}\left[ \log D(x) \right] + E_{z-p_z(z)}\left[ \log (1-D(G(z))) \right] \\\ GAN損失函數的 min max表達 \]

其實是與上面公式一樣的,下面做解釋

  • 這裏的 \(V(D, G)\) 相當於表示真實樣本和生成樣本的差異程度。
  • \(max_D V(D, G)\) 的意思是固定生成器 \(G\), 盡可能地讓判別器能夠最大化地判別出樣本來自於真實數據還是生成的數據。
  • 再將後面的 $L = max_D V(D, G) $ 看成整體,對於 \(min_G L\)這裡是在固定判別器\(D\)的條件下得到生成器 \(G\),這個 \(G\) 要求能夠最小化真實樣本與生成樣本的差異。
  • 通過上述 \(min\) \(max\) 的博弈過程,理想情況下會收斂於生成分佈擬合於真實分佈。

3、卷積對抗生成網絡

卷積對抗生成網絡(DCGAN)是在GAN的基礎上加入了CNN,主要是改進了網絡結構,在訓練過程中狀態穩定,並且可以有效實現高質量圖片的生成以及相關的生成模型應用。DCGAN的生成器網絡結構如下圖:

DCGAN的改進:

  • 使用步長卷積代替上採樣層,卷積在提取圖像特徵上具有很好的作用,並且使用卷積代替全連接層
  • 生成器G和判別器D中幾乎每一層都使用batchnorm層,將特徵層的輸出歸一化到一起,加速了訓練,提升了訓練的穩定性。
  • 在判別器中使用leakrelu激活函數,而不是RELU,防止梯度稀疏,生成器中仍然採用relu,但是輸出層採用tanh。

4、DCGAN代碼實現

shenduimport numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import optimizers, losses, layers, Sequential, Model
class DCGAN():
    '''
    實現深度對抗神經網絡
    生成 MNIST 手寫数字圖片
    輸入的噪聲為服從正態分佈均值為 0 方差為 1 的分佈, shape:(None, 100)
    生成器(G)輸入 噪聲, 輸出為 (None, 28, 28, 1)的圖片
    分類器(D)輸入為 (None, 28, 28, 1)的圖片,輸出圖片的分類真假
    '''
    def __init__(self):
        self.img_rows = 28 
        self.img_cols = 28
        self.channels = 1
        self.img_shape = (self.img_rows, self.img_cols, self.channels)

        optimizer = optimizers.Adam(0.0002)

        # 構建編譯分類器
        self.discriminator = self.build_discriminator()
        self.discriminator.compile(loss='binary_crossentropy', 
            optimizer=optimizer,
            metrics=['accuracy'])

        # 構建編譯生成器
        self.generator = self.build_generator()
        self.generator.compile(loss='binary_crossentropy', optimizer=optimizer)

        # 生成器輸入為噪音,生成圖片
        z = layers.Input(shape=(100,))
        img = self.generator(z)

        # 對於整個對抗網絡模型只優化生成器的參數
        self.discriminator.trainable = False

        # 用生成的圖片輸入分類器判斷
        valid = self.discriminator(img)

        # 對於整個對抗網絡 輸入噪音 => 生成圖片 => 決定圖片是否有效
        self.combined = Model(z, valid)
        self.combined.compile(loss='binary_crossentropy', optimizer=optimizer)

        
    def build_generator(self):
        '''
        構建生成器
        '''
        noise_shape = (100,)
        
        model = tf.keras.Sequential()
        
        # 添加全連接層
        model.add(layers.Dense(7*7*256, use_bias=False, input_shape=noise_shape))
        # 添加 BatchNormalization 層,對數據進行歸一化
        model.add(layers.BatchNormalization())
        model.add(layers.LeakyReLU())

        model.add(layers.Reshape((7, 7, 256)))
        
        # 添加逆卷積層,卷積核大小為 5X5,數量 128, 步長為 1
        model.add(layers.Conv2DTranspose(128, (5, 5), strides=(1, 1), padding='same', use_bias=False))
        assert model.output_shape == (None, 7, 7, 128)
        model.add(layers.BatchNormalization())
        model.add(layers.LeakyReLU())
        
        # 添加逆卷積層,卷積核大小為 5X5,數量 64, 步長為 2
        model.add(layers.Conv2DTranspose(64, (5, 5), strides=(2, 2), padding='same', use_bias=False))
        assert model.output_shape == (None, 14, 14, 64)
        model.add(layers.BatchNormalization())
        model.add(layers.LeakyReLU())
        
        # 添加逆卷積層,卷積核大小為 5X5,數量 1, 步長為 2
        model.add(layers.Conv2DTranspose(1, (5, 5), strides=(2, 2), padding='same', use_bias=False, activation='tanh'))
        assert model.output_shape == (None, 28, 28, 1)
        
        model.summary()
        noise = layers.Input(shape=noise_shape)
        img = model(noise)
        
        # 返回 Model 對象,輸入為 噪聲, 輸出為 圖像
        return keras.Model(noise, img)

    
    def build_discriminator(self):
        '''
        構建分類器
        '''
        img_shape = (self.img_rows, self.img_cols, self.channels)
        
        model = tf.keras.Sequential()
        
        model.add(layers.Conv2D(64, (5, 5), strides=(2, 2), padding='same',
                                         input_shape=img_shape))
        model.add(layers.LeakyReLU())
        # 添加 Dropout 層,減少參數數量
        model.add(layers.Dropout(0.3))

        model.add(layers.Conv2D(128, (5, 5), strides=(2, 2), padding='same'))
        model.add(layers.LeakyReLU())
        model.add(layers.Dropout(0.3))
        # 把數據鋪平
        model.add(layers.Flatten())
        model.add(layers.Dense(1))
        
        model.summary()
        
        img = layers.Input(shape=img_shape)
        validity = model(img)
        
        return keras.Model(img, validity)

    
    def train(self, epochs, batch_size=128, save_interval=50):
        '''
        網絡訓練
        '''
        # 加載 數據集
        (X_train, _), (_, _) = keras.datasets.mnist.load_data()

        # 把數據縮放到 [-1, 1]
        X_train = (X_train.astype(np.float32) - 127.5) / 127.5
        # 添加通道維度
        X_train = np.expand_dims(X_train, axis=3)
        half_batch = int(batch_size / 2)

        for epoch in range(epochs):

            # ---------------------
            #  訓練分類器
            # ---------------------

            # 隨機的選擇一半的 batch 數量圖片
            idx = np.random.randint(0, X_train.shape[0], half_batch)
            imgs = X_train[idx]

            noise = np.random.normal(0, 1, (half_batch, 100))

            # 生成一半 batch 數量的 圖片
            gen_imgs = self.generator.predict(noise)

            # 分類器損失
            d_loss_real = self.discriminator.train_on_batch(imgs, np.ones((half_batch, 1)))
            d_loss_fake = self.discriminator.train_on_batch(gen_imgs, np.zeros((half_batch, 1)))
            d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)


            # ---------------------
            #  訓練生成器
            # ---------------------

            noise = np.random.normal(0, 1, (batch_size, 100))

            # The generator wants the discriminator to label the generated samples
            # as valid (ones)
            # 對於生成器,希望分類器把更多的圖片判為 有效 (用 1 表示)
            valid_y = np.array([1] * batch_size)

            # 訓練生成器
            g_loss = self.combined.train_on_batch(noise, valid_y)

            # 打印訓練進度
            print ("%d [D loss: %f, acc.: %.2f%%] [G loss: %f]" % (epoch, d_loss[0], 100*d_loss[1], g_loss))

            # 每個 save_interval 周期保存一張圖片
            if epoch % save_interval == 0:
                self.save_imgs(epoch)

    def save_imgs(self, epoch):
        r, c = 5, 5
        noise = np.random.normal(0, 1, (r * c, 100))
        gen_imgs = self.generator.predict(noise)

        # 把圖片數據縮放到 0 - 1
        gen_imgs = 0.5 * gen_imgs + 0.5

        fig, axs = plt.subplots(r, c)
        cnt = 0
        for i in range(r):
            for j in range(c):
                axs[i,j].imshow(gen_imgs[cnt, :,:,0], cmap='gray')
                axs[i,j].axis('off')
                cnt += 1
        fig.savefig("dcgan/images/mnist_%d.png" % epoch)
        plt.close()

if __name__ == '__main__':
    dcgan = DCGAN()
    dcgan.train(epochs=10000, batch_size=32, save_interval=200)

網絡參數信息

5、訓練結果

下面是循環了 10000 次 epoch 后,從開始每隔 2000 個 epoch 生成器生成的圖片

  • 可以看到,剛開始全部都是噪聲,隨着訓練的進行,圖片逐漸清晰

  • 生成的圖片還是不太清晰,一方面的原因是我訓練的 epoch 周期太少,因為自己電腦性能問題,太耗時間,所以訓練的epoch 周期少,如果有條件后提高訓練周期應該會好很多。另一方面或許因為我構建的網絡還有不合理之,後期還需要改進。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

網頁設計最專業,超強功能平台可客製化